104

Suppose I wanted to do something like automatically run some code (like saving data to a server) whenever a model's values change. Is the only way to do this by setting something like ng-change on each control that could possibly alter the model?

Ie, with views, things change right as the model is changed without having to explicitly hook anything up. Is there an analog to being able to run code that saves to a server? Something like

myModel.on('change', function() {
  $.post("/my-url", ...);
});

like you might see with something like backbone.

3 Answers 3

153

In views with {{}} and/or ng-model, Angular is setting up $watch()es for you behind the scenes.

By default $watch compares by reference. If you set the third parameter to $watch to true, Angular will instead "shallow" watch the object for changes. For arrays this means comparing the array items, for object maps this means watching the properties. So this should do what you want:

$scope.$watch('myModel', function() { ... }, true);

Update: Angular v1.2 added a new method for this, `$watchCollection():

$scope.$watchCollection('myModel', function() { ... });

Note that the word "shallow" is used to describe the comparison rather than "deep" because references are not followed -- e.g., if the watched object contains a property value that is a reference to another object, that reference is not followed to compare the other object.

Sign up to request clarification or add additional context in comments.

2 Comments

Ah, great! Is there any reason this doesn't seem to be all that documented (ie, I don't think any of the tutorials on the angular site mentioned setting up $watches directly)? Is there anything bad about this that would make setting up (potentially multiple) ng-change hooks on input controls a better idea?
Yeah, it would be nice if the main tutorial mentioned $watch somewhere. What is "bad" about this approach is that it can be time-consuming if your model is large (every digest cycle -- every keystroke in an input field -- will result in this model being deep dirty-checked, possibly multiple times). In that case, selective $watch()es or selective ng-change would be better.
9

And if you need to style your form elements according to it's state (modified/not modified) dynamically or to test whether some values has actually changed, you can use the following module, developed by myself: https://github.com/betsol/angular-input-modified

It adds additional properties and methods to the form and it's child elements. With it, you can test whether some element contains new data or even test if entire form has new unsaved data.

You can setup the following watch: $scope.$watch('myForm.modified', handler) and your handler will be called if some form elements actually contains new data or if it reversed to initial state.

Also, you can use modified property of individual form elements to actually reduce amount of data sent to a server via AJAX call. There is no need to send unchanged data.

As a bonus, you can revert your form to initial state via call to form's reset() method.

You can find the module's demo here: http://plnkr.co/edit/g2MDXv81OOBuGo6ORvdt?p=preview

Cheers!

7 Comments

Is there a way to check this in the controller. for example if the click the x button can I do like an if(myform.modified) show confirmation popup?
Of course, just pass FormController to your controller's function: <form name="myForm">, <button ng-click="vm.doSomething(myForm)">.
thanks this will do something only if the form was modified right?
This will pass FormController to the doSomething() function of your controller. You could do anything you want with it inside of that function, e.g. check if form is actually modified by checking the FormController.modified boolean property.
I occasionally get this error in the console: Type Error cannot read property '$onChildModelModifiedStateChanged' of null at bower-combined.js:44 do you know how to fix this
|
0

Detect change in a single model

$scope.$watchCollection('model1', function () {
    //...
}, true);

Detect change in a multiple models

$scope.$watchCollection('[model1, model2, model3]', function () {
    //...
}, true);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.