How to Use and Create Custom Directives in Angular
Angular provides a powerful and flexible way to extend the behavior and appearance of DOM elements through the use of directives. Directives allow you to create reusable and modular code that can be easily shared across your application. In this expert-level guide, we‘ll take an in-depth look at what directives are, how to use the built-in ones provided by Angular, and step through the process of creating your own custom directives.
Understanding Angular Directives
At their core, directives are classes that can modify the structure, appearance or behavior of DOM elements. When the Angular compiler encounters a directive in a template, it instantiates the corresponding directive class and gives it control over that portion of the DOM.
There are three main types of Angular directives:
- Components – directives with templates
- Attribute directives – change appearance or behavior of an element
- Structural directives – change the DOM layout by adding/removing elements
Components are the most common directive and form the building blocks of an Angular application‘s UI. They have templates, can inject services, and define custom behavior for a portion of a page. Attribute and structural directives are more limited in scope – they don‘t have their own templates but can modify host elements in powerful ways.
Using Built-in Angular Directives
Before we dive into creating custom directives, let‘s take a look at some commonly used built-in directives that ship with Angular.
Attribute Directives
– `ngStyle` – conditionally apply styles to an element
– `ngClass` – dynamically add/remove CSS classes
Structural Directives
– `*ngIf` – conditionally add/remove an element from the DOM
– `*ngFor` – repeat a portion of the DOM for each item in a list
– `*ngSwitch` – conditionally swap portions of the DOM
Here are a few examples of using these built-in directives:
<!-- ngStyle -->
<div [ngStyle]="{‘color‘: textColor, ‘font-size.px‘: fontSize}">
Style me!
</div>
<!-- ngClass -->
<div [ngClass]="{‘active‘: isActive, ‘text-danger‘: hasError}">
What classes do I have?
</div>
<!-- ngIf -->
<p *ngIf="showMe">
I‘m conditionally shown!
</p>
<!-- ngFor -->
<ul>
<li *ngFor="let item of items">{{item.name}}</li>
</ul>
<!-- ngSwitch -->
<div [ngSwitch]="employeeType">
<p *ngSwitchCase="‘fullTime‘">Full-time employee</p>
<p *ngSwitchCase="‘partTime‘">Part-time employee</p>
<p *ngSwitchDefault>Contractor</p>
</div>
As you can see, Angular provides convenient ways to dynamically style elements, show/hide portions of the DOM, and loop over data to generate elements. However, sometimes you may find yourself needing functionality that isn‘t provided out-of-the-box. That‘s where custom directives come into play.
Creating an Attribute Directive
Let‘s walk through creating a custom attribute directive that will allow us to dynamically set the background color of an element based on a condition. We‘ll call it the appHighlight
directive.
import { Directive, ElementRef, Input } from ‘@angular/core‘;
@Directive({
selector: ‘[appHighlight]‘
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@Input() set appHighlight(condition: boolean) {
if (condition) {
this.el.nativeElement.style.backgroundColor = ‘yellow‘;
} else {
this.el.nativeElement.style.backgroundColor = ‘transparent‘;
}
}
}
Here‘s what‘s going on:
-
We import the
Directive
decorator which allows us to define the CSS selector that identifies this directive in a template. In this case[appHighlight]
means this directive will be triggered when theappHightlight
attribute is encountered. -
We import
ElementRef
which provides access to the host DOM element. We inject it via the directive‘s constructor, giving us a reference to the element the directive is attached to. -
We import
Input
which allows data to be passed into the directive. The@Input
decorator defines an input property namedappHighlight
that accepts a boolean value. -
In the directive‘s class, we use the
set
syntax to create a setter function for theappHighlight
input. This function will be called whenever the input‘s value changes. -
Based on the value passed to
appHighlight
, we set the background color of the host element usingthis.el.nativeElement.style.backgroundColor
. AccessingnativeElement
gives us a reference to the actual DOM element so we can modify it directly.
To use this custom directive, we first need to declare it in our module:
import { HighlightDirective } from ‘./highlight.directive‘;
@NgModule({
declarations: [
AppComponent,
HighlightDirective
],
...
})
export class AppModule { }
And then we can use it in a component template like so:
<p [appHighlight]="error">
This paragraph will be highlighted yellow when "error" is true.
</p>
Creating a Structural Directive
Structural directives can modify the structure of the DOM by adding, removing, or replacing elements. Let‘s create a custom structural directive called *appDelay
that will delay the rendering of an element for a specified number of seconds.
import { Directive, Input, TemplateRef, ViewContainerRef } from ‘@angular/core‘;
@Directive({
selector: ‘[appDelay]‘
})
export class DelayDirective {
@Input() set appDelay(time: number) {
setTimeout(() => {
this.viewContainer.createEmbeddedView(this.templateRef);
}, time);
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) { }
}
The key points are:
-
We use
TemplateRef
to access the contents of the host element. You can think of it as a blueprint for creating embedded views. -
ViewContainerRef
provides a reference to the container where one or more views can be attached. It allows us to control the instantiation of embedded views. -
In the directive‘s constructor, we inject both
TemplateRef
andViewContainerRef
. -
We define an input property with
@Input
calledappDelay
that takes a number specifying the delay time in milliseconds. -
In the setter function for
appDelay
, we usesetTimeout
to delay the rendering of the element. -
To actually render the element, we create an embedded view using
this.viewContainer.createEmbeddedView
, passing it the template accessed viathis.templateRef
.
After adding this directive to our module‘s declarations
array, we can use it like this:
<div *appDelay="2000">
This div will be rendered after a 2 second delay.
</div>
Attribute vs Structural Directives
As we‘ve seen, the main difference between attribute and structural directives is that attribute directives simply modify the host element – they don‘t destroy it or create new elements. Structural directives on the other hand can add or remove elements from the DOM.
Some key points to remember:
- Attribute directives are used as attributes in the host element
- Structural directives are used by prefixing with an asterisk (*)
- Attribute directs more limited in what they can do to the host element
- Structural directives have the ability to shape the structure of the DOM
Best Practices
When creating your own custom directives, here are some best practices to keep in mind:
- Use attribute directives for modifying single elements, structural directives for adding/removing elements
- Keep directives focused on one single responsibility
- Use clear and meaningful names for selector (prefix with app, use camelCase)
- Be mindful of performance, especially with structural directives that add/remove from the DOM
- Write unit tests for directives to catch potential bugs
Conclusion
Directives are a powerful way to create reusable behaviors and extend what‘s possible in your Angular templates. By leveraging the built-in ones and knowing how to create your own, you can add all sorts of dynamic functionality to elements efficiently. I hope this guide has given you a solid foundation for understanding and working with attribute and structural directives in your own applications. Happy coding!