Angular, a new way to think about Directives
Tags: angular
Published: 2020-04-02 17:09:42

**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:

```html
<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]"
/>                   
                 
```
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**

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

```

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](https://en.wikipedia.org/wiki/SOLID) design principals. Yes, it's applicable to Typescript and JavaScript.

**Debounce Code**

Credit to [coryrylan.com](https://coryrylan.com/blog/creating-a-custom-debounce-click-directive-in-angular) for this code below.

```typescript
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);
  }
}
```
Notice the @Output event emitter named debounceClick? Sound familiar?
The [@HostListener('click', ['$event'])](https://angular.io/api/core/HostListener) is the hook into the DOM to listen for click events. 

**Summary:**

Directives are the ability to use [Dependency Injection](https://en.wikipedia.org/wiki/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