How to update ngModel's $modelValue based on the $viewValue update by user input

Cody picture Cody · Oct 3, 2014 · Viewed 12.6k times · Source

Say I have the following directive:

myApp.directive('myDirective', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
            ngModel: '='
        },
        link: function(scope, elem, attrs, ngModelCtrl) {
            scope.$watch('ngModel', function() {
                ngModelCtrl.$modelValue = 'foo';
            });
        }
    }
}); 

And the following html:

<input ng-model="name" my-directive></input>

Basically, whenever the user changes the input, my-directive would ideally change the internal model value to "foo" while leaving the view value untouched.

But when I print out $scope.name in the corresponding controller, it doesn't log "foo", it logs whatever the user entered in.

It would seem that ngModelCtrl.$modelValue is not what the controller is accessing -- am I approaching this problem incorrectly?

(Also watching the ngModel in the scope feels really wrong, but I'm not sure of any other way. Any suggestions would be much appreciated!)

Answer

PSL picture PSL · Oct 3, 2014

If you are looking for view change, you should never register a watch. ngModelController's $viewChangeListeners are specifically designed for this purpose and to avoid creating any additional watch on the ngModel. You can also remove 2 way binding set up on the ngModel.

I can think of this way.

.directive('myDirective', function($parse) {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, elem, attrs, ngModelCtrl) {
          /*Register a viewchange listener*/
          ngModelCtrl.$viewChangeListeners.push(function(){ 
              /*Set model value differently based on the viewvalue entered*/
              $parse(attrs.ngModel).assign(scope, ngModelCtrl.$viewValue.split(',')); 
          });
        }
    }
});

Demo

While thinking about it the other way around (Credits @Cody) it becomes more concise and appropriate while using a $parser.

 ngModelCtrl.$parsers.push(function(val) { return val.split(',') });