Как написать службу debounce в AngularJS
Библиотека подчёркивания предоставляет функцию debounce, которая предотвращает множественные вызовы функции в течение заданного периода времени. В их версии используется setTimeout.
Как мы можем сделать это в чистом коде AngularJS?
Кроме того, можно ли использовать стиль $q promises для извлечения возвращаемого значения из вызываемой функции после периода debounce?
Ответы
Ответ 1
Вот рабочий пример такой службы: http://plnkr.co/edit/fJwRER?p=preview.
Он создает отложенный объект $q
, который будет разрешен при окончательном вызове функции debounced.
Каждый раз, когда функция debounce
называется обещанием следующего вызова внутренней функции, возвращается.
// Create an AngularJS service called debounce
app.factory('debounce', ['$timeout','$q', function($timeout, $q) {
// The service is actually this function, which we call with the func
// that should be debounced and how long to wait in between calls
return function debounce(func, wait, immediate) {
var timeout;
// Create a deferred object that will be resolved when we need to
// actually call the func
var deferred = $q.defer();
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if(!immediate) {
deferred.resolve(func.apply(context, args));
deferred = $q.defer();
}
};
var callNow = immediate && !timeout;
if ( timeout ) {
$timeout.cancel(timeout);
}
timeout = $timeout(later, wait);
if (callNow) {
deferred.resolve(func.apply(context,args));
deferred = $q.defer();
}
return deferred.promise;
};
};
}]);
Вы получаете возвращаемое значение из функции debounced, используя метод then по обещанию.
$scope.addMsg = function(msg) {
console.log('addMsg called with', msg);
return msg;
};
$scope.addMsgDebounced = debounce($scope.addMsg, 2000, false);
$scope.logReturn = function(msg) {
console.log('logReturn called with', msg);
var promise = $scope.addMsgDebounced(msg);
promise.then(function(msg) {
console.log('Promise resolved with', msg);
});
};
Если вы вызовете logReturn
несколько раз подряд, вы увидите, что вызов logReturn
занесен в систему снова и снова, но зарегистрирован только один addMsg
.
Ответ 2
Angular 1.3 имеет стандартное отклонение
Стоит упомянуть, что debounce встроен с Angular 1.3. Как и следовало ожидать, он реализован как директива. Вы можете сделать это:
<input ng-model='address' ng-model-options="{ debounce: 500 }" />
Атрибут $scope.address не обновляется до 500 мс после последнего нажатия клавиши.
Если вам нужно больше управления
Если вы хотите больше детализации, вы можете установить разные времена отказов для разных событий:
<input ng-model='person.address' ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }" />
Здесь, например, у нас есть откат на 500 мкс за нажатие клавиши и отсутствие отладки для размытия.
Документация
Прочитайте документацию здесь: https://docs.angularjs.org/api/ng/directive/ngModelOptions
Ответ 3
Поскольку я написал комментарии выше, у меня немного изменилось сердце.
Короткий ответ: вам не нужно отказываться от функций, возвращающих значения.
Почему? Ну, философски, я думаю, что имеет смысл продолжать дебаты для событий и только для событий. Если у вас есть метод, который возвращает значение, которое вы хотите отменить, вы должны вместо этого отменить событие, которое заставляет ваш метод работать вниз по течению.
Ответ 4
Пит БД дал хорошее начало службе debounce, однако я вижу две проблемы:
- возвращает, когда вы должны отправить обратный вызов work(), который использует закрытие javascript, если вам нужно изменить состояние в вызывающем.
- timeout variable - не является ли эта переменная таймаутом проблемой? тайм-аут [] может быть? представьте 2 директивы, использующие debounce - signalr, валидатор формы ввода,
Я считаю, что подход factory сломается.
То, что я сейчас использую:
Я изменил factory на службу, поэтому каждая директива получает НОВЫЙ экземпляр debounce aka нового экземпляра переменной тайм-аута. - Я не столкнулся с ситуацией, когда 1 директиве потребуется тайм-аут для таймаута [].
.service('reactService', ['$timeout', '$q', function ($timeout, $q) {
this.Debounce = function () {
var timeout;
this.Invoke = function (func, wait, immediate) {
var context = this, args = arguments;
var later = function () {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
if (timeout) {
$timeout.cancel(timeout);
}
timeout = $timeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
return this;
}
}]);
в моем удаленном валидаторе angularjs
.directive('remoteValidator', ['$http', 'reactService', function ($http, reactService) {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var newDebounce = new reactService.Debounce();
var work = function(){
//....
};
elm.on('blur keyup change', function () {
newDebounce.Invoke(function(){ scope.$apply(work); }, 1000, false);
});
}
};
}])
Ответ 5
Существует хорошая реализация как службы debounce, так и директивы, которая может работать с любой ng-моделью по адресу: https://github.com/shahata/angular-debounce
Или просто установите его, используя:
bower install ng-debounce
Ответ 6
https://github.com/capaj/ng-tools/blob/master/src/debounce.js
использование:
app.directive('autosavable', function(debounce) {
return {
restrict : 'A',
require : '?ngModel',
link : function(scope, element, attrs, ngModel) {
var debounced = debounce(function() {
scope.$broadcast('autoSave');
}, 5000, false);
element.bind('keypress', function(e) {
debounced();
});
}
};
});
Ответ 7
Поддержка этого приземлилась в angularjs # 1.3.0.beta6, если вы имеете дело с взаимодействием модели.
https://docs.angularjs.org/api/ng/directive/ngModelOptions