changing a controller scope variable in a directive is not reflected in controller function
Solution 1
The reason why it does not show the updated value immediately is because the 2 way binding updates the parent (or the consumer scope of the directive) scope's bound value only during the digest cycle. Digest cycle happens after the ng-click is triggered. And hence $scope.page
in the controller is not yet updated. You can get around this in many ways by using a timeout
which will defer the action to run at the end of the digest cycle. You could also do it by setting an object which holds the value as 2-way bound property. Since 2-way bound property and parent scope share the same object reference you will see the change immediately.
Method 1 - using a timeout:
scope.incrementPage = function() {
scope.page += 1;
$timeout(scope.alertPage)
}
Method 2 - Bind an object:
//In your controller
$scope.page2 = {value:1};
//In your directive
scope.incrementPage = function() {
scope.page.value += 1;
scope.alertPage();
}
Method3 - Pass the value using function binding with argument:
//In your controller
$scope.alertPage = function(val) {
alert(val);
}
and
<!--In the view-->
<div incrementer page="page" alert-page="alertPage(page)"></div>
and
//In the directive
scope.incrementPage = function() {
scope.page += 1;
scope.alertPage({page:scope.page});
}
app = angular.module('app', []);
app.controller('myCtrl', function($scope) {
$scope.page = 1;
$scope.page2 = {value:1};
$scope.alertPage = function() {
alert($scope.page);
}
$scope.alertPage2 = function() {
alert($scope.page2.value);
}
})
app.directive('incrementer', function($timeout) {
return {
scope: {
page: '=',
alertPage: '&',
page2:"=",
alertPage2: '&'
},
template: '<button ng-click="incrementPage()">increment page</button>',
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page += 1;
scope.page2.value += 1;
$timeout(function(){ scope.alertPage() });
scope.alertPage2();
}
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<div ng-app="app" ng-controller="myCtrl">
<div incrementer page="page" alert-page="alertPage()" page2="page2" alert-page2="alertPage2()"></div>
</div>
Solution 2
You can pass the variable by reference, then the update will be immediate (because you wont copy it, but simply pass its location in memory).
View:
<incrementer page="data" alert-page='alertPage()'></incrementer>
Directive:
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page.page += 1;
scope.alertPage();
}
Solution 3
The problem is that the timeline is off.
- button clicked, incrementPage()
- directive scope value incremented (now 2)
- alertPage() parent scope value read (still 1)
- parent scope updated as part of digest (now 2)
To get around this, you need to either call the alert function after the digest cycle (e.g. $timeout
) or you need to watch for changes in the parent scope.
// in controller
$scope.$watch('page', function (currentValue, previousValue) {
// initially triggered with same value
if (currentValue > previousValue) {
alert(currentValue)
}
})
Then change the value naturally.
// in directive html
<button ng-click="page = page + 1">
Related videos on Youtube
sq1020
Updated on September 16, 2022Comments
-
sq1020 over 1 year
In my directive, I have a controller variable, page which gets incremented when you press the button in the directive. However, the next line,
scope.alertPage()
which calls the controller function does not reflect this change. Notice, when you click the button page is still alerted as 1!I know I can fix this by adding $scope.$apply in the controller but then I get the error that says a digest is already taking place.
app = angular.module('app', []); app.controller('myCtrl', function($scope) { $scope.page = 1; $scope.alertPage = function() { alert($scope.page); } }) app.directive('incrementer', function() { return { scope: { page: '=', alertPage: '&' }, template: '<button ng-click="incrementPage()">increment page</button>', link: function(scope, elem, attrs) { scope.incrementPage = function() { scope.page += 1; scope.alertPage(); } } } })
html template:
<body ng-app='app' ng-controller='myCtrl'> page is {{page}} <incrementer page='page' alert-page='alertPage()'></incrementer> </body>
-
PSL over 9 yearsPlace it in a $timeout or bind 2 way to an object with the property. Since it is 2 way binding, the original 2 way bound value will be updated only during the next digest cycle. i.e try
$timeout(scope.alertPage)
or another way you could try in your controller set$scope.page = {value : 1}
and in the directive do$scope.page += 1;
and rest whatever you are doing
-
-
PSL over 9 yearsReally create a watch for this? The purpose of function binding is completely lost here IMHO. I am sure example pasted to increment a number is just a mere example, original code must be more complex than that.
-
PSL over 9 yearsYeah similar to method 2 in my example.. Thinking alike.. Cheers.. :)
-
AJcodez over 9 years@PSL i think $watch is an elegant solution. Using $timeout triggers a whole-new digest cycle afaik.
-
PSL over 9 yearsNot really in this case there will be a digest cycle for ngclick timeout will add it's own to the asyc queue which will run along with the same digest cycle.. also you shouldn't just be creating watches.. it is really unnecessary in this case and will add up to the digest cycle.