Angularjs: input[text] ngChange fires while the value is changing
Asked Answered
F

8

92

ngChange is firing while the value is changing (ngChange are not similiar to the classic onChange event). How can i bind the classic onChange event with angularjs, that will only fire when the contents are commited?

Current binding:

<input type="text" ng-model="name" ng-change="update()" />
Fifth answered 8/8, 2012 at 15:52 Comment(2)
Just looking for something similar and it occured to me that you might also want to consider updating only when the field is valid. That way the user can struggle with getting the input into a valid format (or you might help them with prompts and filters) then you can reward them with an update when they get it right!Infecund
A much more flexible solution that allows for specifying the event to use (not just blur) and other properties should be built in to angular very soon: github.com/angular/angular.js/pull/2129Kbp
C
99

This post shows an example of a directive that delays the model changes to an input until the blur event fires.

Here is a fiddle that shows the ng-change working with the new ng-model-on-blur directive. Note this is a slight tweak to the original fiddle.

If you add the directive to your code you would change your binding to this:

<input type="text" ng-model="name" ng-model-onblur ng-change="update()" />

Here is the directive:

// override the default input to update on blur
angular.module('app', []).directive('ngModelOnblur', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        priority: 1, // needed for angular 1.2.x
        link: function(scope, elm, attr, ngModelCtrl) {
            if (attr.type === 'radio' || attr.type === 'checkbox') return;

            elm.unbind('input').unbind('keydown').unbind('change');
            elm.bind('blur', function() {
                scope.$apply(function() {
                    ngModelCtrl.$setViewValue(elm.val());
                });         
            });
        }
    };
});

Note: as @wjin mentions in the comments below this feature is supported directly in Angular 1.3 (currently in beta) via ngModelOptions. See the docs for more info.

Che answered 8/8, 2012 at 17:55 Comment(15)
Thanks, that works. I changed "blur" with "change" and it works also.Fifth
Caution, elm.unbind('input') throws an 'TypeError' in Internet Explorer 8 so the other unbind statements won't be executed at all. I solved it by moving elm.unbind('input') to a separate line and surrounded it with a try/catch block.Unspent
Angular 1.2 has an ng-blur directive: docs.angularjs.org/api/ng.directive:ngBlurArrio
@JJ ng-blur does something else, unless you want to explain how he'd use that here.Sectarianize
I made this code work, but I had to set the priority of the directive (i.e., priority: 100). I'm using Angular 1.2.10. My guess is that the ngModelOnblur directive was running prior to the ngModel directive so there was nothing to unbind yet. Also, this page makes it seem like there may be a built in solution for this in the future: github.com/angular/angular.js/pull/1783Affricative
@Alan West: that pull request was closed in favor of this one: github.com/angular/angular.js/pull/2129 which hopefully should be merged in soonKbp
@Kbp it has been merged in v1.3.0. Let's wait till it's out of beta!Whittemore
@Whittemore Well it's about time! :)Kbp
@SamanthaAtkins updated the example to set the priority for this directive to work with Angular 1.2.x like Alan mentioned above.Che
Thanks. I moved to the semi-official solution in 1.3 betas. How would one find a list of what priorities are set on a version of angularjs for what events/handlers?Lowpressure
Some other priorities changed in 1.2 (see this for more info) but I think the issue here more related to this.Che
NOTE At least in angular 1.3 this functionality is supported in the default implementation via ng-model-options="{ updateOn: 'default blur' }" See documentationPsalter
@RobJuurlink (or anyone else who encounters the IE8 'TypeError' when trying to bind or unbind to 'input') - Looking at Angular's source, you'll see that they use $sniffer to determine if the browser supports 'input' and if not, they fall back to 'keydown'. If you update the above directive to only unbind 'input' if $sniffer.hasEvent('input') returns true - then you can avoid that error and still work in IE8Ladle
Here's an ng-model-options like module for Angular 1.2.x github.com/fergaldoyle/modelOptionsNicki
Thanks for mentioning ngModelOptions - I didn't know about that directive!Akiko
D
33

This is about recent additions to AngularJS, to serve as future answer (also for another question).

Angular newer versions (now in 1.3 beta), AngularJS natively supports this option, using ngModelOptions, like

ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"

NgModelOptions docs

Example:

<input type="text" name="username"
       ng-model="user.name"
       ng-model-options="{updateOn: 'default blur', debounce: {default: 500, blur: 0} }" />
Dupion answered 13/4, 2014 at 16:59 Comment(3)
It doesn't work correct in 1.3 beta 5, but fixed in current master build 2602Dupion
And that is why it's still in beta and not usable in "real-world" applications such as the one I'm building right now.Hanrahan
Here's an ng-model-options like module for Angular 1.2.x github.com/fergaldoyle/modelOptionsNicki
C
8

In case anyone else looking for additional "enter" keypress support, here's an update to the fiddle provided by Gloppy

Code for keypress binding:

elm.bind("keydown keypress", function(event) {
    if (event.which === 13) {
        scope.$apply(function() {
            ngModelCtrl.$setViewValue(elm.val());
        });
    }
});
Canonicate answered 20/10, 2012 at 3:47 Comment(0)
M
5

For anyone struggling with IE8 (it doesn't like the unbind('input'), I found a way around it.

Inject $sniffer into your directive and use:

if($sniffer.hasEvent('input')){
    elm.unbind('input');
}

IE8 calms down if you do this :)

Mccauley answered 2/1, 2013 at 4:29 Comment(1)
or just use try/catchKbp
S
4

According to my knowledge we should use ng-change with the select option and in textbox case we should use ng-blur.

Shaquitashara answered 13/3, 2015 at 12:15 Comment(2)
This is the correct answer now, back then when the question was asked it didn't exist apparently.Albumenize
Thank you very much... this should be the answerAlgebraist
S
3

Isn't using $scope.$watch to reflect the changes of scope variable better?

Structural answered 14/1, 2014 at 9:58 Comment(1)
Food for thought: benlesh.com/2013/10/title.html (titled: you probably shouldn't use $watch in your controllers)Flag
S
0

Override the default input onChange behavior (call the function only when control loss focus and value was change)

NOTE: ngChange is not similar to the classic onChange event it firing the event while the value is changing This directive stores the value of the element when it gets the focus
On blurs it checks whether the new value has changed and if so it fires the event

@param {String} - function name to be invoke when the "onChange" should be fired

@example < input my-on-change="myFunc" ng-model="model">

angular.module('app', []).directive('myOnChange', function () { 
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
            myOnChange: '='
        },
        link: function (scope, elm, attr) {
            if (attr.type === 'radio' || attr.type === 'checkbox') {
                return;
            }
            // store value when get focus
            elm.bind('focus', function () {
                scope.value = elm.val();

            });

            // execute the event when loose focus and value was change
            elm.bind('blur', function () {
                var currentValue = elm.val();
                if (scope.value !== currentValue) {
                    if (scope.myOnChange) {
                        scope.myOnChange();
                    }
                }
            });
        }
    };
});
Studer answered 25/12, 2013 at 11:49 Comment(0)
C
0

I had exactly the same problem and this worked for me. Add ng-model-update and ng-keyup and you're good to go! Here is the docs

 <input type="text" name="userName"
         ng-model="user.name"
         ng-change="update()"
         ng-model-options="{ updateOn: 'blur' }"
         ng-keyup="cancel($event)" />
Cottager answered 22/3, 2017 at 16:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.