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

Angular 元素(Elements)概覽

Angular elements overview

Angular 元素就是打包成自訂元素的 Angular 元件。所謂自訂元素就是一套與具體框架無關的用於定義新 HTML 元素的 Web 標準。

Angular elements are Angular components packaged as custom elements (also called Web Components), a web standard for defining new HTML elements in a framework-agnostic way.

這裡所說的範例應用,請參閱現場演練 / 下載範例

For the sample app that this page describes, see the現場演練 / 下載範例.

自訂元素這項特性目前受到了 Chrome、Edge(基於 Chromium 的版本)、Opera 和 Safari 的支援,在其它瀏覽器中也能透過Polyfill指令碼(參閱瀏覽器支援)加以支援。 自訂元素擴充套件了 HTML,它允許你定義一個由 JavaScript 程式碼建立和控制的標籤。 瀏覽器會維護一個自訂元素的登錄檔 CustomElementRegistry,它把一個可實例化的 JavaScript 類別對映到 HTML 標籤上。

Custom elements are a Web Platform feature currently supported by Chrome, Edge (Chromium-based), Firefox, Opera, and Safari, and available in other browsers through polyfills (see Browser Support). A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. The browser maintains a CustomElementRegistry of defined custom elements, which maps an instantiable JavaScript class to an HTML tag.

@angular/elements 包匯出了一個 createCustomElement() API,它在 Angular 元件介面與變更檢測功能和內建 DOM API 之間建立了一個橋樑。

The @angular/elements package exports a createCustomElement() API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.

把元件轉換成自訂元素可以讓所有所需的 Angular 基礎設施都在瀏覽器中可用。 建立自訂元素的方式簡單直觀,它會自動把你元件定義的檢視連同變更檢測與資料繫結等 Angular 的功能對映為相應的原生 HTML 等價物。

Transforming a component to a custom element makes all of the required Angular infrastructure available to the browser. Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding native HTML equivalents.

我們正在持續開發自訂元素功能,讓它們可以用在由其它框架所建構的 Web 應用中。 Angular 框架的一個小型的、自包含的版本將會作為服務注入進去,以提供元件的變更檢測和資料繫結功能。 要了解這個開發方向的更多內容,參閱這個視訊演講

We are working on custom elements that can be used by web apps built on other frameworks. A minimal, self-contained version of the Angular framework will be injected as a service to support the component's change-detection and data-binding functionality. For more about the direction of development, check out this video presentation.

使用自訂元素

Using custom elements

自訂元素會自舉 —— 它們在新增到 DOM 中時就會自行啟動自己,並在從 DOM 中移除時自行銷燬自己。一旦自訂元素新增到了任何頁面的 DOM 中,它的外觀和行為就和其它的 HTML 元素一樣了,不需要對 Angular 的術語或使用約定有任何特殊的瞭解。

Custom elements bootstrap themselves - they start automatically when they are added to the DOM, and are automatically destroyed when removed from the DOM. Once a custom element is added to the DOM for any page, it looks and behaves like any other HTML element, and does not require any special knowledge of Angular terms or usage conventions.

  • Angular 應用中的簡易動態內容

    Easy dynamic content in an Angular app

    把元件轉換成自訂元素為你在 Angular 應用中建立動態 HTML 內容提供了一種簡單的方式。 在 Angular 應用中,你直接新增到 DOM 中的 HTML 內容是不會經過 Angular 處理的,除非你使用動態元件來藉助自己的程式碼把 HTML 標籤與你的應用資料關聯起來並參與變更檢測。而使用自訂元件,所有這些裝配工作都是自動的。

    Transforming a component to a custom element provides an easy path to creating dynamic HTML content in your Angular app. HTML content that you add directly to the DOM in an Angular app is normally displayed without Angular processing, unless you define a dynamic component, adding your own code to connect the HTML tag to your app data, and participate in change detection. With a custom element, all of that wiring is taken care of automatically.

  • 富內容應用

    Content-rich applications

    如果你有一個富內容應用(比如正在展示本文件的這個),自訂元素能讓你的內容提供者使用複雜的 Angular 功能,而不要求他了解 Angular 的知識。比如,像本文件這樣的 Angular 指南是使用 Angular 導航工具直接新增到 DOM 中的,但是其中可以包含特殊的元素,比如 <code-snippet>,它可以執行復雜的操作。 你所要告訴你的內容提供者的一切,就是這個自訂元素的語法。他們不需要了解關於 Angular 的任何知識,也不需要了解你的元件的資料結構或實現。

    If you have a content-rich app, such as the Angular app that presents this documentation, custom elements let you give your content providers sophisticated Angular functionality without requiring knowledge of Angular. For example, an Angular guide like this one is added directly to the DOM by the Angular navigation tools, but can include special elements like <code-snippet> that perform complex operations. All you need to tell your content provider is the syntax of your custom element. They don't need to know anything about Angular, or anything about your component's data structures or implementation.

