In above example when I change the value array passed to directive from controller, all the changes gets reflected in the directive html. I mean I can see the changes in the UI.
But changes in the value of $scope.message
variable doesn’t get reflected, even though the value of $scope.message
is being calculated from the value of $scope.myData
, whose value is getting changed using $timeout
in the parent controller
To see those changes in $scope.message
, you need to watch the array using $watchCollection
. My questions are,
$scope.myData
normally?Below is the code snippet
(function(){
angular.module("csjoshi04.2waybinding",[])
.controller("ParentCtrl",["$scope", "$timeout", function($scope, $timeout){
$scope.myCars = ["Ford", "BMW", "Toyata"];
$timeout(function(){
$scope.myCars.push("Honda");
}, 3000);
}])
.directive("showMyData",function(){
return {
restrict: "E",
scope: {
myData : "="
},
controller : ["$scope", function($scope){
$scope.message = ($scope.myData.indexOf("Honda") > -1 && $scope.myData.length >= 4) ? "1 out of 4 cars is always Honda": "OOPS, no honda cars";
}],
template : '<div>{{message}}</div><ul ng-repeat="data in myData"><li>{{data}}</li></ul>'
}
})
})()
Below is html
<body ng-controller="ParentCtrl"><show-my-data my-data="myCars" ></show-my-data></body>
To make above directive work, I made below changes
directive("showMyData",function(){
return {
restrict: "E",
scope: {
myData : "="
},
controller : ["$scope", function($scope){
$scope.message = ($scope.myData.indexOf("Honda") > -1 && $scope.myData.length >= 4) ? "1 out of 4 cars is always Honda": "OOPS, no honda cars";
$scope.$watchCollection(function(){
return $scope.myData;
}, function(new1, old){
$scope.message = ($scope.myData.indexOf("Honda") > -1 && $scope.myData.length >= 4) ? "1 out of 4 cars is always Honda": "OOPS, no honda cars";
});
}],
template : '<div>{{message}}</div><ul ng-repeat="data in myData"><li>{{data}}</li></ul>'
}
})
here is the link to plunkr.
I wouldn't call this a "corner case" of the data binding in angular.
Angular's data-binding works by continuously checking the values attached to $scope. It's cool but it's not magic, in an imperative language like JavaScript statement
$scope.message = condition ? "message": "another message";
will not in itself suggest that the expression would be re-evaluated ever again. Angular initializes the controller only once per attachment to template and that is exactly how many times the statement in the above example will get evaluated. Once. After that it's just another $scope
variable whose value is either "message" or "another message" and that's how it's going to stay if we don't change it ourself.
You have identified the problem yourself. The value of $scope.message
depends on $scope.myData
but without the $watchCollection
there is nothing to keep this linkage up to date. $watchCollection
, on the other hand, will keep re-evaluating first the $scope.myData;
and then the $scope.message = ...
functions because that's what angular makes it to do.
Personally, though, I'd do it with a function getter instead of assigning the value to $scope
and manually keeping it up to date. That's closer to what you were originally trying to do anyway.
.directive("showMyData",function(){
return {
restrict: "E",
scope: {
myData : "="
},
controller : ["$scope", function($scope){
$scope.getMessage = function(){
return ($scope.myData.indexOf("Honda") > -1 && $scope.myData.length >= 4) ? "1 out of 4 cars is always Honda": "OOPS, no honda cars";
};
}],
template : '<div>{{getMessage()}}</div><ul ng-repeat="data in myData"><li>{{data}}</li></ul>'
}
})
Now that we are using a function call instead of a simple value, the expression that creates the message gets re-evaluated over and over again, updating the view when necessary.