DEV Community

John Peters
John Peters

Posted on

Angular, a new way to think about Directives

Problem

We have an html input date-time element which automatically saves changes to the database. As a result, clicking too rapidly creates errors on the back end when we change the hours, minutes, seconds too fast. We need to debounce those clicks to stop rapid clicking.

Design

We will build and Angular Directive to solve the problem.

HTML

Good coders say "Go ahead and just put the code in, even if you don't have the full support for it, we'll flush it out later". So we just put in the code within HTML as follows:

<input
    //Just put the code in (without support yet)
    appDebounceClick
    //Then subscribe to something that will call back.
    (debounceClick)="onDateTimeChanged(dateTime, setting)"
    class="datetime"
    data-testid="datetime"
    type="datetime-local"
    [(ngModel)]="setting.values[0]"
/>                   

Enter fullscreen mode Exit fullscreen mode

We see an input element with a directive named appDebounceClick. Then we see a subscription to a (debounceClick) event. Those two statements prevent users from rapidly clicking something, which can cause issues. We are wiring up our onDateTimeChanged function in code behind to receive the output of debounceClick.

Directives are pseudo-Import statements within HTML

<input
    // the directive is a pseudo Import statement
    appDebounceClick
    // the methods within the "imported" directive
    (debounceClick)="onDateTimeChanged(dateTime, setting)"
...

Enter fullscreen mode Exit fullscreen mode

Points of interest: The input element has no knowledge or support of debounceClick ; but, we don't care, because we are redirecting its output to do something in our Typescript file for this component. It's in that code where we are maintaining state via bindings and doing other "real work".

We are seeing three important principals at work here 1) Separation of concerns and 2) Dependency Injection and 3) Open/Closed principal. These are well defined patterns within the SOLID design principals. Yes, it's applicable to Typescript and JavaScript.

Debounce Code

Credit to coryrylan.com for this code below.

import {
  Directive,
  EventEmitter,
  HostListener,
  Input,
  OnInit,
  Output
} from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

@Directive({
  selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit {
  @Output() debounceClick = new EventEmitter();
  private clicks = new Subject();
  private subscription: Subscription;

  constructor() {}

  ngOnInit() {
    this.subscription = this.clicks
      .pipe(debounceTime(500))
      .subscribe(e => this.debounceClick.emit(e));
  }

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

  @HostListener('click', ['$event'])
  clickEvent(event) {
    event.preventDefault();
    event.stopPropagation();
    this.clicks.next(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice the @Output event emitter named debounceClick? Sound familiar?
The @HostListener('click', ['$event']) is the hook into the DOM to listen for click events.

Summary:

Directives are the ability to use Dependency Injection techniques for HTML Elements. We are saying; in essence, ahh yes, we need just the right software part to do that job, and it will be injected into any HTMLElement. Let's Import it and use it's functions to do something, including altering current content!

JWP2020

Top comments (0)