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

預先(AOT)編譯器

Ahead-of-time (AOT) compilation

Angular 應用主要由元件及其 HTML 範本組成。由於瀏覽器無法直接理解 Angular 所提供的元件和範本,因此 Angular 應用程式需要先進行編譯才能在瀏覽器中執行。

An Angular application consists mainly of components and their HTML templates. Because the components and templates provided by Angular cannot be understood by the browser directly, Angular applications require a compilation process before they can run in a browser.

在瀏覽器下載和執行程式碼之前的編譯階段,Angular 預先(AOT)編譯器會先把你的 Angular HTML 和 TypeScript 程式碼轉換成高效的 JavaScript 程式碼。 在建構期間編譯應用可以讓瀏覽器中的渲染更快速。

The Angular ahead-of-time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase before the browser downloads and runs that code. Compiling your application during the build process provides a faster rendering in the browser.

本指南中解釋瞭如何指定元資料,並使用一些編譯器選項以藉助 AOT 編譯器來更有效的編譯應用。

This guide explains how to specify metadata and apply available compiler options to compile your applications efficiently using the AOT compiler.

下面是你可能要使用 AOT 的部分原因。

Here are some reasons you might want to use AOT.

  • 更快的渲染。 藉助 AOT,瀏覽器可以下載應用的預編譯版本。瀏覽器載入的是可執行程式碼,因此它可以立即渲染應用,而無需等待先編譯好應用。

    Faster rendering With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the app first.

  • 更少的非同步請求。 編譯器會在應用 JavaScript 中內外部 HTML 範本和 CSS 樣式表,從而消除了對那些原始檔的單獨 ajax 請求。

    Fewer asynchronous requests The compiler inlines external HTML templates and CSS style sheets within the application JavaScript, eliminating separate ajax requests for those source files.

  • 較小的 Angular 框架下載大小。 如果已編譯應用程式,則無需下載 Angular 編譯器。編譯器大約是 Angular 本身的一半,因此省略編譯器會大大減少應用程式的有效載荷。

    Smaller Angular framework download size There's no need to download the Angular compiler if the app is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.

  • 儘早檢測範本錯誤。 AOT 編譯器會在建構步驟中檢測並報告範本繫結錯誤,然後使用者才能看到它們。

    Detect template errors earlier The AOT compiler detects and reports template binding errors during the build step before users can see them.

  • 更高的安全性。 AOT 在將 HTML 範本和元件提供給客戶端之前就將其編譯為 JavaScript 檔案。沒有要讀取的範本,沒有潛藏風險的客戶端 HTML 或 JavaScript eval,受到注入攻擊的機會就更少了。

    Better security AOT compiles HTML templates and components into JavaScript files long before they are served to the client. With no templates to read and no risky client-side HTML or JavaScript evaluation, there are fewer opportunities for injection attacks.

選擇編譯器

Choosing a compiler

Angular 提供了兩種方式來編譯你的應用:

Angular offers two ways to compile your application:

  • 即時編譯 (JIT),它會在執行期間在瀏覽器中編譯你的應用。這是 Angular 8 及更早版本的預設值。

    Just-in-Time (JIT), which compiles your app in the browser at runtime. This was the default until Angular 8.

  • 預先(AOT)編譯,它會在建構時編譯你的應用和函式庫。這是 Angular 9 及後續版本的預設值。

    Ahead-of-Time (AOT), which compiles your app and libraries at build time. This is the default since Angular 9.

當執行 CLI 命令 ng build(只建構) 或 ng serve(建構並啟動本地伺服器) 時,編譯型別(JIT 或 AOT)取決於你在 angular.json 中的建構配置所指定的 aot 屬性。預設情況下,對於新的 CLI 應用,其 aottrue

When you run the ng build(build only) or ng serve(build and serve locally) CLI commands, the type of compilation (JIT or AOT) depends on the value of the aot property in your build configuration specified in angular.json. By default, aot is set to true for new CLI apps.

要了解更多,請參閱CLI 文件,和 建構與執行 Angular 應用

See the CLI command reference and Building and serving Angular apps for more information.

AOT 工作原理

How AOT works

Angular AOT 編譯器會提取元資料來解釋應由 Angular 管理的應用程式部分。你可以在裝飾器(例如 @Component()@Input())中顯式指定元資料,也可以在被裝飾的類別的建構函式宣告中隱式指定元資料。元資料告訴 Angular 要如何構造應用程式類別的實例並在執行時與它們進行互動。

