Let us start the Day 4 of Learning Angular 4.0 in 10 Days. In the previous articles, we already discussed Data Binding Concept in Angular 4.0. If you want to read the previous articles of this series, do visit the below link.
A Directive modifies the DOM to change appearance, behavior or layout of DOM elements. Directives are one of the core building blocks Angular 4 uses to build applications. In fact, Angular 4 components are in large part directives with templates. From an Angular 1 perspective, Angular 4 components have assumed a lot of the roles directives used to. The majority of issues that involve templates and dependency injection rules will be done through components, and issues that involve modifying generic behavior is done through directives. There are three main types of directives in Angular 4:
- Component - directive with a template.
- Attribute directives - directives that change the behavior of a component or element but don't affect the template
- Structural directives - directives that change the behavior of a component or element by affecting how the template is rendered.
Before the Component API shipped in Angular 1.5, directives with templates that were used as elements were defined in a similar way as an attribute directive using the same API, which could lead to messy directive declarations that can be hard to grok. Components use the directive API under the hood, but give us a cleaner interface for defining them by hiding a lot of the defaults that would otherwise be cumbersome.
Component Vs Directives
Since as per the above list, component itself act as direcitves. So as a programmer, it is very common to assume that component and direcitves are the same. But actually it is not true. Below the comparison of the Directives and component.
Component |
Directives |
A component is register with the @Component Decorator |
A Directives is register with the @Directives Decorator |
Component is a directive which uses shadow DOM to create encapsulated visual behavior called components. Components are typically used to create UI widgets |
Directive is used to add behavior to an existing DOM element |
Component is used to break up the application into smaller components. |
Directive is use to design re-usable components. |
Only one component can be present per DOM element. |
Many directives can be used per DOM element. |
@View decorator or templateurl template are mandatory in the component. |
Directive doesn't use View. |
Attribute Directives
Attribute directives are a way of changing the appearance or behavior of a component or a native DOM element. Ideally, a directive should work in a way that is component agnostic and not bound to implementation details. Attribute directives actually modifies the appearance or behavior of an element. The attribute directive changes the appearance or behavior of a DOM element. These directives look like regular HTML attributes in templates. The ngModel directive which is used for two-way binding is an perfect example of an attribute directive. For create attribute directives, we always need to use or inject the below objects in our custom attribute directive component class. For creating an attribute directives, we have remember the below topics,
- Import required modules like Directives, ElementRef and Renderer from Angular core library
- Create a TypeScript Class
- Use @Directive decorator in the class
- Set the value of the selector property in @directive decorator function. The directive would be used, using the selector value on the elements.
- In the constructor of the class, inject ElementRef and Renderer object.
We are injecting ElementRef in the directive’s constructor to access the DOM element. We are also injecting Renderer in the directive’s constructor to work with DOM’s element style. We are calling the renderer’s setElementStyle function. In the function, we pass the current DOM element by using the object of ElementRef and setting the behavior or property of the current element. We can use this attribute directive by its selector in our component:
ElementRef
While creating custom attribute directive, we inject ElementRef in the constructor to access the DOM element. Actually elementref provide access to the underlying native element. ElementRef is a service that grants us direct access to the DOM element through its nativeElement property. That’s all we need to set the element’s color using the browser DOM API.
Renderer
While creating custom attribute directive, we inject Renderer in the constructor to access the DOM element’s style. Actually we call the renderer’s setElementStyle function. In this function, we pass the current DOM element with the help of ElementRef object and set the required attribute of the current element.
HostListener
Some times we may require to access input property within the attribute directive so that as per given attribute directive, we can apply related attribute within DOM Element. For trap user actions, we can call different methods to handle the user actions. To access the method for operate user actions we need to decorate the methods within the @HostListener method.
For example, Angular 4 has built-in attribute directives such as ngClass and ngStyle that work on any component or element.
NgStyle Directive
Angular 4 provides a built-in directive, ngStyle , to modify a component or element's style attribute. Here's an example,
- @Component({
- selector: 'app-style-example',
- template: `
- <p style="padding: 1rem"
- [ngStyle]="{
- 'color': 'red',
- 'font-weight': 'bold',
- 'borderBottom': borderStyle
- }">
- <ng-content></ng-content>
- </p>
- `
- })
- export class StyleExampleComponent {
- borderStyle = '1px solid black';
- }
Notice that binding a directive works the exact same way as component attribute bindings. Here, we're binding an expression, an object literal, to the ngStyle directive so the directive name must be enclosed in square brackets. ngStyle accepts an object whose properties and values define that element's style. In this case, we can see that both kebab case and lower camel case can be used when specifying a style property. Also notice that both the html style attribute and Angular 4 ngStyle directive are combined when styling the element. We can remove the style properties out of the template into the component as a property object, which then gets assigned to NgStyle using property binding. This allows dynamic changes to the values as well as provides the flexibility to add and remove style properties.
- @Component({
- selector: 'app-style-example',
- template: `
- <p style="padding: 1rem"
- [ngStyle]="alertStyles">
- <ng-content></ng-content>
- </p>
- `
- })
- export class StyleExampleComponent {
- borderStyle = '1px solid black';
- alertStyles = {
- 'color': 'red',
- 'font-weight': 'bold',
- 'borderBottom': this.borderStyle
- };
- }
NgClass Directive
The ngClass directive changes the class attribute that is bound to the component or element it's attached to. There are a few different ways of using the directive.
Binding a String
We can bind a string directly to the attribute. This works just like adding an html class attribute. In this case, we're binding a string directly so we avoid wrapping the directive in square brackets. Also notice that the ngClass works with the class attribute to combine the final classes.
- @Component({
- selector: 'app-class-as-string',
- template: `
- <p ngClass="centered-text underlined" class="orange">
- <ng-content></ng-content>
- </p>
- `,
- styles: [`
- .centered-text {
- text-align: center;
- }
- .underlined {
- border-bottom: 1px solid #ccc;
- }
- .orange {
- color: orange;
- }
- `]
- })
- export class ClassAsStringComponent {
- }
Binding An Array
Here, since we are binding to the ngClass directive by using an expression, we need to wrap the directive name in square brackets. Passing in an array is useful when you want to have a function put together the list of applicable class names.
- @Component({
- selector: 'app-class-as-array',
- template: `
- <p [ngClass]="['warning', 'big']">
- <ng-content></ng-content>
- </p>
- `,
- styles: [`
- .warning {
- color: red;
- font-weight: bold;
- }
- .big {
- font-size: 1.2rem;
- }
- `]
- })
- export class ClassAsArrayComponent {
- }
Binding An Object
Lastly, an object can be bound to the directive. Angular 4 applies each property name of that object to the component if that property is true. Here we can see that since the object's card and flat properties are true, those classes are applied but since dark is false, it's not applied. NgClass Directive
- @Component({
- selector: 'app-class-as-object',
- template: `
- <p [ngClass]="{ card: true, dark: false, flat: flat }">
- <ng-content></ng-content>
- <br>
- <button type="button" (click)="flat=!flat">Toggle Flat</button>
- </p>
- `,
- styles: [`
- .card {
- border: 1px solid #eee;
- padding: 1rem;
- margin: 0.4rem;
- font-family: sans-serif;
- box-shadow: 2px 2px 2px #888888;
- }
- .dark {
- background-color: #444;
- border-color: #000;
- color: #fff;
- }
- .flat {
- box-shadow: none;
- }
- `]
- })
- export class ClassAsObjectComponent {
- flat: boolean = true;
- }
Sample Code of app.component.attrdirective.ts
- import { Component, OnInit } from '@angular/core';
-
- @Component({
- moduleId: module.id,
- selector: 'attribute-directive',
- templateUrl: 'app.component.attrdirective.html',
- styles: [".red {color:red;}", ".blue {color:blue}", ".cyan {color : cyan}"]
- })
-
- export class AttrDirectiveComponent implements OnInit {
- showColor: boolean = false;
-
- constructor() { }
-
- ngOnInit() { }
-
- changeColor(): void {
- this.showColor = !this.showColor;
- }
- }
Sample Code of app.component.attrdirective.html
- <div>
- <strong>This is a Attribute Directives</h3>
- <span [class.red]="true">Attribute Change</span><br />
- <span [ngClass]="{'blue':true}">Attribute Change by Using NgClass</span><br />
- <span [ngStyle]="{'font-size':'14px','color':'green'}">Attribute Change by Using NgStyle</span>
- <br /><br />
- <span [class.cyan]="showColor">Attribute Change</span><br />
- <span [ngClass]="{'cyan':showColor}">Attribute Change by Using NgClass</span><br />
- <input type="button" value="Change Color" (click)="changeColor()" />
- <br /><br />
- <span [class.cyan]="showColor">Attribute Change</span><br />
- <span [ngClass]="{'cyan':showColor, 'red' : !showColor}">Attribute Change by Using NgClass</span><br />
- <br />
- </div>
Output
Sample Code of app.directive.blink.ts
- import { Directive, ElementRef, Input, Renderer } from '@angular/core';
-
- @Directive({
- selector: '[blink]'
- })
-
- export class BlinkDirective {
-
- constructor(el: ElementRef, renderer: Renderer) {
- setInterval(() => {
- let style = "hidden";
- if (el.nativeElement.style.visibility && el.nativeElement.style.visibility == "hidden") {
- style = "visible";
- }
- renderer.setElementStyle(el.nativeElement, 'visibility', style);
- }, 750);
- }
-
- }
Sample Code of app.component.blink.ts
- import { Component } from '@angular/core'
-
- @Component({
- moduleId: module.id,
- selector: 'blink-demo',
- templateUrl: 'app.component.blink.html'
- })
- export class BlinkComponent {
- title: string;
- constructor() {
- this.title = 'Angular 4 Attribute Directive: Blink'
- }
- }
Sample Code of app.component.blink.html
- <div>
- <strong>{{ title }}</strong>
-
- <strong>Here is a normal strong Tag</strong>
- <strong blink>And here is an example strong tag that blinks!</strong>
-
- <p>This is a normal P Tag</p>
- <p blink>This is a P Tag with our Custom Attribute Directive</p>
- </div>
Output
Structural Directives
Attribute directives are a way of changing the appearance or behavior of a component or a native DOM element. Ideally, a directive should work. Structural Directives are a way of handling how a component or element renders through the use of the template tag. This allows us to run some code that decides what the final rendered output will be. Angular 4 has a few built-in structural directives such as ngIf , ngFor , and ngSwitch . Note: For those who are unfamiliar with the template tag, it is an HTML element with a few special properties. Content nested in a template tag is not rendered on page load and is something that is meant to be loaded through code at runtime. Structural directives have their own special syntax in the template that works as syntactic sugar.
- @Component({
- selector: 'app-directive-example',
- template: `
- <p *structuralDirective="expression">
- Under a structural directive.
- </p>
- `
- })
Instead of being enclosed by square brackets, our dummy structural directive is prefixed with an asterisk. Notice that the binding is still an expression binding even though there are no square brackets. That's due to the fact that it's syntactic sugar that allows using the directive in a more intuitive way and similar to how directives were used in Angular JS 1.
Angular 4 provides a built-in directive ngIf, ngFor and ngSwitch , to modify a component or element's style attribute.
ngIf
In Angular 1.x version, there was the ng-show and ng-hide directives which would show or hide the DOM elements on what the given expression evaluates by setting the display CSS property. In Angular 4.0, these two directives has been remove from the framework and introduced a new directive named ngIf. The main difference of ngIf directive over ng-show or ng-hide is that it actually remove the element or components entirely from DOM. For ng-show or ng-hide, angular keeps the DOM elements/components in the page, so any component behaviors may keep running even the component is not visible in the page. In Angular 4.0, ng-show or ng-hide directive is not available but we can obtain the same functionality by using the [style.display] property of any element. Now, one question always arise in our mind that why angular remove component or elements from DOM in case of ngIf directives? Actually, although in the earlier version, it hide or invisible the component or element, but still the elements or component attached with DOM. So it continues to fired its event listener. Also it keep changing while the model data has been change due to model binding. So in this way, this invisible components or elements uses resources which might be useful for some where else. The performance and memory burden can be substantial and the user may not be benefitted at all. Setting ngIf value to false does effect the component resource consumptions. Angular removes the element from DOM, stop the changes detection for the associated component, detaches it from DOM events and destroys the components. The component can be garbage collected and free up memory. Components often have child components which themselves have children. All of them has been destroys when ngIf destroys the common ancestor.
Sample Code of app.component.ngif.ts
- import { Component, OnInit, Input } from '@angular/core';
-
- @Component({
- moduleId: module.id,
- selector: 'toggle-text',
- templateUrl: 'app.component.ngIf.html'
- })
-
- export class NgIfComponent implements OnInit {
- showInfo: boolean = false;
- caption: string = 'Show Text';
-
- constructor() { }
- ngOnInit() { }
-
- changeData(): void {
- this.showInfo = !this.showInfo;
- if (this.showInfo) {
- this.caption = 'Hide Text';
- }
- else {
- this.caption = 'Show Text';
- }
- }
- }
Sample Code of app.component.ngif.html
- <div>
- <input type="button" value="{{caption}}" (click)="changeData()"/>
- <br />
- <strong *ngIf="showInfo"><span>Demonstrate of Structural Directives - *ngIf</span></strong>
- </div>
Output
ngFor
The ngFor directives instantiates a template once per item from an iterable. The context of each instantiated template inherits from the outer context with the given loop variable. ngFor provides several exported values that can be used to local variables :-
- index - will be set to the current loop iteration for each template context
- first - will be set to a boolean value indicating whether the item is the first one in the iteration.
- last - will be set to a boolean value indicating whether the item is the last one in the iteration.
- even - will be set to a boolean value indicating whether this item has an even index.
- odd - will be set to a boolean value indicating whether this item has an odd index
Angular uses object identity to track insertions and deletions within the iterator and reproduce those changes in the DOM. This has important implications for animations and any stateful controls (such as <input> elements which accept user input) that are present. Inserted rows can be animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state such as user input.
Sample Code for app.component.ngFor.ts
- import { Component, OnInit, Directive } from '@angular/core';
-
- @Component({
- moduleId: module.id,
- selector: 'product-list',
- templateUrl: 'app.component.ngFor.html'
- })
-
- export class NgForComponent implements OnInit {
- productList: Array<string> = ['IPhone','Galaxy 7.0','Blackberry 10Z'];
-
- constructor() { }
- ngOnInit() { }
- }
Sample Code for app.component.ngFor.html
- <div>
- <strong>Demonstrate ngFor</strong>
- <ul>
- <li *ngFor="let item of productList">
- {{item}}
- </li>
- </ul>
- </div>
Output
ngSwitch
The ngSwitch directives is actually compromise of two directives, an attribute directives and a structural directives. It is similar like switch statement in javascript or other languages. ngSwitch stamps our nested views when their match expression value matches the value of the switch expression. The expression bound to the directives defines what will compared against in the switch structural directives. If an expression bound to ngSwitchCase matches the one given to ngSwitch, those components are created and the others destroyed. If none of the cases match, then components that have ngSwitchDefault bound to them will be created and the others destroyed. Note that multiple components can be matched using ngSwitchCase and in those cases all matching components will be created. Since components are created or destroyed be aware of the costs in doing so.
Sample Code for app.component.ngSwitch.ts
- import { Component, OnInit, Directive } from '@angular/core';
-
- @Component({
- moduleId: module.id,
- selector: 'student-list',
- templateUrl: 'app.component.ngSwitch.html'
- })
-
- export class NgSwitchComponent implements OnInit {
- studentList: Array<any> = new Array<any>();
-
- constructor() { }
- ngOnInit() {
- this.studentList = [
- { SrlNo: 1, Name: 'Rajib Basak', Course: 'Bsc(Hons)', Grade: 'A' },
- { SrlNo: 2, Name: 'Rajib Basak1', Course: 'BA', Grade: 'B' },
- { SrlNo: 3, Name: 'Rajib Basak2', Course: 'BCom', Grade: 'A' },
- { SrlNo: 4, Name: 'Rajib Basak3', Course: 'Bsc-Hons', Grade: 'C' },
- { SrlNo: 5, Name: 'Rajib Basak4', Course: 'MBA', Grade: 'B' },
- { SrlNo: 6, Name: 'Rajib Basak5', Course: 'MSc', Grade: 'B' },
- { SrlNo: 7, Name: 'Rajib Basak6', Course: 'MBA', Grade: 'A' },
- { SrlNo: 8, Name: 'Rajib Basak7', Course: 'MSc.', Grade: 'C' },
- { SrlNo: 9, Name: 'Rajib Basak8', Course: 'MA', Grade: 'D' },
- { SrlNo: 10, Name: 'Rajib Basak9', Course: 'B.Tech', Grade: 'A' }
- ];
- }
- }
Sample Code for app.component.ngSwitch.html
- <div>
- <strong>Demonstrate ngSwitch</strong>
- <table style="width:100%;border:solid;border-color:blue;border-width:thin;">
- <thead>
- <tr >
- <td>Srl No</td>
- <td>Student Name</td>
- <td>Course</td>
- <td>Grade</td>
- </tr>
- </thead>
- <tbody>
- <tr *ngFor="let student of studentList;" [ngSwitch]="student.Grade">
- <td>
- <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.SrlNo}}</span>
- <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.SrlNo}}</span>
- <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.SrlNo}}</span>
- <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.SrlNo}}</span>
- </td>
- <td>
- <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Name}}</span>
- <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Name}}</span>
- <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Name}}</span>
- <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Name}}</span>
- </td>
- <td>
- <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Course}}</span>
- <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Course}}</span>
- <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Course}}</span>
- <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Course}}</span>
- </td>
- <td>
- <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Grade}}</span>
- <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Grade}}</span>
- <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Grade}}</span>
- <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Grade}}</span>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
Output