Angular 8 - Directives


Advertisements

Angular 8 directives are DOM elements to interact with your application. Generally, directive is a TypeScript function. When this function executes Angular compiler checked it inside DOM element. Angular directives begin with ng- where ng stands for Angular and extends HTML tags with @directive decorator.

Directives enables logic to be included in the Angular templates. Angular directives can be classified into three categories and they are as follows −

Attribute directives

Used to add new attributes for the existing HTML elements to change its look and behaviour.

<HTMLTag [attrDirective]='value' />

For example,

<p [showToolTip]='Tips' />

Here, showToolTip refers an example directive, which when used in a HTML element will show tips while user hovers the HTML element.

Structural directives

Used to add or remove DOM elements in the current HTML document.

<HTMLTag [structuralDirective]='value' />

For example,

<div *ngIf="isNeeded"> 
   Only render if the *isNeeded* value has true value. 
</div>

Here, ngIf is a built-in directive used to add or remove the HTML element in the current HTML document. Angular provides many built-in directive and we will learn in later chapters.

Component based directives

Component can be used as directives. Every component has Input and Output option to pass between component and its parent HTML elements.

<component-selector-name [input-reference]="input-value"> ... </component-selector-name>

For example,

<list-item [items]="fruits"> ... </list-item>

Here, list-item is a component and items is the input option. We will learn how to create component and advanced usages in the later chapters.

Before moving to this topic, let’s create a sample application (directive-app) in Angular 8 to work out the learnings.

Open command prompt and create new Angular application using below command −

cd /go/to/workspace 
ng new directive-app 
cd directive-app

Create a test component using Angular CLI as mentioned below −

ng generate component test

The above create a new component and the output is as follows −

CREATE src/app/test/test.component.scss (0 bytes) CREATE src/app/test/test.component.html (19 bytes) CREATE src/app/test/test.component.spec.ts (614 bytes) 
CREATE src/app/test/test.component.ts (262 bytes) UPDATE src/app/app.module.ts (545 bytes)

Run the application using below command −

ng serve

DOM Overview

Let us have a look at DOM model in brief. DOM is used to define a standard for accessing documents. Generally, HTML DOM model is constructed as a tree of objects. It is a standard object model to access html elements.

We can use DOM model in Angular 8 for the below reasons −

  • We can easily navigate document structures with DOM elements.
  • We can easily add html elements.
  • We can easily update elements and its contents.

Structural directives

Structural directives change the structure of DOM by adding or removing elements. It is denoted by * sign with three pre-defined directives NgIf, NgFor and NgSwitch. Let’s understand one by one in brief.

NgIf directive

NgIf directive is used to display or hide data in your application based on the condition becomes true or false. We can add this to any tag in your template.

Let us try ngIf directive in our directive-app application.

Add the below tag in test.component.html.

<p>test works!</p> 
<div *ngIf="true">Display data</div>

Add the test component in your app.component.html file as follows −

<app-test></app-test>

Start your server (if not started already) using the below command −

ng serve

Now, run your application and you could see the below response −

NgServe

If you set the condition ngIf=“false” then, contents will be hidden.

ngIfElse directive

ngIfElse is similar to ngIf except, it provides option to render content during failure scenario as well.

Let’s understand how ngIfElse works by doing a sample.

Add the following code in test.component.ts file.

export class TestComponent implements OnInit { 
   isLogIn : boolean = false;
   isLogOut : boolean = true; 
}

Add the following code in test.component.html file as follows −

<p>ngIfElse example!</p> 
<div *ngIf="isLogIn; else isLogOut"> 
   Hello you are logged in 
</div>
<ng-template #isLogOut> 
   You're logged out.. 
</ng-template>

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

NgApplication

Here,

isLogOut

value is assigned as true, so it goes to else block and renders ng-template. We will learn ng-template later in this chapter.

ngFor directive

ngFor is used to repeat a portion of elements from the list of items.

Let’s understand how ngFor works by doing a sample.