工作原理

How it works

使用 createCustomElement() 函式來把元件轉換成一個可註冊成瀏覽器中自訂元素的類別。 註冊完這個配置好的類別之後,你就可以在內容中像內建 HTML 元素一樣使用這個新元素了,比如直接把它加到 DOM 中:

Use the createCustomElement() function to convert a component into a class that can be registered with the browser as a custom element. After you register your configured class with the browser's custom-element registry, you can use the new element just like a built-in HTML element in content that you add directly into the DOM:

      
      <my-popup message="Use Angular!"></my-popup>
    

當你的自訂元素放進頁面中時,瀏覽器會建立一個已註冊類別的實例。其內容是由元件範本提供的,它使用 Angular 範本語法,並且使用元件和 DOM 資料進行渲染。元件的輸入屬性(Property)對應於該元素的輸入屬性(Attribute)。

When your custom element is placed on a page, the browser creates an instance of the registered class and adds it to the DOM. The content is provided by the component's template, which uses Angular template syntax, and is rendered using the component and DOM data. Input properties in the component correspond to input attributes for the element.

把元件轉換成自訂元素

Transforming components to custom elements

Angular 提供了 createCustomElement() 函式,以支援把 Angular 元件及其依賴轉換成自訂元素。該函式會收集該元件的 Observable 型屬性,提供瀏覽器建立和銷燬實例時所需的 Angular 功能,還會對變更進行檢測並做出響應。

Angular provides the createCustomElement() function for converting an Angular component, together with its dependencies, to a custom element. The function collects the component's observable properties, along with the Angular functionality the browser needs to create and destroy instances, and to detect and respond to changes.

這個轉換過程實現了 NgElementConstructor 介面,並建立了一個構造器類別,用於產生該元件的一個自舉型實例。

The conversion process implements the NgElementConstructor interface, and creates a constructor class that is configured to produce a self-bootstrapping instance of your component.

使用內建的 customElements.define()函式把這個配置好的構造器和相關的自訂元素標籤註冊到瀏覽器的 CustomElementRegistry中。 當瀏覽器遇到這個已註冊元素的標籤時,就會使用該構造器來建立一個自訂元素的實例。

Use the built-in customElements.define()function to register the configured constructor and its associated custom-element tag with the browser's CustomElementRegistry. When the browser encounters the tag for the registered element, it uses the constructor to create a custom-element instance.

不要將 @Component選擇器用作自訂元素的標記名稱。由於 Angular 會為單個 DOM 元素建立兩個元件實例,所以這可能導致意外行為:一個是常規的 Angular 元件,而另一個是自訂元素。

Avoid using the @Componentselector as the custom-element tag name. This can lead to unexpected behavior, due to Angular creating two component instances for a single DOM element: One regular Angular component and a second one using the custom element.

對映

Mapping

