填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

範本型別檢查

Template type checking

範本型別檢查概述

Overview of template type checking

正如 TypeScript 在程式碼中捕獲型別錯誤一樣,Angular 也會檢查應用程式範本中的表示式和繫結,並可以報告所發現的任何型別錯誤。 Angular 當前有三種執行此操作的模式,具體取決於 TypeScript 配置檔案 中的 fullTemplateTypeCheckstrictTemplates 標誌的值。

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.

User interface
      
      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 會檢查 configuser 是否存在,並假設為 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 with strictNullChecks 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.

  • 如果要一起使用 strictTemplatesstrictNullChecks,則可以透過 strictNullInputTypes 來選擇性排除專門用於輸入繫結的嚴格空型別檢查。

    If you want to use strictTemplates and strictNullChecks together, you can opt out of strict null type checking specifically for input bindings via strictNullInputTypes.

嚴格標誌

Strictness flag

影響

Effect

strictInputTypes

是否檢查繫結表示式對 @Input() 欄位的可賦值性。也會影響指令泛型型別的推斷。

Whether the assignability of a binding expression to the @Input() field is checked. Also affects the inference of directive generic types.

strictInputAccessModifiers

在把繫結表示式賦值給 @Input() 時,是否檢查像 private/protected/readonly 這樣的訪問修飾符。如果禁用,則 @Input 上的訪問修飾符會被忽略,只進行型別檢查。

Whether access modifiers such as private/protected/readonly are honored when assigning a binding expression to an @Input(). If disabled, the access modifiers of the @Input are ignored; only the type is checked.

strictNullInputTypes

檢查 @Input() 繫結時是否要 strictNullChecks(對於每個 strictInputTypes)。當使用的函式庫不是基於 strictNullChecks 建構的時,將其關閉會很有幫助。

Whether strictNullChecks is honored when checking @Input() bindings (per strictInputTypes). Turning this off can be useful when using a library that was not built with strictNullChecks in mind.

strictAttributeTypes

是否檢查使用文字屬性(例如,<mat-tab label="Step 1"><mat-tab [label]="'Step 1'">)進行的 @Input() 繫結。

Whether to check @Input() bindings that are made using text attributes (for example, <mat-tab label="Step 1"> vs <mat-tab [label]="'Step 1'">).

strictSafeNavigationTypes

是否根據 user 的型別正確推斷出安全導航操作的返回型別(例如 user?.name )。如果禁用,則 user?.name 的型別為 any

Whether the return type of safe navigation operations (for example, user?.name) will be correctly inferred based on the type of user). If disabled, user?.name will be of type any.

strictDomLocalRefTypes

對 DOM 元素的本地參考是否將具有正確的型別。如果禁用,對於 <input #ref> 來說 ref 會是 any 型別的。

Whether local references to DOM elements will have the correct type. If disabled ref will be of type any for <input #ref>.

strictOutputEventTypes

對於繫結到元件/指令 @Output() 或動畫事件的事件繫結,$event 是否具有正確的型別。如果禁用,它將為 any

Whether $event will have the correct type for event bindings to component/directive an @Output(), or to animation events. If disabled, it will be any.

strictDomEventTypes

對於與 DOM 事件的事件繫結,$event 是否具有正確的型別。如果禁用,它將為 any

Whether $event will have the correct type for event bindings to DOM events. If disabled, it will be any.

strictContextGenerics

泛型元件的型別引數是否應該被正確推斷(包括泛型上界和下界). 如果禁用它,所有的型別引數都會被當做 any

Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be any.

strictLiteralTypes

是否要推斷範本中宣告的物件和陣列字面量的型別。如果禁用,則此類別文字的型別就是 any

Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be any.

如果使用這些標誌進行故障排除後仍然存在問題,可以透過禁用 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:

  1. 在範本中,包括非空斷言運算子 ! 用在可為空的表示式的末尾,例如 <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)!" />.

  2. 完全禁用 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 option strictNullInputTypes to false 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;
}