The Angular AOT compiler extracts metadata to interpret the parts of the application that Angular is supposed to manage. You can specify the metadata explicitly in decorators such as @Component() and @Input(), or implicitly in the constructor declarations of the decorated classes. The metadata tells Angular how to construct instances of your application classes and interact with them at runtime.

在下列範例中,@Component() 元資料物件和類別的建構函式會告訴 Angular 如何建立和顯示 TypicalComponent 的實例。

In the following example, the @Component() metadata object and the class constructor tell Angular how to create and display an instance of TypicalComponent.

@Component({ selector: 'app-typical', template: '<div>A typical component for {{data.name}}</div>' }) export class TypicalComponent { @Input() data: TypicalData; constructor(private someService: SomeService) { ... } }
      
      @Component({
  selector: 'app-typical',
  template: '<div>A typical component for {{data.name}}</div>'
})
export class TypicalComponent {
  @Input() data: TypicalData;
  constructor(private someService: SomeService) { ... }
}
    

Angular 編譯器只提取一次元資料,並且為 TypicalComponent 產生一個工廠。 當它需要建立 TypicalComponent 的實例時,Angular 呼叫這個工廠,工廠會產生一個新的可視元素,並且把它(及其依賴)繫結到元件類別的一個新實例上。

The Angular compiler extracts the metadata once and generates a factory for TypicalComponent. When it needs to create a TypicalComponent instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency.

編譯的各個階段

Compilation phases

AOT 編譯分為三個階段。

There are three phases of AOT compilation.

  • 階段 1 是程式碼分析。 在此階段,TypeScript 編譯器和 AOT 收集器會建立原始碼的表現層。收集器不會嘗試解釋其收集到的元資料。它只是儘可能地表達元資料,並在檢測到元資料語法衝突時記錄錯誤。

    Phase 1 is code analysis. In this phase, the TypeScript compiler and AOT collector create a representation of the source. The collector does not attempt to interpret the metadata it collects. It represents the metadata as best it can and records errors when it detects a metadata syntax violation.

  • 第二階段是程式碼產生。 在此階段,編譯器的 StaticReflector 會解釋在階段 1 中收集的元資料,對元資料執行附加驗證,如果檢測到元資料違反了限制,則丟擲錯誤。

    Phase 2 is code generation. In this phase, the compiler's StaticReflector interprets the metadata collected in phase 1, performs additional validation of the metadata, and throws an error if it detects a metadata restriction violation.

  • 階段 3 是範本型別檢查。在此可選階段,Angular 範本編譯器使用 TypeScript 編譯器來驗證範本中的繫結表示式。你可以透過設定 fullTemplateTypeCheck 配置選項來明確啟用此階段。請參閱 Angular 編譯器選項

    Phase 3 is template type checking. In this optional phase, the Angular template compiler uses the TypeScript compiler to validate the binding expressions in templates. You can enable this phase explicitly by setting the fullTemplateTypeCheck configuration option; see Angular compiler options.

元資料的限制

Metadata restrictions

你只能使用 TypeScript 的一個子集書寫元資料,它必須滿足下列限制:

You write metadata in a subset of TypeScript that must conform to the following general constraints:

關於準備 AOT 編譯應用程式的其它準則和說明,請參閱 Angular:編寫 AOT 友好的應用程式

For additional guidelines and instructions on preparing an application for AOT compilation, see Angular: Writing AOT-friendly applications.

AOT 編譯中的錯誤通常是由於元資料不符合編譯器的要求而發生的(下面將更全面地介紹)。為了幫助你理解和解決這些問題,請參閱 AOT 元資料錯誤

Errors in AOT compilation commonly occur because of metadata that does not conform to the compiler's requirements (as described more fully below). For help in understanding and resolving these problems, see AOT Metadata Errors.

配置 AOT 編譯

Configuring AOT compilation

你可以在 tsconfig.json TypeScript 配置檔案中提供控制編譯過程的選項。關於可用選項的完整列表,請參閱 Angular 編譯器選項。

You can provide options in the TypeScript configuration file that controls the compilation process. See Angular compiler options for a complete list of available options.

階段 1:分析

Phase 1: Code analysis

