2014-01-14 18 views
22

我有一個搜索輸入字段,它的ng-change綁定了一個查詢函數。爲ng-change封裝延遲的角度指令

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

但是這對每個角色都會過快。所以我最終做了這樣的事情很多:

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

因此,請求只在用戶停止鍵入後300毫秒。有沒有解決方案將其包含在指令中?

+3

肯定。您可以將您已經擁有的代碼寫入指令。 – Fresheyeball

+0

我想$ timeout.cancel(searchDelay);在你的代碼中沒用。您需要將舊搜索與新搜索進行比較以避免重複查詢。 – Alborz

+2

如果您發現某個人能夠工作,請將答案標記爲正確。 – Doug

回答

0

這完美的作品對我來說: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> 
+0

此答案僅適用於搜索值和modelChanged函數。我認爲作者正在尋找的是類似ng-delayed-change =「」,它在所有情況下自動插入延遲。 – Doug

25

爲了解決這個問題,我創建了一個名爲ngDelay的指令。

ngDelay增強了ngChange的行爲以支持所需的延遲行爲,該行爲在用戶處於非活動狀態時提供更新,而不是在每次按鍵時提供。訣竅是使用子範圍,並將ngChange的值替換爲包含超時邏輯的函數調用,並在父範圍上執行原始表達式。第二個技巧是將任何ngModel綁定移動到父範圍(如果存在)。這些更改全部在ngDelay指令的編譯階段執行。

下面是其中包含使用ngDelay一個例子的小提琴: http://jsfiddle.net/ZfrTX/7/(撰寫和編輯的我,從mainguy和Ryan Q幫助)

您可以在GitHub感謝brentvatne找到這段代碼。謝謝布倫特!

爲了便於參考,這裏的JavaScript來實現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); 
         } 
        }; 
       } 
      } 
     } 
    }; 
}]); 

如果有任何打字稿書呆子,使用打字稿從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>; 
} 
+2

我真的很喜歡這個指令,那正是我需要的!無論如何,因爲這在頁面上有多個輸入時不起作用,所以我允許我自己在這裏修改你的awseome代碼:restrict:'A',scope:true,compile:...看看這個分叉的小提琴看看我的意思:http://jsfiddle.net/ZfrTX/。再次感謝,我提高了你的答案!太糟糕了,它從未被接受。 Whish我可以... – mainguy

+0

@mainguy謝謝!這是我的一個重要監督。該代碼已更新爲使用隔離範圍。 – Doug

+1

@DougR只是一個「範圍:真」不是一個孤立的範圍,但它似乎有伎倆。 「scope:{}」將生成一個隔離範圍,並且沒有從外部範圍傳遞參數。 –

47

由於角1.3這種方式更容易實現,使用ngModelOptions

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

Syntax: {debounce: Miliseconds} 
+0

很高興聽到它 – Doug

+1

你先生是我的英雄,非常容易和很好的解決方案! –

+0

非常好的解決方案,非常感謝! – Adrien

0

我知道我遲到了,但希望這會幫助任何人仍然使用1.2。 Pre ng-model-options我發現這對我很有用,因爲ngchange在數值無效時不會觸發。

這是@豆豆的答案稍有不同,因爲它使用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; 
};