Skip to main content

9. Creating Directives and Advanced Components

1. Creating custom Directives​

Let's imagine we want to open a modal when clicking a certain button or highlight some text. We could do this in our component code but we would run into 2 majors problems:

  1. It isn't easily reusable
  2. We are binding a component to the implementation of a specific behaviour β†’ it's preferable to hide these implementation details

Instead, we are going to create a attribute directive

πŸ’‘ Using an attribute directive we can attach new behaviour to a DOM node

Example 1: Creating a highlight directive​

Let's define a HighlightDirective and specify its associated selector [highlight]

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

@Directive({ selector: '[highlight]' })
/** Set backgroundColor for the attached element to highlight color
* and set the element's customProperty to true */
export class HighlightDirective implements OnChanges {

defaultColor = 'rgb(211, 211, 211)'; // lightgray

@Input('highlight') bgColor: string;

constructor(private el: ElementRef) {
el.nativeElement.style.customProperty = true;
}

ngOnChanges() {
this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
}
}

In our HTML file, we call the highlight directive by declaring it as an attribute on DOM elements we want to highlight

<h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>The Default (Gray)</h2>
<h2>No Highlight</h2>
<input #box [highlight]="box.value" value="cyan"/>

Example 2: Creating a modal-trigger directive​

Let's define a ModalTriggerDirective and specify its associated selector [modal-trigger]. We can then listen for click events on the referenced element to add behaviour.

import { Directive, OnInit } from '@angular/core'
import { JQ_TOKEN } from './jQuery.service'

@Directive({
selector: '[modal-trigger]'
})
export class ModalTriggerDirective implements OnInit {
private el: HTMLElement;

@Input('modal-trigger')
modalId: string;

constructor(ref: ElementRef) {
this.el = ref.nativeElement;
}

ngOnInit() {
this.el.addEventListener('click', e => {
document.getElementById(modalId).modal({})
});
}
}

In our HTML file, we call the modal-trigger directive by declaring it as an attribute on the button element that will trigger our modal

<button class="btn btn-default" modal-trigger="dummyModal">
Search
</button>
<simple-modal elementId="dummyModal">
My Dummy Modal
</simple-modal>

2. Using ViewChild, ViewChildren and ContentChild​

2.1. When to use the @ViewChild decorator?​

Many times we can coordinate these multiple components and HTML elements directly in the template by using template references like #reference or #primaryColorSample, without using a specific Component class.

πŸ’‘ Sometimes, a component might need references to the multiple elements that it contains inside its template, in order to mediate their interaction. If that's the case, then we can obtain references to those template elements and have them injected into our component class by querying the template: that's what @ViewChild is for.

2.2. Using @ViewChild to inject a reference to a component​

We can use @ViewChild to obtain a reference in a component defined inside our template. If we want to write component initialization code that uses the references injected by @ViewChild, we need to do it inside the AfterViewInit lifecycle hook.

For example:

@Component({
selector: 'app-my-custom',
template: `
<section>
<child-component></child-component>
</section>
`
})
export class MyCustomComponent implements AfterViewInit {

@ViewChild(ChildComponent)
childComponent: ChildComponent;

ngAfterViewInit() {
console.log("childComponent:", this.childComponent);
}
}

2.3. Using @ViewChild to inject a reference to a DOM element​

For example:

@Component({
selector: 'app-my-custom',
template: `
<section>
<h2 #title>Hello there</h2>
</section>
`
})
export class MyCustomComponent implements AfterViewInit {

@ViewChild('title')
title: ElementRef;

ngAfterViewInit() {
console.log("title:", this.title.nativeElement);
}
}

⚠️ ElementRef simply wraps the native DOM element, and we can retrieve it by accessing the nativeElement property.

2.4. Using @ViewChild to inject a reference to the DOM element of a component​

For example:

@Component({
selector: 'app-my-custom',
template: `
<section>
<child-component #childReference></child-component>
</section>
`
})
export class MyCustomComponent implements AfterViewInit {

@ViewChild(ChildComponent, {read: ElementRef})
childReference: ElementRef;

ngAfterViewInit() {
console.log("childReference:", this.childReference.nativeElement);
}
}

2.5. Using @ViewChild to inject a reference to one of several directives​

For example:

@Component({
selector: 'app-root',
template: '
<h2 #title>Choose Brand Colors:</h2>

<color-sample [color]="primary" #primaryColorSample (click)="openColorPicker()">
</color-sample>

<mat-input-container>
<mat-label>Primary Color</mat-label>
<input matInput #primaryInput [(colorPicker)]="primary" [(ngModel)]="primary"/>
</mat-input-container>
'
})
export class AppComponent {
primary = '#1976d2';

@ViewChild('primaryInput', {read: ColorPickerDirective})
colorPicker: ColorPickerDirective;

openColorPicker() {
this.colorPicker.openDialog();
}
}

References​