diretiva angular encapsulando um atraso para ng-change

Eu tenho um campo de input de pesquisa com uma function de consulta ligada ao ng-change.

 

No entanto, isso triggers muito rapidamente em todos os personagens. Então acabo fazendo algo parecido com isso:

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

De modo que o pedido só é feito 300ms depois que o usuário parou de digitar. Existe alguma solução para envolver isso em uma diretiva?

A partir de 1.3 angular, isso é muito mais fácil de realizar, usando ngModelOptions :

  Syntax: {debounce: Miliseconds} 

Para resolver esse problema, criei uma diretiva chamada ngDelay.

O ngDelay aumenta o comportamento do ngChange para suportar o comportamento atrasado desejado, que fornece atualizações sempre que o usuário estiver inativo, e não em cada pressionamento de tecla. O truque era usar um escopo filho e replace o valor de ngChange por uma chamada de function que incluísse a lógica de tempo limite e executasse a expressão original no escopo pai. O segundo truque foi mover quaisquer ligações ngModel para o escopo pai, se presente. Essas alterações são todas executadas na fase de compilation da diretiva ngDelay.

Aqui está um violino que contém um exemplo usando ngDelay: http://jsfiddle.net/ZfrTX/7/ (escrito e editado por mim, com a ajuda de mainguy e Ryan Q)

Você pode encontrar este código no GitHub graças ao brentvatne . Obrigado Brent!

Para referência rápida, aqui está o JavaScript para a diretiva ngDelay:

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

E se houver algum won do TypeScript, aqui está o TypeScript usando as definições angulares 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: 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); } }; } } } }; return directive; }]); interface IDelayScope extends ng.IScope { $$delay: IDelayState; } interface IDelayState { delay: number; expression: string; execute(): void; then?: number; action?: ng.IPromise; } 

Isso funciona perfeitamente para mim: JSFiddle

  var app = angular.module('app', []); app.directive('delaySearch', function ($timeout) { return { restrict: 'EA', template: ' ', 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:'&' } } }); 

Usando a diretiva

No seu controlador:

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

Na sua opinião:

 

Eu sei que estou atrasado para o jogo, mas, espero que isso ajude alguém ainda usando 1.2. Pre ng-model-options Achei que isso funcionou para mim, pois o ngchange não será triggersdo quando o valor for inválido.

Esta é uma pequena variação na resposta do @ doug, já que ele usa o ngKeypress, que não se importa com o estado do modelo.

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