javascript - 当从指令更新值时,不会触发 $rootScope 上的 Angularjs $watchCollection

标签 javascript angularjs events angularjs-directive angularjs-watch

我在解决我正在处理的某些 Angularjs 功能的问题时遇到了一些问题。

基本思想是,我有一个系统,在允许用户前进到应用程序的下一部分之前,必须满足某些标准。其中一个示例是,用户必须添加评论并单击链接(在真实的应用程序中,这是文件下载)才能前进。

您可以在此处查看完整示例:https://jsfiddle.net/d81xxweu/10/

我假设 HTML 是非常不言自明的,然后继续我使用 Angular 模块所做的事情。我的应用程序声明和初始化如下:

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

myApp.run(function ($rootScope) {
    // Both of these must be met in order for the user to proceed with 'special-button'
    $rootScope.criteria = {
        criteria1: false,
        criteria2: false
    };
});

这非常简单。我将一个名为 criteria 的对象附加到应用程序的根范围,以便我的指令和 Controller 可以访问它。我有一个指令,可以呈现链接,允许用户在满足条件后前进。在此示例中,链接文本从“等待...”更改为“单击继续”以指示我们可以前进。

myApp.directive('specialButton', function ($rootScope) {
    return {
        scope: true,
        template: "<a href='#'>{{ linkText }}</a>",
        replace: true,
        link: function (scope, el, attrs) {
            scope.linkText = 'Waiting...';

            var setLinkState = function(currentCriteria) {
                var criteriaMet = true;

                for(var k in $rootScope.criteria) {
                    if($rootScope.criteria[k] == false) {
                        criteriaMet = false;
                    }
                }

                if(criteriaMet) {
                    scope.linkText = 'Click to proceed';
                }
            };

            // Watch for changes to this object at the root scope level
            $rootScope.$watchCollection('criteria', function(newValues) {
                setLinkState(newValues);
            });
        }
    };
});

因此,为了触发我们在此指令上设置的监视语句,我可以添加此 Controller 允许的注释:

myApp.controller('comments', function ($scope, $rootScope) {
    $scope.commentText = '';
    $scope.comments = [];

    $scope.addComment = function () {
        $scope.comments.push({ commentText: $scope.commentText });
        $scope.commentText = ''

        // When the user adds a comment they have met the first criteria
        $rootScope.criteria.criteria1 = true;
    };
});

前一个是我用于显示/添加评论的 Controller 。我在这里将 criteria1 设置为 true 以指示用户已添加评论。这实际上工作得很好,并且specialButton指令中的$watchCollection被按预期调用。

当我尝试从必须单击才能前进的链接执行相同操作时,就会出现问题。这是用指令呈现的,因为据我了解,在这种情况下,指令比 Controller 更有意义,这与评论列表/表单不同。

myApp.directive('requiredLink', function($rootScope) {
    return {
        scope: true,
        template: "<a href='#'>Click me!</a>",
        replace: true,
        link: function(scope, el, attrs) {
            el.bind('click', function(evt) {
                evt.preventDefault();

                // When the user clicks this link they have met the second criteria
                $rootScope.criteria.criteria2 = true;
            });
        }
    };
});

正如你在这里看到的,我像在 Controller 中一样传入 $rootScope 。但是,当我将 criteria2 设置为 true 时,不会触发 $watchCollection。

所以最终发生的情况是,如果我先添加评论,然后单击另一个按钮,我看不到specialButton 更新其文本,因为第二个更改永远不会触发 watch 。但是,如果我先单击链接,然后添加评论,specialButton 将按预期更新。点击requiredLink是更新数据,但没有触发 watch 。因此,当我添加评论并触发 $watch 时,它会看到两者都已设置为 true。

预先感谢您为解决此问题提供的任何帮助;感谢您的宝贵时间。

最佳答案

您的实际问题是您正在从 Angular 上下文之外的事件更新 $rootScope ,因此很明显 Angular 绑定(bind)不会更新,因为在这种情况下不会触发摘要循环。您需要使用 $rootScope

$apply() 方法手动触发它
el.bind('click', function(evt) {
    evt.preventDefault();
    // When the user clicks this link they have met the second criteria
    $rootScope.criteria.criteria2 = true;
    $rootScope.$apply(); //this will run digest cycle & will fire `watchCollection` `$watcher`
});

Demo Plunkr

Though this solution work but I'll suggest you to use service instead of using $rootScope

要使用服务实现,您需要遵循以下对您有帮助的事情。

您的服务应使用对象形式的criteria变量,并应遵循 dot rule这样相应的引用将使用 JavaScript 原型(prototype)进行更新

服务

app.service('dataService', function(){
    this.criteria = {
        criteria1: false,
        criteria2: false
    };
    //...here would be other sharable data.
})

每当你想在任何地方使用它时,你都需要将它注入(inject)到 Controller 、指令、过滤器的功能中。

在指令中监视服务变量时,您需要执行如下操作。

指令

myApp.directive('specialButton', function (dataService) {
    return {
        scope: true,
        template: "<a href='#'>{{ linkText }}</a>",
        replace: true,
        link: function (scope, el, attrs) {
            //.. other code

            // deep watch will watch on whole object making last param true
            scope.$watch(function(){ 
                return dataService.criteria //this will get get evaluated on criteria change
            }, function(newValues) {
                setLinkState(newValues);
            }, true);
        }
    };
});

关于javascript - 当从指令更新值时,不会触发 $rootScope 上的 Angularjs $watchCollection,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30986132/

相关文章:

authentication - 如何使用 AngularJS ngView 为未经授权的用户隐藏模板?

javascript - 如何阻止 javascript 中的警报在页面加载时触发,并使其在单击按钮时运行?

javascript - Angular - ng 网格通过分组以语法方式选择下一个/上一个项目

javascript - KOA 中的发电机

javascript - 拖动时更改光标

javascript - 从另一个 html 文件追加 div

javascript - 在 Angular 切换开关上停止传播

javascript - 与 HTMLElement 对象进行数据关联的最佳实践?

javascript - Angular.js - 根据用户输入切片 json 请求

javascript - Angular 1.6 组件嵌入范围