Angular Custom Directives – Part 1: Attribute Directives
210331
Note that this is a 2-part post. Here we cover some intro on Angular Directives and then we give a step-by-step example with code, for a Custom Attribute Directive. An example of a Custom Structural Directive is covered in Part -2 here:
You can find and download the code at https://github.com/zzpzaf/directivetest1/tree/8089a04b870fcef2d0f148dc7346d20976ae7c90
Short Intro to Angular Directives
The concept of the ‘Directive’ comes from the early versions of Angular (e.g., AngularJS 1.X) The concept covers almost all the basic building blocks of Angular, including Components. (We should consider Components as special type of Directives).
According Angular docs, there are three types of directives:
- Components
- Structural directives
- Attribute directives
Actually, directives provide us with the ground to attach and deal with the behavior of the DOM and its elements. To understand the pragmatic difference between Angular directives, we can keep in mind the following simple rule:
- If it has a template, it is a Component.
- else if it has a selector in brackets “[my-directive1]”, it is for sure, an Attribute Directive. For instance, if it is applied to an element as an attribute of that element, like that: <div my-directive1> … </div> then this is an Attribute Directive.
- else it is applied to an element with an asterisk * like that: <div *my-directive1> … </div> ) then this is a Structural Directive.
Note that you can find some short yet useful information on how to use various syntaxes of Directive selectors and how to apply them to DOM elements with examples in our post here.
So apart, from Components, Directives can’t have their own UI (template), but they can be attached to a component or a regular (‘native’) HTML element to change their visual representation.
You can read more at official documentation at https://angular.io/guide/built-in-directives
ATTRIBUTE DIRECTIVES
An Attribute Directive (also known as Behavioral Directive) could be considered as Component without its own template. This is because the main purpose of an Attribute Directive is to provide ways to intervein in formatting and changing the appearance and behavior of other DOM elements, defined and residing into the templates of other components in our app. Generally, Attribute Directives can be used to change the appearance of an HTML element, another directive, or a component.
An Attribute Directive is actually a ‘normal’ Typescript Class, so it has and its own code (properties, variables, methods, etc.) and it can execute its own logic to affect visual changes to ANY element it is applied to. This is useful in cases we want to alter the behavior or style of existing DOM/HTML elements using a generic and reusable approach.
To achieve that, an Attribute Directive has its own named selector in square brackets […], e.g. [my-direct]. Using the selector that way (in square [] brackets, e.g. []), we actually guide Angular to use the directive as an HTML attribute.
That way we can use/apply the directive’s selector in any DOM element, native or any other component/template element, for instance:
<div my-direct >...</div>
The most common Angular built-in Attribute Directives are:
- NgClass – adds and removes a set of CSS classes.
- NgStyle – adds and removes a set of HTML styles.
- NgModel – adds two-way data binding to an HTML form element.
Read more at: https://angular.io/guide/built-in-directives#built-in-attribute-directives
Let’s see a trivial example of a custom Attribute Directive.
Create and use a custom Attribute Directive
Create a new Component
First, we are going to create a new component. This is the component that a new directive (we will create at the next step), will be targeted for changing its behavior (actually, the color of one of its elements). We will use the VS Code Terminal to create the my-component1 component:
ng generate component my-component1
➜ directivetest1 git:(master) ng generate component my-component1 CREATE src/app/my-component1/my-component1.component.css (0 bytes) CREATE src/app/my-component1/my-component1.component.html (28 bytes) CREATE src/app/my-component1/my-component1.component.spec.ts (669 bytes) CREATE src/app/my-component1/my-component1.component.ts (302 bytes) UPDATE src/app/app.module.ts (501 bytes) ➜ directivetest1 git:(master) ✗
Add some code to its class, e.g.:
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-my-component1', templateUrl: './my-component1.component.html', styleUrls: ['./my-component1.component.css'] }) export class MyComponent1Component implements OnInit { curDate: Date = new Date(); constructor() { } ngOnInit(): void { } }
And to its template, e.g.:
<hr> <div> <h2>Angular Demo</h2> <h3> Today is : {{curDate}} </h3> <p>This is a component having a Class named 'MyComponent1Component' with selector 'app-my-component1'.</p> </div> <hr>
‘Bootstrap’ our component, so our app will run from it and not from the default app’s ’AppComponent’ (with selector ‘app-root’). For that, we have to make the necessary changes in ‘app.module.ts’ and index.html files in our app.
In app.module.ts:
From:
... ], providers: [], bootstrap: [AppComponent] }) ...
To:
... ], providers: [], bootstrap: [MyComponent1Component] }) ...
In index.html:
From:
<body> <app-root></app-root> </body>
To:
<body> <app-my-component></app-my-component> </body>
Then, compile / run it:
➜ directivetest1 git:(master) ✗ ng serve --host 0.0.0.0 Warning: This is a simple server for use in testing or debugging Angular applications locally. It hasn't been reviewed for security issues. . . . ** Angular Live Development Server is listening on 0.0.0.0:4200, open your browser on http://localhost:4200/ ** . . . Build at: 2021-03-31T07:43:18.510Z - Hash: 55c9e8be2d9bbb105819 - Time: 786ms ✔ Compiled successfully.
and check it in the browser:
Create a new Attribute Directive
We will use the VS Code Terminal to create the my-colorChanger1 directive:
ng g directive my-colorChanger1
➜ directivetest1 git:(master) ✗ ➜ directivetest1 git:(master) ✗ ng g directive my-colorChanger1 CREATE src/app/my-color-changer1.directive.spec.ts (262 bytes) CREATE src/app/my-color-changer1.directive.ts (159 bytes) UPDATE src/app/app.module.ts (614 bytes) ➜ directivetest1 git:(master) ✗
This is the directive’s class (Typescript Code) auto-generated by Angular:
import { Directive } from '@angular/core'; @Directive({ selector: '[appMyColorChanger1]' }) export class MyColorChanger1Directive { constructor() { } }
In many-many cases, we can use the ElementRef to request a reference to the DOM element to which this directive will apply. The ElementRef is an Angular Core API class, which is actually a ‘wrapper’ around a native DOM element/object (HTML element). This class contains just one property named nativeElement. This property holds the reference to the underlying DOM object. And this is what we can use to manipulate the DOM.
NB: ElementRef allows direct access to the DOM which could risk your app to XSS attacks. There are other safer alternatives (templating, data binding, and also the Renderer2 API) to directly access the DOM which will cover in other tutorials.
We can take a reference of the ElementRef class by injecting it in our directive class constructor. Then we can make all the changes we want using the nativeElement property. Here we will change the background color to red.
Our directive should look like that:
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[appMyColorChanger1]' }) export class MyColorChanger1Directive { constructor(elemRef: ElementRef) { this.chColor(elemRef); } chColor(el: ElementRef): void { el.nativeElement.style.backgroundColor = "red"; } }
After that, we can apply the directive’s selector in any of the DOM elements of the template of our component:
<hr> <div> <h2>Angular Demo</h2> <h3 appMyColorChanger1> Today is : {{curDate}} </h3> <p>This is a component having a Class named 'MyComponent1Component' with selector 'app-my-component1'.</p> </div> <hr>
And the result is:
That’s is OK, however it is not that cool. We could achieve the same result without using a separate directive (e.g., either directly in the template or by using the .css).
In order to demonstrate further some fundamental capabilities of Attribute Directives, we will adjust our code in previous example, so, it will be able to respond to data changes, i.e. to dynamic data of a component element.
We will use also an @Input property/variable in our directive, for capturing data from our component.
First, we will change our component ‘MyComponent1Component’, so it will update the date output every second. For the purpose we will use the DOM built-in setInterval() method to repeat a function every 1 second. The ‘function’ here is an anonymous one, setting up the current date-time with a new Date variable, every second.
So, our component becomes:
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-my-component1', templateUrl: './my-component1.component.html', styleUrls: ['./my-component1.component.css'] }) export class MyComponent1Component implements OnInit { curDate: Date = new Date(); i: number = 1; //interval in seconds interv: any; constructor() { // Here, we use the DOM built-in setInterval method to repeat a function every 1 second // The 'function' here is an anonymous one, setting up the current date-time with a new Date // variable, every second this.interv = setInterval(() => { this.curDate = new Date(); }, this.i*1000); } ngOnInit(): void { } dateRetDate() {} ngOnDestroy() { if (this.interv) { clearInterval(this.interv); } } }
Also, we modify a bit our component’s template, to focus only to the changing time-part of the whole Date string, and also to use interpolation to pass the component’s date variable to our attached directive, like that:
<hr> <div> <h2>Angular Demo</h2> <h3> Today is : {{curDate | date : "dd.MM.y"}} The Time is: <span appMyColorChanger1 myDateTime = {{curDate}} > {{curDate | date : "H.mm.ss"}} </span> </h3> <p>This is a component having a Class named 'MyComponent1Component' with selector 'app-my-component1'.</p> </div> <hr>
Finally, we modify our directive to capture the seconds of the date/time @Input() myDateTime property/variable, and check if it is even or odd and then colors it accordingly in yellow or red. Our directive becomes:
import { Directive, ElementRef, Input, SimpleChanges } from '@angular/core'; @Directive({ selector: '[appMyColorChanger1]' }) export class MyColorChanger1Directive { @Input() myDateTime: string | undefined; elRef: ElementRef; constructor(elemRef: ElementRef) { this.elRef = elemRef; } /* * AfterViewInit: It is the lifecycle hook that is called after a component view has been fully initialized. * To use AfterViewInit, our class will implement it and override its method ngAfterViewInit(). */ ngAfterViewInit() { //this.chColor(this.elRef); } /* To respond to changes to the @Input() variables, uςε se the ngOnChanges() lifecycle hook. * Τηε Angular ngOnChanges is a lifecycle hook that is called when any data-bound property of a directive changes. * It takes a changes argument of type SimpleChanges, that it also can be used. * * Here we use our 'this.myDateTime' @Input property/variable. */ ngOnChanges(changes: SimpleChanges) { let mcolor: string = ""; mcolor = (new Date(this.myDateTime!).getSeconds() % 2 === 0) ? "red" : "yellow"; this.chColor(mcolor); } chColor(putColor: string): void { this.elRef.nativeElement.style.backgroundColor = putColor; } }
So, the result is that the color changes from red to yellow and vice-versa every second:
You can download the code from GitHub at: https://github.com/zzpzaf/directivetest1/archive/8089a04b870fcef2d0f148dc7346d20976ae7c90.zip
That’s it for now!
Thanx for reading!