This article is in continuation of my previous article series. If you haven’t had a look at it, please refer to this link. In Angular 2, almost everything is a directive. We can categorize directives in the following forms.
- Components - Directives with template. These are the most commonly used ones among the others.
- Structural Directive - Manipulates the DOM layout by adding or removing elements e.g. *ngFor, *ngIf etc.
- Attribute Directive - Changes the appearance or behavior of an element. As the name implies, these are used as attributes of the elements.
To create attribute directive, you need to simply decorate your class with @Directive({}) decorator. For demonstrating this, we’ll create a directive which will set a different text color for "Salary" column based on employee’s salary. We’ll be creating this directive inside our “emp” folder, assuming that it’s going to be used only within our “employee” module. Below is the code snippet for the same.
emp-formatsalary.directive.ts
- import { Directive, ElementRef, Input} from '@angular/core';
- import { IEmployee } from './employee';
- @Directive({
- selector: '[formatSalary]'
- })
- export class FormatSalaryDirective {
- @Input("formatSalary") empSalary: number;
- @Input() defaultTextColor: string;
-
- constructor(private el: ElementRef, private renderer: Renderer) {
- el.nativeElement.style.fontWeight = "bold";
- el.nativeelement.style.color = "#D890DF";
- }
- }
NOTE
- The name of the attribute directive should always be in camel case & needs to be enclosed in “box” [] brackets to specify Angular that it’s an attribute directive.
- For referring CSS properties (with – in their name) in Attribute directive code, use camel case. For instance, font-weight is referred to as “fontWeight” in the code snippet. Similarly, to refer to font-family, it will be fontFamily.
To use the attribute directive, we need to simply set it as an attribute of any HTML element on which we want to bind it. To make it work, I’ve bound it on salary column in emp-list.component.html. Below is the updated code snippet for the same.
- <td class="rightAlign" formatSalary>{{emp.salary|currency:'INR':true:'1.2-2'}}</td>
Try running the application. You’ll get the below output.
Passing values to the Attribute directive
The way of passing value to the attribute directive remains the same as what we used in case of parent component passing value to nested components; i.e., by declaring a property decorated with @Input() decorator.
In our above code, we are simply setting the same color to the emp-salary column values irrespective of what the salary is, but we can make it more dynamic by using the concept of @Input decorator. Let’s conditionally update the "Salary" column text color based on the value of it. Below is the updated code snippet for the same.
emp-formatsalary.directive.ts
- import { Directive, ElementRef, Input, OnChanges, Renderer } from '@angular/core';
- import { IEmployee } from './employee';
-
- @Directive({
- selector: '[formatSalary]'
- })
- export class FormatSalaryDirective implements OnChanges {
-
- @Input() empSalary: number;
-
- constructor(private el: ElementRef, private renderer: Renderer) {
-
-
- }
-
- ngOnChanges(): void {
- this.setTextColor();
- }
-
- private setTextColor(): void {
- if (this.empSalary >= 100000) {
- this.changeTextColor("#177E77");
- }
- else if (this.empSalary >= 50000) {
- this.changeTextColor("#77473C");
- }
- else if (this.empSalary > 25000) {
- this.changeTextColor("#6369B4");
- }
- else {
- this.changeTextColor("#D890DF");
- }
- }
-
- private changeTextColor(color: string): void {
- this.renderer.setElementStyle(this.el.nativeElement, "color", color);
- }
- }
There are a couples of changes I made to the application. First, I’ve imported “Input”, “OnChanges” component life cycle hooks & “Renderer” abstract class. In the ngOnChanges() life cycle hook, I am simply calling the user defined method named “setTextColor()” which conditionally sets the text color using the renderer.setElementStyle() method.
NOTE
Input parameter values are not available inside the constructor directly. To use this directive with input property, mentioned below is the change I made to the emp-list.cormponent.html file.
- <td class="rightAlign" formatSalary [empSalary]=‘emp.salary’>{{emp.salary|currency:'INR':true:'1.2-2'}}</td>
Try running the application and you’ll see the below output.
This time, our text color is conditionally set based on the employee’s salary details.
Binding an alias name to the attribute directive input property
You can optionally bind an alias name to the attribute directive input property. The benefit of doing it will be that we don’t have to use any input property name on the HTML element on which we are binding our attribute directive. For instance, in our above example, we used both “formatSalary” (to apply the attribute directive) & “empSalary” (pass the value to the attribute directive using the input property empSalary). Wouldn’t it be nice if I could simply use the attribute directive name and pass the required value to it, using property binding to the attribute directive name itself.
Well, in Angular 2, we can do it by providing an alias name to the input property. For doing this, I’m going to provide an alias name to my input property defined in my attribute directive. To provide alias name, simply pass the alias name in the code given below.
- @Input (“formatSalary”) empSalary:number;
-
- import { Directive, ElementRef, Input, OnChanges, Renderer } from '@angular/core';
- import { IEmployee } from './employee';
-
- @Directive({
- selector: '[formatSalary]'
- })
- export class FormatSalaryDirective implements OnChanges {
-
- @Input("formatSalary") empSalary: number;
-
- constructor(private el: ElementRef, private renderer: Renderer) {
-
-
- }
-
- ngOnChanges(): void {
- this.setTextColor();
- }
-
- private setTextColor(): void {
- if (this.empSalary >= 100000) {
- this.changeTextColor("#177E77");
- }
- else if (this.empSalary >= 50000) {
- this.changeTextColor("#77473C");
- }
- else if (this.empSalary > 25000) {
- this.changeTextColor("#6369B4");
- }
- else {
- this.changeTextColor("#D890DF");
- }
- }
-
- private changeTextColor(color: string): void {
- this.renderer.setElementStyle(this.el.nativeElement, "color", color);
- }
- }
And now, to use it, we’ll update our emp-list.component.html file too. Below is the updated code snippet for the same.
- <td class="rightAlign" [formatSalary]='emp.salary' >{{emp.salary|currency:'INR':true:'1.2-2'}}</td>
Try running the code. The output remains the same.
Wait a minute! What if we have more than one input property which need to be set?
Well, in that case, you need to string the input property names next to each other separating them with a space. For instance, let’s suppose we have one more input property with name “defaultTextColor” inside our attribute directive. So, to set defaultTextColor input property, we’ll be doing something like the below snippet.
- <td class="rightAlign" [formatSalary]='emp.salary' defaultTextColor="#0e0e6c">{{emp.salary|currency:'INR':true:'1.2-2'}}</td>
Respond to user initiated events,
Your attribute directive can also respond to user initiated events, such as - on-click, mouse-enter, mouse-move etc. To respond to user events, we can make use of @HostListener() decorator. Below is the code snippet which highlights the employee salary on mouse hover & resets the text color back to its original on mouse leave.
- import { Directive, ElementRef, Input, OnChanges, Renderer, HostListener } from '@angular/core';
- import { IEmployee } from './employee';
-
- @Directive({
- selector: '[formatSalary]'
- })
- export class FormatSalaryDirective implements OnChanges {
-
- @Input("formatSalary") empSalary: number;
- @Input() defaultTextColor: string;
-
- constructor(private el: ElementRef, private renderer: Renderer) {
- el.nativeElement.style.fontWeight = "bold";
- el.nativeelement.style.color = "#D890DF";
- }
-
- ngOnChanges(): void {
- this.setTextColor();
- }
-
- private setTextColor(): void {
- if (this.empSalary >= 100000) {
- this.changeTextColor("#177E77");
- }
- else if (this.empSalary >= 50000) {
- this.changeTextColor("#77473C");
- }
- else if (this.empSalary > 25000) {
- this.changeTextColor("#6369B4");
- }
-
-
-
- }
- private changeTextColor(color: string): void {
- this.renderer.setElementStyle(this.el.nativeElement, "color", color || this.defaultTextColor);
- }
-
- @HostListener("mouseenter")
- highlightTextColorOnMouseEnter(): void {
- this.changeTextColor("#EDF037");
- }
-
- @HostListener("mouseleave")
- resetTextColorOnMouseLeave(): void {
- this.setTextColor();
- }
- }
Try running the application. You’ll see the below output.
In our next post, we’ll continue further. Until then, you can download the solution and test it locally at your end. I am uploading the solution file with this post; but I won’t be able to upload the node_modules folder because of heavy size. So, I request you install npm before you run the application.