【问题标题】:angular directive encapsulating a delay for ng-change封装 ng-change 延迟的角度指令
【发布时间】:2014-02-02 23:30:54
【问题描述】:

我有一个搜索输入字段,其中包含绑定到 ng-change 的重新查询功能。

 <input ng-model="search" ng-change="updateSearch()">

但是,这对每个角色的触发都太快了。所以我最终做了很多这样的事情:

  $scope.updateSearch = function(){
    $timeout.cancel(searchDelay);
    searchDelay = $timeout(function(){
      $scope.requery($scope.search);
    },300);
  }

因此请求仅在用户停止输入后 300 毫秒发出。有什么解决方案可以将其包装在指令中吗?

【问题讨论】:

  • 当然。你可以把你已经得到的代码写进一个指令中。
  • 我认为 $timeout.cancel(searchDelay);在您的代码中没用。您需要将旧搜索与新搜索进行比较以避免重复查询。
  • 如果您找到了一个可行的答案,请将答案标记为正确。

标签: angularjs angularjs-directive


【解决方案1】:

我知道我迟到了,但希望这对仍在使用 1.2 的人有所帮助。 Pre ng-model-options 我发现这对我有用,因为当值无效时,ngchange 不会触发。

这与@doug 的回答略有不同,因为它使用 ngKeypress,它不关心模型处于什么状态。

function delayChangeDirective($timeout) {
    var directive = {
        restrict: 'A',
        priority: 10,
        controller: delayChangeController,
        controllerAs: "$ctrl",
        scope: true,
        compile: function compileHandler(element, attributes) {
            var expression = attributes['ngKeypress'];
            if (!expression)
                return;

            var ngModel = attributes['ngModel'];
            if (ngModel) {
                attributes['ngModel'] = '$parent.' + ngModel;
            }
            attributes['ngKeypress'] = '$$delay.execute()';

            return {
                post: postHandler,
            };

            function postHandler(scope, element, attributes) {
                scope.$$delay = {
                    expression: expression,
                    delay: scope.$eval(attributes['ngKeypressDelay']),
                    execute: function () {
                        var state = scope.$$delay;
                        state.then = Date.now();
                        if (scope.promise) {
                            $timeout.cancel(scope.promise);
                        }

                        scope.promise = $timeout(function() {
                            delayedActionHandler(scope, state, expression);
                            scope.promise = null;
                        }, state.delay);
                    }
                };
            }
        }
    };

    function delayedActionHandler(scope, state, expression) {
        var now = Date.now();
        if (now - state.then >= state.delay) {
            scope.$parent.$eval(expression);
        }
    };

    return directive;
};

