AngularJS监听父作用域的变化

45

我正在编写一个指令,需要监视父级作用域的变化。不确定我是否使用了首选方法,但以下代码无法工作:

  scope.$watch(scope.$parent.data.overlaytype,function() {
    console.log("Change Detected...");
  })

这段代码在窗口加载时记录,但即使更改了overlaytype,也不会再次记录。

我该如何监视overlaytype的变化?

编辑:这是整个指令。我不太确定为什么会得到一个子作用域。

/* 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();
      });
    }
  };
});

我相信你不能在本地 $scope 之外观察任何东西。但是你可以将 $watch 放在父级 $scope 中,并通过 $scope.$broadcast 将更改传递到子级 $scope。 - Sharondio
1
由于您的指令不创建新作用域,因此您应该能够使用scope.$watch('data.overlaytype',...)。这里有一个示例fiddle,展示了这种方法的工作方式。如果您认为需要使用$parent,那么我们需要查看更多HTML代码以确定其他指令是否在HTML中创建了子作用域或隔离作用域。 - Mark Rajcok
@Sharondio 但是我的子控制器通过扩展父控制器来运作。我不想在父控制器中编写$watch。我想要扩展父控制器,这样父控制器就不需要为特定于子$scope的情况显式定义$watch了。例如,子$scope依赖于它在父$scope中看到的更改,但并非总是如此,因为其他子项不一定依赖于它,并且我不想将特定于子项的行为编码到父项中。我希望保持模块化,将特定于子项的行为放在子项中。 - trusktr
啊哈! Petr Peller的回答似乎是解决方案,但对我仍然不起作用。我会在找到答案后再回来发布,以便其他可能遇到类似问题的人参考。 - trusktr
好的,这是因为像 ngIf 这样的指令会创建子作用域,这是违反直觉的。 - trusktr
5个回答

76
如果您想观察父作用域的属性,可以使用父作用域中的$watch方法。
//intead of $scope.$watch(...)
$scope.$parent.$watch('property', function(value){/* ... */});

编辑 2016: 以上方法应该可以正常工作,但并不是一个优秀的设计。尝试使用 directivecomponent 替代,并将其依赖项声明为绑定。这样可以提高性能并使设计更加清晰。


我正在尝试这个,但是我想要监视一个数组的变化,所以我使用true作为深度监视的第三个参数,但是当一个子作用域修改父作用域的数组时仍然没有任何反应。嗯... - trusktr
啊哈。由于某些尚未解释的原因,该应用程序神秘地在我预期的父级和子级之间创建了一个额外的作用域,所以我必须执行 $scope.$parent.$parent.$watch('property', function(value){/* ... */});。我找到原因后会进行注释。 - trusktr
1
好的,这是因为像 ngIf 这样的指令会创建子作用域,这是违反直觉的。 - trusktr
@trusktr,许多指令会创建一个子作用域。你可以看到这些指令通常会在其元素上添加ng-scope类。你可以使用ngShow代替ngIf来解决问题。 - Petr Peller
@PetrPeller 我在我的项目中见过这种类型的父级别监视器。将父级别数据上的监视器用于子指令控制器内会导致页面渲染时间变长。如果我删除父级别监视器,我能够看到加载时间有很大的差异。你有任何解决这种方法性能问题的建议吗? - Siva
@Siva 我认为最好避免使用这种观察者。你应该能够使用指令或组件代替控制器,并通过绑定从父级传递属性。这不仅可以提高性能,还可以使代码更易于理解。 - Petr Peller

23

我建议您在控制器之间使用$broadcast来执行此操作,这似乎是父/子控制器之间通信的更加angular的方式。

概念很简单,您可以在父控制器中监视值,然后当发生修改时,您可以广播它并在子控制器中捕获它。

这里有一个演示它的fiddle:http://jsfiddle.net/DotDotDot/f733J/

在父控制器中的部分如下:

$scope.$watch('overlaytype', function(newVal, oldVal){
    if(newVal!=oldVal)
        $scope.$broadcast('overlaychange',{"val":newVal})
});

并且在子控制器中:

$scope.$on('overlaychange', function(event, args){
    console.log("change detected")
    //any other action can be perfomed here
});

使用这种解决方案,如果您想在另一个子控制器中观看修改,只需捕获相同的事件即可

祝愉快

编辑:我没有看到你最后一次编辑,但我的解决方案也适用于指令,在先前的 fiddle 中进行了更新(http://jsfiddle.net/DotDotDot/f733J/1/

我修改了您的指令,强制其创建一个子作用域并创建一个控制器:

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();
      });
    }
  };
});

@DotDotDot 我们使用BROADCAST而不是子级WATCHERS有任何性能改进吗? - Siva
谢谢,这是一个不错的干净方法。 - Toby Simmerling

7

如果您想在子作用域中查看父作用域变量,可以在$watch的第二个参数中添加true。这将在每次修改对象时触发您的监视器。

$scope.$watch("searchContext", function (ctx) {
                ...
            }, true);

不确定为什么这个没有更多的赞。它很简单,而且完美地工作。 - ereOn

7

你的子作用域应该有 data 属性,作用域之间使用原型继承实现父子作用域之间的数据共享。

此外,$watch 方法的第一个参数应该是一个表达式或函数,而不是来自变量的值。因此,你需要发送相应的表达式或函数。


哎呀,我根本没有数据属性。甚至不确定为什么在这个指令中有一个子作用域,因为 div 在正常作用域内。你有什么想法吗? - wesbos
哦,原来函数作为第一个参数啊!我写了一个返回scope.$parent.data.overlaytype的函数,效果很好。谢谢 :) - wesbos
@Wes,你不应该引用$parent。相反,你应该创建一个变量,可以通过父级作用域的模型或其某个属性进行传递。如果你展示整个指令,这个答案可以被编辑以包括如何传递对象。 - shaunhusain
你可能正在使用隔离作用域。你可以通过属性从父级作用域传递所需的值,我将在答案中编辑完整的细节。 - Naor Biton
感谢@NaorBiton的建议,我正在编写一个fiddle来展示隔离作用域。 - shaunhusain
这是一个初步的小例子,虽然没有功能,但已经接近要点了。我会更新一个更简单的例子,它是有功能的。http://jsfiddle.net/EMPPy/ - shaunhusain

2

好的,我花了一些时间,这是我的建议,虽然我也喜欢事件选项:

更新后的fiddle http://jsfiddle.net/enU5S/1/

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");
});

我展示的内容是,您可以拥有一个变量,从子组件和父组件进行双向绑定,但不需要让子组件到其父组件获取属性。如果在指令上面添加新的父级组件,那么往上寻找东西的倾向会变得疯狂。
如果您在文本框中输入内容,它将更新控制器上的模型,这反过来又绑定到指令上的属性,因此它将在指令中更新。在指令的链接函数中,它设置了一个观察器,以便任何时候作用域变量发生更改时都会递增计数器。
了解隔离作用域以及使用= @或&之间的区别,请参见此处:http://www.egghead.io/

只是提供信息,您不需要定义一个函数来监视作用域属性,也不需要将true作为第三个参数(这就是fiddle中的内容)。因此,您可以简单地编写:scope.$watch('someData',function() { scope.count++; }); - Mark Rajcok
@MarkRajcok 噢,是的,我一开始使用了错误的属性名称,没有看到更新,我会修复我的 fiddle。谢谢。 - shaunhusain
1
很棒的解决方案,使用 awesomeSauce 得到了加分。 - a2f0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接