从指令更新控制器作用域

9
我正在使用AngularJS指令创建可重用的UI组件。我希望有一个包含业务逻辑和嵌套组件(指令)的控制器。我想让指令能够操纵控制器作用域上的单个属性。因为可能会使用同一个指令多次,所以指令需要具有孤立作用域,并且每个实例都需要绑定到特定的控制器作用域属性。
目前,我唯一的方法是从指令中调用`scope.$apply()`将更改应用回控制器的作用域。但是当我在`ng-click`回调中时,会出现`rootScope:inprog`(操作正在进行中)错误。
我的问题是:最好的方法是什么,可以使控制器知道子指令已经更新了控制器作用域上的值?
我考虑过在控制器上拥有一个函数,指令可以调用该函数进行更新,但我认为那样太麻烦了。
以下是我在`ng-click`回调上断开的代码。请记住,我不仅要解决`ng-click`问题,还要找到应用可重用指令来修改父级作用域/模型的最佳方案。
html
<div ng-controller="myCtrl">
    <my-directive value="val1"></my-directive>
</div>

控制器

...
.controller('myCtrl', ['$scope', function ($scope) {
    $scope.val1 = 'something';
}});

指令

...
.directive('myDirective', [function () {

return {
    link: function(scope) {
        scope.buttonClick = function () {
            var val = 'new value';
            scope.value = val;
            scope.$apply(); 
        };
    },
    scope: {
        value: '='
    },
    template: '<button ng-click="buttonClick()"></button>'
};
}]);

你可以在$rootScope上触发事件。 - camden_kid
@camden_kid 我希望有更好的封装性。至少我会使用我考虑过的控制器方法。但我不认为我想在根作用域上添加更多的交互。 - Brett
1
你不需要调用 $apply(),因为 buttonClick 函数是由 ng-click 指令调用的,因此不会在 Angular 的事件处理之外执行。如果你想修改属性,那么你现在的做法是正确的(除了不应该使用 $apply())。如果你想调用回调函数,则应该使用 '&' 传递可调用函数,而不是 '=' - JB Nizet
你可以注入一个服务。我注意到你在指令元素标记内部有一个按钮。它不会像那样工作。你需要在指令中使用一个模板(或者templateURL)。 - camden_kid
@camden_kid,你对模板的看法是正确的。我之前为了简洁而发布了这个内容,但我会进行编辑。 - Brett
2个回答

5

指令中的双向数据绑定的目的正是您所询问的,即“[允许]指令修改父作用域/模型”。

首先,请确保在指令属性上正确设置了双向数据绑定,以便公开您想要在作用域之间共享的变量。在控制器中,如果需要在值更改时执行某些操作,则可以使用$watch检测更新。此外,您还可以在指令上添加事件处理程序属性。这将使指令在发生某些事情时调用一个函数。以下是一个例子:

<div ng-controller="myCtrl">
    <my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding -->
        <button ng-click="buttonClick()"></button>
    </my-directive>
</div>

你指引我找对了方向。我的问题与双向绑定和基本数据类型混淆在一起。 - Brett

4
我认为你对于$scope.apply的问题是一个误导。我不确定在你演示和提问中它解决了什么问题,但这不是它的作用,而且值得一提的是,在没有使用$scope.apply的情况下,你的例子也可以正常工作
你不应该担心这个问题(“让控制器意识到...某些东西修改了作用域的值”); Angular的数据绑定会自动处理这个问题。
这里有点复杂,因为指令中涉及到多个作用域的问题。外部作用域属于
,并且该作用域具有.val属性,内部作用域由创建,它也具有.val属性,并且在myDirective内的buttonClick处理程序修改了其值。但是,你使用'value: ='声明了myDirective的作用域,这设置了该属性值在内部和外部作用域之间进行双向同步。
所以它应该自动工作,在我从你的问题代码中创建的plunker中,它确实自动工作。
那么,scope.$apply在哪里呢?这明确是为在Angular不知道需要它时触发digest周期而设计的。(如果你在Angular已经知道需要digest周期时使用它,你会得到一个嵌套的digest周期和你注意到的“inprog”错误。)这是文档链接,我引用其中的一段话:“$ apply()用于在Angular外部执行表达式”。你需要使用它,例如,在响应通过非Angular方法设置的事件处理程序时——直接DOM事件绑定、jQuery、socket.io等。如果你在Angular应用程序中使用这些机制,最好将它们封装在一个指令或服务中,以处理Angular与非Angular接口,以便你的应用程序的其余部分不必担心它。
(scope.$apply实际上是一个封装了scope.$digest并管理异常处理的包装器。这在文档中不是很清楚。我认为更容易理解$scope.digest的名称/行为,然后将$scope.apply视为“我实际上应该使用的更友好的版本的$scope.digest”)。
关于$apply的最后一点说明:它需要一个函数回调参数,你应该在这个回调里完成工作。如果你做了一些工作,然后在之后调用$apply而没有传入参数,它仍然可以工作,但此时它与$digest相同。因此,如果你确实需要在这里使用$apply,它应该看起来更像这样: scope.buttonClick = function() { scope.$apply(function() { scope.value = newValue; }); });

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