Angularのモーダルウィンドウと通知

Angular(バージョン2+)では、モーダルウィンドウを作成するタスクに直面しましたが、ハードコードされた機能(柔軟性に欠ける)のせいで既製のソリューションが私に合わなかったか、最新バージョンに更新されなかったため、動作しません。 しかし、公式文書のジャングルの中を歩き回って、モーダルウィンドウ(または通知)を操作する2つの方法について話すことにしました。



この記事では、モーダルウィンドウを操作する2つの方法について説明します。



  1. 「通常の」コンポーネントの追加
  2. コンポーネントを動的に追加する


私の意見では、Habréの記事の 1つでは、この問題を解決する良い方法が示されていますが、NgModuleの導入後(またはそれ以前)に機能しなくなりました。 資料はこの記事と重複するため、この資料をよく理解することをお勧めします。



モーダルウィンドウを追加するには、 ブートストラップスタイル(1つの方法、1つの方法でのみモーダルウィンドウが別のコンポーネントで取り出されたように見えます)、およびtypescriptを使用してdomに直接追加する人はいないなど、いくつかの方法があることをすぐに言わなければなりませんモーダルウィンドウ、このメソッドは好きではありませんが、存在します。



すべての例で、これがロジックに影響を与えない場所ではcssとhtmlを省略します。 ソースリポジトリへのリンクは、記事の最後に提供されます。



「通常の」コンポーネントの追加



最初に、確認のためのシンプルなダイアログボックスになるコンポーネントを作成しましょう。



@Component({ selector : 'modal-dialog', ... }) export class ModalDialogComponent { @Input() header: string; @Input() description: string; @Output() isConfirmed: EventEmitter<boolean> = new EventEmitter<boolean>(); private confirm() { this.isConfirmed.emit(true); } private close() { this.isConfirmed.emit(false); } }
      
      





入力値のヘッダーと説明でコンポーネントを作成し、それに応答して、ウィンドウの結果でブール変数の1つの値を取得します。 モーダルウィンドウから呼び出し元のコンポーネントに値を返す必要がある場合、実行結果を表すクラスを作成できます。



 export class ModalDialogResult { public isConfirmed: boolean; public data:any; }
      
      





そして、それを通してデータを返します。



ここで、ダイアログボックスを使用するには、モジュールに追加する必要があります。 いくつかの方法があります。



  1. モーダルウィンドウを1つのモジュールにまとめる
  2. 使用するモジュールに追加します


モーダルウィンドウを作成するこの方法では、使用するモジュールに追加することを選択しました。



 @NgModule({ imports : [BrowserModule], declarations: [SimpleModalPageComponent, ModalDialogComponent], bootstrap : [SimpleModalPageComponent] }) export class SimpleModalPageModule { }
      
      





ModalDialogComponentは、ダイアログボックスコンポーネント自体です。

SimpleModalPageComponent-これはコンポーネント(以降、ページと呼ぶ名前にPageという単語が含まれるコンポーネント)であり、ダイアログボックスを表示します。



次に、ページテンプレートにモーダルウィンドウを追加します。



 <div class="container"> <div class="configuration"> <div> <label for="header">:</label> <input type="text" id="header" #header class="simple-input"> </div> <label for="description">:</label> <input type="text" id="description" #description content="description" class="simple-input"> </div> <div> <button class="simple-button" (click)="showDialog()">Show Dialog</button> </div> </div> <modal-dialog *ngIf="isModalDialogVisible" [header]="header.value" [description]="description.value" (isConfirmed)="closeModal($event)"></modal-dialog>
      
      





ngIfを介してモーダルウィンドウの可視性を制御します。 必要に応じて、このロジックをダイアログボックス内に移動するか、ボタンを組み合わせてウィンドウを1つのコンポーネントのウィンドウ自体と表示することができます。



ダイアログボックスを表示するページコード:



 .... export class SimpleModalPageComponent { private isModalDialogVisible: boolean = false; public showDialog() { this.isModalDialogVisible = true; } public closeModal(isConfirmed: boolean) { this.isModalDialogVisible = false; ... } }
      
      





ダイアログボックスは使用する準備ができています。 ポップアップ通知(トースト、ポップアップなど)を使用するには、作業が多少異なります。 通知を処理するには、スタックが必要です(画面上に複数のポップアップメッセージが必要な場合)。これはアプリケーション全体に共通する必要があります。 次に、これを行う方法を検討します。



まず、通知と通知モデルへのアクセスを担当するサービスを作成しましょう。



 @Injectable() export class TinyNotificationService { private notifications: Subject<TinyNotificationModel> = new Subject<TinyNotificationModel>(); public getNotifications(): Subject<TinyNotificationModel> { return this.notifications; } public showToast(info: TinyNotificationModel) { this.notifications.next(info); } } export class TinyNotificationModel { public header: string; public description: string; constructor(header: string, description: string) { this.header = header; this.description = description; } }
      
      





モデルでは、タイトルと説明を定義します。 サービスでは、通知を表示するメソッドと通知モデルを取得するメソッドを定義しました。



次に、通知コンポーネントを定義します。



 @Component({ selector : "notifications", template : ` <div class="tiny-notification-panel"> <div *ngFor="let notification of notifications" class="tiny-notification"> <div class="header-block"> <h3 class="header-title">{{notification.header}}</h3> <a class="close-button" (click)="closeNotification(notification)">x</a> </div> <div class="content"> <span>{{notification.description}}</span> </div> </div> </div>` }) export class TinyNotificationComponent { notifications: Set<TinyNotificationModel> = new Set<TinyNotificationModel>(); constructor(private _notificationService: TinyNotificationService) { this._notificationService.getNotification() .subscribe((notification: TinyNotificationModel)=> { this.notifications.add(notification); setTimeout(()=> { this.closeNotification(notification); }, 5000); }); } public closeNotification(notification: TinyNotificationModel) { this.notifications.delete(notification); } }
      
      





コンストラクターで、通知の追加にサブスクライブし、5秒後に通知を自動的に閉じるように設定します。



このような通知を使用するには、通知コンポーネントを追加する必要があります。できれば、コンポーネント階層(メインコンポーネント)のできるだけ高い位置に追加してください。



使用するには、ページをテンプレートに追加します(SimpleModalPageComponent)



 <notifications></notifications>
      
      





その後、サービスを介して、たとえば次の方法で通知を呼び出すことができます



 ... constructor(private notificationService: TinyNotificationService) {} public showToast(header: string, description: string) { this.notificationService.showToast(new TinyNotificationModel(header, description)); } ...
      
      





モジュールにコンポーネントとサービスを追加することを忘れないでください。



コンポーネントを動的に追加する



次のファッショナブルで若者向けのパッケージをnpmで作成しないことにした理由をすぐに言う必要があると思います。 その理由は、ユニバーサルパッケージを作成することは難しく、それでも少数のユーザーに適しているからです(平均的で普遍的なソリューションは誰にも適さないリスクがあることを思い出します)。



では、この記事を書き始めた理由に移りましょう。 コンポーネントを動的に「薄いところから」Angularに追加することはできません(ほとんどの場合、それは困難であり、多くの場合、更新で破損するリスクがあります)。 したがって、すべてをどこかで明示的に定義する必要があります(私の意見では、これは良いことです)。



コンポーネントを動的に追加するには、どこに追加する予定かを知っておく必要があります。 これを行うには、 ViewContainerRefオブジェクトを取得する必要があります。



次の方法で取得できます。



 @Component({ ... template: ` ... <section #notificationBlock></section> `, ... }) export class DynamicModalPageComponent implements OnInit { @ViewChild('notificationBlock', { read: ViewContainerRef }) notificationBlock: ViewContainerRef; constructor(private notificationManager: NotificationManager) { } public ngOnInit(): void { this.notificationManager.init(this.notificationBlock); } .. }
      
      





したがって、ViewContainerRefオブジェクトを取得します。 ご覧のとおり、このオブジェクトに加えて、NotificationManagerを使用して、値ViewContainerRefで初期化します。



NotificationManagerは、モーダルウィンドウと通知で動作するように設計されています。 次に、このクラスを定義します。



 @Injectable() export class NotificationManager { private notificationBlock: ViewContainerRef; ... constructor(private componentFactoryResolver: ComponentFactoryResolver) { } public init(notificationBlock: ViewContainerRef) { this.notificationBlock = notificationBlock; ... } ... private createComponent<T>(componentType: {new (...args: any[]): T;}): ComponentRef<T> { const injector = ReflectiveInjector.fromResolvedProviders([], this.notificationBlock.parentInjector); const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); return factory.create(injector); } private createNotificationWithData<T>(componentType: {new (...args: any[]): T;}, data: any): ComponentRef<T> { const component = this.createComponent(componentType); Object.assign(component.instance, data); return component; } }
      
      





前のリストでは、コードの一部を意図的にスキップして、説明の後にそれらを紹介しました。 コンポーネントをどこかに追加する前に、最初にコンポーネントを作成する必要があります。 createComponentメソッドとcreateNotificationWithDataメソッドは、クラスの内部メソッドであり、それぞれコンポーネントを作成してデータで初期化するように設計されています。



createComponentメソッドを検討してください。



 private createComponent<T>(componentType: {new (...args: any[]): T;}): ComponentRef<T> { const injector = ReflectiveInjector.fromResolvedProviders([], this.notificationBlock.parentInjector); const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); return factory.create(injector); }
      
      





コンポーネントクラスを入力し、 ReflectiveInjectorのfromResolvedProvidersメソッドを使用してReflectiveInjectorオブジェクトを取得します。 次に、 ComponentFactoryResolverを使用して、 コンポーネントのファクトリを作成し、実際にコンポーネントを作成します。



createNotificationWithDataメソッドは、コンポーネントを作成し、データを追加します。



 private createNotificationWithData<T>(componentType: {new (...args: any[]): T;}, data: any): ComponentRef<T> { const component = this.createComponent(componentType); Object.assign(component.instance, data); return component; }
      
      





コンポーネントの作成方法を分析した後、これらのオブジェクトの使用方法を検討する必要があります。 NotificationManagerにメソッドを追加して、モーダルウィンドウを表示します。



 @Injectable() export class NotificationManager { ... public showDialog<T extends ModalDialogBase>(componentType: {new (...args: any[]): T;}, header: string, description: string): Subject<ModalDialogResult> { const dialog = this.createNotificationWithData(componentType, { header : header, description: description }); this.notificationBlock.insert(dialog.hostView); const subject = dialog.instance.getDialogState(); const sub = subject.subscribe(x=> { dialog.destroy(); sub.unsubscribe(); }); return subject; } ... }
      
      





ModalDialogBaseは、モデルの基本クラスです。 ModalDialogResultと共にスポイラーの下に隠します



ModalDialogBaseおよびModalDialogResult
 export abstract class ModalDialogBase { public abstract getDialogState(): Subject<ModalDialogResult>; } export enum ModalDialogResult{ Opened, Confirmed, Closed }
      
      







showDialogメソッドは、コンポーネントクラスとその初期化用のデータを受け取り、Subjectを返してモーダルウィンドウの実行結果を取得します。



コンポーネントを追加するには、notificationBlockのinsertメソッドを使用します



 this.notificationBlock.insert(dialog.hostView);
      
      





このメソッドはコンポーネントを追加し、その後ユーザーに表示されます。 dialog.instanceを介してコンポーネントオブジェクトを取得し、そのメソッドとフィールドにアクセスできます。 たとえば、結果を受け取るようにサブスクライブし、閉じた後にこのダイアログボックスをdomから削除できます。



 const subject = dialog.instance.getDialogState(); const sub = subject.subscribe(x=> { dialog.destroy(); sub.unsubscribe(); });
      
      





ComponentRefオブジェクトでdestroyメソッドを呼び出すと、コンポーネントはdomからだけでなく、notificationBlockからも削除されます。これは非常に便利です。



スポイラーモーダルウィンドウコードの下:



モダリアログ
 @Component({ selector : 'modal-dialog', template : ` <div class="modal-background"> <div class="container"> <div class="header-block"> <h3 class="header-title">{{header}}</h3> <a class="close-button" (click)="close()">x</a> </div> <div class="content"> <span>{{description}}</span> </div> <div class="action-block"> <button class="simple-button" (click)="confirm()"></button> <button class="simple-button" (click)="close()"></button> </div> </div> </div> ` }) export class ModalDialogComponent extends ModalDialogBase { private header: string; private description: string; private modalState: Subject<ModalDialogResult>; constructor() { super(); this.modalState = new Subject(); } public getDialogState(): Subject<ModalDialogResult> { return this.modalState; } private confirm() { this.modalState.next(ModalDialogResult.Confirmed); } private close() { this.modalState.next(ModalDialogResult.Closed); } }
      
      







次に、通知の作成を見てみましょう。 モーダルウィンドウと同じ方法で追加できますが、私の意見では、別の場所で選択する方が良いので、NotificationPanelComponentコンポーネントを作成しましょう。



 @Component({ selector : 'notification-panel', template : ` <div class="notification-panel"> <div #notifications></div> </div> }) export class NotificationPanelComponent { @ViewChild('notifications', { read: ViewContainerRef }) notificationBlock: ViewContainerRef; public showNotification<T extends NotificationBase>(componentRef: ComponentRef<T>, timeOut: number) { const toast = componentRef; this.notificationBlock.insert(toast.hostView); let subscription = toast.instance.getClosedEvent() .subscribe(()=> { this.destroyComponent(toast, subscription); }); setTimeout(()=> { toast.instance.close(); }, timeOut); } private destroyComponent<T extends NotificationBase>(componentRef: ComponentRef<T>, subscription: Subscription) { componentRef.destroy(); subscription.unsubscribe(); } }
      
      





showNotificationメソッドでは、表示するコンポーネントを追加し、ウィンドウクローズイベントをサブスクライブし、タイムアウトを設定してウィンドウを閉じます。 簡単にするために、通知コンポーネントのcloseメソッドを使用してクロージャーを実装します。



すべての通知はNotificationBaseクラスから継承する必要があります。



NotificationBase
 export abstract class NotificationBase { protected closedEvent = new Subject(); public getClosedEvent(){ return this.closedEvent; } public abstract close(): void; }
      
      







通知コンポーネント自体のコードは次のとおりです。



 @Component({ selector : 'tiny-notification', template : ` <div class="container"> <div class="header-block"> <h3 class="header-title">{{header}}</h3> <a class="close-button" (click)="close()">x</a> </div> <div class="content"> <span>{{description}}</span> </div> </div>` }) export class TinyNotificationComponent extends NotificationBase { public header: string; public description: string; close() { this.closedEvent.next(); this.closedEvent.complete(); } }
      
      





通知を使用するには、showToastおよびNotificationPanelComponentメソッドをNotificationManagerに追加する必要があります。



 @Injectable() export class NotificationManager { private notificationBlock: ViewContainerRef; private notificationPanel: NotificationPanelComponent; constructor(private componentFactoryResolver: ComponentFactoryResolver) { } public init(notificationBlock: ViewContainerRef) { this.notificationBlock = notificationBlock; const component = this.createComponent(NotificationPanelComponent); this.notificationPanel = component.instance; this.notificationBlock.insert(component.hostView); } ... public showToast(header: string, description: string, timeOut: number = 3000) { const component = this.createNotificationWithData<TinyNotificationComponent>(TinyNotificationComponent, { header : header, description: description }); this.notificationPanel.showNotification(component, timeOut); } ...
      
      





これより前にもたらされたすべてのことを行おうとすると、何も機能しません。ニュアンス、つまり、これらすべてをモジュールに結合する方法があるためです。 たとえば、を除いて他の場所で情報を検索しようとした場合。 NgModuleのドキュメントでは、 entryComponentsなどの情報が表示されないリスクがあります。



オフィスで。 ドキュメントには次のように書かれています:



 entryComponents : Array<Type<any>|any[]> Specifies a list of components that should be compiled when this module is defined. For each component listed here, Angular will create a ComponentFactory and store it in the ComponentFactoryResolver.
      
      





つまり、ComponentFactoryおよびComponentFactoryResolverを使用してコンポーネントを作成する場合、entryComponentsの宣言に加えてコンポーネントを指定する必要があります。



モジュールの例:



 @NgModule({ declarations : [TinyNotificationComponent, NotificationPanelComponent, ModalDialogComponent], entryComponents: [TinyNotificationComponent, NotificationPanelComponent, ModalDialogComponent], providers : [NotificationManager] }) export class NotificationModule { }
      
      





モジュールへの統合について。 モーダルウィンドウの同様の機能をモジュールに組み合わせてNotificationModuleにインポートするのは良い選択肢だと思います。



モーダルウィンドウを使用するには、インポートでNotificationModuleを指定するだけで、使用できます。



使用例:



 ... export class DynamicModalPageComponent implements OnInit { .... constructor(private notificationManager: NotificationManager) { } public ngOnInit(): void { this.notificationManager.init(this.notificationBlock); } public showToast(header: string, description: string) { this.notificationManager.showToast(header, description, 3000); } public showDialog(header: string, description: string) { this.notificationManager.showDialog(ModalDialogComponent, header, description) .subscribe((x: ModalDialogResult)=> { if (x == ModalDialogResult.Confirmed) { this.showToast(header, "modal dialog is confirmed"); } else { this.showToast(header, "modal dialog is closed"); } }); } }
      
      





この記事では、モーダルウィンドウを動的に作成する方法を検討しました。



→記事のソースコードはこのリポジトリにあります



All Articles