角度要素の概要の例
Angular Elements Overview Example
角度要素 Angularコンポーネントは次のようにパッケージ化されています カスタム要素 (Webコンポーネントとも呼ばれます)、フレームワークに依存しない方法で新しいHTML要素を定義するためのWeb標準。
このページで説明するサンプルアプリについては、ライブの例をご覧ください。
カスタム要素 は、Chrome、Edge(Chromiumベース)、Firefox、Opera、Safariで現在サポートされているWebプラットフォーム機能であり、ポリフィルを介して他のブラウザで利用できます(を参照)。 ブラウザのサポート )。カスタム要素は、コンテンツがJavaScriptコードによって作成および制御されるタグを定義できるようにすることで、HTMLを拡張します。ブラウザは定義されたカスタム要素のCustomElementRegistry。インスタンス化可能なJavaScriptクラスをHTMLタグにマップします。
NS@ angular / elementsパッケージは
createCustomElement()APIは、Angularのコンポーネントインターフェイスと変更検出機能から組み込みのDOMAPIへのブリッジを提供します。
コンポーネントをカスタム要素に変換すると、必要なすべてのAngularインフラストラクチャがブラウザで利用できるようになります。カスタム要素の作成はシンプルで簡単で、コンポーネント定義のビューを変更検出とデータバインディングに自動的に接続し、Angular機能を対応するネイティブHTMLの同等物にマッピングします。
他のフレームワークで構築されたWebアプリで使用できるカスタム要素に取り組んでいます。 Angularフレームワークの最小限の自己完結型バージョンは、コンポーネントの変更検出と>ビデオプレゼンテーションをサポートするサービスとして注入されます 。
カスタム要素の使用
カスタム要素はそれ自体をブートストラップします。DOMに追加されると自動的に開始され、DOMから削除されると自動的に破棄されます。カスタム要素が任意のページのDOMに追加されると、他のHTML要素と同じように表示および動作し、Angularの用語や使用規則に関する特別な知識は必要ありません。
-
Angularアプリの簡単な動的コンテンツ
コンポーネントをカスタム要素に変換すると、AngularアプリでダイナミックHTMLコンテンツを作成するための簡単なパスが提供されます。 AngularアプリのDOMに直接追加するHTMLコンテンツは、定義しない限り、通常はAngular処理なしで表示されます。 動的コンポーネント 、HTMLタグをアプリデータに接続するための独自のコードを追加し、変更の検出に参加します。カスタム要素を使用すると、その配線はすべて自動的に処理されます。
-
コンテンツが豊富なアプリケーション
このドキュメントを提供するAngularアプリなど、コンテンツが豊富なアプリをお持ちの場合、カスタム要素を使用すると、Angularの知識がなくても、コンテンツプロバイダーに高度なAngular機能を提供できます。たとえば、このようなAngularガイドはAngularナビゲーションツールによってDOMに直接追加されますが、次のような特別な要素を含めることができます。
複雑な操作を実行します。コンテンツプロバイダーに伝える必要があるのは、カスタム要素の構文だけです。 Angularについて、またはコンポーネントのデータ構造や実装について何も知る必要はありません。
使い方
使用createCustomElement()関数を使用して、コンポーネントを、ブラウザにカスタム要素として登録できるクラスに変換します。構成されたクラスをブラウザーのカスタム要素レジストリに登録すると、DOMに直接追加するコンテンツの組み込みHTML要素と同じように新しい要素を使用できます。
カスタム要素がページに配置されると、ブラウザは登録されたクラスのインスタンスを作成し、それをDOMに追加します。コンテンツは、Angularテンプレート構文を使用するコンポーネントのテンプレートによって提供され、コンポーネントとDOMデータを使用してレンダリングされます。コンポーネントの入力プロパティは、要素の入力属性に対応しています。
コンポーネントをカスタム要素に変換する
AngularはAngularコンポーネントをその依存関係とともにカスタム要素に変換するためのcreateCustomElement()関数。この関数は、ブラウザがインスタンスを作成および破棄し、変更を検出して応答するために必要なAngular機能とともに、コンポーネントの監視可能なプロパティを収集します。
変換プロセスは、NgElementConstructorインターフェイスであり、コンポーネントのセルフブートストラップインスタンスを生成するように構成されたコンストラクタークラスを作成します。
ビルトインを使用する customElements.define()
構成されたコンストラクターとそれに関連するカスタム要素タグをブラウザーに登録する関数 CustomElementRegistry
。ブラウザは、登録された要素のタグを検出すると、コンストラクタを使用してカスタム要素インスタンスを作成します。
使用を避けてください
カスタム要素タグ名としての@Componentセレクター。これにより、Angularが単一のDOM要素に対して2つのコンポーネントインスタンスを作成するため、予期しない動作が発生する可能性があります。1つは通常のAngularコンポーネントで、もう1つはカスタム要素を使用します。
マッピング
カスタム要素 ホスト Angularコンポーネント。コンポーネントで定義されたデータとロジック、および標準のDOMAPI間のブリッジを提供します。コンポーネントのプロパティとロジックは、HTML属性とブラウザのイベントシステムに直接マップされます。
-
作成APIは、コンポーネントを解析して入力プロパティを探し、カスタム要素に対応する属性を定義します。プロパティ名を変換して、大文字と小文字の区別を認識しないカスタム要素と互換性を持たせます。結果の属性名は、ダッシュで区切られた小文字を使用します。たとえば、
@ Input( 'myInputProp')inputProp、対応するカスタム要素が属性を定義します
my-input-prop。
-
コンポーネントの出力はHTMLとしてディスパッチされます カスタムイベント 、出力名と一致するカスタムイベントの名前。たとえば、
@ Output()valueChanged = new EventEmitter()、対応するカスタム要素は「valueChanged」という名前のイベントをディスパッチし、発行されたデータはイベントに保存されます
詳細プロパティ。エイリアスを指定すると、その値が使用されます。例えば、
@出力( 'myClick')クリック=新しいEventEmitter();その結果、「myClick」という名前のディスパッチイベントが発生します。
詳細については、次のWebコンポーネントのドキュメントを参照してください。 カスタムイベントの作成 。
カスタム要素のブラウザサポート
最近開発された カスタム要素 Webプラットフォーム機能は現在、多くのブラウザでネイティブにサポートされています。
ブラウザ | カスタム要素のサポート |
---|---|
クロム | ネイティブでサポートされています。 |
エッジ(クロムベース) | ネイティブでサポートされています。 |
Firefox | ネイティブでサポートされています。 |
オペラ | ネイティブでサポートされています。 |
サファリ | ネイティブでサポートされています。 |
カスタム要素をネイティブにサポートするブラウザーでは、仕様では、開発者がES2015クラスを使用してカスタム要素を定義する必要があります。開発者は、を設定することでこれにオプトインできます。ターゲット:プロジェクトの「es2015」プロパティ TypeScript構成ファイル 。カスタム要素とES2015のサポートはすべてのブラウザーで利用できるわけではないため、開発者は代わりにポリフィルを使用して古いブラウザーとES5コードをサポートすることを選択できます。
使用 Angular CLI 正しいポリフィルを使用してプロジェクトを自動的に設定するには:
ng add @angular/elements --project=*your_project_name*
-
ポリフィルの詳細については、を参照してください。 ポリフィルのドキュメント 。
-
Angularブラウザのサポートの詳細については、を参照してください。 ブラウザのサポート 。
例:ポップアップサービス
以前は、実行時にアプリにコンポーネントを追加する場合、を定義する必要がありました 動的コンポーネント 、次に、それをロードし、DOM内の要素にアタッチし、すべての依存関係、変更検出、およびイベント処理を接続する必要があります。 ダイナミックコンポーネントローダー 。
Angularカスタム要素を使用すると、すべてのインフラストラクチャとフレームワークが自動的に提供されるため、プロセスがはるかに単純で透過的になります。必要なイベント処理の種類を定義するだけです。 (アプリで使用しない場合は、コンポーネントをコンパイルから除外する必要があります。)
ポップアップサービスのサンプルアプリ(以下に表示)は、動的にロードするか、カスタム要素に変換できるコンポーネントを定義します。
-
popup.component.tsは、アニメーションとスタイルを使用して、入力メッセージを表示する単純なポップアップ要素を定義します。
-
popup.service.tsは、PopupComponentを呼び出す2つの異なる方法を提供する注入可能なサービスを作成します。動的コンポーネントとして、またはカスタム要素として。動的ローディング方式には、さらに多くのセットアップが必要であることに注意してください。
-
app.module.tsは、モジュールのにPopupComponentを追加します
entryComponentsリスト。コンパイルから除外し、起動時の警告やエラーを回避します。
-
app.component.tsは、アプリのルートコンポーネントを定義します。これは、PopupServiceを使用して、実行時にDOMにポップアップを追加します。アプリが実行されると、ルートコンポーネントのコンストラクターがPopupComponentをカスタム要素に変換します。
比較のために、デモでは両方の方法を示しています。 1つのボタンは動的読み込み方法を使用してポップアップを追加し、もう1つのボタンはカスタム要素を使用します。結果は同じであることがわかります。準備だけが違います。
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'my-popup', template: ` Popup: {{message}} ✖ `, 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: string; @Output() closed = new EventEmitter(); }
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core'; import { NgElement, WithProperties } from '@angular/elements'; import { PopupComponent } from './popup.component'; @Injectable() export class PopupService { constructor(private injector: Injector, private applicationRef: ApplicationRef, private componentFactoryResolver: ComponentFactoryResolver) {} // Previous dynamic-loading method required you to set up infrastructure // before adding the popup to the DOM. showAsComponent(message: string) { // Create element const popup = document.createElement('popup-component'); // Create the component and wire it up with the element const factory = this.componentFactoryResolver.resolveComponentFactory(PopupComponent); const popupComponentRef = factory.create(this.injector, [], popup); // Attach to the view so that the change detector knows to run this.applicationRef.attachView(popupComponentRef.hostView); // Listen to the close event popupComponentRef.instance.closed.subscribe(() => { document.body.removeChild(popup); this.applicationRef.detachView(popupComponentRef.hostView); }); // Set the message popupComponentRef.instance.message = message; // Add to the DOM document.body.appendChild(popup); } // This uses the new custom-element method to add the popup to the DOM. showAsElement(message: string) { // Create element const popupEl: NgElement & WithProperties = document.createElement('popup-element') as any; // Listen to the close event popupEl.addEventListener('closed', () => document.body.removeChild(popupEl)); // Set the message popupEl.message = message; // Add to the DOM document.body.appendChild(popupEl); } }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; import { PopupComponent } from './popup.component'; import { PopupService } from './popup.service'; // Include the `PopupService` provider, // but exclude `PopupComponent` from compilation, // because it will be added dynamically. @NgModule({ imports: [BrowserModule, BrowserAnimationsModule], providers: [PopupService], declarations: [AppComponent, PopupComponent], bootstrap: [AppComponent], entryComponents: [PopupComponent], }) export class AppModule { }
import { Component, Injector } from '@angular/core'; import { createCustomElement } from '@angular/elements'; import { PopupService } from './popup.service'; import { PopupComponent } from './popup.component'; @Component({ selector: 'app-root', template: ` Show as component Show as element `, }) export class AppComponent { constructor(injector: Injector, public popup: PopupService) { // Convert `PopupComponent` to a custom element. const PopupElement = createCustomElement(PopupComponent, {injector}); // Register the custom element with the browser. customElements.define('popup-element', PopupElement); } }
カスタム要素の入力
などの一般的なDOMAPIdocument.createElement()または
document.querySelector()は、指定された引数に適切な要素タイプを返します。たとえば、
document.createElement( 'a')は
TypeScriptが知っているHTMLAnchorElementには
hrefプロパティ。同様に、
document.createElement( 'div')は
TypeScriptが知っているHTMLDivElementには
hrefプロパティ。
カスタム要素名などの不明な要素で呼び出された場合(この例ではpopup-element)、メソッドは次のようなジェネリック型を返します
TypeScriptは返された要素の正しいタイプを推測できないため、HTMLElement。
Angularextendで作成されたカスタム要素NgElement(これは順番に拡張されます
HTMLElement)。さらに、これらのカスタム要素には、対応するコンポーネントの入力ごとにプロパティがあります。たとえば、
ポップアップ要素には
タイプのメッセージプロパティ
ストリング。
カスタム要素の正しいタイプを取得する場合は、いくつかのオプションがあります。あなたが作成すると仮定しましょう次のコンポーネントに基づくmy-dialogカスタム要素:
@Component(...) class MyDialog { @Input() content: string; }
正確な型指定を取得する最も簡単な方法は、関連するDOMメソッドの戻り値を正しい型にキャストすることです。そのために、あなたは使用することができますNgElementと
WithPropertiesタイプ(両方ともからエクスポートされます
@ angular / elements):
const aDialog = document.createElement('my-dialog') as NgElement & WithProperties; 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機能をすばやく取得するための優れた方法です。ただし、複数の場所で必要になると、発生するたびにリターンタイプをキャストする必要があるため、面倒になる可能性があります。
各カスタム要素のタイプを1回定義するだけでよい別の方法は、HTMLElementTagNameMap。TypeScriptがタグ名に基づいて返された要素のタイプを推測するために使用します(などのDOMメソッドの場合)
document.createElement()、
document.querySelector()など):
declare global { interface HTMLElementTagNameMap { 'my-dialog': NgElement & WithProperties; 'my-other-element': NgElement & WithProperties; ... } }
これで、TypeScriptは、組み込み要素の場合と同じ方法で正しい型を推測できます。
document.createElement('div') //--> HTMLDivElement (built-in element) document.querySelector('foo') //--> Element (unknown element) document.createElement('my-dialog') //--> NgElement & WithProperties (custom element) document.querySelector('my-other-element') //--> NgElement & WithProperties (custom element)