TietjeDK
TietjeDK

Reputation: 1207

Angular.js $watch directive inside ng-repeat

I'm trying to use a directive which watches changes inside a custom attribute, which is located inside an ng-repeat loop. The array used in the ng-repeat gets updated every 5 seconds. I wanna animate a div when item is changed from the previous value.

I'm using $watch to keep track of the old and new value, but they are always the same. I know the $watch function will never detect changes in the scope because the ng-repeat is trigger again as soon as data gets loaded.

The goal is to animate background-color of the div if the value is changed.

But how can I avoid this?

Directive:

.directive('animateOnChange', function($timeout) {
 return function(scope, element, attr) {
  scope.$watch(attr.animateOnChange, function(nv,ov) {
   if (nv!=ov) {
    if(attr.change > 0){
      element.addClass('changed-positive');
       $timeout(function() {
         element.removeClass('changed-positive');
       }, 1000);
    }else if (attr.change < 0){
      element.addClass('changed-negative');
       $timeout(function() {
        element.removeClass('changed-negative');
       }, 1000);
    }
  }
 });
};
});

HTML:

<div class="row" ng-repeat="item in data">
  <div class="box" animate-on-change="item.activeprice[0]" change="{{item.change[0]}}">
    <h2>{{item.desc[0]}}</h2>
    <p>Time: {{item.asktime[0]}}</p>
  </div>
</div>

$scope.data is getting called/updated via a http request.

Any suggestions?

Upvotes: 0

Views: 1612

Answers (2)

TietjeDK
TietjeDK

Reputation: 1207

I ended up using a $watch function in my main controller to monitor for changes and create animations based on that. I gave each row in my ng-repeat loop a class based on `$index.

<div class="box box-{{$index}}" ng-style="item.change[0] < 0 && { 'color' :'#c0392b' } || item.change[0] >= 0 && { 'color' :'#2980b9' } ">
    <h2>{{item.desc[0]}}</h2>
    <p>Time: {{item.asktime[0]}}</p>
</div>

Controller:

$scope.$watch('data', function(newVal, oldVal) {
     $timeout(function() {
      if (newVal !== oldVal) {
        for (var i = 0; i < newVal.length; i++) {
          for (var c = 0; c < oldVal.length; c++) {
            if(newVal[i].desc[0] == oldVal[c].desc[0]){
                if(newVal[i].activeprice[0] !== oldVal[c].activeprice[0]){
               if(newVal[i].change[0] > 0){
                $( ".box-" + i ).addClass('changed-positive');
                $timeout(function() {
                  $( ".box-" + i ).removeClass('changed-positive');
                }, 10);
              } else if(newVal[i].change[0] < 0){
                 $( ".box-" + i ).addClass('changed-negative');
                $timeout(function() {
                  $( ".box-" + i ).removeClass('changed-negative');
                }, 10);
              }
              }
            }
          }
        }
      }
    }, 300);
  }, true);

Upvotes: 0

pavloko
pavloko

Reputation: 990

I'd suggest moving $watch function to the parent scope.

$scope.$watch('data', function(newVal, oldVal) {
    if (newVal !== oldVal) {
        // You know something is different.
        //  1. Find items that are different with loop, forEach, etc.
        //  2. Create changedPositiveItems and changedNegativeItems arrays
        //     with ids of changed items
    }
}, true);

Inside ng-repeat on the div you would have somethings like this.

<div animate-on-change 
     changed-positive="changedPositiveItems"
     changed-negative="changedNegativeItems"
     ng-class={'changed-positive-class': itemChangedPositive,
               'changed-negative-class': itemChangedNegative }>
     // content of item here
</div>

Inside directive

$scope.itemChangedPositive = $scope.changedPositive.indexOf(item.id) > -1;
$scope.itemChangedNegative = $scope.changedPositive.indexOf(item.id) > -1;

I might be missing details of your data, but I hope I could point you in the direction to think. We make the comparison in the outer scope and keep track of what has changed, which we pass to child scope, where we can check if that particular item has changed.

Also, note that $scope.$watch with second parameter true meaning watching object deeply might be costly if you have large data set. One work-around might be serializing your data and comparing it as strings, if strings are different, only then you perform costly comparison operation.

Upvotes: 1

Related Questions