Custom Directive In Angular

Custom Directive In Angular

In this article, we will learn how to create a Custom Directive in Angular. The Angular directives allow us to expand or alter the Document Object Model (DOM). The directives can be used to change the appearance, behavior, or layout of a DOM element. We will create four directive examples and show you how to use them.

  • Create a custom directive using the @Directive decorator.
  • We will create both custom attribute directive & custom Structural directive.
  • How to setup selectors
  • Pass value to it using the @input.
  • How to respond to user inputs,
  • Manipulate the DOM element (Change the Appearance) etc.

Angular Directives

There are three sorts of directives in Angular.

  • Components
  • Structural Directives
  • Attribute Directives

Components are Template directives (or views). We understand how to create Angular Components. There is no view linked with the structural and attribute directives.

By adding and removing DOM elements, structural directives alter the DOM layout. The Asterix (*) sign precedes all structural Directives.

The Attribute directives can alter an element's look or behavior.

Creating Custom Attribute Directive

Angular comes with a number of built-in attribute directives. Let's construct a ttClass directive that allows us to give an element a class. Angular ngClass directive is similar.

Create a new directive using the following command:

ng generate directive tt-class.directive.ts

The class name must be the input to our directive. The property ttClass is designated as the input property by the Input decorator. The class name can be obtained from the parent component.

We use the same name as the ttClass select name. This allows us to use the property binding syntax in the component <button [ttClass]="'blue'">.

More than one @Input property can be created.
@Input() ttClass: string;

The attribute directive is attached to an element called the parent element. We need to retrieve the reference to alter the properties of the parent element. When we ask for the instance of the ElementRef in its constructor, Angular injects the parent element.
constructor(private el: ElementRef) {
}

The Parent DOM element is wrapped with ElementRef. The nativeElement attribute gives us access to the DOM element. We can add a class to an element using the classList method.
ngOnInit() {
  this.el.nativeElement.classList.add(this.ttClass);
}

The full code can be found below:
import { Directive, ElementRef, Input, OnInit } from '@angular/core'
 
@Directive({
  selector: '[ttClass]',
})
export class ttClassDirective implements OnInit {
 
  @Input() ttClass: string;
 
  constructor(private el: ElementRef) {
  }
 
  ngOnInit() {
    this.el.nativeElement.classList.add(this.ttClass);
  }
 
}

The CSS class blue and the file app.component.css
.blue {
  background-color: lightblue;
}

Finally, add our customer directive ttClass to the button element in the component template.
<button [ttClass]="'blue'">Click Me</button>

Our Custom Directive inserts class='blue' as you can see in the image below.

Creating Custom Structural Directive

Let's create a Custom Structural directive now. Let's make a custom directive that looks like ngIf and calls it ttIf. There isn't much of a difference between adding an attribute and creating a structural directive.

To begin, we'll make a tt-if directive and import the necessary modules.

Create a new directive using the following command:
ng generate directive tt-if.directive.ts

Our if condition will be kept in this variable.
_ttif: boolean;

We'll need ViewContainerRef and TemplateRef instances because we'll be modifying the DOM.
constructor(private _viewContainer: ViewContainerRef,
          private templateRef: TemplateRef<any>) {
}

The if condition must be passed as an input to our directive. The property ttIf is marked as the input property by the Input decorator. We're using the setter function because we want the information to be added or removed anytime if the condition changes.

The same name as the select name ttIf is used. In the template, we'll be able to employ the property binding syntax <div *ttIf="show">.
@Input()
set ttIf(condition) {
  this._ttif = condition
  this._updateView();
}

All of the magic takes place here. If the condition is true, we use the ViewContainerRef's createEmbeddedView function to insert the template. The clean command removes the template from the document object model (DOM).
_updateView() {
 if (this._ttif) {
   this._viewContainer.createEmbeddedView(this.templateRef);
 }
 else {
   this._viewContainer.clear();
 }

That is all there is to it. Remember to include ttIfDirective in the app.module.ts declaration array. The full code can be found below.
import { Directive, ViewContainerRef, TemplateRef, Input } from '@angular/core';
 
@Directive({ 
  selector: '[ttIf]' 
})
export class ttIfDirective  {
 
  _ttif: boolean;
 
  constructor(private _viewContainer: ViewContainerRef,
            private templateRef: TemplateRef<any>) {
  }
 
 
  @Input()
  set ttIf(condition) {
    this._ttif = condition
    this._updateView();
  }
 
  _updateView() {
    if (this._ttif) {
      this._viewContainer.createEmbeddedView(this.templateRef);
    }
    else {
      this._viewContainer.clear();
    }
  }
 
}

Open the app.component.ts and add the following code:

import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title: string = "Custom Directives in Angular";
  show=true;
}

