I was wondering whether or not it is possible to implement a slight delay on $scope.$watch. I have the following which queries the server, so I\'d like to implement a slight del
you can use ng-model-option, if the model 'query' is a Html tag or Angular directive, for Ej:
<input type ng-model="query" ng-model-options="{ updateOn: 'default blur', debounce: { 'default':
2000, 'blur': 1 } }" />
You can see the Angular Doc here: https://docs.angularjs.org/api/ng/directive/ngModelOptions
You can use the current value of query
to decide when you want to fire the call:
$scope.$watch("query", function (value) {
//implement rule here for value
//example value is at least 3 characters
if (value && value.length > 3) {
$scope.loading = true;
returnFactory.query($scope.query).then(function (returns) {
$scope.returns = returns;
$scope.loading = false;
});
}
});
I like to use Lo-Dash which provides two really useful capabilities: debounce and throttle which does exactly what you want. Let's say you want to make sure it only calls the function once per 150 ms:
function update() {
$scope.loading = true;
returnFactory.query($scope.query).then(function (returns) {
$scope.returns = returns;
$scope.loading = false;
});
}
$scope.$watch("query", function () {
_.throttle(update, 150);
});
The throttle
function lets you control when the update
function is called (trailing or leading edge).
I use Lo-Dash all the time in my app. It is a must-have library for me... more useful than jQuery. But, you can create a custom build of Lo-Dash which only includes the throttle
and debounce
functions if you don't want to include the entire library.
Just a snippet that I found useful for similar case:
function watchWithDelay(scope, prop, callback, delayMs) {
delayMs = delayMs || 1000;
var lastTimeChanged = new Date();
scope.$watch(prop, function(n, o) {
lastTimeChanged = new Date();
setTimeout(function() {
var diff = new Date().getTime() - lastTimeChanged.getTime();
if (diff < delayMs-100 || diff > delayMs+100) {
return;
}
callback(n, o);
}, delayMs);
});
}
You can use it in a controller like this:
watchWithDelay($scope, 'client.phone', function(n, o) {
if (n === o) {
return;
}
// any custom validations, for example
if (!n) {
return alert('Phone is required');
}
if (n.length < 11) {
return alert('Phone is shorter than 11 digits');
}
// here I should save it somehow
console.log('Phone is changed to ' + n);
});
Normally i'd say use angular's $timeout for this delay but you cant clear this timeout yet.
//EDIT:you can.
Set a timeout and clear it, if this watcher gets triggered fast enought.
Like this:
var timeoutCode;
var delayInMs = 2000;
$scope.$watch("query", function(query) {
clearTimeout(timeoutCode); //does nothing, if timeout alrdy done
timeoutCode = setTimeout(function(){ //Set timeout
$scope.loading = true;
returnFactory.query(query).then(function(returns) {
$scope.returns = returns;
$scope.loading = false;
});
},delayInMs);
});
http://jsfiddle.net/4FuyY/
UPDATE Thanks to stewie this can be achieved with angular's $timeout.
var timeoutPromise;
var delayInMs = 2000;
$scope.$watch("query", function(query) {
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function(){ //Set timeout
$scope.loading = true;
returnFactory.query(query).then(function (returns) {
$scope.returns = returns;
$scope.loading = false;
});
},delayInMs);
});