AngularJS:如何在父作用域中调用子作用域函数

99
如何从父作用域中调用在子作用域中定义的方法?
function ParentCntl() {
    // I want to call the $scope.get here
}

function ChildCntl($scope) {
    $scope.get = function() {
        return "LOL";    
    }
}

http://jsfiddle.net/wUPdW/


2
最佳选择是定义一个服务,然后将该服务注入到两个控制器中。或者可以使用rootscope。 - tymeJV
4个回答

152

你可以使用$broadcast从父级分发到子级:

function ParentCntl($scope) {

    $scope.msg = "";
    $scope.get = function(){
        $scope.$broadcast ('someEvent');
        return  $scope.msg;        
    }
}

function ChildCntl($scope) {               
    $scope.$on('someEvent', function(e) {  
        $scope.$parent.msg = $scope.get();            
    });

    $scope.get = function(){
        return "LOL";    
    }
}

工作的jsfiddle:http://jsfiddle.net/wUPdW/2/

更新:有另一个版本,耦合性更低,可测试性更强:

function ParentCntl($scope) {
    $scope.msg = "";
    $scope.get = function(){
        $scope.$broadcast ('someEvent');
        return  $scope.msg;        
    }

    $scope.$on('pingBack', function(e,data) {  
        $scope.msg = data;        
    });
}

function ChildCntl($scope) {               
    $scope.$on('someEvent', function(e) {  
        $scope.$emit("pingBack", $scope.get());        
    });

    $scope.get = function(){
        return "LOL";    
    }
}

Fiddle: http://jsfiddle.net/uypo360u/


在这种情况下,调用某个地方的全局监听器是一个好主意吗?这不是反模式并且后期难以测试吗?我们不应该使用注入或其他方法吗? - Pikachu
@calmbird,每个东西都必须小心使用,这是肯定的。有些人喜欢在类似情况下使用服务。无论如何,我添加了更优雅的版本(没有烦人的$parent)。 - Ivan Chernykh
你很幸运$broadcast$emit是同步的,否则这些方法就行不通了。文档中也没有说明它们是按设计同步的。 - poshest
10
这种方法更为简洁。在 $broadcast 传递一个回调函数,你就可以完全省去 pingBack - poshest
我同意poshest的观点。你不需要pingBack或者$scope.msg。如果你不喜欢传递回调函数的想法,你也可以将参数对象传递给$broadcast - JustAMartin
显示剩余2条评论

35

让我建议另一个解决方案:

var app = angular.module("myNoteApp", []);


app.controller("ParentCntl", function($scope) {
    $scope.obj = {};
});

app.controller("ChildCntl", function($scope) {
    $scope.obj.get = function() {
            return "LOL";    
    };
});

更少的代码并使用原型继承。

Plunk


有人能解释一下,在哪些情况下这种方法比上面提到的事件驱动方法更好,如果是的话,为什么?如果你有多层次的子控制器,那怎么办?如果obj.get在一个子控制器内被定义,是否可以工作,只要没有任何控制器定义相同的函数名?谢谢。 - ZvKa
有趣...感谢评论,让我困惑的是我认为js原型/ angularjs作用域继承只适用于一个方向...子->父或子作用域->父作用域,而不是相反的方向...但我猜我漏掉了什么 @_@ - ZvKa
1
让我更正一下之前说的话,那并不完全正确。你说得对:作用域继承只适用于一个方向...子->父。实际上发生的是,如果在子作用域中定义了$scope.get,则'get'仅在子作用域中定义(有时称为原始定义)。但是当您定义$scope.obj.get时,子作用域将查找在父作用域中定义的obj,即在层次结构上方。 - Canttouchit
3
如果子作用域有自己的隔离范围,那么这将如何工作? - Dinesh
2
这个问题并不涉及隔离作用域, 但是如果你有隔离作用域,你可以使用隔离作用域绑定。 在我看来,广播有点混乱。 - Canttouchit
3
如果你需要从父控制器的 controller 而不是模板中访问子控制器的函数,我认为这个例子是不可能的。这个例子之所以有效,是因为在模板上有双向绑定,我想这会不断轮询,直到 $scope.obj.get() 变成有效的函数。请注意,这里不能改变原文的意思。 - danyim

13
在子类初始化时,将子类的函数注册到父类上。我在模板中使用“as”符号来使语句更加清晰。

模板


模板

<div ng-controller="ParentCntl as p">
  <div ng-controller="ChildCntl as c" ng-init="p.init(c.get)"></div>
</div>

控制器

...
function ParentCntl() {
  var p = this;
  p.init = function(fnToRegister) {
    p.childGet = fnToRegister;
  };
 // call p.childGet when you want
}

function ChildCntl() {
  var c = this;
  c.get = function() {
    return "LOL";    
  };
}

"但是," 你会说, "ng-init 不应该这样使用!"。 好吧,是的,但是:

  1. 文档没有解释为什么不能这样用,而且
  2. 我不认为文档作者考虑了所有可能的用例。

我认为这是一个很好的用法。如果您想给我投反对票,请留下评论说明原因!:)

我喜欢这种方法,因为它使组件更加模块化。唯一的绑定在模板中,并且意味着

  • 子控制器不必知道要将其函数添加到哪个对象中(例如 @canttouchit 的答案)
  • 可以将父控制器与具有 GET 函数的任何其他子控制器一起使用
  • 不需要广播,否则在大型应用程序中会变得非常混乱,除非您严格控制事件名称空间

这种方法更接近于Tero用指令进行模块化的想法(请注意,在他的模块化示例中,contestants 从父指令传递给“子”指令 IN THE TEMPLATE)。

实际上,另一种解决方案可能是将 ChildCntl 实现为指令,并使用 & 绑定注册 init 方法。


1
我知道这是一个旧帖子,但这似乎是个坏主意,因为父对象可以在子对象被销毁后仍然保留对其的引用。 - Amy Blankenship
1
这比广播事件更为简洁的解决方案。虽然不完美,但更好。尽管清理确实是一个问题。 - mcv

-1

你可以创建子对象。

var app = angular.module("myApp", []);


app.controller("ParentCntl", function($scope) {
    $scope.child= {};
    $scope.get = function(){
      return $scope.child.get(); // you can call it. it will return 'LOL'
    }
   // or  you can call it directly like $scope.child.get() once it loaded.
});

app.controller("ChildCntl", function($scope) {
    $scope.obj.get = function() {
            return "LOL";    
    };
});

这里的 child 是 get 方法的目标位置。


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