Angular Directives: A Detailed Guide

Welcome to this comprehensive blog post about Angular Directives. In this blog, we will deep-dive into the concepts of Angular Directives, their types, and their usage with practical examples.

Introduction to Angular Directives

link to this section

Angular directives are classes that add additional behavior to elements in your Angular applications. They are one of the core features that power Angular applications. Angular directives allow you to create highly dynamic and responsive web applications by enabling you to manipulate the DOM (Document Object Model).

Types of Directives in Angular

link to this section

There are three types of directives in Angular:

  1. Component Directives : These are the most common directives and are used to create custom HTML elements. Angular components are essentially classes that encapsulate the code required for a specific functionality.

  2. Attribute Directives : These directives change the appearance, behavior, or layout of DOM elements. They're used as attributes in HTML elements.

  3. Structural Directives : These directives alter the layout by adding, removing, and manipulating DOM elements. They usually start with an asterisk (*) symbol.

Component Directives

link to this section

In Angular, every application is a tree of components. Component directives form the main building blocks of Angular applications. They encapsulate the template HTML, data, and behavior of a view. An example of a component directive is as follows:

import { Component } from '@angular/core'; 
    
@Component({ 
    selector: 'app-my-component', 
    template: `<h1>Hello, world!</h1>` 
}) 

export class MyComponent { 
    // Component logic goes here 
} 

In this code, the @Component decorator indicates that the class MyComponent is a component. It provides metadata about the component, including its selector (the name you give to the component when it is represented as an HTML element) and its template.

Attribute Directives

link to this section

Attribute directives are used to change the behavior or appearance of an element, component, or another directive. Here is an example of an attribute directive:

import { Directive, ElementRef, Renderer2 } from '@angular/core'; 
        
@Directive({ 
    selector: '[appHighlight]' 
}) 

export class HighlightDirective { 
    constructor(private el: ElementRef, private renderer: Renderer2) { 
        renderer.setStyle(el.nativeElement, 'backgroundColor', 'yellow'); 
    } 
} 

In this code, the @Directive decorator tells Angular that the HighlightDirective class is a directive. It applies a yellow background color to any element that's annotated with appHighlight .

Structural Directives

link to this section

Structural directives are responsible for the HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Examples include *ngFor and *ngIf .

Here's an example usage of the *ngIf structural directive:

<p *ngIf="condition">Content to show if condition is true</p> 

In this code, the paragraph element will only be included in the DOM if the condition is true.

Directive Selectors

link to this section

In Angular, a directive's selector is the name used to identify the directive within HTML tags. The selector is defined in the directive configuration object. Different types of selectors can be used:

  • element-name : Select by element name.
  • .class : Select by class name.
  • [attribute] : Select by attribute name.
  • [attribute=value] : Select by attribute name and value.
@Directive({ selector: '[appHighlight]' }) 

In this example, appHighlight is the selector. We can use this directive in a template as follows:

<p appHighlight>Highlighted text</p> 

Built-In Directives

link to this section

Angular comes with several built-in directives. Here are some of them:

  • ngClass : This directive dynamically binds one or more CSS classes to an HTML element. The value can be a string, array, or object.

  • ngStyle : This directive dynamically binds one or more inline styles to an HTML element.

  • ngModel : This directive creates a two-way data binding on a form input element.

Communicating with Directives

link to this section

Sometimes it's necessary to pass data into our directives. For this, Angular provides @Input and @Output .

@Input allows data to flow from a component into a directive, and @Output allows a directive to emit events to a component.

import { Directive, Input } from '@angular/core'; 

@Directive({ 
    selector: '[appCustomDirective]' 
}) 
export class CustomDirective { 
    @Input('appCustomDirective') data: any; 
    // Further implementation 
} 

Lifecycle Hooks in Directives

link to this section

Just like Angular components, directives also have lifecycle hooks. The lifecycle hooks give visibility into key life moments and allow you to execute code at specific times. The following are the lifecycle hooks available to directives:

  • ngOnChanges() : Called before ngOnInit() and whenever one or more data-bound input properties change.

  • ngOnInit() : Called once, after the first ngOnChanges() .

  • ngDoCheck() : Called during every change detection run.

  • ngAfterContentInit() : Called once after the first ngDoCheck() .

  • ngAfterContentChecked() : Called after the ngAfterContentInit() and every subsequent ngDoCheck() .

  • ngAfterViewInit() : Called once after the view (and child views) are initialized.

  • ngAfterViewChecked() : Called after the ngAfterViewInit() and every subsequent ngAfterViewChecked() .

  • ngOnDestroy() : Called just before Angular destroys the directive or component.

Directive's Scope

link to this section

In Angular, a directive can either have an isolated scope or can inherit the scope from its parent.

  • Isolated Scope : Here, the directive has its own scope and does not inherit its parent scope. This is useful when we want the directive to be reusable and self-contained.

  • Inherited Scope : In this case, the directive inherits the scope from its parent. This is useful when we want to manipulate something that's defined in the parent scope.

This concept is more prevalent in AngularJS, and in Angular, this is handled more implicitly through property and event bindings.

Testing Directives

link to this section

Like any other part of an application, directives need to be tested as well. Angular provides a number of testing utilities and practices for testing directives. TestBed is commonly used to create a dynamic test component that applies the directive.

import { Component } from '@angular/core'; 
import { ComponentFixture, TestBed } from '@angular/core/testing'; 
import { CustomDirective } from './custom.directive'; 

@Component({ 
    template: `<div appCustom></div>` 
}) 

class TestComponent {} 

describe('CustomDirective', () => { 
    let component: TestComponent; 
    let fixture: ComponentFixture<TestComponent>; 
    
    beforeEach(() => { 
        TestBed.configureTestingModule({ 
            declarations: [TestComponent, CustomDirective] 
        }); 
        
        fixture = TestBed.createComponent(TestComponent); 
        component = fixture.componentInstance; 
    }); 
    
    it('should create an instance', () => { 
        expect(component).toBeDefined(); 
    }); 
}); 

Conclusion

link to this section

Directives are a vital part of Angular applications, and understanding them is key to mastering the framework. Component directives help you organize your application into self-contained, reusable pieces. Attribute directives allow you to modify the behavior and appearance of elements and components, and structural directives give you the power to alter the layout of the DOM.

Remember, while Angular provides a number of useful directives out-of-the-box, one of the most powerful aspects of directives is that you can create your own to encapsulate and reuse functionality throughout your application.

Thank you for reading this blog post. Stay tuned for more informative posts about Angular and its various features!