Angular Directives - A Complete Step by Step Guide
Angular Directives are a powerful tool offered to developers by the Angular framework. In one sentence, Angular directives are classes that add functionality, behavior, or styling to the elements in your Angular application. Directives target DOM elements and manipulate their behavior based on the logic we write in our TypeScript class. It's a great way to create reusable DOM behavior like highlighting elements, input masks, and so on.
Angular Directives Come In 2 Different Types
Attribute directives are used to manipulate the behavior or styling of DOM elements and Angular components based on some logic; structural directives are used to remove or add DOM elements and Angular components based on some logic.
Build-in Angular directives
Angular offers some built-in directives out of the box. The built-in Angular directives are composed of structural and attribute directives. The most commonly used built-in directives are:
NgClass (an attribute directive to add or remove CSS classes from HTML elements)
NgStyle (an attribute directive to add or remove inline styling from HTML elements)
NgModel (an attribute directive to allow two-way data binding on HTML elements)
NgIf (a structural directive to display or hide HTML elements based on a condition)
NgFor (a structural directive that repeats HTML elements for each item in an array)
NgSwitch (a set of structural directives to display different blocks of HTML based on a switch case statement)
You can read more about the built-in Angular directives in this article:
Angular Built-in Attribute Directives
For now, we are going to focus on how to create and use your own attribute and structural directives.
Angular Attribute Directive
Attribute directives can be created from scratch or with the Angular CLI. In this guide, we will use the Angular CLI to create the file and boilerplate code. Using the CLI is always the recommended way of doing things in the Angular framework. If you opted out of the Angular CLI or do not want to use it you can just copy the code and create the file yourself. You can create a directive with the following CLI command:
ng generate directive hoverEffect
The above command will create a file named hover-effect.directive.ts with the following content in it:
As you can see the class has a @Diverective() decorator telling the Angular compiler to handle this class as an Angular Directive. In the decorator, the selector to use the directive in our HTML is declared as appHoverEffect.
The first thing you now want to do is add ElementRef with dependency injection to the class. ElementRef allows you to directly access the host element on which you declare the Angular directive, allowing you to manipulate that particular HTML element. As an example let's give each element that receives our directive a yellow background and change the color to blue when we hover over the HTML element.
As you can see we injected the ElementRef so we have access to the DOM element and give it a yellow background color in the constructor (making it the default background color for each HTML element that received the directive). Another thing you might notice is the HostListener decorator. HostListener allows us to listen to DOM user events like click, mouse enter, mouse leave, key down, and so on.
Simply add the decorator, enter the user event as a parameter in the function brackets, give it your own function name and write your logic inside. In our example, we use the mouse enter and mouse leave events to change the background of the HTML element when we hover it.
You can add the attribute directive to HTML elements and Angular components like this:
Now you know how to create a directive, how to access the host element to manipulate it, and how to listen for DOM user events.
Passing Values Into Angular Attribute Directives
The next piece to the puzzle of Angular attribute directives is inputting values into the directive and using these values inside of the directive. Often times we do not want to use hard-coded values in our directive but make them a bit more dynamic by passing values as inputs. For example, in our hover effect directive, we might want the option to pass the directive a color to display instead of hard coding the color values in the directive itself.
We do this by adding an @input to the directive, important to note here is that the name of the @input should be equal to the selector of your directive.
@Input() appHoverEffect: string = '';
By adding the @input to the directive we can now pass it a color we want to use when we hover the HTML element, allowing us to use different colors on different elements. The updated directive now looks like this:
In the HTML you can pass the value to the directive like this:
As you can see there are now square brackets around the directive name, allowing you to pass a property as input to the directive. You can use a simple property here that declares the value, pass the value directly, or use a function or expression that evaluates to the value you want to pass to the directive.
The last thing we need to look at is passing multiple input values to a directive. In some cases, one input value might not be enough to accomplish what you want to do with your structural directive. To pass two or more values to a directive we simply add more @inputs to the directive. In our example we could add a mouseLeaveColor input so we can set a color for the mouse leave event as well:
@Input() mouseLeaveColor: string = '';
In the HTML you can simply input the mouseLeaveColor value like this:
Now you know all the ins and outs of Angular attribute directives let's have a look at how we can create Angular structural directives.
Angular Structural Directive
If you ever build an Angular application you probably used NgIf and NgFor the two most commonly used structural directives. In this guide, we will share some conceptual information on structural directives and how Angular interprets them. We will also build our own structural directive the ngIfFalse that removed DOM elements if the input value equals true and displays them when it's false (the opposite of ngIf).
When using a structural directive in our HTML template we usually use the asterisk (*) prefix for example *ngIf or *ngFor. This is different from what we saw with the attribute directives where we use square brackets (when there is an input). The asterisk is actually a shorthand that the angular compiler interprets and converts to an <ng-template> element with the directive applied with square brackets. This all happens at compile time, the <ng-template> element never reaches the actual DOM, instead, it only renders the HTML elements you want to output after the directive is evaluated.
For example this:
Another interesting thing to note about Angular structural directives is that you can only use one of them on an HTML element. It's not a very uncommon use case that you want to use two of them (for example a ngIf and ngFor on the same div). Luckily there is an easy workaround for this. You can create an <ng-container> element with one of the structural directives on it and place the other one on the actual HTML element.
Create Your Own Structural Directive
Now let's create your own structural directive. In this example, we will build a simple directive, basically the opposite of the ngIf directive and we will call it the IfFalse directive. Just like we did with the attribute directive we start creating our structural directive with the Angular CLI by running the following command:
ng generate directive ifFalse
This command will create a file named if-false.directive.ts that looks like this:
First, need to inject TemplateRef and ViewContainerRef into the directive with dependency injection. Our directive will create an embedded view from the Angular-generated <ng-template> element and insert that in a view container adjacent to the host element containing the directive. The TemplateRef lets you access the content inside the Angular generated <ng-template> and the ViewContainerRef lets you access the view container.
Next, you want to add in @import but as a setter so we can act when the value changes. Just like with the attribute directive it's important to name the input equal to the selector of the directive. Your directive now should look something like this:
Now you need to add the logic to display the HTML element if the condition is false and remove it from the view container if the condition equals true. Let's say we have an <p> element with our directive on it:
<p *appIfFalse="condition">Show this sentence unless the condition is true.</p>
Angular will generate an <ng-template> element with our <p> element inside it and expose it to our directive through the TemplateRef. If our condition is false we want to show this TemplateRef element, we do this by embedding it into the view container. If we do not want to display the element we need to clear the view container. After we add this logic to our directive it looks like this:
Because we use a setter on our input it triggers our logic every time the input value is changed. When the condition is false and Angular hasn't created a view yet we create an embedded from our template reference with the view container, if our condition is true and we already have a template inside the view container we clear it removing the template from our view.
Angular Directives are a powerful concept that can be used to clean up your TS and HTML code by creating reusable DOM effects for your HTML elements. Angular offers some great build-in directives out of the box and creating your own can be done quite easily. I would recommend using directives within your Angular application whenever you can to improve reusability within your code and supercharge the user experience in your UI.