10

Debounce NgModel Form Directive

Monday, November 9, 2020

If you are using Angular's template-driven forms you are likely very familiar with the NgModel directive that creates a FormControl instance for a form element. In some cases you may want to debounce the value before executing statements as a result of the value change event. This can be pretty easy to do when working with Angular's reactive forms API but can be less intuitive when building forms using the template-driven forms API.

Here is a Directive for debouncing the valueChanges() observable with a template declarative implementation.

import { Directive, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { NgModel } from '@angular/forms';
import { Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, skip } from 'rxjs/operators';

@Directive({
  selector: '[ngModelDebounceChange]',
})
export class NgModelDebounceChangeDirective implements OnDestroy, OnInit {
  /** Emit event when model has changed. */
  @Output() ngModelDebounceChange = new EventEmitter<any>();

  /** Subscriptions for cleanup. */
  private subscription: Subscription;

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  constructor(private ngModel: NgModel) {}

  ngOnInit(): void {
    this.subscription = this.ngModel.control.valueChanges
      .pipe(skip(1), debounceTime(200), distinctUntilChanged())
      .subscribe(value => this.ngModelDebounceChange.emit(value));
  }
}

Let's quickly review the code above.

  • First, in the Directive's metadata I've specified the ngModelDebounceChange selector. We'll use this selector in place of the ngModelChange() selector that we normally apply to a form element with the template-driven forms API.
  • Next, we define an Output EventEmitter property matching the selector for the Directive.
  • We track the Subscription that is created upon subscribing to the FormControl's valueChanges Observable.
  • The NgModel instance is injected into our constructor() function.
  • Within the ngOnInit() lifecycle method we use the debounceTime() and distinctUntilChanged() operators to debounce the value and to only emit a value that is not equal to the previous value emitted by the Observable.
  • Finally, we subscribe to the Observable and emit the value.

Instructions

Consider

Consider using the ngModelDebounceChange directive to declaratively debounce value changes on a form element

Why

Prevent execution of statements for each value change next notification from a FormControl

Prevent multiple redundant network requests if persisting value changes of a form element

Code Examples

Debounce NgModel value changes in a template-driven form

<form>
  <input name="userName" [ngModel]="userName" (ngModelDebounceChange)="onUserNameChange($event)" />
</form>
Brian Love

I am a software engineer and Google Developer Expert in Web Technologies and Angular with a passion for learning, writing, speaking, teaching and mentoring. I regularly speaks at conferences and meetups around the country, and co-authored "Why Angular for the Enterprise" for O'Reilly. When not coding, I enjoy skiing, hiking, and being in the outdoors. I started lookout.dev to break down the barriers of learning in public. Learning in public fosters growth - for ourselves and others.

Google Developers Expert

Discussions are healthy ❤️