Add the list in test.component.ts file as shown below −

list = [1,2,3,4,5];

Add ngFor directive in test.component.html as shown below −

<h2>ngFor directive</h2> 
<ul> 
   <li *ngFor="let l of list">
      {{l}} 
   </li>
</ul>

Here, the let keyword creates a local variable and it can be referenced anywhere in your template. The let l creates a template local variable to get the list elements.

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

Ngdirective

trackBy

Sometimes, ngFor performance is low with large lists. For example, when adding new item or remove any item in the list may trigger several DOM manipulations. To iterate over large objects collection, we use trackBy.

It is used to track when elements are added or removed. It is performed by trackBy method. It has two arguments index and element. Index is used to identity each element uniquely. Simple example is defined below.

Let’s understand how trackBy works along with ngFor by doing a sample.

Add the below code in test.component.ts file.

export class TestComponent { 
   studentArr: any[] = [ { 
      "id": 1, 
      "name": "student1" 
   }, 
   { 
      "id": 2,
      "name": "student2" 
   }, 
   { 
      "id": 3, "name": "student3"
   },
   { 
      "id": 4, 
      "name": "student4" 
   } 
   ]; 
   trackByData(index:number, studentArr:any): number { 
      return studentArr.id; 
   }

Here,

We have created,

trackByData()

method to access each student element in a unique way based on the id.

Add the below code in test.component.html file to define trackBy method inside ngFor.

<ul> 
   <li *ngFor="let std of studentArr; trackBy: trackByData">
      {{std.name}} 
   </li>
</ul>

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

Directive

Here, the application will print the student names. Now, the application is tracking student objects using the student id instead of object references. So, DOM elements are not affected.

NgSwitch directive

NgSWitch is used to check multiple conditions and keep the DOM structure as simple and easy to understand.

Let us try ngSwitch directive in our directive-app application.

Add the following code in test.component.ts file.

export class TestComponent implements OnInit {  
   logInName = 'admin'; 
}

Add the following code in test.component.html file as follows −

<h2>ngSwitch directive</h2> 
<ul [ngSwitch]="logInName"> 
   <li *ngSwitchCase="'user'"> 
      <p>User is logged in..</p> 
   </li> 
   <li *ngSwitchCase="'admin'"> 
      <p>admin is logged in</p> 
   </li> 
   <li *ngSwitchDefault> 
      <p>Please choose login name</p> 
   </li> 
</ul>

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

NgSwitch

Here, we have defined logInName as admin. So, it matches second SwitchCase and prints above admin related message.

Attribute directives

Attribute directives performs the appearance or behavior of DOM elements or components. Some of the examples are NgStyle, NgClass and NgModel. Whereas, NgModel is two-way attribute data binding explained in previous chapter.

ngStyle

ngStyle directive is used to add dynamic styles. Below example is used to apply blue color to the paragraph.

Let us try ngStyle directive in our directive-app application.

Add below content in test.component.html file.

<p [ngStyle]="{'color': 'blue', 'font-size': '14px'}"> 
   paragraph style is applied using ngStyle 
</p>

Start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

ngStyle

ngClass

ngClass is used to add or remove CSS classes in HTML elements.

Let us try ngClass directive in our directive-app application.

Create a class User using the below command

ng g class User

You could see the following response −

CREATE src/app/user.spec.ts (146 bytes) 
CREATE src/app/user.ts (22 bytes)

Move to src/app/user.ts file and add the below code −

export class User { 
   userId : number; userName : string; 
}

Here, we have created two property userId and userName in the User class.

Open test.component.ts file and add the below changes −

import { User } from '../user'; 
export class TestComponent implements OnInit {  
   users: User[] = [ 
      { 
         "userId": 1, 
         "userName": 'User1' 
      }, 
      { 
         "userId": 2, 
         "userName": 'User2' 
      }, 
   ]; 
}

Here, we have declared a local variable, users and initialise with 2 users object.

Open test.component.css file and add below code

.highlight { 
   color: red; 
}

Open your test.component.html file and add the below code −

<div class="container"> 
   <br/> 
   <div *ngFor="let user of users" [ngClass]="{ 
      'highlight':user.userName === 'User1' 
   }"> 
      {{ user.userName }} 
   </div> 
