Angular for Everyone: Part 1

Angular Directives

What’s a directive? When to use it? What can I do with it?

Redin Gaetan
6 min readAug 6, 2021

Okay, let’s start with the basics. Do you know that there is 2 kind of directives? Yes, of course, you know. So, on the one hand, we have attribute directives, and on the other hand, we have structural directives.

What’s the difference? What are the use cases? And also how to code it?

To start, we will discuss the decorator to use. Then we will study the attribute directives, and at last, we will discuss the template directives.

The decorator: @Directive

Thanks to this decorator, a class is recognized by Angular as a directive.

selector?: string

To be used in HTML templates, a directive must have a selector option set. It’s simply a CSS selector used to identify the directive and trigger its instantiation.

@Directive({
selector: '[medAttrExample]',
})

As you can see, the selector is in camelCase and surrounded by “[].” It’s a convention. Normally a directive is considered like an element’s attribute. The directive selector is prefixed by the app prefix defined in the angular.json file. Here I choose ‘med’ as a prefix because my code is for Medium’s articles :). Technically nothing prevents to have a selector like this:

@Directive({
selector: 'med-attr-example',
})

Here, the selector is not surrounded and is in kebab-case. This syntax is normally used to represent an element (so it’s not our use case). If you try this, you will get an ESLint error:

ESLint: The selector should be used as an attribute (@angular-eslint/directive-selector)”>https://angular.io/guide/styleguide#style-02-06)(@angular-eslint/directive-selector)

Actually, this syntax is for components.

You can also have multiple selectors:

@Directive({
selector: '[medAttrExample][css-attribute-selector-1], [medAttrExample][css-attribute-selector-2]',
})

inputs?: string[]

You should know that Angular does not recommend this option. Instead, you should prefer using the @Input decorator.

A directive could require/offer some parameters to work or to allow to customize the target behavior.

outputs?: string[]

You should know that Angular does not recommend this option. Instead, you should prefer using the @Output decorator.

Sometimes, we need to inform the parent element that something from the directive change. That’s the goal of an output. EventEmitter objects represent outputs. It allows notifying the parent element, with a simple call to the emit() method, that this directive's value has changed.

host?: [key: string]: string;

You should know that Angular does not recommend this option. But it exists, and as a reasonable developer, we will choose the best coding style according to each case we have to develop :-).

This option allows binding properties to the host element: CSS classes, styles, attributes, or events. You can also do it with @HostBinding and the @HostListener decorators.

exportAs?: string

It allows defining the name used in the parent template to assign this directive to a variable. I think we will go into it in-depth in another article with a real use case. Not needed for starting to develop.

providers?: Provider[]

This option use case is described here: Services

queries?: [key: string]: any;

This option is described in the articles about Content DOM and View DOM. What’s more, I don’t find its usage really readable. Personally, I don’t recommend it.

jit?: boolean

If true, the directive is ignored by the AOT compiler.

Attribute directives

Use cases

If you want to add behavior to an element, then yes, you must create a directive. For example, you must create an attribute directive if you want to add a CSS class that corresponds to a set of styles and/or corresponds to a UI logic style.

Example

Suppose we want to create a directive that represents an input form field. Here are some specifications:

  • Our target is only text input (1)
  • We want to add a specific class to set it some style (2)
  • We want to set a value (3)
  • We want to be notified when the value change (4)
  • We want to know when it’s focused and tag input with a CSS class if true (5)
  • We want to know when it’s disabled and tag input with a CSS class if true (6)

Okay, that’s the code that corresponds to the demand (if you prefer the link):

import { Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core';

@Directive({
selector: 'input[medInput][type=text]', // (1) select only input text with our directive attribute
host: {
class: 'md-input', // (2) Set the specific class
'[attr.disabled]': 'disabled || null', // part of (6) tips to better handle the attribute value
}
})
export class InputDirective {
/**
* (3) Allows to specify the value with an input
* We use getter and setter to directly work with the native element value
*/
@Input()
set value(value: string | undefined) {
this._elementRef.nativeElement.value = value;
}

get value(): string | undefined {
return this._elementRef.nativeElement.value;
}

/**
* part of (6) Set med-disabled class if disabled = true
* Allows to also set the disabled attribute
* Allows to take the value from an input
*/
@HostBinding('class.med-disabled')
@Input()
public disabled = false;

/**
* (4) create an EventEmitter to notify the parent
*/
@Output()
public readonly valueChange: EventEmitter<string> = new EventEmitter<string>();

/**
* part of (5) set a specific class when focused is true
*/
@HostBinding('class.med-focused')
public focused = false; // part of (5)

/**
* @param _elementRef required to work with the native value
*/
constructor(private _elementRef: ElementRef) {}

/**
* part of (5) Here we specified a boolean parameter when focus and blur are
* triggered. Respectively it's true of false and allow to directly set the
* right value.
*/
@HostListener('focus', ['true'])
@HostListener('blur', ['false'])
public toggleFocus(focused: boolean): void {
this.focused = focused;
}

/**
* part of (4) here we listen to the input event and emit the new value
*/
@HostListener('input', ['$event.target.value'])
public onInput(val: string): void {
this.value = val;
this.valueChange.emit(val);
}
}

And here how to use it (if you prefer the link):

import { Component } from '@angular/core';

@Component({
selector: 'med-input-template',
template: `
<input medInput type="text" [(value)]="value" />
<input medInput type="text" [value]="value" (valueChange)="value = $event"/>
<button (click)="log()">Show in console</button>
<br>
<input medInput type="text" [disabled]="true" placeholder="I'm disabled" />
`,
})
export class InputTemplateComponent {
public value = 'Hey';

public log(): void {
console.log(this.value);
}
}

The first input uses the banana-in-box syntax. You can do this when input and output names are the same with the output’s ‘Change’ suffix.

The second input uses the classic syntax and you can use a method instead of value = $event if you need to do more stuff.

The third one just shows the disabled input in action. If you want to know how to handle better the boolean attributes, you will have more information here.

Structural directives

You already use it

Maybe you don’t know, but if you already have coded an Angular application, you should have used a structural directive. Are you sure you don’t see what I talk about? Yes, you do? I knew you have a good intuition :-) yes, it’s about *ngIf, *ngFor, and *ngSwitch. These 3 directives are built-in structural directives provided by the Angular framework.

  • NgIf allows displaying content on condition.
  • NgFor allows repeating a node for each item in a collection.
  • NgSwitch allows switching among alternative views.

Use cases

Structural directives are responsible for HTML layout. It allows to add, remove and manipulate the DOM to which they are attached.

Tips: you cannot attach more than 1 structural directive per element. So if you need to use a NgIf and a NgFor, you should use an ng-container (a fictive element) or maybe a Pipe.

Example

Until now, I never have needed to create a new one. So to not just copy the official documentation, I will redirect you to its example.

Conclusion

Actually, we don’t just talk about directives… Yes, I know you got it. We have created a directive. We are now able to bind events, attributes, and classes. We can take input properties and notify a parent thanks to an Output (EventEmitter), a common use case (parent/children communication). Now let your imagination work and develop behaviors that you need in your app(s). If you have any questions, don’t hesitate :-). Thanks for reading.

--

--