Angular Basics II - Decorator & Directive
1. Decorator(Annotation)
Annotation or Decorator is esssentially a function, which also returns a function. Next we will create a decorator step by step.
Practice 1: @Emoji
@Emoji
can add a Laugh Emoji at both sides of the string.
@Emoji() result = 'Hello';
Codeing the decorator:
export function Emoji() {
return (target: object, key: string) => {
let val = target[key];
const getter = () => {
return val;
}
const setter = (value: string) => {
val = `😂 ${value} 😂`
}
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
})
}
}
Through debugging we can see the target
is the component
, and the key is string result
.
Practice 2: @Confirmable(‘…’)
We add it above handleClick()
. When clicking the button, we want to pop up a window to warn our user. This decorator has param.
@Confirmable('Are you sure to do something?')
handleClick() {
console.log('handle click works');
}
Codeing the decorator:
export function Confirmable(message: string) {
return (target: object, key: string, descriptor: PropertyDescriptor) => {
const original = descriptor.value;
descriptor.value = function(...args: any) {
const allow = window.confirm (message)
if (allow) {
const result = original.apply(args);
return result;
}
return null;
};
return descriptor;
}
}
2. Directive
There are two types of directives in Angular. They are,
- structural directives:
ngIf
,ngFor
,ngSwitch
- attribute directives:
ngClass
,ngStyle
,ngModel
Next, we will create some directives.
Create Directives:
Create a file gird-item.directive.ts
, in this file use a shortcut ng-directive
to gernerate a directive. Then a template can be generated as below.
import { Directive } from '@angular/core';
@Directive({
selector: 'app-Name',
})
export class NameDirective { }
It has some mistakes. To let it work, we need to make some modifications.
- use
[]
to surround theapp-Name
.import { Directive } from '@angular/core'; @Directive({ selector: '[appGridItem]', }) export class GridItemDirective { }
- add its decleration in
shared.component.ts
and also export it@NgModule({ declarations: [ ScrollableTabComponent, ImageSliderComponent, HorizontalGridComponent, GridItemDirective ], ... exports: [ CommonModule, FormsModule, ScrollableTabComponent, ImageSliderComponent, HorizontalGridComponent, GridItemDirective ] })
Now let’s code this directive.
import { Directive } from '@angular/core';
@Directive({
selector: '[appGridItem]',
})
export class GridItemDirective {
constructor (
private elr: ElementRef,
private rd2: Renderer2
) {
this.rd2.setStyle(this.elr.nativeElement, 'display', 'grid');
this.rd2.setStyle(this.elr.nativeElement, 'grid-template', `'image' 'title'`);
this.rd2.setStyle(this.elr.nativeElement, 'place-itmes', 'center');
this.rd2.setStyle(this.elr.nativeElement, 'width', '4rem');
}
}
It’s not good to set styles in the constructor. We can do this job in ngOnInit()
, so let directives implement OnInit
and realize ngOnInit()
.
import { Directive } from '@angular/core';
@Directive({
selector: '[appGridItem]',
})
export class GridItemDirective {
constructor (
private elr: ElementRef,
private rd2: Renderer2
) {}
ngOnInit() {
this.rd2.setStyle(this.elr.nativeElement, 'display', 'grid');
this.rd2.setStyle(this.elr.nativeElement, 'grid-template-areas', `'image' 'title'`);
this.rd2.setStyle(this.elr.nativeElement, 'place-items', 'center');
this.rd2.setStyle(this.elr.nativeElement, 'width', '4rem');
}
}
Then create GridItemImageDirective
and GridItemTitleDirective
following same procedures.
Use Created Directives:
Directives are like properties. Now we can use these directives in the template of HorizontalGridComponent
.
<div appGridItem *ngFor="let item of channels">
<img [src]="item.imgUrl" alt="" appGridItemImage>
<span appGridItemTitle></span>
</div>
We need to defind channels in horizontal-grid.component.ts
. First define interface Channel
, then define the Channel[]
.
export interface Channel {
id: number;
icon: string;
title: string;
link: string;
}
In summary:
- Components like tags in HTML.
- Directives like properties, which can be applied to HTML tags.
3. Style / Event Binding in Directive
Since there is no template in directive. Directive needs a host(an element). In Angular, @HostBinding
can be used to bind attributes and styles to host, @HostListener
can be used to bind evnets to host.
@HostBinding
Previously, we use rd2
to set styles of host, now we can use @HostBinding
to realize the same function.
export class GridItemDirective {
@HostBinding('style.display') display = 'grid';
@HostBinding('style.grid-template-areas') template = `'image' 'title'`;
@HostBinding('style.place-items') align = 'center';
@HostBinding('style.width') width = '4rem';
}
@HostListener
It accepts two params, one is event name, another is event data.
@HostListener('click', ['$event.target'])
handleClick(ev) {
console.log(ev);
}
We don’t need to add additional event binding in template like we previously did. The code in template is,
<div appGridItem *ngFor="let item of channels">
<img [src]="item.icon" alt=""
[appGridItemImage]="'4rem'"
[fitMode]="'cover'">
<span appGridItemTitle="0.6rem" class="title"></span>
</div>
Host Concept in Component
:host is used to design component styles in .component.css
. It is a pseudo-classes selector. For example,
:host {
display: flex;
justify-content: center;
}
The pattern defined by :host
is applied to the component itself.