TypeScript 編譯器會做一些初步的分析工作,它會產生型別定義檔案.d.ts,其中帶有型別資訊,Angular 編譯器需要藉助它們來產生程式碼。

The TypeScript compiler does some of the analytic work of the first phase. It emits the .d.ts type definition files with type information that the AOT compiler needs to generate application code.

同時,AOT 收集器(collector) 會記錄 Angular 裝飾器中的元資料,並把它們輸出到.metadata.json檔案中,和每個 .d.ts 檔案相對應。

At the same time, the AOT collector analyzes the metadata recorded in the Angular decorators and outputs metadata information in .metadata.jsonfiles, one per .d.ts file.

你可以把 .metadata.json 檔案看做一個包括全部裝飾器的元資料的全景圖,就像抽象語法樹 (AST) 一樣。

You can think of .metadata.json as a diagram of the overall structure of a decorator's metadata, represented as an abstract syntax tree (AST).

Angular 的 schema.ts 把這個 JSON 格式表示成了一組 TypeScript 介面。

Angular's schema.ts describes the JSON format as a collection of TypeScript interfaces.

表示式語法限制

Expression syntax limitations

AOT 收集器只能理解 JavaScript 的一個子集。 定義元資料物件時要遵循下列語法限制:

The AOT collector only understands a subset of JavaScript. Define metadata objects with the following limited syntax:

語法

Syntax

範例

Example

物件字面量

Literal object

{cherry: true, apple: true, mincemeat: false}

陣列字面量

Literal array

['cherries', 'flour', 'sugar']

展開陣列字面量

Spread in literal array

['apples', 'flour', ...the_rest]

函式呼叫

Calls

bake(ingredients)

新建物件

New

new Oven()

屬性訪問

Property access

pie.slice

陣列索引訪問

Array index

ingredients[0]

參考識別符號

Identity reference

Component

範本字串

A template string

pie is ${multiplier} times better than cake

字串字面量

Literal string

pi

數字字面量

Literal number

3.14153265

邏輯字面量

Literal boolean

true

null 字面量

Literal null

null

受支援的字首運算子

Supported prefix operator

!cake

受支援的二元運算子

Supported binary operator

a+b

條件運算子

Conditional operator

a ? b : c

括號

Parentheses

(a+b)

如果表示式使用了不支援的語法,收集器就會往 .metadata.json 檔案中寫入一個錯誤節點。稍後,如果編譯器用到元資料中的這部分內容來產生應用程式碼,它就會報告這個錯誤。

If an expression uses unsupported syntax, the collector writes an error node to the .metadata.json file. The compiler later reports the error if it needs that piece of metadata to generate the application code.

如果你希望 ngc 立即彙報這些語法錯誤,而不要產生帶有錯誤資訊的 .metadata.json 檔案,可以到 TypeScript 的配置檔案中設定 strictMetadataEmit 選項。

If you want ngc to report syntax errors immediately rather than produce a .metadata.json file with errors, set the strictMetadataEmit option in the TypeScript configuration file.

"angularCompilerOptions": { ... "strictMetadataEmit" : true }
      
      "angularCompilerOptions": {
  ...
  "strictMetadataEmit" : true
}
    

Angular 函式庫透過這個選項來確保所有的 Angular .metadata.json 檔案都是乾淨的。當你要建構自己的程式碼函式庫時,這也同樣是一項最佳實踐。

Angular libraries have this option to ensure that all Angular .metadata.json files are clean and it is a best practice to do the same when building your own libraries.

不要有箭頭函式

No arrow functions

AOT 編譯器不支援 函式表示式箭頭函式(也叫 Lambda 函式)。

The AOT compiler does not support function expressions and arrow functions, also called lambda functions.

考慮如下元件裝飾器:

Consider the following component decorator:

@Component({ ... providers: [{provide: server, useFactory: () => new Server()}] })
      
      @Component({
  ...
  providers: [{provide: server, useFactory: () => new Server()}]
})
    

AOT 的收集器不支援在元資料表示式中出現箭頭函式 () => new Server()。 它會在該函式中就地產生一個錯誤節點。 稍後,當編譯器解釋該節點時,它就會報告一個錯誤,讓你把這個箭頭函式轉換成一個匯出的函式

The AOT collector does not support the arrow function, () => new Server(), in a metadata expression. It generates an error node in place of the function. When the compiler later interprets this node, it reports an error that invites you to turn the arrow function into an exported function.