</div>

Here,

We have applied, ngClass for User1 so it will highlight the User1.

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

ngClass

Custom directives

Angular provides option to extend the angular directive with user defined directives and it is called Custom directives. Let us learn how to create custom directive in this chapter.

Let us try to create custom directive in our directive-app application.

Angular CLI provides a below command to create custom directive.

ng generate directive customstyle

After executing this command, you could see the below response −

CREATE src/app/customstyle.directive.spec.ts (244 bytes) 
CREATE src/app/customstyle.directive.ts (151 bytes) UPDATE src/app/app.module.ts (1115 bytes)

Open app.module.ts. The directive will be configured in the AppModule through declarations meta data.

import { CustomstyleDirective } from './customstyle.directive'; 
@NgModule({ 
   declarations: [ 
      AppComponent, 
      TestComponent, 
      CustomstyleDirective 
   ] 
})

Open customstyle.directive.ts file and add the below code −

import { Directive, ElementRef } from '@angular/core'; 
@Directive({ 
   selector: '[appCustomstyle]' 
}) 
export class CustomstyleDirective {
   constructor(el: ElementRef) { 
      el.nativeElement.style.fontSize = '24px'; 
   } 
}

Here, constructor method gets the element using CustomStyleDirective as el. Then, it accesses el’s style and set its font size as 24px using CSS property.

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

Custom directives

ng-template

ng-template is used to create dynamic and reusable templates. It is a virtual element. If you compile your code with ng-template then is converted as comment in DOM.

For example,

Let’s add a below code in test.component.html page.

<h3>ng-template</h3> 
<ng-template>ng-template tag is a virtual element</ng-template>

If you run the application, then it will print only h3 element. Check your page source, template is displayed in comment section because it is a virtual element so it does not render anything. We need to use ng-template along with Angular directives.

Normally, directive emits the HTML tag it is associated. Sometimes, we don’t want the tag but only the content. For example, in the below example, li will be emitted.

<li *ngFor="let item in list">{{ item }}</li>

We can use ng-template to safely skip the li tag.

ng-template with structural directive

ng-template should always be used inside ngIf, ngFor or ngSwitch directives to render the result.

Let’s assume simple code.

<ng-template [ngIf]=true> 
   <div><h2>ng-template works!</h2></div> 
</ng-template>

Here, if ngIf condition becomes true, it will print the data inside div element. Similarly, you can use ngFor and ngSwitch directives as well.

NgForOf directive

ngForOf is also a structural directive used to render an item in a collection. Below example is used to show ngForOf directive inside ng-template.

import { Component, OnInit } from '@angular/core'; 
@Component({ 
   selector: 'app-test', 
   template: ` 
   <div> 
   <ng-template ngFor let-item [ngForOf]="Fruits" let-i="index"> 
   <p>{{i}}</p> 
   </ng-template> 
   </div>` 
   , 
   styleUrls: ['./test.component.css'] 
}) 
export class TestComponent implements OnInit { 
   Fruits = ["mango","apple","orange","grapes"]; 
   ngOnInit() 
   { 
   } 
}

If you run the application, it will show the index of each elements as shown below −

0 
1 
2 
3

Component directives

Component directives are based on component. Actually, each component can be used as directive. Component provides @Input and @Output decorator to send and receive information between parent and child components.

Let us try use component as directive in our directive-app application.

Create a new ChildComponent using below command −

ng generate component child
CREATE src/app/child/child.component.html (20 bytes) CREATE src/app/child/child.component.spec.ts (621 bytes) 
CREATE src/app/child/child.component.ts (265 bytes) CREATE src/app/child/child.component.css (0 bytes) UPDATE src/app/app.module.ts (466 bytes)