Open the app.component.html and add the following code:
<h1> {{title}} </h1>
 
Show Me
<input type="checkbox" [(ngModel)]="show">
 
<div *ttIf="show">
  Using the ttIf directive
</div>
 
<div *ngIf="show">
  Using the ngIf directive
</div>

Now, run the application and compare the ngIf & our custom directive ttIf side by side.

Custom Directive Examples

Here are two additional examples of custom directives. Directives for Toggle and Tooltip

Toggle Directive

The CSS class toggle is added or removed from the Parent element using the following directive. We accomplish this by monitoring the click event on the host or parent element.

The @HostListener function decorator in Angular makes it simple to listen to events from the parent or host element. It's used to dress up the function (onClick method in the example). It takes the event's name as an argument and calls the decorated method every time the user raises the event.
@HostListener('click')
 private onClick() { 
 
 }

The ttToggleDirective's whole code is as follows.
import { Directive, ElementRef, Renderer2, Input, HostListener, HostBinding } from '@angular/core'
 
@Directive({
  selector: '[ttToggle]',
})
export class ttToggleDirective {
 
  private elementSelected = false;
 
  constructor(private el: ElementRef) {
  }
 
  ngOnInit() {
  }
 
  @HostListener('click')
  private onClick() {
    this.elementSelected = !this.elementSelected;
    if (this.elementSelected) {
      this.el.nativeElement.classList.add('toggle')
    } else {
      this.el.nativeElement.classList.remove('toggle')
    }
  }
 
}

Open the app.component.css and add the following code:

.toggle {
  background-color: yellow
}

Use it in the following example.
<button ttToggle>Click To Toggle</button>

Tooltip Directive

When the user hovers over the tooltip directive, the tip appears. The mouseenter and mouseleave events are monitored using the HostListener directive.

The showHint method inserts a span element into the DOM and positions it just below the host element on top and left. The removeHint method deletes the hint from the DOM.
import { Directive, ElementRef, Renderer2, Input, HostListener } from '@angular/core'
 
@Directive({
  selector: '[ttToolTip]',
})
export class ttTooltipDirective {
 
  @Input() toolTip: string;
 
  elToolTip: any;
 
  constructor(private elementRef: ElementRef,
            private renderer: Renderer2) {
  }
 
  @HostListener('mouseenter') 
  onMouseEnter() {
    if (!this.elToolTip) { this.showHint(); }
  }
 
  @HostListener('mouseleave') 
  onMouseLeave() {
    if (this.elToolTip) { this.removeHint(); }
  }
 
  ngOnInit() {
  }
 
  removeHint() {
    this.renderer.removeClass(this.elToolTip, 'tooltip');
    this.renderer.removeChild(document.body, this.elToolTip);
    this.elToolTip = null;
  }
 
  showHint() {
 
    this.elToolTip = this.renderer.createElement('span');
    const text = this.renderer.createText(this.toolTip);
    this.renderer.appendChild(this.elToolTip, text);
 
    this.renderer.appendChild(document.body, this.elToolTip);
    this.renderer.addClass(this.elToolTip, 'tooltip');
    
    let hostPos = this.elementRef.nativeElement.getBoundingClientRect();
    let tooltipPos= this.elToolTip.getBoundingClientRect();
 
    let top = hostPos.bottom+10 ; 
    let left = hostPos.left;
 
    this.renderer.setStyle(this.elToolTip, 'top', `${top}px`);
    this.renderer.setStyle(this.elToolTip, 'left', `${left}px`);
  }
}

Open the app.component.css and add the following code:
.tooltip {
  display: inline-block;
  border-bottom: 1px dotted black; 
  position: absolute;
}

Open the app.component.html and add the following code:
<button ttToolTip toolTip="Tip of the day">Show Tip</button> 

Now, run the application and see the output screen.

Conclusion

In this article, we learned how to create a Custom Directive in Angular.  In next article we learn about the Pipe In Angular.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post