$ apply已在进行中使用自定义指令和事件

时间:2014-04-10 18:27:02

标签: javascript jquery angularjs events angularjs-directive

我正在围绕第三方jQuery插件编写自定义指令。第三方jQuery插件使用事件处理其数据的更新,因此我的指令监视事件并使用$ apply来确保它正确地更新从控制器传入的数据。我还希望能够更新插件中的数据,因此我有一种方法可以通过我自己的方法调用在我的指令范围内修改/更新数据。但是,当一个angular指令触发我的指令上的方法调用时(在这种情况下,通过ng-click),它会导致正在进行的' $ apply'角度误差。我认为这种情况正在发生,因为Angular的内置指令已经为我调用$ apply或$ digest或类似的东西。我已经找到了一种方法来解决这个问题,使用$ timeout,但感觉就像其中一个" ick,不要这样做"各种各样的事情。当第三方插件通过与插件的正常交互触发其自身的事件时,一切正常(即,我没有得到$ apply in progress错误,并且数据按照我期望的方式在范围上更新)。 / p>

任何人都可以帮助我找到一个更好的方法来做我想做的事情吗?我在这里提供了示例代码和一个小提琴来说明问题(这不是真正的指令,它更简单,我无法控制第三方jQuery插件和它抛出的事件等。)。

<德尔> jsfiddle example

Better jsfiddle example

更新 新代码可以更好地说明问题:

HTML

<div ng-app="blarf">
<div ng-controller="blah">
    <p>Controller view of numbers:</p>
    <ul><li ng-repeat="number in numbers">{{number}}</li></ul>
    <p>Directive view</p>
    <some-awesome-directive numbers="numbers"></some-awesome-directive>
</div>
<button id="sneaky-adder" type="button">Add (Sneaky) Number</button>

的JavaScript

(function(){ 
    var data = [1, 2, 3];

    window.getData = function(){ return data; }
    window.addNumber = function(number){ 
        data.push(number); 
        $('.adder-directive').trigger('my-custom-event', [number]);
    };

    $(document).on('click', '#sneaky-adder', function(){
        var newNum = (Math.random() * 20) + ' sneaky :)';
        data.push(newNum);
        $('.adder-directive').trigger('my-custom-event', [newNum]);
    });
})();

angular.module('blarf', [])
.controller('blah', function($scope){
    $scope.numbers = getData();
})
.directive('someAwesomeDirective', function($timeout){
    return {
        restrict: 'E',
        scope: {
            numbers: '=',
        },
        template: '<div class="adder-directive"><ul><li ng-repeat="number in numbers">{{number}}</li></ul><button type="button" id="adder" ng-click="addAnotherNumber()">Add Another Number</button></div>',
        replace: true,
        link: function(scope, element){
            scope.addAnotherNumber = function(){
                // 1.
                /*$timeout(function(){
                    var newNum = Math.random() * 20;
                    addNumber(newNum);
                });*/

                // 2.
                var newNum = Math.random() * 20;
                addNumber(newNum);
            };

            $(element).on('my-custom-event', function(e, number){
                // 3.
                scope.$apply(function(){
                    scope.numbers = getData();
                });
            });
        }
    };
});

OLD STUFF

HTML

<div ng-app="blarf">
    <div ng-controller="blah">
        <some-awesome-directive numbers="numbers"></some-awesome-directive>
    </div>
</div>

的JavaScript

angular.module('blarf', [])
.controller('blah', function($scope){
    $scope.numbers = [1,2,3];
})
.directive('someAwesomeDirective', function($timeout){
    return {
        restrict: 'E',
        scope: {
            numbers: '=',
        },
        template: '<div><ul><li ng-repeat="number in numbers">{{number}}</li></ul><button type="button" ng-click="addAnotherNumber()">Add Another Number</button></div>',
        replace: true,
        link: function(scope, element){
            scope.addAnotherNumber = function(){
                // 1. This works, uncomment this and comment out 2 to see
                //$timeout(function(){ element.triggerHandler('my-custom-event', [Math.random() * 20])});

                // 2. This causes an "$apply already in progress" error 
                element.triggerHandler('my-custom-event', [Math.random() * 20])
            };

            element.on('my-custom-event', function(e, number){
                scope.$apply(function(){
                    scope.numbers.push(number);
                });
            });
        }
    };
});

2 个答案:

答案 0 :(得分:0)

你可以尝试在这样的函数中将调用包装到$ apply:

$scope.safeApply = function(fn) {
    var phase = this.$root.$$phase;
    if(phase == '$apply' || phase == '$digest') {
        if(fn && (typeof(fn) === 'function')) {
            fn();
        }
    } else {
        this.$apply(fn);
    }
};

答案 1 :(得分:0)

您可以将逻辑放入一个由按钮(从角度)和事件(仅在角度外调用)调用的函数中:

scope.handleNumber(number) {
    scope.numbers.push(number);
};

scope.addAnotherNumber = function() {
    scope.handleNumber(Math.random() * 20);
};

element.on('my-custom-event', function(e, number) {
    scope.$apply(function() {
        scope.handleNumber(number);
    });
};