寄宿著 Angular 元件的自訂元素在元件中定義的"資料及邏輯"和標準的 DOM API 之間建立了一座橋樑。元件的屬性和邏輯會直接對映到 HTML 屬性和瀏覽器的事件系統中。

A custom element hosts an Angular component, providing a bridge between the data and logic defined in the component and standard DOM APIs. Component properties and logic maps directly into HTML attributes and the browser's event system.

  • 用於建立的 API 會解析該元件,以查詢輸入屬性(Property),並在這個自訂元素上定義相應的屬性(Attribute)。 它把屬性名轉換成與自訂元素相容的形式(自訂元素不區分大小寫),產生的屬性名會使用中線分隔的小寫形式。 比如,對於帶有 @Input('myInputProp') inputProp 的元件,其對應的自訂元素會帶有一個 my-input-prop 屬性。

    The creation API parses the component looking for input properties, and defines corresponding attributes for the custom element. It transforms the property names to make them compatible with custom elements, which do not recognize case distinctions. The resulting attribute names use dash-separated lowercase. For example, for a component with @Input('myInputProp') inputProp, the corresponding custom element defines an attribute my-input-prop.

  • 元件的輸出屬性會用 HTML 自訂事件的形式進行分發,自訂事件的名字就是這個輸出屬性的名字。 比如,對於帶有 @Output() valueChanged = new EventEmitter() 屬性的元件,其相應的自訂元素將會分發名叫 "valueChanged" 的事件,事件中所攜帶的資料儲存在該事件物件的 detail 屬性中。 如果你提供了別名,就改用這個別名。比如,@Output('myClick') clicks = new EventEmitter<string>(); 會導致分發名為 "myClick" 事件。

    Component outputs are dispatched as HTML Custom Events, with the name of the custom event matching the output name. For example, for a component with @Output() valueChanged = new EventEmitter(), the corresponding custom element will dispatch events with the name "valueChanged", and the emitted data will be stored on the event’s detail property. If you provide an alias, that value is used; for example, @Output('myClick') clicks = new EventEmitter<string>(); results in dispatch events with the name "myClick".

要了解更多,請參閱 Web Components 的文件:Creating custom events

For more information, see Web Component documentation for Creating custom events.

自訂元素的瀏覽器支援

Browser support for custom elements

最近開發的 Web 平臺特性:自訂元素目前在一些瀏覽器中實現了原生支援,而其它瀏覽器或者尚未決定,或者已經制訂了計劃。

The recently-developed custom elements Web Platform feature is currently supported natively in a number of browsers.

瀏覽器

Browser

自訂元素支援

Custom Element Support

Chrome

原生支援。

Supported natively.

Edge (基於 Chromium 的版本)

Edge (Chromium-based)

原生支援。

Supported natively.

Firefox

原生支援。

Supported natively.

Opera

原生支援。

Supported natively.

Safari

原生支援。

Supported natively.

對於原生支援了自訂元素的瀏覽器,該規範要求開發人員使用 ES2016 的類別來定義自訂元素 —— 開發人員可以在專案的 TypeScript 配置檔案中設定 target: "es2015" 屬性來滿足這一要求。並不是所有瀏覽器都支援自訂元素和 ES2015,開發人員也可以選擇使用Polyfill指令碼來讓它支援老式瀏覽器和 ES5 的程式碼。

In browsers that support Custom Elements natively, the specification requires developers use ES2015 classes to define Custom Elements - developers can opt-in to this by setting the target: "es2015" property in their project's TypeScript configuration file. As Custom Element and ES2015 support may not be available in all browsers, developers can instead choose to use a polyfill to support older browsers and ES5 code.

使用 Angular CLI 可以自動為你的專案新增正確的Polyfill指令碼:

Use the Angular CLI to automatically set up your project with the correct polyfill:

      
      ng add @angular/elements --project=*your_project_name*
    

範例:彈窗服務

Example: A Popup Service

