Strange Angular $ watch behavior

Today I come across some really weird behavior in AngularJS using $ watch. I simplified my code in the following example:

https://jsfiddle.net/yLeLuard/

This example contains a service that will track the state variable. Directives are used to bind the click event to the DOM, which modifies the state variable through the service.

There are two problems in this example:

  • The first close button (with the ng-click property on it) changes only the state on the second click
  • Two buttons without ng-click do not change 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 }; }); 
+5
source share
2 answers

This is because you are using your own event listener on click . This event is asynchronous and out of the Angular digest cycle, so you need to manually digest the area.

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

Fiddle fixed: https://jsfiddle.net/7h95ct1y/

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

+4
source

You should put your link function as a link function, this solves the second click problem.

https://jsfiddle.net/2c9pv4xm/

 myApp.directive('close', ['state', function(state) { return { restrict: 'A', scope: {}, link:{ pre: function(scope, element) { element.on('click', function() { state.isOpen = false; console.log('click', state); }); } } }; }]); 

As for the non-working part, you put your directive in a div, so when you click on a div, it works, but not on the button. Your open directive should be on the button.

Edit: Other commentators suggested you change the state directly in ng-click. I would not recommend it, it could work in this case, but if you have more than an appointment, to make it unviable.

0
source

All Articles