42

I would like to do an equivalent of ng-change for the entire form whenever there is a change in one of its input fields.

I know that since AngularJS 1.3 I have the debounce option but it applies only for a single input.

I'm looking for a "debounce"/"on change" functionality that will apply for the entire form.

isherwood
  • 58,414
  • 16
  • 114
  • 157
chenop
  • 4,743
  • 4
  • 41
  • 65
  • 1
    I believe your best bet is to make a directive for all your inputs (that you want debounce on) and set the debounce setting there, but let it pass the ng-model and other stuff from your inputs. – Mark Pieszak - Trilon.io Feb 23 '15 at 16:10

4 Answers4

61

There isn't a built-in way to do ng-change for a form.

It may not even be needed, because if you organized your view model properly, then your form inputs are likely bound to a certain scope-exposed property:

$scope.formData = {};

and in the View:

<form name="form1">
  <input ng-model="formData.a">
  <input ng-model="formData.b">
</form>

Then you could deep-watch (with $watch) for model changes (and apply whatever debounce option on elements that you need):

$scope.$watch("formData", function(){
  console.log("something has changed");
}, true);

Then problem is, of course, that this is a deep-watch and it is expensive. Also, it reacts not only to changes in the Form, but also to a change in formData from any source.

So, as an alternative, you could create your own directive to compliment the form and react to form's change events.

.directive("formOnChange", function($parse){
  return {
    require: "form",
    link: function(scope, element, attrs){
       var cb = $parse(attrs.formOnChange);
       element.on("change", function(){
          cb(scope);
       });
    }
  }
});

and the usage is:

<form name="form1" form-on-change="doSomething()">
  <input ng-model="formData.a">
  <input ng-model="formData.b">
</form>

plunker for illustration.

Note, that the "change" event is fired only on blur for a text input, as per jQuery documentation:

The change event is sent to an element when its value changes. This event is limited to <input> elements, <textarea> boxes and <select> elements. For select boxes, checkboxes, and radio buttons, the event is fired immediately when the user makes a selection with the mouse, but for the other element types the event is deferred until the element loses focus.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • 1
    You can also detect text input changes by also listening for keyboard events in that directive, add:`element.bind("keydown keypress", function (event) { cb(scope); });` – Eric Soyke Jun 21 '16 at 19:49
  • Is it also possible to exclude some types (input, select, button ect.)? I added the click event and I'm trying to exclude the button type. I can see the button logged with console.log(attrs); – Jeroen Steen Jan 19 '18 at 13:30
  • It seems that this won't work if using the angular built in jqlite variant of jQuery. – ChrisM Apr 14 '19 at 14:00
10

one "hacky" way to do this is by setting a watcher to the form dirty, valid depending on your requirements you can do something like

   $scope.$watch('form.$dirty',function(v){
         if(!v){return}
         form.$setPristine()
         /*do something here*/
    })

this will execute everytime the form gets modified, if you only want to execute your code on valid modified form you can do

       if(!v || form.$invalid){return}

and if you only want to execute your code when the form steps to $valid state just need to set up your watcher for 'form.$valid'

if you don't like to pollute your scope with watchers, you can always create a directive around the form that exposes a on-change api event and internally takes care of the watcher

AKFourSeven
  • 1,305
  • 3
  • 18
  • 31
Dayan Moreno Leon
  • 5,357
  • 2
  • 22
  • 24
8

As per Eric Soyke comment you could hook up the check of the form change on the keyup event.

This way you could simply use the builtin directive ng-keyup:

<form name="form1" ng-keyup="doSomething()">
Lorenzo Meriggi
  • 157
  • 2
  • 5
  • +1 This is the answer I needed since New Dev's solution was only working if the focus is lost (as he already mentioned) which does not give the user experience I wanted. Ty both though :) – LFish Oct 10 '16 at 15:14
  • 2
    Wouldn't work with autofill plugins (unless they fire keyup event). – setec Nov 14 '16 at 12:17
2

okay, super super late answer ... but this works pretty neat

// html
<form name="$ctrl.form">...</form>

// controller
function $postLink() {
    ctrl.form.$$element.on('change', function () {
        console.log('fired');
    });
}
Eydrian
  • 10,258
  • 3
  • 19
  • 29