你可以把它改寫成這樣來修復這個錯誤:

You can fix the error by converting to this:

export function serverFactory() { return new Server(); } @Component({ ... providers: [{provide: server, useFactory: serverFactory}] })
      
      export function serverFactory() {
  return new Server();
}

@Component({
  ...
  providers: [{provide: server, useFactory: serverFactory}]
})
    

在版本 5 和更高版本中,編譯器會在發出 .js 檔案時自動執行此重寫。

In version 5 and later, the compiler automatically performs this rewriting while emitting the .js file.

程式碼摺疊

Code folding

編譯器只會解析到 已匯出 符號的參考。 收集器可以在收集期間執行表示式,並用其結果記錄到 .metadata.json 中(而不是原始表示式中)。 這樣可以讓你把非匯出符號的使用限制在表示式中。

The compiler can only resolve references to exported symbols. The collector, however, can evaluate an expression during collection and record the result in the .metadata.json, rather than the original expression. This allows you to make limited use of non-exported symbols within expressions.

比如,收集器可以執行表示式 1 + 2 + 3 + 4,並使用它的結果 10 替換它。

For example, the collector can evaluate the expression 1 + 2 + 3 + 4 and replace it with the result, 10.

這個過程被稱為摺疊。能用這種方式進行簡化的表示式就是可摺疊的

This process is called folding. An expression that can be reduced in this manner is foldable.

收集器可以計算對模組區域性變數的 const 宣告和初始化過的 varlet 宣告,並從 .metadata.json 檔案中移除它們。

The collector can evaluate references to module-local const declarations and initialized var and let declarations, effectively removing them from the .metadata.json file.

考慮下列元件定義:

Consider the following component definition:

const template = '<div>{{hero.name}}</div>'; @Component({ selector: 'app-hero', template: template }) export class HeroComponent { @Input() hero: Hero; }
      
      const template = '<div>{{hero.name}}</div>';

@Component({
  selector: 'app-hero',
  template: template
})
export class HeroComponent {
  @Input() hero: Hero;
}
    

編譯器不能參考 template 常量,因為它是未匯出的。 但是收集器可以透過內聯 template 常量的方式把它摺疊進元資料定義中。 最終的結果和你以前的寫法是一樣的:

The compiler could not refer to the template constant because it isn't exported. The collector, however, can fold the template constant into the metadata definition by in-lining its contents. The effect is the same as if you had written:

@Component({ selector: 'app-hero', template: '<div>{{hero.name}}</div>' }) export class HeroComponent { @Input() hero: Hero; }
      
      @Component({
  selector: 'app-hero',
  template: '<div>{{hero.name}}</div>'
})
export class HeroComponent {
  @Input() hero: Hero;
}
    

這裡沒有對 template 的參考,因此,當編譯器稍後對位於 .metadata.json 中的收集器輸出進行解釋時,不會再出問題。

There is no longer a reference to template and, therefore, nothing to trouble the compiler when it later interprets the collector's output in .metadata.json.

你還可以透過把 template 常量包含在其它表示式中來讓這個例子深入一點:

You can take this example a step further by including the template constant in another expression:

const template = '<div>{{hero.name}}</div>'; @Component({ selector: 'app-hero', template: template + '<div>{{hero.title}}</div>' }) export class HeroComponent { @Input() hero: Hero; }
      
      const template = '<div>{{hero.name}}</div>';

@Component({
  selector: 'app-hero',
  template: template + '<div>{{hero.title}}</div>'
})
export class HeroComponent {
  @Input() hero: Hero;
}
    

收集器把該表示式縮減成其等價的已摺疊字串:

The collector reduces this expression to its equivalent folded string:

'<div>{{hero.name}}</div><div>{{hero.title}}</div>'
      
      '<div>{{hero.name}}</div><div>{{hero.title}}</div>'
    

可摺疊的語法

Foldable syntax

下表中描述了收集器可以摺疊以及不能摺疊哪些表示式:

The following table describes which expressions the collector can and cannot fold:

語法

Syntax

可摺疊?

Foldable

物件字面量

Literal object

Yes

陣列字面量

Literal array

Yes

展開陣列字面量

Spread in literal array

no

函式呼叫

Calls

no

新建物件

New

no

屬性訪問

