Angularjs: how to make input[text] ngModel delay valued while typing
Solution 1
The tidiest way to handle this is probably to write a directive which wraps up the <input>
element and adds the delaying behaviour. Here is a directive I wrote for the same purpose:
angular.module('MyModule')
.directive('easedInput', function($timeout) {
return {
restrict: 'E',
template: '<div><input class="{{externalClass}} my-eased-input" type="text" ng-model="currentInputValue" ng-change="update()" placeholder="{{placeholder}}"/></div>',
scope: {
value: '=',
timeout: '@',
placeholder: '@',
externalClass: '@class'
},
transclude: true,
link: function ($scope) {
$scope.timeout = parseInt($scope.timeout);
$scope.update = function () {
if ($scope.pendingPromise) { $timeout.cancel($scope.pendingPromise); }
$scope.pendingPromise = $timeout(function () {
$scope.value = $scope.currentInputValue;
}, $scope.timeout);
};
}
}
});
This directive would be called in your HTML like so:
<eased-input value="myValue" timeout="500" placeholder="Please enter text..." />
Dissecting the directive:
Timeout Service
This directive uses angular's $timeout
service to handle timing: it is an injectable, mockable, idiomatic alternative to calling setTimeout
. This service is injected in the directive constructor.
Attributes
The directive accepts three attributes: value
, timeout
and placeholder
.
The value
attribute here binds to a variable on the scope of the controller which owns the enclosing 'context'. In this case it binds to myValue
, i.e. to $scope.myValue
on whichever controller is in charge of this code. It has a two-way binding, denoted by the '='
entry in the scope
property of the directive. This means that when this directive updates value
, the change is propagated up to the controller which owns the directive; hence, $scope.myValue
will change when value
is changed inside the directive.
The timeout
and placeholder
attributes have one-way bindings: the directive reads their values from the attributes but does not alter them. They are effectively configuration values.
HTML Template
The template
property on the directive shows the HTML which will be generated in its place once Angular compiles and links it. It's basically just an input
element with some special and not-so-special attributes. The value in the input box is bound to the currentInputValue
variable on the directive's $scope
via ng-model
. The change
event on the input box is bound to the update
function on the directive's $scope
via the ng-change
directive.
Link function
The guts of the process lie in the link
function on the directive: we define an update
method. As stated above, this method is bound to the change
event of the input box within the directive's HTML template. Thus, every time the user changes the input in the box, update
is called.
This method uses the $timeout
service. It tells the $timeout
service to wait for timeout
milliseconds, then to apply a callback which sets $scope.value = $scope.currentInputValue
. This is similar to calling setTimeout(function () {$scope.value = $scope.currentInputValue}, timeout)
.
The $timeout
call returns a promise. We are able to cancel a promise p
produced by $timeout
which is waiting to execute by calling $timeout.cancel(p)
. This is what update
does in its first line: if we have a promise from a previous change event, we cancel it before creating a new one. This means that if we have e.g. a 500ms timeout, and update gets called twice, with the calls 400ms apart, we will only have one promise waiting to fire.
Overall result
The promise, when resolved, sets $scope.value = currentInputValue
; i.e. it sets the 'outwardly visible' value
property to have the value of the contents of the input box. value
will only change -- and external controllers will only see value
change -- after a quiescent period of timeout
milliseconds, which I believe is the behaviour you were after.
Solution 2
If you're okay with having a second property in your model, you can use $scope.$watch
together with a debounce
function:
HTML
<input type="text" ng-model="typing" />
<input type="text" value="{{ typed }}" />
Javascript
$scope.$watch('typing', debounce(function() {
$scope.typed = $scope.typing;
$scope.$apply();
}, 500));
You can write your own debounce function, or use an existing one. There's a good implementation here, or, if you happen to be using undescore.js, you're already set.
Here's a jsFiddle example.
UPDATE: Angular 1.3 has now a built-in way to debounce user input: ngModelOptions.
Related videos on Youtube
Stiger
Updated on September 15, 2022Comments
-
Stiger over 1 year
I have a textbox with
ngModel
binding, like this:<input type="text" ng-model="typing" />
and value of this texbox
value: {{ typing }}
I want the model delay to update value while i'm typing. Maybe if I stop type in 500ms, the model will update all value (all things I typed in textbox). I make some google but no luck. Anyong has any idea? please help.
EDIT
This Angularjs: input[text] ngChange fires while the value is changing doesn't give solution for my case. It bring solution update value after blur, but I want the value update after stop typing, not blur textbox.
EDIT 2 (Answers)
With angular version 1.4, directive
ngModelOptions
is useful in my case. I can write like this<input ng-model="typing" ng-model-options="{ updateOn: 'default', debounce: {'default': 500, 'blur': 0} }" />
to delay update value to model 500ms in default and update immediately if lost focus.-
Stiger over 10 yearsthat solution bind event blur, so when you blur, the value will be updated. But in here, I want it update after stop typing, not blur from textbox.
-
Doug about 10 yearsTake a look at this answer here, which provides a directive that allows you to put a delay on ng-change: stackoverflow.com/questions/21121460/…. You can use ngChange to update the model with an ngDelay of 500 ms.
-
-
Steven Keith over 9 yearsNice. Proxy to the rescue. :) +1
-
Raphael Müller over 9 yearsNice one. But I noticed something: If I set the second field, the second field "loses" the binding. To reproduce in your fiddle, write something in the first one, then in the second and afterwards in the first again. Is this a bug or just some strange behavior?
-
Michael Benford over 9 years@RaphaelMüller Nice catch. I don't know why that's happening. If you inspect the markup you'll see that the second input's value property is correctly set. I should have used
ng-model
instead, though. Anyways, in Angular 1.3 there's no need to do any of that since the framework has now a built-in solution for that problem. -
Raphael Müller over 9 years@MichaelBenford thanks, and I will take a look at this new option tomorrow. I hope that's what I'm searching without dirty hacks and so on.
-
Raphael Müller over 9 yearsThe new Model Options from angular 1.3 are what I was looking for. They make Life easier.