Существуют различные сценарии для использования дросселирования (throttling) ввода так, что пересчет значений фильтра будет происходить не каждый раз при изменении значения, а реже. Более подходящий термин — это «устранение дребезга» (debounce), так как в сущности вы ожидаете стабилизации значения на каком-либо постоянном уровне перед вызовом функции, чтобы не вызвать «дребезг» постоянных запросов к серверу. Канонический случай такого рода — это пользователь, вводящий текст в поле ввода для фильтрации списка элементов. Если логика вашего фильтра включает некоторый оверхед (например, фильтрация происходит через REST-ресурс, который выполняет запрос на базе данных бекенда), то вы точно не захотите все время перезапускать и перезагружать результаты запроса в то время, как пользователь пишет текст в поле. Более правильным будет вместо этого подождать, пока он закончит, и уже после этого выполнить запрос один раз.
Простое решение этой проблемы находится тут: jsfiddle.net/nZdgm/
Представим, что у вас есть список ($scope.list), который вы публикуете как фильтрованный список ($scope.filteredList) на основе чего-либо содержащего текст из поля $scope.searchText. Ваша форма выглядела бы примерно следующим образом (не обращайте внимание на чекбокс throttle пока что):
<div data-ng-app='App'>
<div data-ng-controller="MyCtrl">
<form>
<label for="searchText">Search Text:</label>
<input data-ng-model="searchText" name="searchText" />
<input type="checkbox" data-ng-model="throttle"> Throttle
<label>You typed:</label> <span>{{searchText}}</span>
</form>
<ul><li data-ng-repeat="item in filteredList">{{item}}</li></ul>
</div>
</div>
Типичный сценарий — наблюдать за полем поиска и реагировать мгновенно. Метод фильтрации:
var filterAction = function($scope) {
if (_.isEmpty($scope.searchText)) {
$scope.filteredList = $scope.list;
return;
}
var searchText = $scope.searchText.toLowerCase();
$scope.filteredList = _.filter($scope.list, function(item) {
return item.indexOf(searchText) !== -1;
});
};
Контроллер устанавливает $watch примерно следующим образом:
$scope.$watch('searchText', function(){filterAction($scope);});
Такой подход будет запускать фильтрацию каждый раз при вводе в поле. Чтобы устаканить ситуацию, используем встроенную в underscore.js функцию debounce. Функция довольна проста: передайте ей функцию для выполнения и время в миллисекундах. Это задержит реальный вызов функции до тех пор, пока с момента последней попытки ее вызова не пройдет указанное время. Другими словами, при задержке в 1 секунду (которую я использую в этом примере для утрирования эффекта) и непрерывном потоке вызовов функции во время быстрого ввода текста в поле, реальная функция не будет вызвана до тех пор, пока я не прекращу печатать и с этого момента не пройдет 1 секунда.
Может возникнуть искушение сделать простой debounce примерно таким образом:
var filterThrottled = _.debounce(filterAction, 1000);
$scope.$watch('searchText', function(){filterThrottled($scope);});
Однако, тут есть проблема. Такой подход использует таймер, который срабатывает вне цикла $digest, поэтому это в итоге никак не отразится на UI, ведь Angular не знает о случившихся изменениях. Вместо этого вы должны обернуть вызов в $apply:
var filterDelayed = function($scope) {
$scope.$apply(function(){filterAction($scope);});
};
После этого вы можете установить $watch и отреагировать, как только ввод остановится:
var filterThrottled = _.debounce(filterDelayed, 1000);
$scope.$watch('searchText', function(){filterThrottled($scope);});
Конечно, полноценный пример тут должен включать в себя и throttling, так чтобы можно было увидеть разницу между «мгновенной» фильтрацией и отложенной. Фидл на случай можно посмотреть здесь: jsfiddle.net/nZdgm/
Автор: Rumatah