Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour of Angular's $watch

Tags:

Today I encounter some really strange behaviour in AngularJS using $watch. I simplified my code to the following example:

https://jsfiddle.net/yLeLuard/

This example contains a service which will keep track of a state variable. The directives are used to bind a click event to the DOM changing the state variable through the service.

There are two problems in this example:

  1. The first close button (with the ng-click property on it) only changes the state on the second click
  2. The two buttons without the ng-click are not changing the state at all

main.html

<div ng-controller="main">
  State: {{ api.isOpen | json }}
  <div ng-click="" open>
    <button>Open - Working fine</button>
  </div>
  <div ng-click="" close>
    <button>Close - Works, but only on second click</button>
  </div>
  <div open>
    <button>Open - Not Working</button>
  </div>
  <div close>
    <button>Close - Not Working</button>
  </div>
</div>

main.js

var myApp = angular.module('myApp', []);

myApp.controller('main', ['$scope', 'state', function($scope, state) {
  $scope.api = state;

  $scope.api.isOpen = false;

  $scope.$watch('api.isOpen', function() {
    console.log('state has changed');
  });
}]);

myApp.directive('open', ['state', function(state) {
  return {
    restrict: 'A',
    scope: {},
    replace: true,
    template: '<button>Open</button>',
    link: function(scope, element) {
      element.on('click', function() {
        state.isOpen = true;
      });
    }
  };
}]);


myApp.directive('close', ['state', function(state) {
  return {
    restrict: 'A',
    scope: {},
    replace: true,
    template: '<button>Close</button>',
    link: function(scope, element) {
      element.on('click', function() {
        state.isOpen = false;
      });
    }
  };
}]);

myApp.service('state', function() {
  return {
    isOpen: null
  };
});
like image 286
Pascal Avatar asked Apr 29 '16 13:04

Pascal


1 Answers

That's because you are using a native event listener on click. This event is asynchronous and out of the Angular digest cycle, so you need to manually digest the scope.

myApp.directive('open', ['state', function(state) {
  return {
    restrict: 'A',
    scope: {},
    link: function(scope, element) {
      element.on('click', function() {
        scope.$apply(function() {
          state.isOpen = true;
        });
      });
    }
  };
}]);

Fixed fiddle: https://jsfiddle.net/7h95ct1y/

I would suggest changing the state directly in the ng-click: ng-click="api.isOpen = true"

like image 70
floribon Avatar answered Sep 28 '22 01:09

floribon