John Doe
John Doe

Reputation: 365

Angular.js - ng-change not firing when ng-pattern is $invalid

I am using ng-pattern to validate some form fields, and I am using ng-change with it to watch and process any changes, however ng-change (or $scope.$watch) will only fire when the form element is in the $valid state! I'm new to angular, so I don't know how to solve this issue, although I suspect a new directive is the way to go.

How can I get ng-change to fire in both $invalid and $valid form element states, with ng-pattern still setting the form element states as before?

Html:

<div ng-app="test">
  <div ng-controller="controller">
    <form name="form">
        <input type="text" name="textbox" ng-pattern="/^[0-9]+$/" ng-change="change()" ng-model="inputtext"> Changes: {{ changes }}
    </form>

    <br>
    Type in any amount of numbers, and changes should increment.

    <br><br>
    Now enter anything that isn't a number, and changes will stop incrementing. When the form is in the $invalid state, ng-change doesn't fire.

    <br><br>
    Now remove all characters that aren't numbers. It will increment like normal again. When the form is in the $valid state, ng-change will fire.

    <br><br>
    I would like ng-change to fire even when the the form is $invalid.

    <br><br>
        form.$valid: <font color="red">{{ form.$valid }}</font>

  </div>
</div>

Javascript:

angular.module('test', []).controller('controller', function ($scope) {
    $scope.changes = 0;
    $scope.change = function () {
        $scope.changes += 1;
    };
});

I have created a working JS Fiddle which shows the problem I am having.

http://jsfiddle.net/JAN3x/1/

By the way, this angular issue also seems to be relevant: https://github.com/angular/angular.js/issues/1296

Upvotes: 36

Views: 24622

Answers (4)

Lucius
Lucius

Reputation: 2872

Edit This was answered when ng-model-options was not available. Please see the top-voted answer.

you can write a simple directive to listen input event.

HTML:

<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change()" ng-model="inputtext"> Changes: {{ changes }}

JS:

app.directive('watchChange', function() {
    return {
        scope: {
            onchange: '&watchChange'
        },
        link: function(scope, element, attrs) {
            element.on('input', function() {
                scope.$apply(function () {
                    scope.onchange();
                });
            });
        }
    };
});

http://jsfiddle.net/H2EAB/

Upvotes: 5

Evon Dos
Evon Dos

Reputation: 155

you just need to add

 ng-model-options="{ updateOn: 'default' , allowInvalid:'true'}"

this indicates that the model can be set with values that did not validate correctly instead of the default behaviour.

Upvotes: 15

Eugene
Eugene

Reputation: 2974

You can change the behavior of your input by using ng-model-options.

Just add this attribute to your input and the ng-change event will fire:

      ng-model-options="{allowInvalid: true}"

see: https://docs.angularjs.org/api/ng/directive/ngModelOptions

Upvotes: 77

M&#39;sieur Toph&#39;
M&#39;sieur Toph&#39;

Reputation: 2676

Inspired by the Li Yin Kong ingenious solution :

His solution has an issue concerning the ndModel update (see the comments of his post).

My fix essentially changes the scope type of the directive. It lets directive access to controller scope (and methods) Then, watch-change directive does not need an "instruction to eval" (change()) anymore, but only the "name of the controller method to call" (change).

And to get the new value of the input in this function, I pass the context (this = the input itself). So I can get the value or any property of it.

This way, we don't care about ngModel updates (or if the form is invalid, which was another issue of the initial solution : ngModel is deleted if form is invalid)

HTML :

<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change" ng-model="inputtext">

JAVASCRIPT :

app.directive('watchChange', function() {
    return {
        restrict : 'A',
        link: function(scope, element, attrs) {
            element.on('input', function(){
                scope[attrs.watchChange](this);
            })
        }
    };
});

DEMO : http://jsfiddle.net/msieurtoph/0Ld5p2t4/

Upvotes: 4

Related Questions