ng-model-options in Angular 1.3
Hi again. This is the second article of “Exploring Angular 1.3”. If you haven’t read the first one you might want to check out that too. In this article, we cover another feature that turns out to be very useful in our daily development of Angular applications. Introducing the ngModelOptions
directive.
We’ve written a few other articles on 1.3 already. Here’s a list:
ngModelOptions
allows us to control how ngModel
updates are done. This includes things like updating the model only after certain events are triggered or a debouncing delay, so that the view value is reflected back to the model only after a timer expires. To get an idea of what that actually means, let’s start with the probably simplest use case that sets up a two-way binding using an input
element that has a ngModel
directive applied:
<input type="text" ng-model="name">
<p>Hello {{name}}!</p>
Now, when typing something into the input
element, the model gets updated accordingly and then reflected back to the view, which displays the value in our p
element.
Magic. If you’re not familiar with what’s going on here, I recommend heading over to the official docs and reading the chapter about the concepts of Angular.
The reason why the view is updated immediately, is that every time the input
element fires an input
event, Angulars $digest
loop is executed until the model stabilizes. And that’s nice because we don’t have set up any event listeners and update the DOM manually to reflect model values in the view; Angular takes care of that.
However, that also means that, because of the $digest
that happens to be triggered on every single keystroke, Angular has to process all registered watchers on the scope whenever you type something into the input
element. Depending on how many watchers are registered and of course how efficient the watcher callbacks are, this can be very expensive. So wouldn’t it be great if we could somehow manage to trigger a $digest
only after the user stopped typing for, let’s say, 300 milliseconds? Or only when the user removes the focus of the input
element?
Yes, and we can do so thanks to Angular 1.3 and the ngModelOptions
directive.
Updating ngModel
with updateOn
ngModelOptions
comes with a couple of options to control how ngModel
updates are done. With the updateOn
parameter, we can define which events our input
should be bound to. For example, if we want our model to be updated only after the user removed the focus of our input
element, we can simply do so by applying the ngModelOptions
with the following configuration:
<input
type="text"
ng-model="name"
ng-model-options="{ updateOn: 'blur' }">
<p>Hello {{name}}!</p>
This tells Angular that instead of updating the model immediately after each keystroke, it should only update when the input
fires an onBlur
event.
If we do want to update the model with the default events that belong to that control and add other events on top of that, we can use a special event called default
. Adding more then just one event can be done with a space delimited list. The following code updates the model whenever a user types into the input, or removes the focus of it.
<input
type="text"
ng-model="name"
ng-model-options="{ updateOn: 'default blur' }">
<p>Hello {{name}}!</p>
Alright, now that we know how that works, let’s take a look at how we can update the model after a timer expires.
Delaying the model update with debounce
We can delay the model update with ngModelOptions
in order to reduce the amount of $digest
cycles that are going to be triggered when a user interacts with our model. But not only that this ensures fewer $digest
cycles, it’s also a powerful feature that can be used to implement a nice user experience when dealing with asynchronous code execution.
Just imagine an input[type="search"]
element, where every time a user types into the field, the model gets updated and an asynchronous request is made to a server to get a response with search results depending on the given query. This works. However, we probably don’t want to update the model on every keystroke but rather once the user has finished typing a meaningful search term. We can do exactly that with ngModelOptions
’ debounce
parameter.
debounce
defines an integer value which represents a model update delay in milliseconds. Which means, if we take the example mentioned above, that we want to update our model 300 milliseconds after the user stopped typing, we can do so by defining a debounce value of 300
like this:
<input
type="search"
ng-model="searchQuery"
ng-model-options="{ debounce: 300 }">
<p>Search results for: {{searchQuery}}</p>
Now, when typing into the input
field, there’s a slight delay until the model updates.
We can go even further and configure how the update delay should be done for certain events. Controlling the debounce delay for specific events can be done by defining an object literal instead of a primitive integer value, where keys represent the event name and values the debounce delay. A delay of 0
triggers an immediate model update.
The following code generates a model update delay of 300 milliseconds when the user types into our input
, but an immediate update when removing the focus:
<input
type="search"
ng-model="searchQuery"
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }">
<p>Search results for: {{searchQuery}}</p>
Super powerful right? There are a few other options that are worth to checkout out. You can read about them in the official docs.
Synchronizing model and view with $rollbackViewValue
Due to the fact that we are able to control with ngModelOptions
how and when model updates are done, the model and the view can get out of sync. For example, when we configure our input
element to update the model only when it loses its focus, the moment when the user types into the field, the input
value differs from the actual value in the model.
There might be situations, where you want to roll the view value back to what it was, before the change has been made. For such cases, Angular introduces a so called $rollbackViewValue
method that can be invoked to synchronize the model and view. Basically what this method does is, it takes the value that is currently in the model and reflects it back to the view. In addition, it cancels all debounced changes.
To demonstrate this use case, we can setup a form
that has an input
element that updates the model when the user removes the focus. As long as the user didn’t remove the focus of the input
element, he can hit the Esc
key to discard his changes and get the value of the model back.
So it turns out that ngModelOptions
is a super powerful directive that helps us making our apps more intuitive. Go and check out the docs about the allowInvalid
and getterSetter
options, to see what else is possible!