以前,如果你要在執行期間把一個元件新增到應用中,就得定義成動態元件,然後還要載入它、把它附加到 DOM 中的元素上,並且裝配所有的依賴、變更檢測和事件處理,詳見動態元件載入器

Previously, when you wanted to add a component to an app at runtime, you had to define a dynamic component, and then you would have to load it, attach it to an element in the DOM, and wire up all of the dependencies, change detection, and event handling, as described in Dynamic Component Loader.

用 Angular 自訂元件會讓這個過程更簡單、更透明。它會自動提供所有基礎設施和框架,而你要做的就是定義所需的各種事件處理邏輯。(如果你不準備在應用中直接用它,還要把該元件在編譯時排除出去。)

Using an Angular custom element makes the process much simpler and more transparent, by providing all of the infrastructure and framework automatically—all you have to do is define the kind of event handling you want. (You do still have to exclude the component from compilation, if you are not going to use it in your app.)

這個彈窗服務的範例應用(見後面)定義了一個元件,你可以動態載入它也可以把它轉換成自訂元件。

The Popup Service example app (shown below) defines a component that you can either load dynamically or convert to a custom element.

  • popup.component.ts 定義了一個簡單的彈窗元素,用於顯示一條輸入訊息,附帶一些動畫和樣式。

    popup.component.ts defines a simple pop-up element that displays an input message, with some animation and styling.

  • popup.service.ts 建立了一個可注入的服務,它提供了兩種方式來執行 PopupComponent:作為動態元件或作為自訂元素。注意動態元件的方式需要更多的程式碼來做搭建工作。

    popup.service.ts creates an injectable service that provides two different ways to invoke the PopupComponent; as a dynamic component, or as a custom element. Notice how much more setup is required for the dynamic-loading method.

  • app.module.ts 把 PopupComponent 新增到模組的 entryComponents 列表中,而從編譯過程中排除它,以消除啟動時的警告和錯誤。

    app.module.ts adds the PopupComponent in the module's entryComponents list, to exclude it from compilation and avoid startup warnings or errors.

  • app.component.ts 定義了該應用的根元件,它藉助 PopupService 在執行時把這個彈窗新增到 DOM 中。在應用執行期間,根元件的建構函式會把 PopupComponent 轉換成自訂元素。

    app.component.ts defines the app's root component, which uses the PopupService to add the pop-up to the DOM at run time. When the app runs, the root component's constructor converts PopupComponent to a custom element.

為了對比,這個範例中同時示範了這兩種方式。一個按鈕使用動態載入的方式新增彈窗,另一個按鈕使用自訂元素的方式。可以看到,兩者的結果是一樣的,其差別只是準備過程不同。

For comparison, the demo shows both methods. One button adds the popup using the dynamic-loading method, and the other uses the custom element. You can see that the result is the same; only the preparation is different.

      
      import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'my-popup',
  template: `
    <span>Popup: {{message}}</span>
    <button (click)="closed.next()">&#x2716;</button>
  `,
  animations: [
    trigger('state', [
      state('opened', style({transform: 'translateY(0%)'})),
      state('void, closed', style({transform: 'translateY(100%)', opacity: 0})),
      transition('* => *', animate('100ms ease-in')),
    ])
  ],
  styles: [`
    :host {
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      background: #009cff;
      height: 48px;
      padding: 16px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-top: 1px solid black;
      font-size: 24px;
    }

    button {
      border-radius: 50%;
    }
  `]
})
export class PopupComponent {
  @HostBinding('@state')
  state: 'opened' | 'closed' = 'closed';

  @Input()
  get message(): string { return this._message; }
  set message(message: string) {
    this._message = message;
    this.state = 'opened';
  }
  private _message = '';

  @Output()
  closed = new EventEmitter();
}
    

為自訂元素新增型別支援

Typings for custom elements

一般的 DOM API,比如 document.createElement()document.querySelector(),會返回一個與指定的引數相匹配的元素型別。比如,呼叫 document.createElement('a') 會返回 HTMLAnchorElement,這樣 TypeScript 就會知道它有一個 href 屬性,而 document.createElement('div') 會返回 HTMLDivElement,這樣 TypeScript 就會知道它沒有 href 屬性。

