angular directive encapsulating a delay for ng-change

前端 未结 4 624
南笙
南笙 2020-11-28 09:21

I have a search input field with a requery function bound to the ng-change.

 
相关标签:
4条回答
  • 2020-11-28 09:49

    I know i'm late to the game but,hopefully this will help anyone still using 1.2. Pre ng-model-options i found this worked for me, as ngchange will not fire when the value is invalid.

    this is a slight variation on @doug's answer as it uses ngKeypress which doesn't care what state the model is in.

    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;
    };
    
    0 讨论(0)
  • 2020-11-28 09:58

    To solve this problem, I created a directive called ngDelay.

    ngDelay augments the behavior of ngChange to support the desired delayed behavior, which provides updates whenever the user is inactive, rather than on every keystroke. The trick was to use a child scope, and replace the value of ngChange to a function call that includes the timeout logic and executes the original expression on the parent scope. The second trick was to move any ngModel bindings to the parent scope, if present. These changes are all performed in the compile phase of the ngDelay directive.

    Here's a fiddle which contains an example using ngDelay: http://jsfiddle.net/ZfrTX/7/ (Written and edited by me, with help from mainguy and Ryan Q)

    You can find this code on GitHub thanks to brentvatne. Thanks Brent!

    For quick reference, here's the JavaScript for the ngDelay directive:

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

    And if there are any TypeScript wonks, here's the TypeScript using the angular definitions from 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>;
    }
    
    0 讨论(0)
  • 2020-11-28 10:04

    This works perfectly for me: 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:'&'
                }
            }
        });
    

    Using the directive

    In your controller:

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

    In your view:

    <div ng-app="app">
        <div ng-controller="ctrl">
            <delay-search delayed-method="requery(search)"></delay-search>
        </div>
    </div>
    
    0 讨论(0)
  • 2020-11-28 10:08

    As of angular 1.3 this is way easier to accomplish, using ngModelOptions:

    <input ng-model="search" ng-change="updateSearch()" ng-model-options="{debounce:3000}">
    
    Syntax:  {debounce: Miliseconds}
    
    0 讨论(0)
提交回复
热议问题