Property access

如果目標物件也是可摺疊的,則是

yes, if target is foldable

陣列索引訪問

Array index

如果目標陣列和索引都是可摺疊的,則是

yes, if target and index are foldable

參考識別符號

Identity reference

如果參考的是區域性識別符號,則是

yes, if it is a reference to a local

沒有替換表示式的範本字串

A template with no substitutions

yes

有替換表示式的範本字串

A template with substitutions

如果替換表示式是可摺疊的,則是

yes, if the substitutions are foldable

字串字面量

Literal string

yes

數字字面量

Literal number

yes

邏輯字面量

Literal boolean

yes

null 字面量

Literal null

yes

受支援的字首運算子

Supported prefix operator

如果運算元是可摺疊的,則是

yes, if operand is foldable

受支援的二元運算子

Supported binary operator

如果左運算元和右運算元都是可摺疊的,則是

yes, if both left and right are foldable

條件運算子

Conditional operator

如果條件是可摺疊的,則是

yes, if condition is foldable

括號

Parentheses

如果表示式是可摺疊的,則是

yes, if the expression is foldable

如果表示式是不可摺疊的,那麼收集器就會把它作為一個 AST(抽象語法樹) 寫入 .metadata.json 中,留給編譯器去解析。

If an expression is not foldable, the collector writes it to .metadata.json as an AST for the compiler to resolve.

階段 2:程式碼產生

Phase 2: code generation

收集器不會試圖理解它收集並輸出到 .metadata.json 中的元資料,它所能做的只是儘可能準確的表述這些元資料,並在檢測到元資料中的語法違規時記錄這些錯誤。 解釋這些 .metadata.json 是編譯器在程式碼產生階段要承擔的工作。

The collector makes no attempt to understand the metadata that it collects and outputs to .metadata.json. It represents the metadata as best it can and records errors when it detects a metadata syntax violation. It's the compiler's job to interpret the .metadata.json in the code generation phase.

編譯器理解收集器支援的所有語法形式,但是它也可能拒絕那些雖然語法正確語義違反了編譯器規則的元資料。

The compiler understands all syntax forms that the collector supports, but it may reject syntactically correct metadata if the semantics violate compiler rules.

公共符號

Public symbols

編譯器只能參考已匯出的符號

The compiler can only reference exported symbols.

  • 帶有裝飾器的類別成員必須是公開的。你不可能把一個私有或內部使用的屬性做成 @Input()

    Decorated component class members must be public. You cannot make an @Input() property private or protected.

  • 資料繫結的屬性同樣必須是公開的。

    Data bound properties must also be public.

// BAD CODE - title is private @Component({ selector: 'app-root', template: '<h1>{{title}}</h1>' }) export class AppComponent { private title = 'My App'; // Bad }
      
      // BAD CODE - title is private
@Component({
  selector: 'app-root',
  template: '<h1>{{title}}</h1>'
})
export class AppComponent {
  private title = 'My App'; // Bad
}
    

支援的類別和函式

Supported classes and functions

只要語法有效,收集器就可以用 new 來表示函式呼叫或物件建立。但是,編譯器在後面可以拒絕產生對特定函式的呼叫或對特定物件的建立。

The collector can represent a function call or object creation with new as long as the syntax is valid. The compiler, however, can later refuse to generate a call to a particular function or creation of a particular object.

編譯器只能建立某些類別的實例,僅支援核心裝飾器,並且僅支援對返回表示式的巨集(函式或靜態方法)的呼叫。

The compiler can only create instances of certain classes, supports only core decorators, and only supports calls to macros (functions or static methods) that return expressions.

  • 新建實例

    New instances

    編譯器只允許建立來自 @angular/coreInjectionToken 類別建立實例。

    The compiler only allows metadata that create instances of the class InjectionToken from @angular/core.

  • 支援的裝飾器

    Supported decorators

    編譯器只支援來自 @angular/core 模組的 Angular 裝飾器的元資料。

    The compiler only supports metadata for the Angular decorators in the @angular/core module.

  • 函式呼叫

    Function calls

    工廠函式必須匯出為命名函式。 AOT 編譯器不支援用 Lambda 表示式(箭頭函式)充當工廠函式。

    Factory functions must be exported, named functions. The AOT compiler does not support lambda expressions ("arrow functions") for factory functions.

函式和靜態方法呼叫

