範本型別檢查
Template type checking
範本型別檢查概述
Overview of template type checking
正如 TypeScript 在程式碼中捕獲型別錯誤一樣,Angular 也會檢查應用程式範本中的表示式和繫結,並可以報告所發現的任何型別錯誤。 Angular 當前有三種執行此操作的模式,具體取決於 TypeScript 配置檔案 中的 fullTemplateTypeCheck
和 strictTemplates
標誌的值。
Just as TypeScript catches type errors in your code, Angular checks the expressions and bindings within the templates of your application and can report any type errors it finds. Angular currently has three modes of doing this, depending on the value of the fullTemplateTypeCheck
and strictTemplates
flags in the TypeScript configuration file.
基本模式
Basic mode
在最基本的型別檢查模式下,將 fullTemplateTypeCheck
標誌設定為 false
,Angular 僅驗證範本中的最上層表示式。
In the most basic type-checking mode, with the fullTemplateTypeCheck
flag set to false
, Angular validates only top-level expressions in a template.
如果編寫 <map [city]="user.address.city">
,則編譯器將驗證以下內容:
If you write <map [city]="user.address.city">
, the compiler verifies the following:
user
是該元件類別的屬性。user
is a property on the component class.user
是具有address
屬性的物件。user
is an object with an address property.user.address
是具有city
屬性的物件。user.address
is an object with a city property.
編譯器不會驗證 user.address.city
的值是否可賦值給 <map>
元件的輸入屬性 city
。
The compiler does not verify that the value of user.address.city
is assignable to the city input of the <map>
component.
編譯器在此模式下也有一些主要限制:
The compiler also has some major limitations in this mode:
重要的是,它不會檢查嵌入式檢視,例如
*ngIf
,*ngFor
和其它<ng-template>
嵌入式檢視。Importantly, it doesn't check embedded views, such as
*ngIf
,*ngFor
, other<ng-template>
embedded view.它無法弄清
#refs
的型別、管道的結果、事件繫結中$event
的型別等等。It doesn't figure out the types of
#refs
, the results of pipes, the type of$event
in event bindings, and so on.
在許多情況下,這些東西最終都以 any
型別結束,這可能導致表示式的後續部分不受檢查。
In many cases, these things end up as type any
, which can cause subsequent parts of the expression to go unchecked.
完全模式
Full mode
如果將 fullTemplateTypeCheck
標誌設定為 true
,則 Angular 在範本中進行型別檢查時會更加主動。特別是:
If the fullTemplateTypeCheck
flag is set to true
, Angular is more aggressive in its type-checking within templates. In particular:
檢查嵌入式檢視(例如
*ngIf
或*ngFor
內的*ngFor
)。Embedded views (such as those within an
*ngIf
or*ngFor
) are checked.管道具有正確的返回型別。
Pipes have the correct return type.
對指令和管道的本地參考具有正確的型別(any 泛型引數除外,該通用引數將是
any
)。Local references to directives and pipes have the correct type (except for any generic parameters, which will be
any
).
以下仍然具有 any
型別。
The following still have type any
.
對 DOM 元素的本地參考。
Local references to DOM elements.
$event
物件。The
$event
object.安全導航表示式。
Safe navigation expressions.
嚴格模式
Strict mode
Angular 延續了 fullTemplateTypeCheck
標誌的行為,並引入了第三個“嚴格模式”。嚴格模式是完全模式的超集,可以透過將 strictTemplates
標誌設定為 true 來訪問。該標誌取代 fullTemplateTypeCheck
標誌。在嚴格模式下,Angular 添加了超出 8 版型別檢查器的檢查。請注意,嚴格模式僅在使用 Ivy 時可用。
Angular maintains the behavior of the fullTemplateTypeCheck
flag, and introduces a third "strict mode". Strict mode is a superset of full mode, and is accessed by setting the strictTemplates
flag to true. This flag supersedes the fullTemplateTypeCheck
flag. In strict mode, Angular uses checks that go beyond the version 8 type-checker. Note that strict mode is only available if using Ivy.
除了完全模式的行為之外,Angular 版本 9 還會:
In addition to the full mode behavior, Angular does the following:
驗證元件/指令繫結是否可賦值給它們的
@Input()
。Verifies that component/directive bindings are assignable to their
@Input()
s.驗證以上內容時,遵守 TypeScript 的
strictNullChecks
標誌。Obeys TypeScript's
strictNullChecks
flag when validating the above.推斷元件/指令的正確型別,包括泛型。
Infers the correct type of components/directives, including generics.
推斷配置範本上下文的型別(例如,允許對
NgFor
進行正確的型別檢查)。Infers template context types where configured (for example, allowing correct type-checking of
NgFor
).在元件/指令、DOM 和動畫事件繫結中推斷
$event
的正確型別。Infers the correct type of
$event
in component/directive, DOM, and animation event bindings.根據標籤(tag)名稱(例如,
document.createElement
將為該標籤返回正確的型別),推斷出對 DOM 元素的區域性參考的正確型別。Infers the correct type of local references to DOM elements, based on the tag name (for example, the type that
document.createElement
would return for that tag).
*ngFor
檢查
Checking of *ngFor
型別檢查的三種模式對嵌入式檢視的處理方式不同。考慮以下範例。
The three modes of type-checking treat embedded views differently. Consider the following example.
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 知道該 user
在 <span>
中是 User
型別,而 address
是與一個物件,它有一個 string
型別的屬性 city
。
The <h2>
and the <span>
are in the *ngFor
embedded view. In basic mode, Angular doesn't check either of them. However, in full mode, Angular checks that config
and user
exist and assumes a type of any
. In strict mode, Angular knows that the user
in the <span>
has a type of User
, and that address
is an object with a city
property of type string
.
排除範本錯誤
Troubleshooting template errors
使用嚴格模式,你可能會遇到在以前的兩種模式下都沒有出現過的範本錯誤。這些錯誤通常表示範本中的真正型別不匹配,而以前的工具並未捕獲這些錯誤。在這種情況下,該錯誤訊息會使該問題在範本中的位置清晰可見。
With strict mode, you might encounter template errors that didn't arise in either of the previous modes. These errors often represent genuine type mismatches in the templates that were not caught by the previous tooling. If this is the case, the error message should make it clear where in the template the problem occurs.
當 Angular 函式庫的型別不完整或不正確,或者在以下情況下型別與預期不完全一致時,也可能存在誤報。
There can also be false positives when the typings of an Angular library are either incomplete or incorrect, or when the typings don't quite line up with expectations as in the following cases.
當函式庫的型別錯誤或不完整時(例如,如果編寫函式庫的時候沒有注意
strictNullChecks
,則可能缺少null | undefined
)。When a library's typings are wrong or incomplete (for example, missing
null | undefined
if the library was not written withstrictNullChecks
in mind).當函式庫的輸入型別太窄並且函式庫沒有為 Angular 新增適當的元資料來解決這個問題時。這通常在禁用或使用其它通用布林輸入作為屬性時發生,例如
<input disabled>
。When a library's input types are too narrow and the library hasn't added appropriate metadata for Angular to figure this out. This usually occurs with disabled or other common Boolean inputs used as attributes, for example,
<input disabled>
.在將
$event.target
用於 DOM 事件時(由於事件冒泡的可能性,DOM 型別中的$event.target
不具有你可能期望的型別)。When using
$event.target
for DOM events (because of the possibility of event bubbling,$event.target
in the DOM typings doesn't have the type you might expect).
如果發生此類別誤報,則有以下幾種選擇:
In case of a false positive like these, there are a few options:
在某些情況下,使用
$any()
型別轉換函式可以選擇不對部分表示式進行型別檢查。Use the
$any()
type-cast function in certain contexts to opt out of type-checking for a part of the expression.你可以透過在應用程式的 TypeScript 配置檔案
tsconfig.json
中設定strictTemplates: false
來完全禁用嚴格檢查。You can disable strict checks entirely by setting
strictTemplates: false
in the application's TypeScript configuration file,tsconfig.json
.透過將嚴格性標誌設定為
false
,可以在保持其它方面的嚴格性的同時,單獨禁用某些特定的型別檢查操作。You can disable certain type-checking operations individually, while maintaining strictness in other aspects, by setting a strictness flag to
false
.如果要一起使用
strictTemplates
和strictNullChecks
,則可以透過strictNullInputTypes
來選擇性排除專門用於輸入繫結的嚴格空型別檢查。If you want to use
strictTemplates
andstrictNullChecks
together, you can opt out of strict null type checking specifically for input bindings viastrictNullInputTypes
.
嚴格標誌 Strictness flag | 影響 Effect |
---|---|
strictInputTypes | 是否檢查繫結表示式對 Whether the assignability of a binding expression to the |
strictInputAccessModifiers | 在把繫結表示式賦值給 Whether access modifiers such as |
strictNullInputTypes | 檢查 Whether |
strictAttributeTypes | 是否檢查使用文字屬性(例如, Whether to check |
strictSafeNavigationTypes | 是否根據 Whether the return type of safe navigation operations (for example, |
strictDomLocalRefTypes | 對 DOM 元素的本地參考是否將具有正確的型別。如果禁用,對於 Whether local references to DOM elements will have the correct type. If disabled |
strictOutputEventTypes | 對於繫結到元件/指令 Whether |
strictDomEventTypes | 對於與 DOM 事件的事件繫結, Whether |
strictContextGenerics | 泛型元件的型別引數是否應該被正確推斷(包括泛型上界和下界). 如果禁用它,所有的型別引數都會被當做 Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be |
strictLiteralTypes | 是否要推斷範本中宣告的物件和陣列字面量的型別。如果禁用,則此類別文字的型別就是 Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be |
如果使用這些標誌進行故障排除後仍然存在問題,可以透過禁用 strictTemplates
退回到完全模式。
If you still have issues after troubleshooting with these flags, you can fall back to full mode by disabling strictTemplates
.
如果這不起作用,則最後一種選擇是完全關閉 full 模式,並使用 fullTemplateTypeCheck: false
,因為在這種情況下,我們已經做了一些特殊的努力來使 Angular 9 向後相容。
If that doesn't work, an option of last resort is to turn off full mode entirely with fullTemplateTypeCheck: false
.
你無法使用任何推薦方式解決的型別檢查錯誤可能是因為範本型別檢查器本身存在錯誤。如果遇到需要退回到基本模式的錯誤,則很可能是這樣的錯誤。如果發生這種情況,請提出問題,以便開發組解決。
A type-checking error that you cannot resolve with any of the recommended methods can be the result of a bug in the template type-checker itself. If you get errors that require falling back to basic mode, it is likely to be such a bug. If this happens, please file an issue so the team can address it.
輸入屬性與型別檢查
Inputs and type-checking
範本型別檢查器會檢查繫結表示式的型別是否與相應指令輸入的型別相容。例如,請考慮以下元件:
The template type checker checks whether a binding expression's type is compatible with that of the corresponding directive input. As an example, consider the following component:
export interface User {
name: string;
}
@Component({
selector: 'user-detail',
template: '{{ user.name }}',
})
export class UserDetailComponent {
@Input() user: User;
}
AppComponent
範本按以下方式使用此元件:
The AppComponent
template uses this component as follows:
@Component({
selector: 'my-app',
template: '<user-detail [user]="selectedUser" />',
})
export class AppComponent {
selectedUser: User | null = null;
}
這裡,在檢查 AppComponent
的範本期間,[user]="selectedUser"
繫結與 UserDetailComponent.user
輸入屬性相對應。因此,Angular 會將 selectedUser
屬性賦值給 UserDetailComponent.user
,如果它們的型別不相容,則將導致錯誤。TypeScript 會根據其型別系統進行賦值檢查,並遵循在應用程式中配置的標誌(例如 strictNullChecks
)。
Here, during type checking of the template for AppComponent
, the [user]="selectedUser"
binding corresponds with the UserDetailComponent.user
input. Therefore, Angular assigns the selectedUser
property to UserDetailComponent.user
, which would result in an error if their types were incompatible. TypeScript checks the assignment according to its type system, obeying flags such as strictNullChecks
as they are configured in the application.
透過向範本型別檢查器提出更具體的範本內型別要求,可以避免一些執行時型別錯誤。透過在指令定義中提供各種“範本守衛”功能,可以讓自訂指令的輸入型別要求儘可能具體。參閱本指南中的強化自訂指令的範本型別檢查和輸入屬性 setter 的強制轉換。
You can avoid run-time type errors by providing more specific in-template type requirements to the template type checker. Make the input type requirements for your own directives as specific as possible by providing template-guard functions in the directive definition. See Improving template type checking for custom directives, and Input setter coercion in this guide.
嚴格的空檢查
Strict null checks
當你啟用 strictTemplates
和 TypeScript 標誌 strictNullChecks
,在某些情況下可能會發生型別檢查錯誤,這些情況很難避免。例如:
When you enable strictTemplates
and the TypeScript flag strictNullChecks
, typecheck errors may occur for certain situations that may not easily be avoided. For example:
一個可空值,該值繫結到未啟用
strictNullChecks
的函式庫中的指令。A nullable value that is bound to a directive from a library which did not have
strictNullChecks
enabled.
對於沒有使用 strictNullChecks
編譯的函式庫,其宣告檔案將不會指示欄位是否可以為 null
。對於函式庫正確處理 null
的情況,這是有問題的,因為編譯器將根據宣告檔案進行空值檢查,而它省略了 null
型別。這樣,編譯器會產生型別檢查錯誤,因為它要遵守 strictNullChecks
。
For a library compiled without strictNullChecks
, its declaration files will not indicate whether a field can be null
or not. For situations where the library handles null
correctly, this is problematic, as the compiler will check a nullable value against the declaration files which omit the null
type. As such, the compiler produces a type-check error because it adheres to strictNullChecks
.
將
async
管道與 Observable 一起使用會同步發出值。Using the
async
pipe with an Observable which you know will emit synchronously.
async
管道當前假定它預訂的 Observable 可以是非同步的,這意味著可能還沒有可用的值。在這種情況下,它仍然必須返回某些內容 —— null
。換句話說,async
管道的返回型別包括 null
,這在知道此 Observable 會同步發出非空值的情況下可能會導致錯誤。
The async
pipe currently assumes that the Observable it subscribes to can be asynchronous, which means that it's possible that there is no value available yet. In that case, it still has to return something—which is null
. In other words, the return type of the async
pipe includes null
, which may result in errors in situations where the Observable is known to emit a non-nullable value synchronously.
對於上述問題,有兩種潛在的解決方法:
There are two potential workarounds to the above issues:
在範本中,包括非空斷言運算子
!
用在可為空的表示式的末尾,例如<user-detail [user]="user!" />
。In the template, include the non-null assertion operator
!
at the end of a nullable expression, such as<user-detail [user]="user!" />
.在此範例中,編譯器在可空性方面會忽略型別不相容,就像在 TypeScript 程式碼中一樣。對於
async
管道,請注意,表示式需要用括號括起來,如<user-detail [user]="(user$ | async)!" />
。In this example, the compiler disregards type incompatibilities in nullability, just as in TypeScript code. In the case of the
async
pipe, note that the expression needs to be wrapped in parentheses, as in<user-detail [user]="(user$ | async)!" />
.完全禁用 Angular 範本中的嚴格空檢查。
Disable strict null checks in Angular templates completely.
當啟用
strictTemplates
時,仍然可以禁用型別檢查的某些方面。將選項strictNullInputTypes
設定為false
將禁用 Angular 範本中的嚴格空檢查。此標誌會作用於應用程式中包含的所有元件。When
strictTemplates
is enabled, it is still possible to disable certain aspects of type checking. Setting the optionstrictNullInputTypes
tofalse
disables strict null checks within Angular templates. This flag applies for all components that are part of the application.
給函式庫作者的建議
Advice for library authors
作為函式庫作者,你可以採取多種措施為使用者提供最佳體驗。首先,啟用 strictNullChecks
並在輸入的型別中包括 null
(如果適用),可以與消費者溝通,看他們是否可以提供可空的值。 此外,可以提供特定範本型別檢查器的型別提示,請參閱本指南的為自訂指令改進範本型別檢查和輸入設定器強制轉型部分。
As a library author, you can take several measures to provide an optimal experience for your users. First, enabling strictNullChecks
and including null
in an input's type, as appropriate, communicates to your consumers whether they can provide a nullable value or not. Additionally, it is possible to provide type hints that are specific to the template type checker. See Improving template type checking for custom directives, and Input setter coercion below.
輸入設定器強制轉型
Input setter coercion
有時,指令或元件的 @Input()
最好更改繫結到它的值,通常使用此輸入的 getter / setter 對。例如,考慮以下自訂按鈕元件:
Occasionally it is desirable for the @Input()
of a directive or component to alter the value bound to it, typically using a getter/setter pair for the input. As an example, consider this custom button component:
考慮以下指令:
Consider the following directive:
@Component({
selector: 'submit-button',
template: `
<div class="wrapper">
<button [disabled]="disabled">Submit</button>'
</div>
`,
})
class SubmitButton {
private _disabled: boolean;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = value;
}
}
在這裡,元件的輸入 disabled
將傳給範本中的 <button>
。只要將 boolean
值繫結到輸入,所有這些工作都可以按預期進行。但是,假設使用者使用範本中的這個輸入作為屬性:
Here, the disabled
input of the component is being passed on to the <button>
in the template. All of this works as expected, as long as a boolean
value is bound to the input. But, suppose a consumer uses this input in the template as an attribute:
<submit-button disabled></submit-button>
這與繫結具有相同的效果:
This has the same effect as the binding:
<submit-button [disabled]="''"></submit-button>
在執行時,輸入將設定為空字串,這不是 boolean
值。處理此問題的角元件庫通常將值“強制轉換”到 setter 中的正確型別中:
At runtime, the input will be set to the empty string, which is not a boolean
value. Angular component libraries that deal with this problem often "coerce" the value into the right type in the setter:
set disabled(value: boolean) {
this._disabled = (value === '') || value;
}
最好在這裡將 value
的型別從 boolean
更改為 boolean|''
以匹配 setter 實際會接受的一組值。TypeScript 要求 getter 和 setter 的型別相同,因此,如果 getter 應該返回 boolean
則 setter 會卡在較窄的型別上。
It would be ideal to change the type of value
here, from boolean
to boolean|''
, to match the set of values which are actually accepted by the setter. TypeScript requires that both the getter and setter have the same type, so if the getter should return a boolean
then the setter is stuck with the narrower type.
如果消費者對範本啟用了 Angular 的最嚴格的型別檢查功能,則會產生一個問題:空字串 ''
實際上無法賦值給 disabled
欄位,使用屬性格式寫會產生型別錯誤。
If the consumer has Angular's strictest type checking for templates enabled, this creates a problem: the empty string ''
is not actually assignable to the disabled
field, which will create a type error when the attribute form is used.
作為解決此問題的一種取巧方式,Angular 支援對 @Input()
檢查比宣告的輸入欄位更寬鬆的型別。 透過向元件類別新增帶有 ngAcceptInputType_
字首的靜態屬性來啟用此功能:
As a workaround for this problem, Angular supports checking a wider, more permissive type for @Input()
than is declared for the input field itself. Enable this by adding a static property with the ngAcceptInputType_
prefix to the component class:
class SubmitButton {
private _disabled: boolean;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = (value === '') || value;
}
static ngAcceptInputType_disabled: boolean|'';
}
該欄位不需要值。它只要存在就會通知 Angular 的型別檢查器,disabled
輸入應被視為接受與 boolean|''
型別匹配的繫結。字尾應為 @Input
欄位的名稱。
This field does not need to have a value. Its existence communicates to the Angular type checker that the disabled
input should be considered as accepting bindings that match the type boolean|''
. The suffix should be the @Input
field name.
請注意,如果給定輸入存在 ngAcceptInputType_
覆蓋,則設定器應能夠處理任何覆蓋型別的值。
Care should be taken that if an ngAcceptInputType_
override is present for a given input, then the setter should be able to handle any values of the overridden type.
使用 $any()
禁用型別檢查
Disabling type checking using $any()
可以透過把繫結表示式包含在型別轉換偽函式 $any()
中來禁用型別檢查。 編譯器會像在 TypeScript 中使用 <any>
或 as any
進行型別轉換一樣對待它。
Disable checking of a binding expression by surrounding the expression in a call to the $any()
cast pseudo-function. The compiler treats it as a cast to the any
type just like in TypeScript when a <any>
or as any
cast is used.
在以下範例中,將 person
強制轉換為 any
型別可以壓制錯誤 Property address does not exist
。
In the following example, casting person
to the any
type suppresses the error Property address does not exist
.
@Component({
selector: 'my-component',
template: '{{$any(person).addresss.street}}'
})
class MyComponent {
person?: Person;
}