directive angulaire encapsulant un retard pour changement de ng

j'ai un champ d'entrée de recherche avec une fonction requery liée au ng-change.

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

cependant cela tire trop vite sur chaque personnage. Donc je finis par faire quelque chose comme ça souvent:

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

de sorte que la requête n'est faite que 300ms après que l'Utilisateur a arrêté de taper. Est-il une solution pour l'envelopper de cette directive?

24
demandé sur Homan 2014-01-14 22:45:10

4 réponses

à partir de l'angle 1.3 c'est beaucoup plus facile à accomplir, en utilisant ngModelOptions:

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

Syntax:  {debounce: Miliseconds}
50
répondu schellmax 2014-12-03 17:23:12

pour résoudre ce problème, j'ai créé une directive appelée ngDelay.

ngDelay augmente le comportement de ngChange pour supporter le comportement différé désiré, qui fournit des mises à jour lorsque l'utilisateur est inactif, plutôt que sur chaque touche. Le truc était d'utiliser un scope enfant, et de remplacer la valeur de ngChange par un appel de fonction qui inclut la logique de timeout et exécute l'expression originale sur le scope parent. Le deuxième truc était de déplacer toutes les fixations ngModel vers le parent champ d'application, le cas échéant. Ces modifications sont toutes effectuées dans la phase de compilation de la directive ngDelay.

voici un violon qui contient un exemple utilisant ngDelay: http://jsfiddle.net/ZfrTX/7/ (écrit et édité par moi, avec l'aide de mainguy et Ryan Q)

Vous pouvez trouver ce code sur GitHub grâce à brentvatne. Merci Brent!

pour une référence rapide, voici le JavaScript pour le 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);
                        }
                    };
                }
            }
        }
    };
}]);

et s'il y a des fautes de frappe, voici la frappe en utilisant les définitions angulaires de 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>;
}
25
répondu Doug 2017-05-23 11:47:01

Cela fonctionne parfaitement pour moi: 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:'&'
            }
        }
    });

utiliser la directive

Dans votre contrôleur:

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

selon vous:

<div ng-app="app">
    <div ng-controller="ctrl">
        <delay-search delayed-method="requery(search)"></delay-search>
    </div>
</div>
0
répondu Alborz 2014-01-14 20:20:07

je sais que je suis en retard au jeu mais, espérons que cela aidera quelqu'un qui utilise encore 1.2. Pré ng-model-options j'ai trouvé que cela a fonctionné pour moi, puisque ngchange ne se déclenche pas lorsque la valeur n'est pas valide.

c'est une légère variation sur la réponse de @doug car il utilise ngKeypress qui ne se soucie pas de l'état du modèle.

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
répondu Nuku 2016-12-15 17:29:50