昨天提了一下 ng-template 的不會出現在 DOM 的小秘密,今天要來分享的是另一個實用的 directive:ngTemplateOutlet。
ngTemplateOutlet 通常會跟 ng-container 一起出現,而且會長的像下面這種樣子:
<ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
<br>
<ng-container *ngTemplateOutlet="tw; context: myContext"></ng-container>
再搭配上擁有 template variable 的 ng-template:
<ng-template #eng let-name><span>Hello {{name}}!</span></ng-template>
<ng-template #tw let-person="localTw"><span>您好 {{person}}!</span></ng-template>
將所有程式碼放在一起看的話就會是:
<ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
<br>
<ng-container *ngTemplateOutlet="tw; context: myContext"></ng-container>
<ng-template #eng let-name><span>Hello {{name}}!</span></ng-template>
<ng-template #tw let-person="localTw"><span>您好 {{person}}!</span></ng-template>
export class AppComponent {
title = 'day19';
myContext = {$implicit: 'World', localTw: '世界'};
}
ng-container 與 ng-template 擁有類似的特性,當 Angular 的編譯器將 HTML template 轉成 JavaScript 的時候,會產生一個 template function 來取代原先的 ng-container tag,所以就能避免開發者為了讓版型可以更加靈活而新增的元素,不至於影響 CSS 樣式的套用。
套用的時需要注意一些事情:
以 structural directive 的方式來使用 ngTemplate,這會使 Angular 將之視為:
<ng-container
[ngTemplateOutlet]="eng"
[ngTemplateOutletContext]="myContext"
></ng-container>
詳情請看 [Day 14] * 與 microsyntax ?
ngTemplateOutlet 的型別是 TemplateRef<any> | null
。
ngTemplateOutletContext 的型別是 Object | null
。
以上是比較簡單的應用,接下來的範例(Angular Material Stepper)就有點小複雜:
<mat-horizontal-stepper>
<mat-step label="Step 1" state="phone">
<p>Put down your phones.</p>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step label="Step 2" state="chat">
<p>Socialize with each other.</p>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step label="Step 3">
<p>You're welcome.</p>
</mat-step>
<!-- Icon overrides. -->
<ng-template matStepperIcon="phone">
<mat-icon>call_end</mat-icon>
</ng-template>
<ng-template matStepperIcon="chat">
<mat-icon>forum</mat-icon>
</ng-template>
</mat-horizontal-stepper>
↑ Block 1
Block 1 的例子是從 Angular Material Guide 引用來的,讓我們把焦點放在 matStepperIcon
這個 directive 身上,它本身的設計就是用來放在 ng-template 身上,這點我們可以透過它的 selector 得知:
@Directive({
selector: 'ng-template[matStepperIcon]',
})
export class MatStepperIcon {
/** Name of the icon to be overridden. */
@Input('matStepperIcon') name: StepState;
constructor(public templateRef: TemplateRef<MatStepperIconContext>) {}
}
↑ Block 2:MatStepperIcon 的原始碼
MatStepperIcon 本身並沒有做什麼事情,只是透過 DI 取得 templateRef 並 public 出去而已,所以其他人只要使用 MatStepperIcon 的 templateRef,就可以取得 Block 1 中被包在 ng-template 內的 mat-icon 元素。
目前還看不出這個 mat-icon 元素最後會被誰使用,所以我們接著在看最外層的 mat-horizontal-stepper 元素,也就是 MatHorizontalStepper component:
@Component(
// ... 略
)
export class MatHorizontalStepper extends MatStepper {
// ... 略
}
↑ Block 3:MatHorizontalStepper component
它繼承了 MatStepper directive,也就是實際上會將 MatStepperIcon query 出來的物件:
@Directive({
selector: '[matStepper]',
providers: [{provide: CdkStepper, useExisting: MatStepper}]
})
export class MatStepper extends CdkStepper implements AfterContentInit {
// ... 略
/** Custom icon overrides passed in by the consumer. */
@ContentChildren(MatStepperIcon, {descendants: true}) _icons: QueryList<MatStepperIcon>;
// ... 略
/** Consumer-specified template-refs to be used to override the header icons. */
_iconOverrides: {[key: string]: TemplateRef<MatStepperIconContext>} = {};
// ... 略
ngAfterContentInit() {
super.ngAfterContentInit();
this._icons.forEach(({name, templateRef}) =>
this._iconOverrides[name] = templateRef);
// ... 略
}
// ... 略
}
↑ Block 4:MatStepper directive
可以從 Block 4 的 MatStepper directive 看出 query 完 MatStepperIcon 之後,MatStepper 會將 _icons
陣列的內容轉換另一個物件:_iconOverrides,然後在 HTML template 內傳給 MatStepHeader component(Link)。
最後當然就是由 MatStepHeader component 來處理 mat-icon 的 templateRef 囉:
<!-- 略 -->
<div class="mat-step-icon-state-{{state}} mat-step-icon" [class.mat-step-icon-selected]="selected">
<div class="mat-step-icon-content" [ngSwitch]="!!(iconOverrides && iconOverrides[state])">
<ng-container
*ngSwitchCase="true"
[ngTemplateOutlet]="iconOverrides[state]"
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
<!-- 略 -->
</div>
</div>
<!-- 略 -->
↑ Block 5:MatStepHeader component HTML template(Link)
以上就是今天要分享的 Tip,透過定義好 ngTemplate,並在其他的 HTML element 上使用 ngTemplateOutlet、ngTemplateOuteletContext 就可以將 ngTemplate 嵌在該元素內顯示,進而達成更彈性的版面設計!
以下按照入團順序列出我們團隊夥伴的系列文章!
請自由參閱 ?