Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - In a directive that changes the model value, why do I have to call $render?

Tags:

I made a directive designed to be attached to an element using the ngModel directive. If the model's value matches something the value should then set to the previous value. In my example I'm looking for "foo", and setting it back to the previous if that's what's typed in.

My unit tests passed fine on this because they're only looking at the model value. However in practice the DOM isn't updated when the "put back" triggers. Our best guess here is that setting old == new prevents a dirty check from happening. I stepped through the $setViewValue method and it appears to be doing what it ought to. However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value. It works fine, but I just want to see if there's a more appropriate way of doing this.

Code is below, here's a fiddle with the same.

angular.module('myDirective', [])     .directive('myDirective', function () {     return {         restrict: 'A',         terminal: true,         require: "?ngModel",         link: function (scope, element, attrs, ngModel) {             scope.$watch(attrs.ngModel, function (newValue, oldValue) {                 //ngModel.$setViewValue(newValue + "!");                     if (newValue == "foo")                 {                     ngModel.$setViewValue(oldValue);                        /*                          I Need this render call in order to update the input box; is that OK?                         My best guess is that setting new = old prevents a dirty check which would trigger $render()                     */                     ngModel.$render();                 }             });         }     }; });  function x($scope) {     $scope.test = 'value here'; } 
like image 794
David Peters Avatar asked Oct 03 '13 19:10

David Peters


People also ask

Which directive defines the model variable to be used in AngularJS application?

The ng-model directive defines the model/variable to be used in AngularJS Application.

How does directive work in AngularJS?

AngularJS directives are extended HTML attributes with the prefix ng- . The ng-app directive initializes an AngularJS application. The ng-init directive initializes application data. The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.

Which directive binds the values of application data to HTML input controls in AngularJS?

ng-model directive binds the values of AngularJS application data to HTML input controls.

Which directive do we use to inform AngularJS about the parts controlled by it?

The ngRef attribute tells AngularJS to assign the controller of a component (or a directive) to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM element to the scope. The ngRepeat directive instantiates a template once per item from a collection.


1 Answers

Our best guess here is that setting old == new prevents a dirty check from happening

A watcher listener is only called when the value of the expression it's listening to changes. But since you changed the model back to its previous value, it won't get called again because it's like the value hasn't changed at all. But, be careful: changing the value of a property inside a watcher monitoring that same property can lead to an infinite loop.

However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value.

That's correct. $setViewValue sets the model value as if it was updated by the view, but you need to call $render to effectively render the view based on the (new) model value. Check out this discussion for more information.

Finally, I think you should approach your problem a different way. You could use the $parsers property of NgModelController to validate the user input, instead of using a watcher:

link: function (scope, element, attrs, ngModel) {   if (!ngModel) return;    ngModel.$parsers.unshift(function(viewValue) {     if(viewValue === 'foo') {                        var currentValue = ngModel.$modelValue;       ngModel.$setViewValue(currentValue);       ngModel.$render();        return currentValue;     }     else        return viewValue;   }); } 

I changed your jsFiddle script to use the code above.

angular.module('myDirective', [])  .directive('myDirective', function () {    return {      restrict: 'A',      terminal: true,      require: "?ngModel",      link: function (scope, element, attrs, ngModel) {        if (!ngModel) return;          ngModel.$parsers.unshift(function(viewValue) {          if(viewValue === 'foo') {                             var currentValue = ngModel.$modelValue;            ngModel.$setViewValue(currentValue);            ngModel.$render();             return currentValue;          }          else             return viewValue;        });      }    };  });    function x($scope) {    $scope.test = 'value here';  }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>  <h1>Foo Fighter</h1>  I hate "foo", just try and type it in the box.  <div ng-app="myDirective" ng-controller="x">    <input type="text" ng-model="test" my-directive>    <br />    model: {{test}}  </div>
like image 53
Michael Benford Avatar answered Oct 10 '22 19:10

Michael Benford