模板类型检查
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;
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;
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;
}