本主題演示如何創(chuàng)建結(jié)構(gòu)型指令,并提供有關(guān)指令如何工作、Angular 如何解釋簡寫形式以及如何添加模板守衛(wèi)屬性以捕獲模板類型錯誤的概念性信息。
有關(guān)此頁面描述的示例應(yīng)用程序,請參見現(xiàn)場演練 / 下載范例 。
本節(jié)將指導(dǎo)你創(chuàng)建 ?UnlessDirective
?以及如何設(shè)置 ?condition
?值。 ?UnlessDirective
?與 ?NgIf
?相反,并且 ?condition
?值可以設(shè)置為 ?true
?或 ?false
?。 ?NgIf
?為 ?true
?時顯示模板內(nèi)容;而 ?UnlessDirective
?在這個條件為 ?false
?時顯示內(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 會創(chuàng)建指令類,并指定 CSS 選擇器 ?appUnless
?,它會在模板中標(biāo)識指令。
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
?會通過 Angular 生成的 ?<ng-template>
? 創(chuàng)建一個嵌入的視圖,然后將該視圖插入到該指令的原始 ?<p>
? 宿主元素緊后面的視圖容器中。
?TemplateRef
?可幫助你獲取 ?<ng-template>
? 的內(nèi)容,而 ?ViewContainerRef
?可以訪問視圖容器。
@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)條件的值更改時,Angular 都會設(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)用程序,以測試 ?UnlessDirective
?。
condition
?設(shè)置為 ?false
?的 ?AppComponent
?。condition = false;
*appUnless
? 位于兩個具有相反 ?condition
?的 ?<p>
? 標(biāo)記上,一個為 ?true
?,一個為 ?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>
星號是將 ?appUnless
?標(biāo)記為結(jié)構(gòu)型指令的簡寫形式。如果 ?condition
?是假值,則會讓頂部段落 A,而底部段落 B 消失。當(dāng) ?condition
?為真時,頂部段落 A 消失,而底部段落 B 出現(xiàn)。
condition
?的值,請?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>
要驗證指令是否有效,請單擊按鈕以更改 ?condition
?的值。
結(jié)構(gòu)型指令(例如 ?*ngIf
?)上的星號 ?*
?語法是 Angular 解釋為較長形式的簡寫形式。 Angular 將結(jié)構(gòu)型指令前面的星號轉(zhuǎn)換為圍繞宿主元素及其后代的 ?<ng-template>
?。
下面是一個 ?*ngIf
? 的示例,如果 ?hero
?存在,則顯示英雄的名稱:
<div *ngIf="hero" class="name">{{hero.name}}</div>
?*ngIf
? 指令移到了 ?<ng-template>
? 上,在這里它成為綁定在方括號 ?[ngIf]
? 中的屬性。 ?<div>
? 的其余部分(包括其 class 屬性)移到了 ?<ng-template>
? 內(nèi)部。
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
</ng-template>
Angular 不會創(chuàng)建真正的 ?<ng-template>
? 元素,只會將 ?<div>
? 和注釋節(jié)點(diǎn)占位符渲染到 DOM 中。
<!--bindings={
"ng-reflect-ng-if": "[object Object]"
}-->
<div _ngcontent-c0>Mr. Nice</div>
?*ngFor
? 中的星號的簡寫形式與非簡寫的 ?<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
?字符串外,宿主元素上的其他修飾都會保留在 ?<ng-template>
? 中。在這個例子中,?[class.odd]="odd"
? 就留在了 ?<div>
? 中。
?let
?關(guān)鍵字會聲明一個模板輸入變量,你可以在模板中引用該變量。在這個例子中,是 ?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 會將 ?i
? 和 ?odd
?設(shè)置為上下文中 ?index
?和 ?odd
?屬性的當(dāng)前值。
解析器會將 PascalCase 應(yīng)用于所有指令,并為它們加上指令的屬性名稱(例如 ngFor)。比如,?ngFor
?的輸入特性 ?of
?和 ?trackBy
?,會映射為 ?ngForOf
?和 ?ngForTrackBy
?。當(dāng) ?NgFor
?指令遍歷列表時,它會設(shè)置和重置它自己的上下文對象的屬性。這些屬性可以包括但不限于 ?index
?、?odd
?和一個名為 ?$implicit
? 的特殊屬性。
Angular 會將 ?let-hero
? 設(shè)置為上下文的 ?$implicit
? 屬性的值, ?NgFor
?已經(jīng)將其初始化為當(dāng)前正在迭代的英雄。
Angular 的 ?<ng-template>
? 元素定義了一個默認(rèn)情況下不渲染任何內(nèi)容的模板。使用 ?<ng-template>
? ,你可以手動渲染內(nèi)容,以完全控制內(nèi)容的顯示方式。
如果沒有結(jié)構(gòu)型指令,并且將某些元素包裝在 ?<ng-template>
? 中,則這些元素會消失。在下面的示例中,Angular 不會渲染中間的 “Hip!”,因為它被 ?<ng-template>
? 包裹著。
<p>Hip!</p>
<ng-template>
<p>Hip!</p>
</ng-template>
<p>Hooray!</p>
當(dāng)你編寫自己的結(jié)構(gòu)型指令時,請使用以下語法:
*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"
下表描述了結(jié)構(gòu)型指令語法的每個部分:
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 會將結(jié)構(gòu)型指令的簡寫形式轉(zhuǎn)換為普通的綁定語法,如下所示:
簡寫形式 |
翻譯結(jié)果 |
---|---|
|
[prefix]="expression"
|
keyExp
|
|
let
|
let-local="export"
|
下表提供了一些簡寫形式示例:
簡寫形式 |
Angular 如何解釋此語法 |
---|---|
*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">
|
你可以通過將模板守衛(wèi)屬性添加到指令定義中來改進(jìn)自定義指令的模板類型檢查。這些屬性可幫助 Angular 的模板類型檢查器在編譯時發(fā)現(xiàn)模板中的錯誤,從而避免運(yùn)行時錯誤。這些屬性如下:
ngTemplateGuard_(someInputProperty)
? 屬性使你可以為模板中的輸入表達(dá)式指定更準(zhǔn)確的類型。
ngTemplateContextGuard
?聲明了模板上下文的類型。模板中的結(jié)構(gòu)型指令會根據(jù)輸入表達(dá)式來控制是否要在運(yùn)行時渲染該模板。為了幫助編譯器捕獲模板類型中的錯誤,你應(yīng)該盡可能詳細(xì)地指定模板內(nèi)指令的輸入表達(dá)式所期待的類型。
類型保護(hù)函數(shù)會將輸入表達(dá)式的預(yù)期類型縮小為可能在運(yùn)行時傳遞給模板內(nèi)指令的類型的子集。你可以提供這樣的功能來幫助類型檢查器在編譯時為表達(dá)式推斷正確的類型。
例如,?NgIf
?的實(shí)現(xiàn)使用類型窄化來確保只有當(dāng) ?*ngIf
? 的輸入表達(dá)式為真時,模板才會被實(shí)例化。為了提供具體的類型要求,?NgIf
?指令定義了一個靜態(tài)屬性 ?ngTemplateGuard_ngIf: 'binding'
?。這里的 ?binding
?值是一種常見的類型窄化的例子,它會對輸入表達(dá)式進(jìn)行求值,以滿足類型要求。
要為模板中指令的輸入表達(dá)式提供更具體的類型,請在指令中添加 ?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;
}
在這個例子中, ?LoadingState<T>
? 類型允許兩個狀態(tài)之一, ?Loaded<T>
? 或 ?Loading
?。用作指令的 ?state
?輸入的表達(dá)式是寬泛的傘形類型 ?LoadingState
?,因為還不知道此時的加載狀態(tài)是什么。
?IfLoadedDirective
?定義聲明了靜態(tài)字段 ?ngTemplateGuard_state
?,以表示其窄化行為。在 ?AppComponent
?模板中,?*ifLoaded
? 結(jié)構(gòu)型指令只有當(dāng)實(shí)際的 ?state
?是 ?Loaded<Person>
? 類型時,才會渲染該模板。類型守護(hù)允許類型檢查器推斷出模板中可接受的 ?state
?類型是 ?Loaded<T>
?,并進(jìn)一步推斷出 ?T
? 必須是一個 ?Person
?的實(shí)例。
如果你的結(jié)構(gòu)型指令要為實(shí)例化的模板提供一個上下文,可以通過提供靜態(tài)的 ?ngTemplateContextGuard
?函數(shù)在模板中給它提供合適的類型。下面的代碼片段展示了該函數(shù)的一個例子。
@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; };
// …
}
更多建議: