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:

  1. Components – directives with templates
  2. Attribute directives – change appearance or behavior of an element
  3. 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:

  1. 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 the appHightlight attribute is encountered.

  2. 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.

  3. We import Input which allows data to be passed into the directive. The @Input decorator defines an input property named appHighlight that accepts a boolean value.

  4. In the directive‘s class, we use the set syntax to create a setter function for the appHighlight input. This function will be called whenever the input‘s value changes.

  5. Based on the value passed to appHighlight, we set the background color of the host element using this.el.nativeElement.style.backgroundColor. Accessing nativeElement 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:

  1. We use TemplateRef to access the contents of the host element. You can think of it as a blueprint for creating embedded views.

  2. 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.

  3. In the directive‘s constructor, we inject both TemplateRef and ViewContainerRef.

  4. We define an input property with @Input called appDelay that takes a number specifying the delay time in milliseconds.

  5. In the setter function for appDelay, we use setTimeout to delay the rendering of the element.

  6. To actually render the element, we create an embedded view using this.viewContainer.createEmbeddedView, passing it the template accessed via this.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!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *