42

Consider this Plnkr for example. I don't know how many members of fooCollection will be created beforehand. So I don't know how many bar models are going to exist.

But I know they are going to be angular models, and I know where they are going to be.

How do I do a $watch on these?

I need to do that because I need to trigger behavior when a bar model is changed. Watching the fooCollection itself is not enough, the $watch listener does not fire when a bar is changed.

Relevant html:

<body ng-controller="testCtrl">
  <div ng-repeat="(fooKey, foo) in fooCollection">
    Tell me your name: <input ng-model="foo.bar">
    <br />
    Hello, my name is {{ foo.bar }}
  </div>
  <button ng-click="fooCollection.push([])">Add a Namer</button>
</body>

Relevant JS:

angular
.module('testApp', [])
.controller('testCtrl', function ($scope) {
  $scope.fooCollection = [];

  $scope.$watch('fooCollection', function (oldValue, newValue) {
    if (newValue != oldValue)
      console.log(oldValue, newValue);
  });
});
Kees de Kooter
  • 7,078
  • 5
  • 38
  • 45
Aditya M P
  • 5,127
  • 7
  • 41
  • 72

6 Answers6

34

Create individual list-item controllers: demo on Plnkr

js

angular
  .module('testApp', [])
  .controller('testCtrl', function ($scope) {
    $scope.fooCollection = [];
  })
  .controller('fooCtrl', function ($scope) {
    $scope.$watch('foo.bar', function (newValue, oldValue) {
      console.log('watch fired, new value: ' + newValue);
    });
  });

HTML

<html ng-app="testApp">
  <body ng-controller="testCtrl">
    <div ng-repeat="(fooKey, foo) in fooCollection" ng-controller="fooCtrl">
      Tell me your name: <input ng-model="foo.bar" ng-change="doSomething()">
      <br />
      Hello, my name is {{ foo.bar }}
    </div>
    <button ng-click="fooCollection.push([])">Add a Namer</button>
  </body>
</html>
Venkat.R
  • 7,420
  • 5
  • 42
  • 63
kapv89
  • 1,692
  • 1
  • 17
  • 20
  • Wow. I need to think more in terms of controllers being first-class citizens in angular, for attaching behavior to *any* scope. – Aditya M P Nov 16 '13 at 23:01
  • 6
    Just a comment to remind users to NEVER link *only* to an external website. You **must** give the code in the answer body. I have the same problem as OP, an plnkr link is down, so this answer is useless :( – Byscripts Jul 17 '14 at 07:54
  • This does not answer the original question: how to put a watch on items in a ng-repeat collection. What would be the $watch code in this nested controller? – Kees de Kooter Jul 14 '15 at 09:48
  • Copied the relevant code from the plunkr so answers become useful again ;-). – Kees de Kooter Jul 14 '15 at 09:53
  • Could someone please explain why two controllers are needed? – stackPusher Feb 09 '17 at 06:27
9

If you have your collection populated, you can place a watch on each item of the ng-repeat:

html

<div ng-repeat="item in items">
   {{ item.itemField }}
</div>

js

for (var i = 0; i < $scope.items.length; i++) {
   $scope.$watch('items[' + i + ']', function (newValue, oldValue) {
      console.log(newValue.itemField + ":::" + oldValue.itemField);
   }, true);
}
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Marcel
  • 7,909
  • 5
  • 22
  • 25
5

You can pass true as third argument into $watch

$scope.$watch('something', function() { doSomething(); }, true);

https://docs.angularjs.org/api/ng/type/$rootScope.Scope

v-tec
  • 586
  • 6
  • 6
3

You can also create a custom directive that will tell your main controller for the changes

YourModule.directive("batchWatch",[function(){
return {
    scope:"=",
    replace:false,
    link:function($scope,$element,$attrs,Controller){
        $scope.$watch('h',function(newVal,oldVal){
            if(newVal !== oldVal){
              Controller.updateChange(newVal,oldVal,$scope.$parent.$index);
            }

        },true);

    },
    controller:"yourController"
};
}]);

assume your markup is like this

<ul>
  <li ng-repeat="h in complicatedArrayOfObjects">
      <input type="text" ng-model="someModel" batch-watch="$index" />
  </li>
</ul>

and this is your controller

YourModule.controller("yourController",[$scope,function($scope){
    this.updateChange = function(newVal,oldVal,indexChanged){
      console.log("Details about the change");
    }
}]);

You can also play around the value provided by the directive link function which sits on first 3 arguments, scope,element and attr.

Jomaf
  • 91
  • 1
  • 4
2

Since I didn't want another controller I ended up using ng-change instead.

Simple jsFiddle: https://jsfiddle.net/maistho/z0xazw5n/

Relevant HTML:

<body ng-app="testApp" ng-controller="testCtrl">
    <div ng-repeat="foo in fooCollection">Tell me your name:
        <input ng-model="foo.bar" ng-change="fooChanged(foo)">
        <br />Hello, my name is {{foo.bar}}</div>
    <button ng-click="fooCollection.push({})">Add a Namer</button>
</body>

Relevant JS:

angular.module('testApp', [])
    .controller('testCtrl', function ($scope) {
    $scope.fooCollection = [];

    $scope.fooChanged = function (foo) {
        console.log('foo.bar changed, new value of foo.bar is: ', foo.bar);
    };
});
Maistho
  • 23
  • 3
0

Try to do this

 <div ng-repeat="foo in fooCollection" ng-click="select(foo)">Tell me your ame:
        <input ng-model="foo.bar" ng-change="fooChanged(foo)">
        <br />Hello, my name is {{foo.bar}}</div>
        <button ng-click="fooCollection.push({})">Add a Namer</button>
 </div>

There is the code in Directive/Controller

 $scope.selectedfoo = {};
 $scope.select = (foo) => {
    $scope.selectedfoo = foo;
 }

 $scope.$watch('selectedfoo ', (newVal, oldVal) => {
  if (newVal) {

  }
 },true)