[Angular] Template Tag

Angular2裡面有提供一個 * 的語法糖, 這個語法糖是用來表示 <template> 標籤. 例如 *ngIf*ngFor 等, 而這篇就來討論怎麼利用 <template>

Template Tag

來先看一段Code吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
selector: 'my-dynamic-component',
template: `<div>Hello world</div>`
})
export class MyDynamicComponent{
}

@Component({
selector: 'app-root',
template: `
<div>
<template #target></template>
<h1>{{title}}</h1>
</div>
`
})

export class AppComponent {
@ViewChild('target', {read: ViewContainerRef}) target: ViewContainerRef;

title = 'app works!';
constructor(private cfr: ComponentFactoryResolver){
}

ngAfterViewInit(){
let myFactory = this.cfr.resolveComponentFactory(MyDynamicComponent);
let compRef = this.target.createComponent(myFactory);

}
}

這段Code有幾個地方要解釋一下的是

  1. <template>是一個placeholder, 像是註記符號,讓angular2知道說要將template注入到哪一個位置
  2. ViewContainerRef 是代表容器的位置
  3. ComponentFactoryResolver是動態產生Component的一個Factory Class

動態產生Component不會在此探討,會留在以後來做討論. 重點是 <template> 這個所產生出來的結果

紅色框起來的就是在上面 <template> 的所在位置,而所要產生的html會在下方被注入。同樣的運作原理適用於 Rotuer

以上就是介紹 『

template tag dances with data

單純的顯示HTML多無聊啊,來個動態顯示資料吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
selector: 'app-root',
template: `

<template #nametag let-y>
<div>Hello {{ y.name }}</div>
</template>

<div [ngTemplateOutlet]="nametag" [ngOutletContext]="myContext"></div>
<div [ngTemplateOutlet]="nametag" [ngOutletContext]="myContext2"></div>
`
})

export class AppComponent {
name = 'kevin';
myContext = { '$implicit': {name: 'kevin'}};
myContext2 = { '$implicit': {name: 'Jeff'}};
}

名詞解釋

  1. [ngTemplateOutlet] : Inserts an embedded view from a prepared TemplateRef
  2. [ngOutletContext]: should be an object, the object’s keys will be the local template variables available within the TemplateRef.
    • Note: using the key $implicit in the context object will set it’s value as default.
  3. let-(alias): let- 是將ngOutletContext所傳進去的object中的$implicit給予一個別名,使 <template> 內可以使用該資料.

所以上面的程式就可以將不同的資料放到相同的 <template> 裡但又不用新增一個component來處理,工作就減少很多了,是不是很方便,而這個也是 *ngFor 等的基本寫法

自訂Directive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import {
Component, Directive, Input, TemplateRef,
ViewContainerRef, EmbeddedViewRef, ChangeDetectorRef, ChangeDetectionStrategy
} from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Directive({
selector: '[rxContext][rxContextOn]'
})
export class RxContext {
@Input() rxContextOn: Observable<any>;

_viewRef: EmbeddedViewRef<any>;

constructor(private templateRef: TemplateRef<any>,
private vcr: ViewContainerRef) {
}

ngOnInit() {
// console.log(this.rxContextOn);

this.rxContextOn.subscribe(state => {
console.log(state);
if (!this._viewRef) {
this._viewRef = this.vcr.createEmbeddedView(this.templateRef, { '$implicit': state });
}
this._viewRef.context.$implicit = state;
});
}
}


@Component({
selector: 'app-root',
template: `
<div *rxContext="let user on userStream">
<h2>{{ user.name }}</h2>
</div>
`
})

export class AppComponent {
userStream = Observable.of({
name: 'kevin',
age: 35
}).concat(Observable.timer(3000).mapTo({ name: 'Jeff', age: 30 }));
}

一段一段的來解釋吧

1
2
3
<div *rxContext="let user on userStream">
<h2>{{ user.name }}</h2>
</div>

我們知道 * 會被更換成 <template> ,所以上面的那段code會替換成

1
2
3
4
5
<template rxContext let-user [rxContextOn]="userStream">
<div>
<h2>{{ user.name }}</h2>
</div>
<template>

所以我們需要一個叫 rxContext的directive,和 rxContextOn的屬性

1
2
3
4
5
6
7
8
9
10
11
@Directive({
selector: '[rxContext][rxContextOn]'
})
export class RxContext {
@Input() rxContextOn: Observable<any>;
_viewRef: EmbeddedViewRef<any>;

constructor(private templateRef: TemplateRef<any>,
private vcr: ViewContainerRef) {
}
}

這裡的rxContextOn型別設定為Observable是因為我們會傳入一個Observable的物件進去,這裡請依實際狀況調整

名詞解釋

  1. TemplateRef : Represents an Embedded Template that can be used to instantiate Embedded Views.
  2. ViewContainerRef: Represents a container where one or more Views can be attached.
  3. createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number) : EmbeddedViewRef

因為Directive本身是不會有任何的template的,所以這裡所要操作的template會是指使用到該directive的 html element.

1
2
3
4
5
6
7
8
ngOnInit() {
this.rxContextOn.subscribe(state => {
if (!this._viewRef) {
this._viewRef = this.vcr.createEmbeddedView(this.templateRef, { '$implicit': state });
}
this._viewRef.context.$implicit = state;
});
}

因為rxContextOn所傳入的資料是Observable型別的資料,所以必須透過subscribe才能將資料產生出來。當資料產生出來後,再把資料塞回到template中,這樣子就完成最基本的directive了

延伸閱讀