Open child.component.ts and add below code −

@Input() userName: string;

Here, we are setting a input property for ChildComponent.

Open child.component.html and add below code −

<p>child works!</p> 
<p>Hi {{ userName }}</p>

Here, we are using the value userName to welcome the user.

Open test.component.ts and add below code −

name: string = 'Peter';

Open test.component.html and add below code −

<h1>Test component</h1> 
<app-child [userName]="name"><app-child>

Here, we are using AppComponent inside the TestComponent as a directive with input property.

Finally, start your application (if not done already) using the below command −

ng serve

Now, run your application and you could see the below response −

[](images/directive-app/component_as_directive.PNG"

Working example

Let us add a new component in our ExpenseManager application to list the expense entries.

Open command prompt and go to project root folder.

cd /go/to/expense-manager

Start the application.

ng serve

Create a new component, ExpenseEntryListComponent using below command −

ng generate component ExpenseEntryList

Output

The output is as follows −

CREATE src/app/expense-entry-list/expense-entry-list.component.html (33 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.spec.ts (700 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.ts (315 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.css (0 bytes) 
UPDATE src/app/app.module.ts (548 bytes)

Here, the command creates the ExpenseEntryList Component and update the necessary code in AppModule.

Import ExpenseEntry into ExpenseEntryListComponent component (src/app/expense-entry-list/expense-entry-list.component)

import { ExpenseEntry } from '../expense-entry';

Add a method, getExpenseEntries() to return list of expense entry (mock items) in ExpenseEntryListComponent (src/app/expense-entry-list/expense-entry-list.component)

getExpenseEntries() : ExpenseEntry[] { 
   let mockExpenseEntries : ExpenseEntry[] = [ 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1,
         item: "Pizza",
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) 
      }, 
   ]; 
   return mockExpenseEntries; 
}

Declare a local variable, expenseEntries and load the mock list of expense entries as mentioned below −

title: string; 
expenseEntries: ExpenseEntry[]; 
constructor() { } 
ngOnInit() { 
   this.title = "Expense Entry List"; 
   this.expenseEntries = this.getExpenseEntries(); 
}

Open the template file (src/app/expense-entry-list/expense-entry-list.component.html) and show the mock entries in a table.

<!-- Page Content -->
<div class="container"> 
   <div class="row"> 
      <div class="col-lg-12 text-center" style="padding-top: 20px;">
         <div class="container" style="padding-left: 0px; padding-right: 0px;"> 
            <div class="row"> 
               <div class="col-sm" style="text-align: left;"> 
                  {{ title }} 
               </div> 
               <div class="col-sm" style="text-align: right;"> 
                  <button type="button" class="btn btn-primary">Edit</button> 
               </div> 
            </div> 
         </div> 
         <div class="container box" style="margin-top: 10px;"> 
            <table class="table table-striped"> 
               <thead> 
                  <tr> 
                     <th>Item</th> 
                     <th>Amount</th> 
                     <th>Category</th> 
                     <th>Location</th> 
                     <th>Spent On</th> 
                  </tr> 
               </thead> 
               <tbody> 
                  <tr *ngFor="let entry of expenseEntries"> 
                     <th scope="row">{{ entry.item }}</th> 
                     <th>{{ entry.amount }}</th> 
                     <td>{{ entry.category }}</td> 
                     <td>{{ entry.location }}</td> 
                     <td>{{ entry.spendOn | date: 'short' }}</td> 
                  </tr> 
               </tbody> 
            </table> 
         </div> 
      </div> 
   </div> 
</div>

Here,

  • Used bootstrap table. table and table-striped will style the table according to Boostrap style standard.

  • Used ngFor to loop over the expenseEntries and generate table rows.

Open AppComponent template, src/app/app.component.html and include ExpenseEntryListComponent and remove ExpenseEntryComponent as shown below −

... 
<app-expense-entry-list></app-expense-entry-list>

Finally, the output of the application is as shown below.

AppComponent
Advertisements