正如 TypeScript 可以捕獲你程式碼中的型別錯誤一樣,Angular 也會檢查你的應用範本中的表示式和繫結,並報告它發現的任何型別錯誤。
Angular 目前有三種執行此操作的模式,具體取決於 Angular 編譯器選項中 fullTemplateTypeCheck 和 strictTemplates 標誌的值。
在最基本的型別檢查模式下,當 fullTemplateTypeCheck 標誌設定為 false 時,Angular 僅驗證範本中的頂層表示式。
如果你編寫了 <map [city]="user.address.city">,編譯器會驗證以下內容:
user 是元件類別上的一個屬性(Property)user 是一個具有 address 屬性(property)的物件user.address 是一個具有 city 屬性(property)的物件編譯器不驗證 user.address.city 的值是否可賦值給 <map> 元件的 city 輸入。
在這種模式下,編譯器還有一些主要的侷限性:
*ngIf、*ngFor 和其他 <ng-template> 內嵌檢視。#refs 的型別、通道的結果或事件繫結中 $event 的型別。在許多情況下,這些東西最終都變成 any 型別,這可能會導致表示式的後續部分未經檢查。
如果 fullTemplateTypeCheck 標誌設定為 true,Angular 會在範本中更積極地進行型別檢查。
特別是:
*ngIf 或 *ngFor 中的檢視)會被檢查any 型別)以下各項仍然是 any 型別。
$event 物件重要提示: fullTemplateTypeCheck 標誌已在 Angular 13 中被棄用。
應該改用 strictTemplates 系列的編譯器選項。
Angular 保留了 fullTemplateTypeCheck 標誌的行為,並引入了第三種『嚴格模式』。
嚴格模式是完整模式的超集,可以透過將 strictTemplates 標誌設定為 true 來訪問。
此標誌取代了 fullTemplateTypeCheck 標誌。
除了完整模式的行為之外,Angular 還執行以下操作:
input()strictNullChecks 標誌NgFor 進行正確的型別檢查)$event 的正確型別document.createElement 將為該標籤回傳的型別)*ngFor
三種型別檢查模式對內嵌檢視的處理方式不同。 請考慮以下範例。
interface User { name: string; address: { city: string; state: string; }}
<div *ngFor="let user of users"> <h2>{{config.title}}</h2> <span>City: {{user.address.city}}</span></div>
<h2> 和 <span> 位於 *ngFor 內嵌檢視中。
在基本模式下,Angular 不檢查它們中的任何一個。
但是,在完整模式下,Angular 會檢查 config 和 user 是否存在,並假定型別為 any。
在嚴格模式下,Angular 知道 <span> 中的 user 具有 User 型別,並且 address 是一個物件,其 city 屬性的型別為 string。
在嚴格模式下,你可能會遇到在以前的任何模式下都不會出現的範本錯誤。 這些錯誤通常表示範本中真正的型別不對應,而以前的工具沒有捕獲到這些不對應。 如果是這種情況,則錯誤訊息應明確指出範本中出現問題的具體位置。
當 Angular 函式庫的型別定義不完整或不正確,或者當型別定義與預期不完全一致時,也可能出現誤報,如下例所示。
當函式庫的型別定義錯誤或不完整時(例如,如果函式庫的編寫沒有考慮到 strictNullChecks,則會缺少 null | undefined)
當函式庫的輸入型別太窄,並且函式庫沒有新增適當的元資料供 Angular 來確定這一點時。
這通常發生在用作屬性的 disabled 或其他常見 Boolean 輸入上,例如 <input disabled>。
當對 DOM 事件使用 $event.target 時(由於事件冒泡的可能性,DOM 型別定義中的 $event.target 不具有你可能期望的型別)
如果遇到像這樣的誤報,有以下幾種選擇:
$any() 型別轉換函式來選擇不對表示式的某一部分進行型別檢查tsconfig.json 中設定 strictTemplates: false,完全停用嚴格檢查false,可以單獨停用某些型別檢查操作,同時保持其他方面的嚴格性strictTemplates 和 strictNullChecks,請使用 strictNullInputTypes 專門為輸入繫結選擇退出嚴格的 null 型別檢查除非另有註解,否則以下每個選項都設定為 strictTemplates 的值(當 strictTemplates 為 true 時為 true,反之亦然)。
| 嚴格性標誌 | 效果 |
|---|---|
strictInputTypes |
是否檢查繫結表示式對 @Input() 欄位的可賦值性。還會影響指令泛型型別的推斷。 |
strictInputAccessModifiers |
在將繫結表示式賦值給 @Input() 時,是否考慮 private/protected/readonly 等訪問修飾符。如果停用,則會忽略 @Input 的訪問修飾符;只檢查型別。即使將 strictTemplates 設定為 true,此選項的預設值也為 false。 |
strictNullInputTypes |
在檢查 @Input() 繫結時(根據 strictInputTypes),是否考慮 strictNullChecks。停用此選項在使用未考慮 strictNullChecks 建立的函式庫時可能很有用。 |
strictAttributeTypes |
是否檢查使用文字屬性進行的 @Input() 繫結。例如,<input matInput disabled="true">(將 disabled 屬性設定為字串 'true')與 <input matInput [disabled]="true">(將 disabled 屬性設定為布林值 true)的比較。 |
strictSafeNavigationTypes |
是否正確推斷安全導向操作的回傳型別(例如,user?.name 將根據 user 的型別正確推斷)。如果停用,user?.name 的型別將為 any。 |
strictDomLocalRefTypes |
DOM 元素的本地引用是否具有正確的型別。如果停用,對於 <input #ref>,ref 的型別將為 any。 |
strictOutputEventTypes |
對於元件/指令 @Output() 的事件繫結或動畫事件的繫結,$event 是否具有正確的型別。如果停用,它將為 any。 |
strictDomEventTypes |
對於 DOM 事件的事件繫結,$event 是否具有正確的型別。如果停用,它將是 any。 |
strictContextGenerics |
是否正確推斷泛型元件的型別引數(包括任何泛型邊界)。如果停用,任何型別引數都將是 any。 |
strictLiteralTypes |
是否推斷範本中宣告的物件和數組字面量的型別。如果停用,此類別字面量的型別將為 any。當設定了 fullTemplateTypeCheck 或 strictTemplates 之一時,此標誌為 true。 |
如果在透過這些標誌進行排查後仍然遇到問題,請透過停用 strictTemplates 回退到完整模式。
如果這不起作用,最後的選擇是使用 fullTemplateTypeCheck: false 完全關閉完整模式。
你無法使用任何推薦方法求解的型別檢查錯誤可能是範本型別檢查器本身的錯誤造成的。 如果你遇到的錯誤需要回退到基本模式,則很可能是這種錯誤。 如果發生這種情況,請送出 issue,以便團隊可以解決它。
範本型別檢查器會檢查繫結表示式的型別是否與相應指令輸入的型別相容。 例如,考慮以下元件:
export interface User { name: string;}@Component({ selector: 'user-detail', template: '{{ user.name }}',})export class UserDetailComponent { user = input.required<User>();}
AppComponent 範本按如下方式使用此元件:
@Component({ selector: 'app-root', template: '<user-detail [user]="selectedUser"></user-detail>',})export class AppComponent { selectedUser: User | null = null;}
在這裡,在對 AppComponent 的範本進行型別檢查期間,[user]="selectedUser" 繫結對應於 UserDetailComponent.user 輸入。
因此,Angular 將 selectedUser 屬性(Property)賦值給 UserDetailComponent.user,如果它們的型別不相容,這將導致錯誤。
TypeScript 根據其型別體系檢查賦值,並遵循諸如 strictNullChecks 之類別的標誌,因為它們是在應用中設定的。
透過為範本型別檢查器提供更具體的範本內型別要求,避免執行時型別錯誤。 透過在指令定義中提供範本保護函式,使你自己的指令的輸入型別要求儘可能具體。 請參閱本指南中的改進自定義指令的範本型別檢查。
當你啟用 strictTemplates 和 TypeScript 標誌 strictNullChecks 時,在某些情況下可能會發生型別檢查錯誤,而這些情況可能不容易避免。
例如:
從一個未啟用 strictNullChecks 的函式庫繫結到指令的可空值。
對於在沒有 strictNullChecks 的情況下編譯的函式庫,其物件宣告檔案不會指示欄位是否可以為 null。
對於函式庫正確處理 null 的情況,這是有問題的,因為編譯器將根據省略 null 型別的物件宣告檔案檢查可空值。
因此,編譯器會生成型別檢查錯誤,因為它遵循 strictNullChecks。
將 async 通道與你已知將同步發出的可觀察者一起使用。
async 通道當前假定它訂閱的可觀察者可以是非同步的,這意味著可能還沒有可用的值。
在這種情況下,它仍然必須回傳一些東西——即 null。
換句話說,async 通道的回傳型別包括 null,這可能會在已知可觀察者同步發出非空值的情況下導致錯誤。
對於上述問題,有兩種可能的解決方法:
在範本中,在可空表示式的末尾包含非空斷言運算子 !,例如
<user-detail [user]="user!"></user-detail>
在此範例中,編譯器忽略了 nullability 中的型別不相容性,就像在 TypeScript 程式碼中一樣。
在 async 通道的情況下,請注意表示式需要用括號括起來,如
<user-detail [user]="(user$ | async)!"></user-detail>
完全停用 Angular 範本中的嚴格 Null 檢查。
當啟用 strictTemplates 時,仍然可以停用型別檢查的某些方面。
將選項 strictNullInputTypes 設定為 false 會停用 Angular 範本中的嚴格 Null 檢查。
此標誌適用於作為應用一部分的所有元件。
作為函式庫作者,你可以採取一些措施來為你的使用者提供最佳體驗。
首先,啟用 strictNullChecks 並在輸入的型別中包含 null(如果適用),可以告知你的使用者他們是否可以提供可空值。
此外,還可以提供特定於範本型別檢查器的型別提示。
請參閱改進自定義指令的範本型別檢查和輸入 setter 強制轉換。
有時,指令或元件的 input() 屬性希望更改繫結到它的值,通常是使用輸入的 transform 函式。
例如,請考慮這個自定義按鈕元件:
考慮以下指令:
@Component({ selector: 'submit-button', template: ` <div class="wrapper"> <button [disabled]="disabled">Submit</button> </div> `,})class SubmitButton { disabled = input.required({transform: booleanAttribute });}
在這裡,元件的 disabled 輸入正在傳遞到範本中的 <button>。
只要將 boolean 值繫結到輸入,所有這些都按預期工作。
但是,假設使用者在範本中將此輸入用作 Attribute:
<submit-button disabled></submit-button>
這與以下繫結具有相同的效果:
<submit-button [disabled]="''"></submit-button>
在執行時,輸入將設定為空字串,這不是 boolean 值。
處理此問題的 Angular 元件庫通常會在 setter 中將值『強制轉換』為正確的型別:
set disabled(value: boolean) { this._disabled = (value === '') || value;}
理想的情況是將此處的 value 型別從 boolean 更改為 boolean|'',以對應 setter 實際接受的值集。
TypeScript 4.3 之前的版本要求 getter 和 setter 具有相同的型別,因此如果 getter 應該回傳 boolean,則 setter 會停留在較窄的型別上。
如果使用者為範本啟用了 Angular 最嚴格的型別檢查,則會產生一個問題:空字串 ('') 實際上不可賦值給 disabled 欄位,這在使用 Attribute 形式時會產生型別錯誤。
作為此問題的解決方法,Angular 支援為 @Input() 檢查比輸入欄位本身宣告的更寬泛、更寬鬆的型別。
透過向元件類別新增帶有 ngAcceptInputType_ 字首的靜態屬性來啟用此功能:
class SubmitButton { private _disabled: boolean; @Input() get disabled(): boolean { return this._disabled; } set disabled(value: boolean) { this._disabled = (value === '') || value; } static ngAcceptInputType_disabled: boolean|'';}
自從 TypeScript 4.3 以來,setter 可以宣告為接受 boolean|'' 作為型別,這使得輸入 setter 強制轉換欄位變得過時。
因此,輸入 setter 強制轉換欄位已被棄用。
此欄位不需要有值。
它的存在會告知 Angular 型別檢查器,disabled 輸入應被視為接受與 boolean|'' 型別對應的繫結。
字尾應該是 @Input 欄位名稱。
應注意,如果給定的輸入存在 ngAcceptInputType_ 重寫,則 setter 應該能夠處理任何重寫型別的值。
$any() 停用型別檢查
透過將繫結表示式包圍在對 $any() 強制轉換偽函式的呼叫中,停用對繫結表示式的檢查。
編譯器將其視為強制轉換為 any 型別,就像在 TypeScript 中使用 <any> 或 as any 強制轉換一樣。
在以下範例中,將 person 強制轉換為 any 型別會阻止錯誤 Property address does not exist。
@Component({ selector: 'my-component', template: '{{$any(person).address.street}}'})class MyComponent { person?: Person;}