Reputation: 35
I've stumbled upon an Angular issue that I just don't understand or know how to solve the problem.
I'd like to have a callback function located in a controller that a directive calls once something is done. The directive first alters some scope variable and runs the callback. The callback function then proceeds to do something with that same scope variable. This might seem confusing as why I'd want this but consider that the directive is generic and handles many different cases whereas the callback is a very specific function. It's important to note I CANNOT pass this variable as an argument as other code will need use of this scope variable as well.
Now onto the problem. I have everything working up til the callback is executed. It seems within the callback, the variable has yet to change. However, if a timeout is set (in my example, a single second) the variable is recognized as changed. Why does it have this behavior? Even in the directive before the callback is called, it shows that the variable is in fact changed. I've created a Codepen that demonstrates this issue. It simply toggles the button label from 'A' to 'B'. You'll notice though in the console log that when the variable is printed in the callback it is the 'old' value until I have waited.
Any info would be awesome, thanks!
NOTE: An idea I thought of was saving a copy of the scope variable as a local variable in the directive, and sending that local variable as an argument to the callback since the callback is currently the only function i care about that immediately knows the change of the variable and acts upon it.
Codepen: http://codepen.io/anon/pen/KayaRW
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-test="" button-label="myLabel" callback="cbFunc()">
</div>
</div>
JS:
angular
.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.myLabel = "A";
$scope.cbFunc = function() {
console.log("myLabel in callback: " + $scope.myLabel);
setTimeout(function() {
console.log("myLabel in callback, after 1 sec: " + $scope.myLabel);
console.log("");
}, 1000);
}
}).directive('ngTest', function() {
return {
scope: {
buttonLabel: '=',
callback: '&'
},
template: function(element, attrs) {
element.html("<button class=\"btn\" ng-click=\"changeLabel()\">Button Label: {{buttonLabel}}</button>");
},
link: function($scope, $element, $attrs) {
$scope.changeLabel = function() {
if ($scope.buttonLabel == "A") {
$scope.buttonLabel = "B";
} else {
$scope.buttonLabel = "A";
}
console.log("myLabel before callback: "+ $scope.buttonLabel);
$scope.callback();
}
}
}
});
Upvotes: 2
Views: 275
Reputation: 470
You have a few options. I believe your issue is happening because the directive is creating an isolated scope. I also changed the directive to be an element for readability:
1) Dont use the callback and your $scope.myLabel is already 2 way bound.
2) You can set your timeout to 0 and it still seems to work. I used the $timout service:
<div ng-app="myApp" ng-controller="myCtrl">
<ng-test button-label="myLabel" callback="cbFunc()"> </ng-test>
<p>
Here is the updated label in the timeout::::: {{updatedLabel}}
</p>
</div>
angular
.module('myApp', [])
.controller('myCtrl', function($scope, $timeout) {
$scope.myLabel = "A"; //inital value
$scope.cbFunc = function() {
$timeout(function() {
//$scope.myLabel is updated, $timeout is like an $apply but better
}, 0);
}
}).directive('ngTest', function() {
return {
restrict: 'E',
scope: {
buttonLabel: '=',
callback: '&'
},
template: function(element, attrs) {
element.html("<button class=\"btn\" ng-click=\"changeLabel()\">Button Label: {{buttonLabel}}</button>");
},
link: function($scope, $element, $attrs) {
$scope.changeLabel = function() {
if ($scope.buttonLabel == "A") {
$scope.buttonLabel = "B";
} else {
$scope.buttonLabel = "A";
}
$scope.callback();
}
}
}
});
3) Your callback could take the parameter and the directive would pass it back, which you said is not really an option.
<div ng-app="myApp" ng-controller="myCtrl">
<ng-test button-label="myLabel" callback="cbFunc(data)"> </ng-test>
<p>
Here is the updated label in the timeout::::: {{updatedLabel}}
</p>
</div>
angular
.module('myApp', [])
.controller('myCtrl', function($scope,$timeout) {
$scope.myLabel = 'A';
$scope.cbFunc = function(data){
$scope.updatedLabel = data;
}
}).directive('ngTest', function() {
return {
restrict: 'E',
scope: {
buttonLabel: '=',
callback: '&'
},
template: function(element, attrs) {
element.html("<button class=\"btn\" ng-click=\"changeLabel()\">Button Label: {{buttonLabel}}</button>");
},
link: function($scope, $element, $attrs) {
$scope.changeLabel = function() {
if ($scope.buttonLabel == "A") {
$scope.buttonLabel = "B";
} else {
$scope.buttonLabel = "A";
}
$scope.callback({data: $scope.buttonLabel});
}
}
}
});
4) $emit the object back up every time its updated, and the controller can listen for it. In your directive instead of calling callback, call
$scope.$emit('sudoCallback', $scope.buttonLabel);
and in your controller instead of the callback function you have
$scope.$on('sudoCallback', function(event, data) {$scope.updatedLabel = data });
I don't like this option because scope hierarchy can cause problems
5) use $watch in your controller for $scope.myLabel and get rid of the callback completely.
$scope.$watch('myLabel', function(newVal){
$scope.updatedLabel = newVal;
});
I don't like adding a bunch of watches but it works.
There is also a pretty cool library on github that acts as a message hub so you do not need to care about scope hierarchy and can just subscribe and publish. GitHub Angular Message Bus
Upvotes: 1