Дросселирование ввода на AngularJS с помощью debounce

от автора

Существуют различные сценарии для использования дросселирования (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/

ссылка на оригинал статьи http://habrahabr.ru/post/210344/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *