AngularJS: Custom debounce events

With the introduction of 1.3, Angular gives us more control over how and when to update ngModel values by using ngModelOptions. One great feature is debounce, effectively delaying updates from the view value to the model. However, when using debounce, you’re stuck with the default HTML events. So how to add your custom events? Let’s dive in!

TL;DR? Write your own directive that depends on ngModel and executes the following code if your scenario occurs:

ngModelCtrl.$setViewValue(element.val(), 'yourEvent');

Want to use a clear event with debounce for when a user clears a field? Install our directive.


What is debounce?

With debounce, you can add a delay for updating the ngModel value. This delay resets after each change. If you choose your debounce value wisely, the update will happen only when the user stops editing the field, not on each single keystroke.

<input type="text"
ng-model="myVar"
ng-model-options="{ debounce: 500 }" />

Angular provides you more fine-grained control by specifying debounce durations per event:

<input type="text"
ng-model="myVar"
ng-model-options="{ updateOn: 'default blur', debounce: {
'default': 500, 'blur': 0 } }" />

In this example, all default events (Angular catch-all) will have a debounce delay of 500ms. However, if you choose to go to the next field and thereby blur the field, the model update will be done directly.


Why custom events?

Imagine you have a required field. If you clear the field, your form is in an invalid state. Having a debounce setting of 500ms, the user will be able to submit an invalid form during that delay. You are however a 100% certain when the input is cleared, the model is invalid and the form should reflect that. Setting a debounce value for a clear event would be nice:

<input type="text"
ng-model="myVar"
ng-model-options="{ updateOn: 'default blur clear', debounce: {
'default': 500, 'blur': 0, 'clear': 0 } }" />

You are however bound to the events that Angular listens to. Therefore, firing a custom HTML clear event won’t do the trick. We’ll have to dive deeper into Angular.


Angular internals

Let’s have a quick look at how the AngularJS ngModel works.

On line 772, $$debounceViewValueCommit is defined. This method commits the view value using debounce, given a trigger. This trigger (e.g. blur) is looked up in your ngModelOptions settings. A debounce delay is initiated reflecting your configured duration for the given trigger.

This method is called in the setup of ngModel (line 1038) and in the $setViewValue method (line 768). If you read the docs for $setViewValue, you’ll see this is the method to call when a view value changes. Whenever necessary, debouncing is delegated to $$debounceViewValueCommit.

This gives us enough to work with. Let’s solve our problem.


Solution

Now, let’s write our own nlDebounceClear directive that adds a clear event for ngModelOptions which is triggered when the user clears the field.

Do you need exactly this directive? Don’t copy-paste from here, just install our bower component.

angular.module('module').directive('nlDebounceClear', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function($scope, element, attrs, ngModelCtrl) {
// Do the magic... read on...
});
};
});

We have now defined our own directive that can only be used as an attribute and requires an ngModel being present. We also inject the ngModelCtrl, which is the controller of the ngModel directive.

The only thing left to do is to detect whether the field is cleared and trigger the clear event for Angular:

element.on('input', function() {
if(element.val() === '') {
ngModelCtrl.$setViewValue(element.val(), 'clear');
}
});

That’s all! Angular does not listen to a clear event, but it will happily pass your trigger on to the debounce method. The debounce method will look up the debounce duration for your trigger in your ngModelOptions.

Now, we just have to add the nlDebounceClear directive and we are able to define custom debounce values for clear.

<input type="text"
ng-model="myVar"
ng-model-options="{ updateOn: 'default blur clear', debounce: {
'default': 500, 'blur': 0, 'clear': 0 } }"
nl-debounce-clear />

You can use this trick for whatever custom event you need. Do you need exactly this clear example, then have a look at our bower component.

Like what you read? Give Jasper Kuperus a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.