【讨论】:

    【解决方案2】:

    从 Angular 1.3 开始,这更容易实现,使用 ngModelOptions:

    <input ng-model="search" ng-change="updateSearch()" ng-model-options="{debounce:3000}">
    
    Syntax:  {debounce: Miliseconds}
    

    【讨论】:

    • @kreepN ,我试过这个解决方案,但我马上就明白了。你能告诉我为什么吗?这是plunker
    • @codelearner 刚刚看到你的问题,但这可能与你的 plnkr 没有使用足够新的角度版本有关。它至少需要 1.3。
    • 这个解决方案似乎延迟了模型更新,而不是更改操作。当我需要模型立即更新时,它似乎不起作用,但还需要延迟使用模型的操作。
    【解决方案3】:

    为了解决这个问题,我创建了一个名为 ngDelay 的指令。

    ngDelay 增强了 ngChange 的行为以支持所需的延迟行为,它在用户不活动时提供更新,而不是在每次击键时提供更新。诀窍是使用子范围,并将 ngChange 的值替换为包含超时逻辑并在父范围上执行原始表达式的函数调用。第二个技巧是将任何 ngModel 绑定移动到父范围(如果存在)。这些更改都是在 ngDelay 指令的编译阶段执行的。

    这是一个包含使用 ngDelay 的示例的小提琴: http://jsfiddle.net/ZfrTX/7/(由我编写和编辑,在 mainguy 和 Ryan Q 的帮助下)

    感谢brentvatne,您可以在GitHub 上找到此代码。谢谢布伦特!

    为了快速参考,这里是 ngDelay 指令的 JavaScript:

    app.directive('ngDelay', ['$timeout', function ($timeout) {
        return {
            restrict: 'A',
            scope: true,
            compile: function (element, attributes) {
                var expression = attributes['ngChange'];
                if (!expression)
                    return;
    
                var ngModel = attributes['ngModel'];
                if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
                attributes['ngChange'] = '$$delay.execute()';
    
                return {
                    post: function (scope, element, attributes) {
                        scope.$$delay = {
                            expression: expression,
                            delay: scope.$eval(attributes['ngDelay']),
                            execute: function () {
                                var state = scope.$$delay;
                                state.then = Date.now();
                                $timeout(function () {
                                    if (Date.now() - state.then >= state.delay)
                                        scope.$parent.$eval(expression);
                                }, state.delay);
                            }
                        };
                    }
                }
            }
        };
    }]);
    

    如果有任何 TypeScript 专家,这里的 TypeScript 使用了来自definitelyTyped 的角度定义:

    components.directive('ngDelay', ['$timeout', ($timeout: ng.ITimeoutService) => {
        var directive: ng.IDirective = {
            restrict: 'A',
            scope: true,
            compile: (element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
                var expression = attributes['ngChange'];
                if (!expression)
                    return;
    
                var ngModel = attributes['ngModel'];
                if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
                attributes['ngChange'] = '$$delay.execute()';
                return {
                    post: (scope: IDelayScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
                        scope.$$delay = {
                            expression: <string>expression,
                            delay: <number>scope.$eval(attributes['ngDelay']),
                            execute: function () {
                                var state = scope.$$delay;
                                state.then = Date.now();
                                $timeout(function () {
                                    if (Date.now() - state.then >= state.delay)
                                        scope.$parent.$eval(expression);
                                }, state.delay);
                            }
                        };
                    }
                }
            }
        };
    
        return directive;
    }]);
    
    interface IDelayScope extends ng.IScope {
        $$delay: IDelayState;
    }
    
    interface IDelayState {
        delay: number;
        expression: string;
        execute(): void;
        then?: number;
        action?: ng.IPromise<any>;
    }
    

    【讨论】:

    • 我真的很喜欢这个指令,这正是我所需要的!无论如何,因为当页面上有多个输入时这不起作用,我允许自己在这里通过一小行修改您的 awseome 代码:restrict:'A',scope:true,compile:... 看看这个分叉的小提琴看看我的意思:jsfiddle.net/ZfrTX。再次感谢,我赞成你的回答!太糟糕了,它从未被接受。希望我能...
    • @mainguy 谢谢!这是我的一个重要疏忽。代码已更新为使用隔离范围。
    • @DougR 只是一个仅供参考的“范围:真”不是一个隔离范围,但它似乎可以解决问题。 "scope: {}" 将产生一个没有从外部范围传递参数的隔离范围。
    • @RyanQ 您是绝对正确的,并且将其更改为隔离范围确实修复了一个错误。查看更新的示例。各种值不再被限制在同一个对象上,这让我感到困惑。我很困惑,因为我以为我正在使用一个隔离范围,而实际上我正在使用一个原型继承自元素范围的范围。谢谢。
    • @DougR - 为了方便其他读者,我将您的解决方案打包并放在凉亭上,感谢您。 Github repo:github.com/brentvatne/angular-delay你可以在凉亭看到它:bower.io/search/?q=angular-delay
    【解决方案4】:

    这非常适合我:JSFiddle

      var app = angular.module('app', []);
        app.directive('delaySearch', function ($timeout) {
            return {
                restrict: 'EA',
                template: ' <input ng-model="search" ng-change="modelChanged()">',
                link: function ($scope, element, attrs) {
                    $scope.modelChanged = function () {
                        $timeout(function () {
                            if ($scope.lastSearch != $scope.search) {
                                if ($scope.delayedMethod) {
                                    $scope.lastSearch = $scope.search;
                                    $scope.delayedMethod({ search: $scope.search });
                                }
                            }
                        }, 300);
                    }
                },
                scope: {
                    delayedMethod:'&'
                }
            }
        });
    

    使用指令

    在您的控制器中:

    app.controller('ctrl', function ($scope,$timeout) {
        $scope.requery = function (search) {
            console.log(search);
        }
    });
    

    在你看来:

    <div ng-app="app">
        <div ng-controller="ctrl">
            <delay-search delayed-method="requery(search)"></delay-search>
        </div>
    </div>
    

    【讨论】:

    • 此答案仅适用于搜索值和 modelChanged 函数。我认为作者正在寻找类似 ng-delayed-change="" 的东西,它在所有情况下都会自动插入延迟。
    猜你喜欢
    • 2014-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-12
    相关资源
    最近更新 更多