Functions and static method calls

收集器接受任何只包含一個 return 語句的函式或靜態方法。 編譯器也支援在返回表示式的函式或靜態函式中使用巨集

The collector accepts any function or static method that contains a single return statement. The compiler, however, only supports macros in the form of functions or static methods that return an expression.

考慮下面的函式:

For example, consider the following function:

export function wrapInArray<T>(value: T): T[] { return [value]; }
      
      export function wrapInArray<T>(value: T): T[] {
  return [value];
}
    

你可以在元資料定義中呼叫 wrapInArray,因為它所返回的表示式的值滿足編譯器支援的 JavaScript 受限子集。

You can call the wrapInArray in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset.

你還可以這樣使用 wrapInArray()

You might use wrapInArray() like this:

@NgModule({ declarations: wrapInArray(TypicalComponent) }) export class TypicalModule {}
      
      @NgModule({
  declarations: wrapInArray(TypicalComponent)
})
export class TypicalModule {}
    

編譯器會把這種用法處理成你以前的寫法:

The compiler treats this usage as if you had written:

@NgModule({ declarations: [TypicalComponent] }) export class TypicalModule {}
      
      @NgModule({
  declarations: [TypicalComponent]
})
export class TypicalModule {}
    

Angular 的 RouterModule匯出了兩個靜態巨集函式 forRootforChild,以幫助宣告根路由和子路由。 檢視這些方法的原始碼,以瞭解巨集函式是如何簡化複雜的 NgModule 配置的。

The Angular RouterModuleexports two macro static methods, forRoot and forChild, to help declare root and child routes. Review the source code for these methods to see how macros can simplify configuration of complex NgModules.

元資料重寫

Metadata rewriting

編譯器會對含有 useClassuseValueuseFactorydata 的物件字面量進行特殊處理,把用這些欄位之一初始化的表示式轉換成一個匯出的變數,並用它替換該表示式。 這個重寫表示式的過程,會消除它們受到的所有限制,因為編譯器並不需要知道該表示式的值,它只要能產生對該值的參考就行了。

The compiler treats object literals containing the fields useClass, useValue, useFactory, and data specially, converting the expression initializing one of these fields into an exported variable that replaces the expression. This process of rewriting these expressions removes all the restrictions on what can be in them because the compiler doesn't need to know the expression's value—it just needs to be able to generate a reference to the value.

你可以這樣寫:

You might write something like:

class TypicalServer { } @NgModule({ providers: [{provide: SERVER, useFactory: () => TypicalServer}] }) export class TypicalModule {}
      
      class TypicalServer {

}

@NgModule({
  providers: [{provide: SERVER, useFactory: () => TypicalServer}]
})
export class TypicalModule {}
    

如果不重寫,這就是無效的,因為這裡不支援 Lambda 表示式,而且 TypicalServer 也沒有被匯出。 為了支援這種寫法,編譯器自動把它重寫成了這樣:

Without rewriting, this would be invalid because lambdas are not supported and TypicalServer is not exported. To allow this, the compiler automatically rewrites this to something like:

class TypicalServer { } export const ɵ0 = () => new TypicalServer(); @NgModule({ providers: [{provide: SERVER, useFactory: ɵ0}] }) export class TypicalModule {}
      
      class TypicalServer {

}

export const ɵ0 = () => new TypicalServer();

@NgModule({
  providers: [{provide: SERVER, useFactory: ɵ0}]
})
export class TypicalModule {}
    

這就讓編譯器能在工廠中產生一個對 ɵ0 的參考,而不用知道 ɵ0 中包含的值到底是什麼。

This allows the compiler to generate a reference to ɵ0 in the factory without having to know what the value of ɵ0 contains.

編譯器會在產生 .js 檔案期間進行這種重寫。它不會重寫 .d.ts 檔案,所以 TypeScript 也不會把這個變數當做一項匯出,因此也就不會汙染 ES 模組中匯出的 API。

The compiler does the rewriting during the emit of the .js file. It does not, however, rewrite the .d.ts file, so TypeScript doesn't recognize it as being an export. and it does not interfere with the ES module's exported API.

階段 3:範本型別檢查

Phase 3: Template type checking

