Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs: how to make input[text] ngModel delay valued while typing [duplicate]

Tags:

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.

like image 615
Stiger Avatar asked Aug 05 '13 03:08

Stiger


People also ask

What does [( ngModel )] do?

The ngModel directive is a directive that is used to bind the values of the HTML controls (input, select, and textarea) or any custom form controls, and stores the required user value in a variable and we can use that variable whenever we require that value. It also is used during form validations.

Which property of ngModel is set to true when an input value is modified by user?

By setting the allowInvalid property to true, the model will still be updated even if the value is invalid.

Can ngModel have multiple values?

The solution is ng-bind-template, it can bind more than one {{}} expression, so it can show more than a single value that was declared in the Script function.


2 Answers

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.

like image 91
Peter Avatar answered Oct 31 '22 06:10

Peter


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.

like image 38
Michael Benford Avatar answered Oct 31 '22 07:10

Michael Benford