当从指令

时间:2015-06-22 17:24:30

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

我遇到了一些问题,解决了我正在研究的一些Angularjs功能的问题。

基本思想是我有一个系统,在允许用户前进到应用程序的下一部分之前必须满足某些条件。这方面的一个例子是,用户必须同时添加评论,然后单击链接(在真实应用程序中,这是文件下载),以便他们前进。

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

我将假设HTML非常自我解释,并继续我正在使用我的Angular模块。我的app声明和初始化如下:

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的对象附加到应用程序的根范围,以使我的指令和控制器可以访问它。我有一个指令,用于呈现允许用户在符合条件后前进的链接。在此示例中,链接的文本从“等待...”更改为“单击以继续”以指示我们可以前进。

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

之前是我的控制器,用于显示/添加评论。我在这里将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,就像在控制器中一样。但是,当我将criteria2设置为true时,不会触发$ watchCollection。

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

提前感谢您在解决此问题时提供的任何帮助;我很感激你的时间。

1 个答案:

答案 0 :(得分:1)

你的实际问题是你是从角度上下文之外的事件更新$rootScope,所以它的意思是角度绑定不会更新,因为在这种情况下,摘要周期不会被触发。您需要使用$apply()

$rootScope方法手动触发它
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

  

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

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

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

<强> SEVICE

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