Angularjs watch for change in parent scope
Solution 1
You should have the data property on your child scope, scopes use prototypal inheritance between parent and child scopes.
Also, the first argument the $watch method expects is an expression or a function to evaluate and not a value from a variable., So you should send that instead.
Solution 2
If you want to watch a property of a parent scope you can use $watch
method from the parent scope.
//intead of $scope.$watch(...)
$scope.$parent.$watch('property', function(value){/* ... */});
EDIT 2016: The above should work just fine, but it's not really a clean design. Try to use a directive or a component instead and declare its dependencies as bindings. This should lead to better performance and cleaner design.
Solution 3
I would suggest you to use the $broadcast between controller to perform this, which seems to be more the angular way of communication between parent/child controllers
The concept is simple, you watch the value in the parent controller, then, when a modification occurs, you can broadcast it and catch it in the child controller
Here's a fiddle demonstrating it : http://jsfiddle.net/DotDotDot/f733J/
The part in the parent controller looks like that :
$scope.$watch('overlaytype', function(newVal, oldVal){
if(newVal!=oldVal)
$scope.$broadcast('overlaychange',{"val":newVal})
});
and in the child controller :
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//any other action can be perfomed here
});
Good point with this solution, if you want to watch the modification in another child controller, you can just catch the same event
Have fun
Edit : I didn't see you last edit, but my solution works also for the directive, I updated the previous fiddle ( http://jsfiddle.net/DotDotDot/f733J/1/ )
I modified your directive to force it to create a child scope and create a controller :
directive('center',function($window){
return {
restrict : "A",
scope:true,
controller:function($scope){
$scope.overlayChanged={"isChanged":"No","value":""};
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//whatever you need to do
});
},
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
Solution 4
If you're looking for watching a parent scope variable inside a child scope, you can add true
as second argument on your $watch
. This will trigger your watch every time your object is modified
$scope.$watch("searchContext", function (ctx) {
...
}, true);
Solution 5
Alright that took me a while here's my two cents, I do like the event option too though:
Updated fiddle http://jsfiddle.net/enU5S/1/
The HTML
<div ng-app="myApp" ng-controller="MyCtrl">
<input type="text" model="model.someProperty"/>
<div awesome-sauce some-data="model.someProperty"></div>
</div>
The JS
angular.module("myApp", []).directive('awesomeSauce',function($window){
return {
restrict : "A",
template: "<div>Ch-ch-ch-changes: {{count}} {{someData}}</div>",
scope: {someData:"="},
link : function(scope,elem,attrs){
scope.count=0;
scope.$watch("someData",function() {
scope.count++;
})
}
};
}).controller("MyCtrl", function($scope){
$scope.model = {someProperty: "something here");
});
What I'm showing here is you can have a variable that has two way binding from the child and the parent but doesn't require that the child reach up to it's parent to get a property. The tendency to reach up for things can get crazy if you add a new parent above the directive.
If you type in the box it will update the model on the controller, this in turn is bound to the property on the directive so it will update in the directive. Within the directives link function it has a watch setup so anytime the scope variable changes it increments a counter.
See more on isolate scope and the differences between using = @ or & here: http://www.egghead.io/
Related videos on Youtube
wesbos
Author of Sublime Text Book HTML5, JavaScript, Sublime Text, Node.js, CSS3 and all good things.
Updated on July 09, 2022Comments
-
wesbos almost 2 years
I'm writing a directive and I need to watch the parent scope for a change. Not sure if I'm doing this the preferred way, but its not working with the following code:
scope.$watch(scope.$parent.data.overlaytype,function() { console.log("Change Detected..."); })
This it logged on window load, but never again, even when overlaytype is changed.
How can I watch
overlaytype
for a change?Edit: here is the entire Directive. Not entirely sure why I'm getting a child scope
/* Center overlays vertically directive */ aw.directive('center',function($window){ return { restrict : "A", link : function(scope,elem,attrs){ var resize = function() { var winHeight = $window.innerHeight - 90, overlayHeight = elem[0].offsetHeight, diff = (winHeight - overlayHeight) / 2; elem.css('top',diff+"px"); }; var watchForChange = function() { return scope.$parent.data.overlaytype; } scope.$watch(watchForChange,function() { $window.setTimeout(function() { resize(); }, 1); }) angular.element($window).bind('resize',function(e){ console.log(scope.$parent.data.overlaytype) resize(); }); } }; });
-
Sharondio almost 11 yearsPretty sure you can't watch anything outside of local $scope. But you can put the $watch in the parent $scope and $scope.$broadcast the change down to the child $scope.
-
Mark Rajcok almost 11 yearsSince your directive does not create a new scope, you should be able to
scope.$watch('data.overlaytype', ...)
. Here's a fiddle that shows this working. If you think you need to use$parent
, then we'd need to see more of your HTML to determine if a child or isolate scope is being created by some other directive in the HTML. -
trusktr about 10 years@Sharondio But my child controller behaves by extending the parent controller. I don't want to write the $watch in the parent. I want to extend the parent so the parent doesn't need to explicitly have a $watch defined for a case that is specific to the child $scope. I wonder how to do this. FOr example, the child scope depends on the changes it sees in the parent scope, but this isn't always the case as other children don't necessarily depend on it, and I don't want to code a child-specific behavior into the parent. I want to keep things modular, with the child-specific behavior in the child.
-
trusktr about 10 yearsAha! Petr Peller's answer seems like the solution, but it still won't work for me. I'll post back when I find out for others who might stumble on this.
-
trusktr about 10 yearsOk, so it's because directives like
ngIf
create child scopes, which is counter intuitive.
-
-
wesbos almost 11 yearsHrm, I don't have the data property at all. Not even sure why I have a child scope within this directive, the div is inside the normal scope. Any ideas?
-
wesbos almost 11 yearsOh, the function as the first argument was it! I made a function that returned scope.$parent.data.overlaytype and it worked great. thanks :)
-
shaunhusain almost 11 years@Wes you shouldn't refer to $parent. You should instead make a variable that you can pass through the parent's scope's model or some property of it. If you show your whole directive this answer can be edited to include how you should pass through the object.
-
Naor Biton almost 11 yearsYou're probably using isolate scope. You can pass in the value you need from the parent scope via attribute, I'll edit the answer with complete details.
-
shaunhusain almost 11 yearsThanks @NaorBiton as you said I was putting together a fiddle to show isolate scope.
-
shaunhusain almost 11 yearsHere's a prelim fiddle that is getting at the point but not functional, I'll update with a simpler example that functions. jsfiddle.net/EMPPy
-
Mark Rajcok almost 11 yearsJust FYI, you don't need to define a function to watch a scope property, and you don't need
true
as the third parameter (that's what the fiddle has). So, you can simply write:scope.$watch('someData',function() { scope.count++; });
. -
shaunhusain almost 11 years@MarkRajcok ah yeah I was using the wrong property name at first and wasn't seeing the updates, will fix my fiddle. Thx
-
trusktr about 10 yearsI'm trying this, but I'm trying to watch for changes to an array, so i'm using
true
as the third parameter for deep watch, but still nothing happens a child scope modifies the array of the parent scope. Hmmm.... -
trusktr about 10 yearsAha. For some yet-to-be-explained reason, the app is mysteriously creating an extra scope between my intended parent and child, so I have to do
$scope.$parent.$parent.$watch('property', function(value){/* ... */});
. I'll comment when I find out why. -
trusktr about 10 yearsOk, so it's because directives like
ngIf
create child scopes, which is counter intuitive. -
Petr Peller about 10 years@trusktr Yeah, many directives create a child scope. You can see that these directives usually add
ng-scope
class to its element. You can usengShow
instead of thengIf
as a workaround. -
ereOn almost 8 yearsNot sure why this wasn't more upvoted. It's simple and works perfectly.
-
Siva over 7 years@DotDotDot do we have any performance improvement of using BROADCAST instead of child level WATCHERS ?
-
Siva over 7 years@PetrPeller I have seen this type of parent level watchers on my project, usage of the watchers on parent level data inside child directive controller is causing my page taking long to get rendered, I am able to see lot of difference in loading time if I simply remove the parent level watchers, do you have any suggestions to solve the performance issues with this approach?
-
Petr Peller over 7 years@Siva I think it's best to avoid these kind of watchers. You should be able to use a directive or component instead of a controller and pass the property from the parent via binding. This should not only improve performance but also make the code easier to understand.
-
a2f0 over 6 years👌 great solution +1 for usage of
awesomeSauce
-
Toby Simmerling over 5 yearsThank you, a nice clean approach.