Angular, Angular 4

Angular 4/5 Tutorial : Understanding Directives

directives

Hello everyone, as you may know, Angular 5 is been released and a lot of new features has been introduced. But the basics components are same as it is an update to the Angular 4. So everything till now we learnt will work both on Angular 4 and 5.
In the previous post, http://www.mytechthinking.in/2017/10/14/angular-4-tutorial-component-lifecycle/, we saw the lifecycle of the components. In this post, we will learn about the directives provided by Angular and also how to create custom directive.

Directives can be divided into two types, attribute and structural.

Attribute Directives:  Attribute directives are similar to HTML attribute, they change only the element they are added to. They do not create or delete any element.

Structural Directives: Structural directives also look like normal HTML attribute but they have a leading *. They affect the DOM, elements get created or deleted using structural directives.

Let us see some examples of different types of directives. I ‘ll be using the angular material for designing.

ngIf – ngFor

Let’s create an array of vegetables and fruits in the typescript.


import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
onlyFruits = false;
fruits = ['Apple', 'Banana', 'Cherry', 'Strawberry'];
vegetables = ['Potato', 'Onion', 'Capsicum', 'Spinach'];
}

Now let’s display the values of arrays using the ngFor directive in the template.


<div fxFlex="row">
<div fxFlex>
<buttonmat-raised-button (click)="onlyFruits = !onlyFruits"color="primary">Show Fruits</button>
</div>
<div fxFlex>
<mat-list>
<mat-list-item *ngFor="let fruit of fruits">
{{fruit}}
</mat-list-item>
<mat-list-item *ngFor="let vegetable of vegetables">
{{vegetable}}
</mat-list-item>
</mat-list>
</div>
</div>

ngfor directive

As we can see the value of both arrays has been displayed. Also, you can see the onlyFruits button which does nothing, because we have not added any conditions on array display statement. Now, lets add the conditions to display fruits only if the onlyFruits button is clicked.
 <div fxFlex="row"> 
 <div fxFlex> 
  <buttonmat-raised-button (click)="onlyFruits = !onlyFruits"color="primary">Show Fruits</button> 
  </div> 
  <div fxFlex> 
  <mat-list> 
  <mat-list-item *ngFor="let fruit of fruits"> 
  {{fruit}} 
  </mat-list-item> 
  <div *ngIf="!onlyFruits"> 
  <mat-list-item *ngFor="let vegetable of vegetables"> 
  {{vegetable}} 
  </mat-list-item> 
  </div> 
  </mat-list> 
  </div> 
  </div> 
As you can see to add ngIf I have added an extra <div>, as we cannot add more than one structural directive in one element. So now if we click on the button we will see only fruits list.
ngIf directive

ngSwitch

Sometimes we have multiple conditions and using ngIf else might not be the good option to use. In that cases, we can use the ngSwitch directive. ngSwitch can have multiple condition check, and implementing it is easy. So let’s try out an example:


So let’s take I have created a numeric variable ‘someValue’ with value 10 and which may change in later stage and I want to display some block depending upon the value of ‘someValue’ variable. So that can be achieved by:


 <div [ngSwitch]="someValue"> 
 <div *ngSwitchCase="2">The Value is 2</div> 
 <div *ngSwitchCase="10">The Value is 10</div> 
 <div *ngSwitchCase="200">The Value is 200</div> 
 <div *ngSwitchDefault>The value is zero</div> 
 </div> 
 
The angular will display the text when the value of variable satisfies the condition. Why not change the value and give it a try?

Creating custom Attribute Directive

Angular provides the option to create a custom directive of our own. We can create custom directive using the CLI or manually. To create using CLI use the command ng g directive custom-name. But if you want to create it manually remember to add the custom directive in declarations array of app.module.  Let’s create a custom attribute directive to change the background colour of the element when in focus using HostListener and Property binding.

Create a new file with the name custom-focus.directive.ts

  import { Directive, ElementRef, OnInit, HostListener, HostBinding, Input } from '@angular/core'; 
 @Directive({ 
 selector:'[appCustomFocus]' 
 }) 
 export class CustomFocusDirective implements OnInit { 
 @Input() defaultColor: string = 'transparent'; 
 @Input() focusColor: string = 'indigo'; 
 @HostBinding('style.backgroundColor') backgourndColor: string ; 
 constructor(private elementRef:ElementRef) { 
 } 
 ngOnInit() { 
 this.backgourndColor=this.defaultColor; 
 } 
 @HostListener('mouseenter') mouseover(eventData: Event) { 
 this.backgourndColor=this.focusColor; 
 } 
 @HostListener('mouseleave') mouseleave(eventData: Event) { 
 this.backgourndColor=this.defaultColor; 
 } 
 } 
 
