Mr H Mr H - 2 months ago 19
AngularJS Question

Deep $watch a collection in AngularJS

Here is my object:

$scope.steps = {
service: {
selected_service: '',
selected_month: '',
selected_year: '',
selected_day: ''
},
payment: {
selected_dd_type: '',
cc: {
selected_card_type: '',
credit_card_number: '',
name_on_card: '',
expiry_month: '',
expiry_year: ''
},
bank_acc: {
bsb_number: '',
account_number: '',
account_holder_name: ''
}
},
agreement: {
agreement_acceptance: false
}
}


Here are the
$watchCollection
:

$scope.$watchCollection('steps.service', function(newVal, oldVal) {
$scope.canExit = true;
});

$scope.$watchCollection('steps.payment', function(newVal, oldVal) {
$scope.canExit = true;
}, true);

$scope.$watchCollection('steps.payment.cc', function(newVal, oldVal) {
$scope.canExit = true;
}, true);

$scope.$watchCollection('steps.payment.bank_acc', function(newVal, oldVal) {
$scope.canExit = true;
}, true);

$scope.$watchCollection('steps.agreement', function(newVal, oldVal) {
$scope.canExit = true;
}, true);


Everything is working fine.

There must be a better way to
$watch
the
$scope.steps
object.


If I
$watch
steps
it is not going to work. I guess Angular can't watch deep enough

$scope.$watchCollection('steps', function(newVal, oldVal) {
$scope.canExit = true;
}, true);


Thank you for your guidance.

Answer

Here you go. Pass the value true as the third option in the $watch:

$scope.$watch('steps', function(newValue, oldValue) {
     // callback on deep watch
     $scope.canExit = true;
}, true);

Also, make sure you add an if condition inside the watch since watch will also be triggered when you assign the value to $scope.steps.

Working example:

var app = angular.module("sa", []);

app.controller("FooController", function($scope) {
  $scope.canExit = false;

  $scope.steps = {
    service: {
      selected_service: '',
      selected_month: '',
      selected_year: '',
      selected_day: ''
    },
    payment: {
      selected_dd_type: '',
      cc: {
        selected_card_type: '',
        credit_card_number: '',
        name_on_card: '',
        expiry_month: '',
        expiry_year: ''
      },
      bank_acc: {
        bsb_number: '',
        account_number: '',
        account_holder_name: ''
      }
    },
    agreement: {
      agreement_acceptance: false
    }
  };

  $scope.reset = function() {
    $scope.canExit = false;
  }

  // Using Math.random() just for demo so that the watch can be invoked
  $scope.changeService = function() {
    $scope.steps.service.selected_service = Math.random();
  }

  $scope.changeDate = function() {
    $scope.steps.payment.cc.selected_card_type = Math.random();
  }

  $scope.changeAgreement = function() {
    $scope.steps.agreement.agreement_acceptance = Math.random();
  }

  $scope.$watch('steps', function(newValue, oldValue) {
    if (newValue && (newValue !== oldValue)) {
      // callback on deep watch
      $scope.canExit = true;
    }
  }, true);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="sa" ng-controller="FooController">
  <button ng-click="changeService()">Change service</button>
  <button ng-click="changeDate()">Change expiry date</button>
  <button ng-click="changeAgreement()">Change agreement</button>
  <br>
  <br>
  <br>Can exit: {{canExit}}
  <button ng-click="reset()">Reset</button>
</div>

Comments