【问题标题】:Angularjs $watchCollection on $rootScope not triggered when values updated from directive从指令更新值时未触发 $rootScope 上的 Angularjs $watchCollection
【发布时间】:2015-09-08 06:28:01
【问题描述】:

我在解决我正在开发的一些 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
    };
});

这很简单。我将一个称为条件的对象附加到应用程序的根范围,以便我的指令和控制器可以访问它。我有一个指令,它呈现允许用户在满足条件后前进的链接。在这个例子中,链接的文本从“Waiting...”变为“Click to continue”,表示我们可以前进。

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

因此,为了触发我们在此指令上设置的 watch 语句,我可以添加此控制器允许的注释:

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

上一个是我用于显示/添加 cmets 的控制器。我在这里将 criteria1 设置为 true 以指示用户添加了评论。这实际上工作正常,并且按预期调用了 specialButton 指令中的 $watchCollection。

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

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

正如您在此处看到的,我传入 $rootScope 就像在控制器中一样。但是,当我将条件 2 设置为 true 时,不会触发 $watchCollection。

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

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

【问题讨论】:

    标签: javascript angularjs events angularjs-directive angularjs-watch


    【解决方案1】:

    您的实际问题是您从角度上下文之外的事件更新$rootScope,因此很明显角度绑定不会更新,因为在这种情况下不会触发摘要循环。您需要使用$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

    虽然此解决方案有效,但我建议您改用 service 使用$rootScope

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

    您的服务应使用对象形式的 criteria 变量,应遵循 dot rule 以便相应的引用将使用 JavaScript 原型更新

    服务

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

    当你想在任何你需要的地方使用它时,你可以将它注入到任何你想要的控制器、指令、过滤器的功能中。

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

    指令

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

    【讨论】:

    • 谢谢,这解决了问题。我考虑过使用服务,但问题是我需要能够从外部观察我的 specialButton 指令中的数据变化。我如何设法查看服务数据?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2015-01-31
    • 2020-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多