Setting $scope.$watch on each element in an array

All we need is an easy explanation of the problem, so here it is.

I am trying to figure out how to set a $scope.$watch on each element in an array. I only care about certain attributes of each element.

In my example, each rental_date object has three attributes: date, start_time, and stop_time. Whenever the start_time is changed, I want the stop_time to be set to 2 hours after the start_time.

The $ngResource call (using Angular 1.1.5):

Agreement.show( 
  id: 5 
).$then ((success) ->
  $scope.agreement = success.data.agreement

  # returns an array of rental dates
  $scope.rental_dates = success.data.rental_dates

  # $watch goes here

Here are the four variations of the $watch function I tried:

1:

angular.forEach $scope.rental_dates, (date, pos) ->
  $scope.$watch date.start_time, ((newval, oldval) ->
    $log.info 'watch changed'
    $log.info newval
    $log.info oldval
  ), true

2:

angular.forEach $scope.rental_dates, (date, pos) ->
  $scope.$watch $scope.rental_dates[pos].start_time, ((newval, oldval) ->
    $log.info 'watch changed'
    $log.info newval
    $log.info oldval
  ), true

3:

angular.forEach $scope.rental_dates, (date, pos) ->
  $scope.$watch 'rental_dates[pos].start_time', ((newval, oldval) ->
    # also tried '$scope.rental_dates[pos].start_time'

    $log.info 'watch changed'
    $log.info newval
    $log.info oldval
  ), true

4:

This does work, but, at the moment, I can’t think of an elegant solution to access only the values I care about $watching instead of the whole array.

$scope.$watch 'rental_dates', ((newval, oldval) ->
  $log.info 'watch changed'
  $log.info newval
  $log.info oldval
), true

Has anybody done something similar in their own projects?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I am assuming you’re somehow using ng-repeat to generate UI elements for each entry of the array? Assign an ng-controller to each of these UI elements. The controller will be instantiated for each array element. Inside that controller, use $watch.

<div ng-repeat="rentalDate in rental_dates" ng-controller="RentalDateController">
</div>

And then in your controller code:

angular.module(...).controller('RentalDateController', function ($scope) {
    $scope.$watch('rentalDate.start_time', function (startTime) {
        ...
    });
});

Here is a little example: http://plnkr.co/edit/lhJ3MbULmahzdehCxVeM?p=preview

Method 2

I think solution number 3 will work if you change the first $watch argument to: 'rental_dates['+pos+'].start_time' ($watch takes an angular expression as the first parameter, and pos isn’t part of your scope).

(While this should work, I think this won’t be really efficient. Maybe you should look into updating both start_time and stop_time at the same time instead of watching for changes on all the start times.)

Method 3

If you want to watch on a specific field in an object that is part of a collection and if you have the collection’s items iterated in the view by ng-repeat and if you have the field to be watched bound to a control that can have ng-change, you can always handle the watching aspect using the ng-change handler. eg:

 <div ng-repeat="binding in bindings track by $index">
     <input ng-model="binding" ng-change="changed(binding)">
 </div>

and in js:

 scope.changed = function(binding){
      .....
 }

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply