AngularJS: Using $setViewValue and $render() to Update Models & Forms

Earlier this week, I ran into a situation in Angular that I hadn’t run across before. I had a form that had several fields: address, city, state, zip, latitude and longitude. The page also had a Google Map, that would allow the user to click on the map, and update the form fields with the newly selected address on the map. To give the user a visual clue that the form had updated with the new values from the map, I was using $dirty to change the styling of the form fields. To change the form fields, I was updating the model directly. However, it turns out that directly changing the model in a Controller, wasn NOT triggering the $dirty flag in my Angular form. It took me awhile to figure out what was going wrong, and how to solve it. I thought I would post my solution here, in case it might help someone else (or in case one of you might have an even better solution to the problem).

I don’t want to include my work code, so I created a plnkr to simulate the issue. You can view the plnkr here. Basically, I have 3 form fields. A snippet of one of them is below:

<div ng-class=”{‘has-warning’: myCtrl.simpleForm.city.$dirty}”>
<label for=”city” class=”col-lg-2 control-label”>
    City <span ng-show=”myCtrl.simpleForm.city.$dirty”>*</span>
</label>

<div class=”col-lg-2″>
    <input type=”text” class=”form-control” id=”city” name=”city”
        ng-model=”myCtrl.location.city”>
</div>

And in the controller, I am updating the model like so (in my real app, I would get the data from a service, but am keeping the code here simple):

self.updateModel = function updateModel() {
self.location = {
    city: ‘Atlanta’,
    state: ‘Georgia’,
    zip: ‘30339’
};

Notice that when you click the Update Model button, the model is updated, but the form fields are not marked $dirty, so the extra styling is not added to let the user know the form values have changed.

Doing some reseach on StackOverflow, I found a few references to the same problem, with the solution being to call $setViewValue() and $render(). Problem was, I didn’t know how to call $render. Thanks to the console.debug() statement, I dug into the object graph of one of the input fields and found a way to get the controller for each input. Once i have the ngModel controller for each input, I can call the $render() method on it.

So the answer turned out to be a 2 step solution. First, you update the view value in the input by calling $setViewValue. Updating the view value also triggers the $dirty flag for the view/input.

self.simpleForm.city.$setViewValue(‘Atlanta’);
self.simpleForm.state.$setViewValue(‘Georgia’);
self.simpleForm.zip.$setViewValue(‘37013’);

While this updates the view value, the mode value does not seem to get updated. (i.e if you just execute the 3 lines above, without $render(), the form fields will be marked $dirty, but the values in the form will not change). This is where $render() comes in. You need to tell the view (input) that just changed, to render itself so the model value is changed (so the new value displays in the form field). To call render, you need to get the ngModelController for the 3 elements we changed above. For example, the input for the city field has an id of city. We can get the controller for that input and call $render() on it like this:

angular.element(‘city’).controller(‘ngModel’).$render();

In the plnkr, you will notice that I made a utility method to call $render(), so that I could pass in an array of id’s to $render(). You can view the working plnkr here

So that is one way to update both the view and the model inside a controller, while also triggering $dirty on the form fields. I’m not thrilled with having to access the DOM in my controller, or having to reference element id’s in my controller. If someone knows of a better way to solve this issue, feel free to let me know in the comments.

One thought on “AngularJS: Using $setViewValue and $render() to Update Models & Forms

  1. Thanks Jeff, this was very helpful. I’ve needed to do this in a custom directive, and so didn’t have access to the controller. The directive was already “built” and used all over our code base, but this aspect not working, hence bit tricky. You’ve got access to the controller in your code. In our case I was able to get to the controller off the angular.element() and thereon to the setViewValue.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s