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:
- It isn't easily reusable
- 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();
}
}