本主題演示如何創(chuàng)建結(jié)構(gòu)型指令,并提供有關(guān)指令如何工作、Angular 如何解釋簡(jiǎn)寫形式以及如何添加模板守衛(wèi)屬性以捕獲模板類型錯(cuò)誤的概念性信息。
有關(guān)此頁(yè)面描述的示例應(yīng)用程序,請(qǐng)參見(jiàn)現(xiàn)場(chǎng)演練 / 下載范例 。
本節(jié)將指導(dǎo)你創(chuàng)建 ?UnlessDirective
?以及如何設(shè)置 ?condition
?值。 ?UnlessDirective
?與 ?NgIf
?相反,并且 ?condition
?值可以設(shè)置為 ?true
?或 ?false
?。 ?NgIf
?為 ?true
?時(shí)顯示模板內(nèi)容;而 ?UnlessDirective
?在這個(gè)條件為 ?false
?時(shí)顯示內(nèi)容。
以下是應(yīng)用于 p 元素的 ?UnlessDirective
?選擇器 ?appUnless
?當(dāng) ?condition
?為 ?false
?,瀏覽器將顯示該句子。
<p *appUnless="condition">Show this sentence unless the condition is true.</p>
unless
?是偽指令的名稱:ng generate directive unless
Angular 會(huì)創(chuàng)建指令類,并指定 CSS 選擇器 ?appUnless
?,它會(huì)在模板中標(biāo)識(shí)指令。
Input
?、?TemplateRef
?和 ?ViewContainerRef
?。import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
}
TemplateRef
?和 ?ViewContainerRef
?注入成私有變量。constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
?UnlessDirective
?會(huì)通過(guò) Angular 生成的 ?<ng-template>
? 創(chuàng)建一個(gè)嵌入的視圖,然后將該視圖插入到該指令的原始 ?<p>
? 宿主元素緊后面的視圖容器中。
?TemplateRef
?可幫助你獲取 ?<ng-template>
? 的內(nèi)容,而 ?ViewContainerRef
?可以訪問(wèn)視圖容器。
@Input()
? 屬性 ?appUnless
?。@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
每當(dāng)條件的值更改時(shí),Angular 都會(huì)設(shè)置 ?appUnless
?屬性。
完整的指令如下:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
/**
* Add the template content to the DOM unless the condition is true.
*/
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
在本節(jié)中,你將更新你的應(yīng)用程序,以測(cè)試 ?UnlessDirective
?。
condition
?設(shè)置為 ?false
?的 ?AppComponent
?。condition = false;
*appUnless
? 位于兩個(gè)具有相反 ?condition
?的 ?<p>
? 標(biāo)記上,一個(gè)為 ?true
?,一個(gè)為 ?false
?。<p *appUnless="condition" class="unless a">
(A) This paragraph is displayed because the condition is false.
</p>
<p *appUnless="!condition" class="unless b">
(B) Although the condition is true,
this paragraph is displayed because appUnless is set to false.
</p>
星號(hào)是將 ?appUnless
?標(biāo)記為結(jié)構(gòu)型指令的簡(jiǎn)寫形式。如果 ?condition
?是假值,則會(huì)讓頂部段落 A,而底部段落 B 消失。當(dāng) ?condition
?為真時(shí),頂部段落 A 消失,而底部段落 B 出現(xiàn)。
condition
?的值,請(qǐng)?zhí)砑右欢螛?biāo)記代碼以顯示狀態(tài)和按鈕。<p>
The condition is currently
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>.
<button
(click)="condition = !condition"
[ngClass] = "{ 'a': condition, 'b': !condition }" >
Toggle condition to {{condition ? 'false' : 'true'}}
</button>
</p>
要驗(yàn)證指令是否有效,請(qǐng)單擊按鈕以更改 ?condition
?的值。
結(jié)構(gòu)型指令(例如 ?*ngIf
?)上的星號(hào) ?*
?語(yǔ)法是 Angular 解釋為較長(zhǎng)形式的簡(jiǎn)寫形式。 Angular 將結(jié)構(gòu)型指令前面的星號(hào)轉(zhuǎn)換為圍繞宿主元素及其后代的 ?<ng-template>
?。
下面是一個(gè) ?*ngIf
? 的示例,如果 ?hero
?存在,則顯示英雄的名稱:
<div *ngIf="hero" class="name">{{hero.name}}</div>
?*ngIf
? 指令移到了 ?<ng-template>
? 上,在這里它成為綁定在方括號(hào) ?[ngIf]
? 中的屬性。 ?<div>
? 的其余部分(包括其 class 屬性)移到了 ?<ng-template>
? 內(nèi)部。
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
</ng-template>
Angular 不會(huì)創(chuàng)建真正的 ?<ng-template>
? 元素,只會(huì)將 ?<div>
? 和注釋節(jié)點(diǎn)占位符渲染到 DOM 中。
<!--bindings={
"ng-reflect-ng-if": "[object Object]"
}-->
<div _ngcontent-c0>Mr. Nice</div>
?*ngFor
? 中的星號(hào)的簡(jiǎn)寫形式與非簡(jiǎn)寫的 ?<ng-template>
? 形式進(jìn)行比較:
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
這里,?ngFor
?結(jié)構(gòu)型指令相關(guān)的所有內(nèi)容都應(yīng)用到了 ?<ng-template>
? 中。而元素上的所有其他綁定和屬性應(yīng)用到了 ?<ng-template>
? 中的 ?<div>
? 元素上。除了 ?ngFor
?字符串外,宿主元素上的其他修飾都會(huì)保留在 ?<ng-template>
? 中。在這個(gè)例子中,?[class.odd]="odd"
? 就留在了 ?<div>
? 中。
?let
?關(guān)鍵字會(huì)聲明一個(gè)模板輸入變量,你可以在模板中引用該變量。在這個(gè)例子中,是 ?hero
?、?i
? 和 ?odd
?。解析器將 ?let hero
?、?let i
? 和 ?let odd
? 轉(zhuǎn)換為名為 ?let-hero
?、?let-i
? 和 ?let-odd
? 的變量。 ?let-i
? 和 ?let-odd
? 變量變?yōu)?nbsp;?let i=index
? 和 ?let odd=odd
? 。 Angular 會(huì)將 ?i
? 和 ?odd
?設(shè)置為上下文中 ?index
?和 ?odd
?屬性的當(dāng)前值。
解析器會(huì)將 PascalCase 應(yīng)用于所有指令,并為它們加上指令的屬性名稱(例如 ngFor)。比如,?ngFor
?的輸入特性 ?of
?和 ?trackBy
?,會(huì)映射為 ?ngForOf
?和 ?ngForTrackBy
?。當(dāng) ?NgFor
?指令遍歷列表時(shí),它會(huì)設(shè)置和重置它自己的上下文對(duì)象的屬性。這些屬性可以包括但不限于 ?index
?、?odd
?和一個(gè)名為 ?$implicit
? 的特殊屬性。
Angular 會(huì)將 ?let-hero
? 設(shè)置為上下文的 ?$implicit
? 屬性的值, ?NgFor
?已經(jīng)將其初始化為當(dāng)前正在迭代的英雄。
Angular 的 ?<ng-template>
? 元素定義了一個(gè)默認(rèn)情況下不渲染任何內(nèi)容的模板。使用 ?<ng-template>
? ,你可以手動(dòng)渲染內(nèi)容,以完全控制內(nèi)容的顯示方式。
如果沒(méi)有結(jié)構(gòu)型指令,并且將某些元素包裝在 ?<ng-template>
? 中,則這些元素會(huì)消失。在下面的示例中,Angular 不會(huì)渲染中間的 “Hip!”,因?yàn)樗?nbsp;?<ng-template>
? 包裹著。
<p>Hip!</p>
<ng-template>
<p>Hip!</p>
</ng-template>
<p>Hooray!</p>
當(dāng)你編寫自己的結(jié)構(gòu)型指令時(shí),請(qǐng)使用以下語(yǔ)法:
*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"
下表描述了結(jié)構(gòu)型指令語(yǔ)法的每個(gè)部分:
prefix
|
HTML 屬性的鍵名 |
key
|
HTML 屬性的鍵名 |
local
|
在模板中使用的局部變量名 |
export
|
該指令以特定名稱導(dǎo)出的值 |
expression
|
標(biāo)準(zhǔn) Angular 表達(dá)式 |
keyExp = :key ":"? :expression ("as" :local)? ";"?
|
||
let = "let" :local "=" :export ";"?
|
||
as = :export "as" :local ";"?
|
Angular 會(huì)將結(jié)構(gòu)型指令的簡(jiǎn)寫形式轉(zhuǎn)換為普通的綁定語(yǔ)法,如下所示:
簡(jiǎn)寫形式 |
翻譯結(jié)果 |
---|---|
|
[prefix]="expression"
|
keyExp
|
|
let
|
let-local="export"
|
下表提供了一些簡(jiǎn)寫形式示例:
簡(jiǎn)寫形式 |
Angular 如何解釋此語(yǔ)法 |
---|---|
*ngFor="let item of [1,2,3]"
|
<ng-template ngFor let-item [ngForOf]="[1,2,3]">
|
*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i"
|
<ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index">
|
*ngIf="exp"
|
<ng-template [ngIf]="exp">
|
*ngIf="exp as value"
|
<ng-template [ngIf]="exp" let-value="ngIf">
|
你可以通過(guò)將模板守衛(wèi)屬性添加到指令定義中來(lái)改進(jìn)自定義指令的模板類型檢查。這些屬性可幫助 Angular 的模板類型檢查器在編譯時(shí)發(fā)現(xiàn)模板中的錯(cuò)誤,從而避免運(yùn)行時(shí)錯(cuò)誤。這些屬性如下:
ngTemplateGuard_(someInputProperty)
? 屬性使你可以為模板中的輸入表達(dá)式指定更準(zhǔn)確的類型。
ngTemplateContextGuard
?聲明了模板上下文的類型。模板中的結(jié)構(gòu)型指令會(huì)根據(jù)輸入表達(dá)式來(lái)控制是否要在運(yùn)行時(shí)渲染該模板。為了幫助編譯器捕獲模板類型中的錯(cuò)誤,你應(yīng)該盡可能詳細(xì)地指定模板內(nèi)指令的輸入表達(dá)式所期待的類型。
類型保護(hù)函數(shù)會(huì)將輸入表達(dá)式的預(yù)期類型縮小為可能在運(yùn)行時(shí)傳遞給模板內(nèi)指令的類型的子集。你可以提供這樣的功能來(lái)幫助類型檢查器在編譯時(shí)為表達(dá)式推斷正確的類型。
例如,?NgIf
?的實(shí)現(xiàn)使用類型窄化來(lái)確保只有當(dāng) ?*ngIf
? 的輸入表達(dá)式為真時(shí),模板才會(huì)被實(shí)例化。為了提供具體的類型要求,?NgIf
?指令定義了一個(gè)靜態(tài)屬性 ?ngTemplateGuard_ngIf: 'binding'
?。這里的 ?binding
?值是一種常見(jiàn)的類型窄化的例子,它會(huì)對(duì)輸入表達(dá)式進(jìn)行求值,以滿足類型要求。
要為模板中指令的輸入表達(dá)式提供更具體的類型,請(qǐng)?jiān)谥噶钪刑砑?nbsp;?ngTemplateGuard_xx
?屬性,其中靜態(tài)屬性名稱 ?xx
?就是 ?@Input()
? 字段的名字。該屬性的值可以是基于其返回類型的常規(guī)類型窄化函數(shù),也可以是字符串,例如 ?NgIf
?中的 ?"binding"
?。
例如,考慮以下結(jié)構(gòu)型指令,該指令以模板表達(dá)式的結(jié)果作為輸入:
export type Loaded = { type: 'loaded', data: T };
export type Loading = { type: 'loading' };
export type LoadingState = Loaded | Loading;
export class IfLoadedDirective {
@Input('ifLoaded') set state(state: LoadingState) {}
static ngTemplateGuard_state(dir: IfLoadedDirective, expr: LoadingState): expr is Loaded { return true; };
}
export interface Person {
name: string;
}
@Component({
template: `<div *ifLoaded="state">{{ state.data }}</div>`,
})
export class AppComponent {
state: LoadingState;
}
在這個(gè)例子中, ?LoadingState<T>
? 類型允許兩個(gè)狀態(tài)之一, ?Loaded<T>
? 或 ?Loading
?。用作指令的 ?state
?輸入的表達(dá)式是寬泛的傘形類型 ?LoadingState
?,因?yàn)檫€不知道此時(shí)的加載狀態(tài)是什么。
?IfLoadedDirective
?定義聲明了靜態(tài)字段 ?ngTemplateGuard_state
?,以表示其窄化行為。在 ?AppComponent
?模板中,?*ifLoaded
? 結(jié)構(gòu)型指令只有當(dāng)實(shí)際的 ?state
?是 ?Loaded<Person>
? 類型時(shí),才會(huì)渲染該模板。類型守護(hù)允許類型檢查器推斷出模板中可接受的 ?state
?類型是 ?Loaded<T>
?,并進(jìn)一步推斷出 ?T
? 必須是一個(gè) ?Person
?的實(shí)例。
如果你的結(jié)構(gòu)型指令要為實(shí)例化的模板提供一個(gè)上下文,可以通過(guò)提供靜態(tài)的 ?ngTemplateContextGuard
?函數(shù)在模板中給它提供合適的類型。下面的代碼片段展示了該函數(shù)的一個(gè)例子。
@Directive({…})
export class ExampleDirective {
// Make sure the template checker knows the type of the context with which the
// template of this directive will be rendered
static ngTemplateContextGuard(dir: ExampleDirective, ctx: unknown): ctx is ExampleContext { return true; };
// …
}
更多建議: