Angular9では動的に生成するComponentをentryComponentsに追加しなくて良い
Angular9 の変更点の1つとして entryComponentsの非推奨
があります。
詳しい理由は以下の記事に記述されていますが、 Ivy を有効にしていると entryComponents
に追加した Component で無くても CompnentFactorey
を利用できるようになったからです。
では本当に不要になったのか、 Angular Material の Dialog で試してみます。
環境
Angular CLI: 9.0.2 Node: 12.16.0 OS: win32 x64 Angular: 9.0.1 ... animations, common, compiler, compiler-cli, core, forms ... language-service, platform-browser, platform-browser-dynamic ... router Ivy Workspace: Yes Package Version ----------------------------------------------------------- @angular-devkit/architect 0.900.2 @angular-devkit/build-angular 0.900.2 @angular-devkit/build-optimizer 0.900.2 @angular-devkit/build-webpack 0.900.2 @angular-devkit/core 9.0.2 @angular-devkit/schematics 9.0.2 @angular/cdk 9.0.0 @angular/cli 9.0.2 @angular/material 9.0.0 @ngtools/webpack 9.0.2 @schematics/angular 9.0.2 @schematics/update 0.900.2 rxjs 6.5.4 typescript 3.7.5 webpack 4.41.2
ダイアログの表示
適当なコンポーネントを作成し、ダイアログを表示させます。
// src/app/app.component.ts import { Component } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { SampleComponent } from './sample/sample.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { constructor(private readonly _dialog: MatDialog) { } showDialog() { this._dialog.open(SampleComponent); } }
// src/app/app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { MatDialogModule } from '@angular/material/dialog'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { SampleComponent } from './sample/sample.component'; @NgModule({ declarations: [ AppComponent, SampleComponent ], imports: [ BrowserModule, BrowserAnimationsModule, MatDialogModule, ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
entryComponents
に表示対象のコンポーネントを追加していませんが、無事表示されました。
終わりに
Angular8 まではダイアログみたいな動的に生成されるコンポーネントを使用する場合 entryComponents
に追加する必要がありましたが、 Angular9 の Ivy 環境では不要になりました。
今までは LazyModule で使用されるサービスが LazyModule に所属するコンポーネントをダイアログとして表示する場合に、サービスの所属を provideIn: 'root'
から特定のモジュールに変更する必要がありました。これはとてもとても面倒で、循環参照の発生や LazyModule 間で依存があった場合の更なるモジュール分割など、手元のプロジェクトでは色々な問題が生まれていました。
Angular9 に更新することで LazyModule の EntryComponents 問題に悩まなくても良くなるのはとても嬉しいです。
RxJSのfromEventとfromEventPattern
RxJS にはイベントハンドリングをする API が2種類あります。 fromEvent
と fromEventPattern
です。前者は DOM イベントを処理するための API で、後者は任意のイベントハンドラーに対して add
と remove
を手動で処理する API です。
例えば KeyboardEvent を処理するときに、やっていることは同じですが2種類の書き方ができます。
const destroy$: Subject<void> = new Subject<void>(); const input: HTMLInputElement = document.createElement('input'); // fromEventを使用してDOMイベントを処理する fromEvent<KeyboardEvent>(input, 'keydown') .pipe( filter(ev => ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)), takeUntil(destroy$), ) .subscribe(ev => { // なにか }); // fromEventPatternを使用してDOMイベントを処理する fromEventPattern<KeyboardEvent>( handler => input.addEventListener('keydown', handler), handler => input.removeEventListener('keydown', handler)) .pipe( filter(ev => ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)), takeUntil(destroy$), ) .subscribe(ev => { // なにか });
ちなみに fromEvent
は DOM の addEventListener/removeEventListener
、 jQuery の on/off
、 Node.js の addListener/removeListener
の組み合わせに対して使用できます。逆に言うとこれ以外の組み合わせに対してイベントハンドリングをしたい場合は fromEventPattern
を使用します。
OnPushに設定したComponentのテストを実装する
Angular の高パフォーマンスな Component を実装する上で重要なのは ChangeDetection: ChangeDetectionStrategy.OnPush
に設定して不要な変更チェックを走らせないようにすることが上げられます。しかしこれを設定すると Component のテストを書く時に変更されたプロパティが DOM に反映されず辛い思いをします。
そこで便利なのがこの関数。元は Github の Issue にあったものですが、これによって OnPush Component の変更反映を待つことができ、期待したテスト結果を得られます。
async function runOnPushChangeDetection<T>(cf: ComponentFixture<T>): Promise<any> { const cd = cf.debugElement.injector.get(ChangeDetectorRef); cd.detectChanges(); return await cf.whenStable(); }