3

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.

Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
Mr H
  • 5,254
  • 3
  • 38
  • 43

2 Answers2

3

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>
Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
  • Thanks mate. This did the job. So why the `$watchCollection` on `steps` doesn't work? Even when I add the `true` flag to the `$watchCollection`. – Mr H Sep 10 '16 at 10:44
  • First of all [$watchCollectiol](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection) doesn't accept any third argument so passing anything there won't have any effect :) And the documentation says that `$watchCollection` **Shallow watches the properties of an object** so it won't work in cases – Shashank Agrawal Sep 10 '16 at 10:47
  • 1
    Hmmm. Thanks again. I think this question would be a duplicate of another questions. I might mark it now. cheers. – Mr H Sep 10 '16 at 10:48
  • Yes, it is a duplicate of the question I marked. Glad you found it duplicate :) – Shashank Agrawal Sep 10 '16 at 10:50
0

1) As caramiriel posted in comments, you can use $watch with function argument;

$watch(
  function(){
     var calculatedVal = /*make all required checks*/;
     return calculatedVal;
  },
  function(newVal){
     $scope.canExit = true;
  }
);

2) Better option is to avoid watch completely and use events/callbacks such ng-change; It's more perfomant and easier to read;

Valery Kozlov
  • 1,557
  • 2
  • 11
  • 19