AngularJS:使用隔离作用域的指令中,父级作用域未更新双向绑定

10

我有以下代码,也可以在http://jsfiddle.net/garukun/u69PT/上进行调试。

视图:


视图:
<div data-ng-app="testApp">
    <div data-ng-controller="testCtrl">
        <strong>{{pkey}}</strong>
        <span data-test-directive data-parent-item="pkey" 
            data-parent-update="update(pkey)"></span>
    </div>
</div>

JS:

var testApp = angular.module('testApp', []);

testApp.directive('testDirective', function ($timeout) {
    return {
        scope: {
            key: '=parentItem',
            parentUpdate: '&'
        },
        replace: true,
        template: '<div><p>{{key}}</p>' +
            '<button data-ng-click="lock()">Lock</button>' +
            '</div>',
        controller: function ($scope, $element, $attrs) {
            $scope.lock = function () {
                $scope.key = 'D+' + $scope.key;
                console.log('DIR :', $scope.key);

                // Expecting $scope.$parent.pkey to have also been
                // updated before invoking the next line.
                $scope.parentUpdate();
                // $timeout($scope.parentUpdate); // would work.
            };
        }
    };
});

testApp.controller('testCtrl', function ($scope) {
    $scope.pkey = 'golden';
    $scope.update = function (k) {
        // Expecting local variable k, or $scope.pkey to have been
        // updated by calls in the directive's scope.
        console.log('CTRL:', $scope.pkey, k);
        $scope.pkey = 'C+' + k;
        console.log('CTRL:', $scope.pkey);
    };
});

基本上,我正在使用隔离作用域设置指令,在其中双向绑定来自父级作用域(pkey)的属性(key),还将一个方法(parentUpdate)委托为在父级作用域的上下文中调用。

现在,在指令中的ng-click事件处理程序期间,我想调用parentUpdate方法并在其中执行某些操作。当我调用该方法时,我希望我的父级作用域的模型已经得到更新。但实际上没有,这就是困扰我的原因。

这可能是由于中间缺少了一些$digest周期,因为用$timeout包装parentUpdate调用会按预期工作。

有人可以解释一下缺少了什么吗?或如何正确地调用parentUpdate?


好的,现在我们来聚焦一下...你想知道为什么lock()函数中的$scope.key = 'D+' + $scope.key;似乎没有任何效果,对吧? - jandersen
2个回答

28

好的,我来试着解释一下...似乎你正在在一个 $digest 循环之前更改了被隔离的子作用域和父作用域变量,而在这个循环中双向绑定逻辑同步了它们。以下是详细信息:

  1. 首先,当点击按钮时,将执行你的 lock() 函数。这会更新被隔离的 $scope.key 变量。注意:这并没有立即更新父级 $scope.pKey 的值;这通常会在下一个 $digest 循环中发生,但在这种情况下不会。请继续阅读...
  2. lock() 中,你调用了 parentUpdate(),从而更新了父作用域的 $scope.pKey 变量。
  3. 然后执行 $digest 循环。当循环到达父级作用域时,正确检测到了对 $scope.pKey 的更改。
  4. $scope.pKey 的更改触发了由被隔离作用域中创建的双向绑定所创建的一个 watch()。这些代码行是关键的。
  5. 由被隔离作用域创建的 watch() 检查其双向绑定的值是否与父级值同步。如果不同步(在这种情况下确实如此),则将父级的值复制到被隔离作用域中。即使被隔离作用域的值已经发生了变化,而且实际上是先发生了更改

Misko关于Angular数据绑定的著名帖子描述了 $digest 循环方法的好处。这里所看到的是 $digest 的一种有意的副作用,其中,“父级更改并具有优先权”,正如源代码的注释所说parent changed and it has precedence... 这意味着你的被隔离作用域的更改将失效。

你上面提到的使用 $timeout() 方法避免了这个问题,因为它只在第一个 $digest 循环中更改被隔离作用域的值,从而成功地将其复制到父级作用域,然后再调用 parentUpdate()

$compile 文档说:

通常希望通过表达式将数据从被隔离作用域传递到父级作用域,可以通过将本地变量名称和值的映射传递到表达式包装器函数中来实现。例如,如果表达式是增量(amount),那么我们可以通过调用 localFn({amount: 22}) 来指定 amount

parentUpdate({pkey: 'D+' + $scope.key })

这是更新后的代码片段:http://jsfiddle.net/KbYcr/


谢谢解释,非常有帮助!虽然我理解这是由于$digest循环的设计,但我正在寻找一种方法,使隔离作用域的属性建立在父作用域的属性之上,而不必调用超时函数。在这种情况下,由于我们已经处于一个$digest循环中,我们不能再调用$digest或$apply了。理想情况下,我想调用evalAsync,但似乎也不起作用。如果Angular有一个可以绕过这个设计的技巧,那就太棒了。 - Steve
我认为有一个小技巧 :-) 我刚刚更新了我的答案,加上了详细说明和一个新的 jsFiddle,其中包含一个小改动来演示它。 - jandersen

0
使用$scope.$apply()而不是$scope.$digest()也可以工作。这也将在rootScope上触发脏检查。

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