Angularjs watch for change in parent scope

63,234

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/

Share:
63,234

Related videos on Youtube

wesbos
Author by

wesbos

Author of Sublime Text Book HTML5, JavaScript, Sublime Text, Node.js, CSS3 and all good things.

Updated on July 09, 2022

Comments

  • wesbos
    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
      Sharondio almost 11 years
      Pretty 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
      Mark Rajcok almost 11 years
      Since 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
      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
      trusktr about 10 years
      Aha! 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
      trusktr about 10 years
      Ok, so it's because directives like ngIf create child scopes, which is counter intuitive.
  • wesbos
    wesbos almost 11 years
    Hrm, 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
    wesbos almost 11 years
    Oh, the function as the first argument was it! I made a function that returned scope.$parent.data.overlaytype and it worked great. thanks :)
  • shaunhusain
    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
    Naor Biton almost 11 years
    You'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
    shaunhusain almost 11 years
    Thanks @NaorBiton as you said I was putting together a fiddle to show isolate scope.
  • shaunhusain
    shaunhusain almost 11 years
    Here'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
    Mark Rajcok almost 11 years
    Just 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
    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
    trusktr about 10 years
    I'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
    trusktr about 10 years
    Aha. 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
    trusktr about 10 years
    Ok, so it's because directives like ngIf create child scopes, which is counter intuitive.
  • Petr Peller
    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 use ngShow instead of the ngIf as a workaround.
  • ereOn
    ereOn almost 8 years
    Not sure why this wasn't more upvoted. It's simple and works perfectly.
  • Siva
    Siva over 7 years
    @DotDotDot do we have any performance improvement of using BROADCAST instead of child level WATCHERS ?
  • Siva
    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
    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
    a2f0 over 6 years
    👌 great solution +1 for usage of awesomeSauce
  • Toby Simmerling
    Toby Simmerling over 5 years
    Thank you, a nice clean approach.