Angular 編譯器最有用的功能之一就是能夠對範本中的表示式進行型別檢查,在由於出錯而導致執行時崩潰之前就捕獲任何錯誤。在範本型別檢查階段,Angular 範本編譯器會使用 TypeScript 編譯器來驗證範本中的繫結表示式。

One of the Angular compiler's most helpful features is the ability to type-check expressions within templates, and catch any errors before they cause crashes at runtime. In the template type-checking phase, the Angular template compiler uses the TypeScript compiler to validate the binding expressions in templates.

透過在該專案的 TypeScript 配置檔案中的 "angularCompilerOptions" 中新增編譯器選項 "fullTemplateTypeCheck",可以顯式啟用本階段(見 Angular 編譯器選項 )。

Enable this phase explicitly by adding the compiler option "fullTemplateTypeCheck" in the "angularCompilerOptions" of the project's TypeScript configuration file (see Angular Compiler Options).

Angular Ivy 中 中,範本型別檢查器已被完全重寫,以使其功能更強大,更嚴格。這意味著它可以捕獲以前的型別檢查器無法檢測到的各種新錯誤。

In Angular Ivy, the template type checker has been completely rewritten to be more capable as well as stricter, meaning it can catch a variety of new errors that the previous type checker would not detect.

這就導致了,以前在 View Engine 下能透過編譯的範本可能無法在 Ivy 下透過型別檢查。之所以會發生這種情況,是因為 Ivy 更嚴格的檢查會捕獲真正的錯誤:或者因為應用程式程式碼中的型別不正確,或者因為應用程式使用的函式庫中的型別不正確或不夠具體。

As a result, templates that previously compiled under View Engine can fail type checking under Ivy. This can happen because Ivy's stricter checking catches genuine errors, or because application code is not typed correctly, or because the application uses libraries in which typings are inaccurate or not specific enough.

在版本 9 中,預設未啟用此更嚴格的型別檢查,但可以透過設定 strictTemplates 配置選項來啟用它。我們真的希望將來可以把嚴格型別檢查作為預設值。

This stricter type checking is not enabled by default in version 9, but can be enabled by setting the strictTemplates configuration option. We do expect to make strict type checking the default in the future.

關於這些型別檢查選項的更多資訊以及 Angular 9 及後續版本對範本型別檢查做出的改進,請參閱 範本型別檢查

For more information about type-checking options, and about improvements to template type checking in version 9 and above, see Template type checking.

當範本繫結表示式中檢測到型別錯誤時,進行範本驗證時就會產生錯誤。這和 TypeScript 編譯器在處理 .ts 檔案中的程式碼時報告錯誤很相似。

Template validation produces error messages when a type error is detected in a template binding expression, similar to how type errors are reported by the TypeScript compiler against code in a .ts file.

比如,考慮下列元件:

For example, consider the following component:

@Component({ selector: 'my-component', template: '{{person.addresss.street}}' }) class MyComponent { person?: Person; }
      
      @Component({
  selector: 'my-component',
  template: '{{person.addresss.street}}'
})
class MyComponent {
  person?: Person;
}
    

這會產生如下錯誤:

This produces the following error:

my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'?
      
      my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'?
    

錯誤資訊中彙報的檔名 my.component.ts.MyComponent.html 是一個由範本編譯器產生出的合成檔案, 用於儲存 MyComponent 類別的範本內容。 編譯器永遠不會把這個檔案寫入磁碟。這個例子中,這裡的行號和列號都是相對於 MyComponent@Component 註解中的範本字串的。 如果元件使用 templateUrl 來代替 template,這些錯誤就會在 templateUrl 參考的 HTML 檔案中彙報,而不是這個合成檔案中。

The file name reported in the error message, my.component.ts.MyComponent.html, is a synthetic file generated by the template compiler that holds contents of the MyComponent class template. The compiler never writes this file to disk. The line and column numbers are relative to the template string in the @Component annotation of the class, MyComponent in this case. If a component uses templateUrl instead of template, the errors are reported in the HTML file referenced by the templateUrl instead of a synthetic file.

錯誤的位置是從包含出錯的插值表示式的那個文字節點開始的。 如果錯誤是一個屬性繫結,比如 [value]="person.address.street",錯誤的位置就是那個包含錯誤的屬性的位置。

The error location is the beginning of the text node that contains the interpolation expression with the error. If the error is in an attribute binding such as [value]="person.address.street", the error location is the location of the attribute that contains the error.