Generic DOM APIs, such as document.createElement() or document.querySelector(), return an element type that is appropriate for the specified arguments. For example, calling document.createElement('a') will return an HTMLAnchorElement, which TypeScript knows has an href property. Similarly, document.createElement('div') will return an HTMLDivElement, which TypeScript knows has no href property.

當呼叫未知元素(比如自訂的元素名 popup-element)時,該方法會返回泛化型別,比如 HTMLELement,這時候 TypeScript 就無法推斷出所返回元素的正確型別。

When called with unknown elements, such as a custom element name (popup-element in our example), the methods will return a generic type, such as HTMLElement, since TypeScript can't infer the correct type of the returned element.

用 Angular 建立的自訂元素會擴充套件 NgElement 型別(而它擴充套件了 HTMLElement)。除此之外,這些自訂元素還擁有相應元件的每個輸入屬性。比如,popup-element 元素具有一個 string 型的 message 屬性。

Custom elements created with Angular extend NgElement (which in turn extends HTMLElement). Additionally, these custom elements will have a property for each input of the corresponding component. For example, our popup-element will have a message property of type string.

如果你要讓你的自訂元素獲得正確的型別,還可使用一些選項。假設你要建立一個基於下列元件的自訂元素 my-dialog

There are a few options if you want to get correct types for your custom elements. Let's assume you create a my-dialog custom element based on the following component:

      
      @Component(...)
class MyDialog {
  @Input() content: string;
}
    

要獲得精確型別,最直白的方式是把相關 DOM 方法的返回值轉換成正確的型別。要做到這一點,你可以使用 NgElementWithProperties 型別(都匯出自 @angular/elements):

The most straightforward way to get accurate typings is to cast the return value of the relevant DOM methods to the correct type. For that, you can use the NgElement and WithProperties types (both exported from @angular/elements):

      
      const aDialog = document.createElement('my-dialog') as NgElement & WithProperties<{content: string}>;
aDialog.content = 'Hello, world!';
aDialog.content = 123;  // <-- ERROR: TypeScript knows this should be a string.
aDialog.body = 'News';  // <-- ERROR: TypeScript knows there is no `body` property on `aDialog`.
    

這是一種讓你的自訂元素快速獲得 TypeScript 特性(比如型別檢查和自動完成支援)的好辦法,不過如果你要在多個地方使用它,可能會有點囉嗦,因為不得不在每個地方對返回型別做轉換。

This is a good way to quickly get TypeScript features, such as type checking and autocomplete support, for your custom element. But it can get cumbersome if you need it in several places, because you have to cast the return type on every occurrence.

另一種方式可以對每個自訂元素的型別只宣告一次。你可以擴充套件 HTMLElementTagNameMap,TypeScript 會在 DOM 方法(如 document.createElement()document.querySelector() 等)中用它來根據標籤名推斷返回元素的型別。

An alternative way, that only requires defining each custom element's type once, is augmenting the HTMLElementTagNameMap, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as document.createElement(), document.querySelector(), etc.):

      
      declare global {
  interface HTMLElementTagNameMap {
    'my-dialog': NgElement & WithProperties<{content: string}>;
    'my-other-element': NgElement & WithProperties<{foo: 'bar'}>;
    ...
  }
}
    

現在,TypeScript 就可以像內建元素一樣推斷出它的正確型別了:

Now, TypeScript can infer the correct type the same way it does for built-in elements:

      
      document.createElement('div')               //--> HTMLDivElement (built-in element)
document.querySelector('foo')               //--> Element        (unknown element)
document.createElement('my-dialog')         //--> NgElement & WithProperties<{content: string}> (custom element)
document.querySelector('my-other-element')  //--> NgElement & WithProperties<{foo: 'bar'}>      (custom element)