Question

I'm still quite new to AngularJS and I think I'm having trouble understanding the timing with $scope. In a controller, I've setup a watcher for some model data that is bound to various form elements. The watcher fires off an ajax request when the data changes, except if the form is not valid. I'm checking the form validity with myForm.$valid. This is all pretty straight forward, however, except when the model data is updated in the controller, and not the form. The validations runs as expected, but form.$valid still has the previous value, not what it should be with the updated data. For example, if the form was previously valid, then I delete the model value bound to a required input, the watcher will fire because the model data has changed, but when I log the value of myForm.$valid it's value is still true, even though it should be false.

So my question is A. why is this happening?, but more importantly B. what is the correct way to handle what I am trying to accomplish? Would a custom directive make sense?

Here is a simplified example.

HTML:

<div ng-controller="MyCtrl">
    <form name="myForm">
        <input type="text" name="myField" ng-model="myData" required>
        <button type="button" ng-click="myData=''">Delete</button>
    </form>

    <div>
        The watcher says the form is: <strong>{{ formStatus }}</strong>    
    </div>
</div>

Controller:

myApp.controller('MyCtrl', ['$scope', function($scope) {
    $scope.myData = 'abc';
    $scope.formStatus = '';

    $scope.$watch('myData', function(newVal, oldVal) {
        if (newVal != oldVal) {
            console.log("my data changed");
            console.log("my form valid = ", $scope.myForm.$valid);        
            $scope.formStatus = $scope.myForm.$valid ? 'Valid' : 'Invalid';
        }
    });
}]);

Fiddle: http://jsfiddle.net/anpsince83/cK6cc/1/

Was it helpful?

Solution

A custom directive is the right way to go. It's generally recommended for a handful of reasons that watches be put in directives vs controllers. At a high level, one reason is that Angular recommends controllers be thin and only operate as the glue between the view and services. But you're hitting a specific, interesting reason.

Controllers run before Angular directives are linked in. So your controller watch gets added to the watch list before Angular's ngModelWatch() does for myData. ngModelWatch() is where Angular runs $formatters (recalling that $formatters, amongst other things, are where validation is triggered when a change happens to the model).

Since watches are executed in the order in which they were added to the watch list, your controller $watch executes prior to validation being done by $formatters.

If you instead put the same $watch inside a directive it'll be added during link after ngModelWatch() has been added to the watch list. Thus the directive's $watch will execute after validation is done.

Here's an updated fiddle where you can watch the order of execution of $formatters and each $watch. And you'll see the directive version works as expected- running after $formatters.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top