這個驗證過程使用 TypeScript 的型別檢查器,這些選項也會提供給 TypeScript 編譯器以控制型別驗證的詳細程度。 比如,如果指定了 strictTypeChecks,就會像上面的錯誤資訊一樣報告 my.component.ts.MyComponent.html(1,1): : Object is possibly 'undefined' 錯誤。

The validation uses the TypeScript type checker and the options supplied to the TypeScript compiler to control how detailed the type validation is. For example, if the strictTypeChecks is specified, the error my.component.ts.MyComponent.html(1,1): : Object is possibly 'undefined' is reported as well as the above error message.

型別窄化

Type narrowing

ngIf 指令中使用的表示式用來在 Angular 範本編譯器中窄化聯合型別,就像 TypeScript 中的 if 表示式一樣。 比如,要在上述範本中消除 Object is possibly 'undefined' 錯誤,可以把它改成只在 person 的值初始化過的時候才產生這個插值。

The expression used in an ngIf directive is used to narrow type unions in the Angular template compiler, the same way the if expression does in TypeScript. For example, to avoid Object is possibly 'undefined' error in the template above, modify it to only emit the interpolation if the value of person is initialized as shown below:

@Component({ selector: 'my-component', template: '<span *ngIf="person"> {{person.addresss.street}} </span>' }) class MyComponent { person?: Person; }
      
      @Component({
  selector: 'my-component',
  template: '<span *ngIf="person"> {{person.addresss.street}} </span>'
})
class MyComponent {
  person?: Person;
}
    

使用 *ngIf 能讓 TypeScript 編譯器推斷出這個繫結表示式中使用的 person 永遠不會是 undefined

Using *ngIf allows the TypeScript compiler to infer that the person used in the binding expression will never be undefined.

關於輸入型別窄化的更多資訊,請參閱 Input setter 的強制型別轉換為自訂指令強化範本型別檢查

For more information about input type narrowing, see Input setter coercion and Improving template type checking for custom directives.

非空型別斷言運算子

Non-null type assertion operator

使用 非空型別斷言運算子可以在不方便使用 *ngIf 或 當元件中的某些約束可以確保這個繫結表示式在求值時永遠不會為空時,防止出現 Object is possibly 'undefined' 錯誤。

Use the non-null type assertion operator to suppress the Object is possibly 'undefined' error when it is inconvenient to use *ngIf or when some constraint in the component ensures that the expression is always non-null when the binding expression is interpolated.

在下面的例子中,personaddress 屬性總是一起出現的,如果 person 非空,則 address 也一定非空。沒有一種簡便的寫法可以向 TypeScript 和範本編譯器描述這種約束。但是這個例子中使用 address!.street 避免了報錯。

In the following example, the person and address properties are always set together, implying that address is always non-null if person is non-null. There is no convenient way to describe this constraint to TypeScript and the template compiler, but the error is suppressed in the example by using address!.street.

@Component({ selector: 'my-component', template: '<span *ngIf="person"> {{person.name}} lives on {{address!.street}} </span>' }) class MyComponent { person?: Person; address?: Address; setData(person: Person, address: Address) { this.person = person; this.address = address; } }
      
      @Component({
  selector: 'my-component',
  template: '<span *ngIf="person"> {{person.name}} lives on {{address!.street}} </span>'
})
class MyComponent {
  person?: Person;
  address?: Address;

  setData(person: Person, address: Address) {
    this.person = person;
    this.address = address;
  }
}
    

應該保守點使用非空斷言運算子,因為將來對元件的重構可能會破壞這個約束。

The non-null assertion operator should be used sparingly as refactoring of the component might break this constraint.

這個例子中,更建議在 *ngIf 中包含對 address 的檢查,程式碼如下:

In this example it is recommended to include the checking of address in the *ngIf as shown below:

@Component({ selector: 'my-component', template: '<span *ngIf="person && address"> {{person.name}} lives on {{address.street}} </span>' }) class MyComponent { person?: Person; address?: Address; setData(person: Person, address: Address) { this.person = person; this.address = address; } }
      
      @Component({
  selector: 'my-component',
  template: '<span *ngIf="person && address"> {{person.name}} lives on {{address.street}} </span>'
})
class MyComponent {
  person?: Person;
  address?: Address;

  setData(person: Person, address: Address) {
    this.person = person;
    this.address = address;
  }
}