To create a directive we need to use the Directive decorator from angular/core package. The directive decorator accepts one property called selector. In the selector provide the name with which you want to use the directive. As we are creating an attribute directive we will add square brackets to the selector name as shown above. This will allow the angular to identify this custom directive as an attribute to the element.
In the above snippet, we have also used three other decorators: Input, HostBinding and HostListener. Input decorator will enable the property to be changed by the user, like defaultColor and focusColor properties which we will set when using this directive.
HostBinding decorator binds the directive property (in this case backgroundColor) to the Host’s properties. As here we are creating an attribute directive to change colour we know that element will have style property with backgroundColor as sub-property so we are binding the style.backgroundColor property of host by passing it to decorator constructor. We can bind any property or sub-property of the host.
HostListener decorator will bind any event associated with the host and the member function. Like here we are binding the ‘mouseleave’ and ‘mouseenter’ event to the two functions. In these functions, we are changing the backgroundColor property which will change the elements backgroundColor.
Now as our custom directive is created let’s add this to app.module.
app.module.ts


  import { BrowserModule } from '@angular/platform-browser'; 
 import { NgModule } from '@angular/core'; 
 import { MatButtonModule, MatListModule } from '@angular/material'; 
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 
 import { AppComponent } from './app.component'; 
 import { CustomFocusDirective } from './custom-focus.directive'; 
 @NgModule({ 
 declarations: [ 
 AppComponent, 
 CustomFocusDirective 
 ], 
 imports: [ 
 BrowserModule, 
 MatButtonModule, 
 MatListModule 
 ], 
 providers: [], 
 bootstrap: [AppComponent] 
 }) 
 export class AppModule { } 
 

Now let’s see our custom directive in use.


   <p appCustomFocus [defaultColor]="'yellow'" [focusColor]="'blue'"> 
 <spanclass="mat-h1"> 
 Stye with me Custom Focus. 
 </span> 
 </p> 
 

In the above HTML as you can see we have not added the square brackets to the appCustomFocus directive but in the selector, we have used the square directives. Why didn’t we add the square brackets, because we are using this as an attribute to the element, therefore, we don’t need to add brackets, however, to set the properties of appCustomFocus directive we use the square brackets. Now let’s see how it looks.

CustomDirective CustomHighlight
In the second screenshot, the mouse pointer is not visible but you can try this out.

Custom attribute directive using the Renderer2

The above-explained example can be achieved using the Renderer2 also. For that, we need to do some changes to our directive class.


   import { Directive, ElementRef, OnInit, Renderer2, HostListener, HostBinding, Input } from '@angular/core'; 
 @Directive({ 
 selector:'[appCustomFocus]' 
 }) 
 export class CustomFocusDirective implements OnInit { 
 @Input() defaultColor: string = 'transparent'; 
 @Input() focusColor: string = 'indigo'; 
 /* @HostBinding('style.backgroundColor') backgourndColor: string; */ 
 constructor(private elementRef:ElementRef, private renderer:Renderer2) { 
 } 
 ngOnInit() { 
 /* this.backgourndColor = this.defaultColor; */ 
 this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', this.defaultColor); 
 } 
 @HostListener('mouseenter') mouseover(eventData: Event) { 
 this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', this.focusColor); 
 /* this.backgourndColor = this.focusColor; */ 
 } 
 @HostListener('mouseleave') mouseleave(eventData: Event) { 
 this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', this.defaultColor); 
 /* this.backgourndColor = this.defaultColor; */ 
 } 
 } 
 
Here instead of using HostBinding, we are using Renderer2 of the angular core package, which allow us to set Style using the functions. It accepts the element, property and property value.

Creating custom structural directive

Before we start creating structural directive we need to why structural directive has leading *. The * tells angular that the directive is a structural directive, then in background applies the directive as an attribute to the <ng-template> element (which is an angular defined element). For example:


<div *ngIf="someCondtion" >
.....
</div>

In background, it is transformed into-


<ng-template [ngIf]="someCondition" >
<div>
.....
</div>

</ng-template>

So as you can see its the similar to attribute directive, therefore, the ways to create structural direct is also same.



Now let’s create a custom If condition directive, which we will for the previous fruits and vegetable examples.

Create custom-if.directive.ts:

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; 
 @Directive({ 
 selector:'[appIf]' 
 }) 
 export class CustomIfDirective { 
 @Input() set appIf(condition: boolean) { 
 if (condition) { 
 this.vcRef.createEmbeddedView(this.templateRef); 
 } else { 
 this.vcRef.clear(); 
 } 
 } 
 constructor(private templateRef:TemplateRef<any>, private viewRef:ViewContainerRef) { } 
 } 
 

In above directive example, we have added a property appIf with a setter using ‘set’ keyword. This will allow the value assigned to the directive to be set to the property. Remember to have the same name for selector and setter method. In the constructor, we are injecting two properties: TemplateRef and ViewContainerRef. As the name suggests TemplateRef give the reference to the <ng-template> element which we saw angular creates in the background if we use *. And ViewContainerRef gives the reference to the view container where we have to render the element.
So if the condition is true we are rendering the Template using the createEmbeddedView method of ViewContainerRef and on the false state, we are clearing the ViewContainer.
Now, lets add the directive entry in the app.module.ts file if you have created this manually.

Let’s use the directive in our example:

   <div fxFlex="row"> 
 <divfxFlex> 
 <buttonmat-raised-button (click)="onlyFruits = !onlyFruits"color="primary">Show Fruits</button> 
 </div> 
 <divfxFlex> 
 <mat-list> 
 <mat-list-item *ngFor="let fruit of fruits"> 
 {{fruit}} 
 </mat-list-item> 
 <!-- <div *ngIf="!onlyFruits"> 
 <mat-list-item *ngFor="let vegetable of vegetables"> 
 {{vegetable}} 
 </mat-list-item> 
 </div> --> 
 <div *appIf="!onlyFruits"> 
 <mat-list-item *ngFor="let vegetable of vegetables"> 
 {{vegetable}} 
 </mat-list-item> 
 </div> 
 </mat-list> 
 </div> 
 </div> 
 
It works as expected.

Leave a Reply

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