Angular Directive

There are 3 types of directives:

  1. Components
  2. Attribute Directives
  3. Structural Directives

1. Components –

A component directive requires a view along with its attached behaviour.This type of directive adds DOM elements.

import {Component} from 'angular2/core'
@Component({
  selector: 'my-app',
  template: `

The Super Button

` }) export class App { btnClick() { alert('You clicked me!'); } }

Attribute & Structural Directives – Rather than adding new DOM elements, both these types of directives modify the existing DOM and do not have templates.

Attribute: Modifies the appearance or behavior of an element.
Structural: Modifies the DOM layout.

app.component.ts

import { Component } from '@angular/core';
import {MyOutline} from './my-outline.directive';
import {RepeatMe} from './repeat-me.directive';
@Component({
  selector: 'my-app',
  template: `
    <h3>Component (Main App)</h3>
    <button (click)="btnClick()">Click Me</button>
    
    <h3>Attribute Directive</h3>
    <div my-outline>OUTLINE</div>
    
    <h3>Structural Directive</h3>
    <p *repeatMe="5">I am being repeated...</p>
  `,
  directives: [MyOutline, RepeatMe],
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  
}

2. Attribute Directive – ngStyle and ngClass(built-in attribute dir)

As mentioned before, these directives modify the DOM

Examples of built-in attribute directives that ship with Angular 2 are ngStyle and ngClass. As mentioned before, these directives modify the DOM and we are going to do the same in a simple custom attribute directive of our own.

import {Directive, ElementRef, Renderer2} from '@angular/core';

@Directive({
  selector: '[my-outline]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()'
  }
})

export class MyOutline {
  constructor(private _element: ElementRef, private _renderer:Renderer2) { }
  onMouseEnter() { this._outlineToggle(); }
  onMouseLeave() { this._outlineToggle(); }
  
  private _outlineToggle() {
    this._renderer.setStyle(this._element.nativeElement, 'border', 'solid red 1px' );
  }
}

There are differences between component and directive, one is as mentioned before we do not have a template because this directive modifies the DOM.

This host refers to the DOM element that this directive is attached to. It is recommended to use the host rather than attaching listeners to the element directly. This is because we could introduce memory leaks or be confronted with naming issues. In the host object, we declare the event listeners that we are interested in listening too. These are linked to methods in the MyOutline class.

MyOutline class declares a constructor that injects the ElementRef service and creates a private _element property which gives us access to the DOM element. ElementRef has a nativeElement property that we could use to add our border directly like so:

el.nativeElement.style.border = ‘solid red 1px’;

But that is not recommended and dangerous. Instead, we use the Renderer and pass it our ElementRef’s nativeElement. The Renderer class contains everything we need to manipulate the element safely. This includes the setElementStyle method that we are using to set the border of the element.

That is it, a very simple directive that adds a border to an element when the user hovers over it.

3. Structural Directive – ngIf, ngFor and ngSwitch

Examples of built-in structural directives that ship with Angular 2 are ngIf, ngFor and ngSwitch.

As mentioned before these directives change the layout of the DOM, and we will also do the same in a small simple custom example.

We are now going to create a simple custom variation of the ngFor directive. Our version will take an integer input. This int represents how many times we want to repeat the element that we are attached to.

import {Directive, Input} from '@angular/core';
import {TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({ selector: '[repeatMe]' })
export class RepeatMe {
  constructor(
    private _templateRef: TemplateRef<any>,
    private _viewContainer: ViewContainerRef
    ) {}
  
  @Input() set repeatMe(count: Number) {
    for (var i = 0; i < count; i++) {
      this._viewContainer.createEmbeddedView(this._templateRef);
    }
   }
}

Much like our previous example, in our repeatMe class we declare a constructor that injects two things: TemplateRef and ViewContainerRef. TemplateRef points to our template and ViewContainerRef will handle creating the view and attaching it to the container.

Custom Validator Using directive in angular

import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
import { Directive } from '@angular/core';

@Directive({
    selector: '[appSelectValidator]',
    providers: [
        {
            provide: NG_VALIDATORS,
            useExisting: SelectRequiredValidatorDirective,
            multi: true
        }]
})
export class SelectRequiredValidatorDirective implements Validator {
    validate(control: AbstractControl): { [key: string]: any } | null {
        return control.value === '-1' ? { 'defaultSelected': true } : null;
    }
}

NG_VALIDATORS is a collection of validators. It already contains all the built-in validators like required, pattern etc. Before we can use our custom validator we have to add it to the list of validators by adding it to NG_VALIDATORS token. To specify that we want to add our validator to the list of validators, we set multi property to true

Implement Validator interface as we are creating a custom validator "export class SelectRequiredValidatorDirective implements Validator"

Since we are implementing validator interface, we have to provide implementation for the interface validate() method. This method has one input parameter and it's type is AbstractControl. AbstractControl extends both FormControl and FormGroup. In some cases you may want to validate a Formgroup instead of a single FormControl. So to cater for both scenarios, the parent type - AbstractControl is specified. This function returns an object if the validation fails or null if the validation succeeds. The object that is returned when the validation fails contains a key/value pair. The key is a string and the value can be anything.

"validate(control: AbstractControl): { [key: string]: any } | null"

If the selected value in the SELECT list is the default value (-1), then we return an object with key 'defaultSelected' and value of true to indicate that the validation has failed. Otherwise we return NULL to indicate validation succeeded. In the HTML we can use the "defaultSelected" key to display the validation error specific to this custom validator.

"return control.value === '-1' ? { 'defaultSelected': true } : null;"

Import the custom directive in a module where you want to use it.

We have one module - Root module. So in app.module.ts file include the following import statement


import { SelectRequiredValidatorDirective } from './shared/select-required-validator.directive';

Also include SelectRequiredValidatorDirective in the declarations array of the NgModule() decorator

Using the custom required validator on the SELECT element

<select id="department" #department="ngModel" name="department"
  [(ngModel)]="employee.department" appSelectValidator
  class="form-control">
  

"appSelectValidator" is the selector we gave for our custom validation directive and we are using it as an attribute on the SELECT list that we want to validate.

Referrence

  1. angular-2-the-three-types-of-directives
  2. angular-custom-validator-example
  3. angular-value-vs-ngvalue

Leave a Reply

Your email address will not be published. Required fields are marked *