概要
確認用ダイアログなど、モーダルが必要になるシーンは多々あると思います。
今回はAngular2で実装する方法を紹介します。
環境
- angular 2.4.7
- angular-cli 1.0.0-beta.32.3
要件
今回満たしているのは以下の項目です。
- serviceとしてどこからでも呼べる
- 中身を好きなcomponentで作ることができる
また今回満たしていない要件は以下です。これは次回にやり方を紹介します。
- モーダルの中に外から(呼び出しているComponentなどから)何かしらパラメータを渡す
成果物
今回の成果物は以下です。
ブログの説明でよく分からない時は参考にしてください。
フォルダ構造
├── app.component.css ├── app.component.html ├── app.component.spec.ts ├── app.component.ts ├── app.module.ts ├── complete │ ├── complete.component.css │ ├── complete.component.html │ ├── complete.component.spec.ts │ └── complete.component.ts ├── confirmation │ ├── confirmation.component.css │ ├── confirmation.component.html │ ├── confirmation.component.spec.ts │ └── confirmation.component.ts └── modal ├── modal.component.css ├── modal.component.html ├── modal.component.spec.ts ├── modal.component.ts ├── modal.service.spec.ts └── modal.service.ts
動的に生成するComponentをCompleteComponent
、ConfirmationComponent
としています。
実装
ポイントとなるところを抽出して説明します
modal.component.ts
モーダルを生成する場所(エントリーポイント)となる部分です。
@Component({ selector: 'app-modal', templateUrl: './modal.component.html', styleUrls: ['./modal.component.css'] }) export class ModalComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('inner', { read: ViewContainerRef }) vcr;
今回実現したいのは動的にComponentを生成することです。
これを実現してくれる機能がAngularのViewContainerRef
です。これが持つcreateComponent()
というメソッドで生成します。
これは各要素から取得できるので、今回はinner
というタグをつけた要素から取得しています。
modal.component.html
<div class="overlay" (click)="close()" [style.display]="display"> <div class="container" (click)="containerClick($event)"> <div #inner></div> </div> </div>
これの<div #inner></div>
の部分ですね。この辺の詳細は@ViewChild
の使い方を学ぶと良いです。
他にもCustom Directive
から取得するやり方もあります。参考にさせて頂いた人たちは皆Directiveでした。
modal.component.ts
constructor(private modal: ModalService) { } ngAfterViewInit() { this.modal.vcr = this.vcr; }
次に取得したViewContainerRef(以降vcr)
をmodalServiceへ渡します。
生成処理はService内で実行するためです。
ポイントとしてvcr
はビューの生成後じゃないの取得できないので、ngAfterViewInit()
で渡します。
modal.service.ts
@Injectable() export class ModalService { public vcr: ViewContainerRef; private currentComponent = null; constructor(private resolver: ComponentFactoryResolver) { } open(data: any): void { if (!data) { return; } const factory = this.resolver.resolveComponentFactory(data); const component = this.vcr.createComponent(factory); if (this.currentComponent) { this.currentComponent.destroy(); } this.currentComponent = component; } close(): void { if (this.currentComponent) { this.currentComponent.destroy(); } } }
次はserviceです。
vcr
のcreateComponent()
で動的に生成する、と先ほど書きましたが、このメソッドはComponentFactory
を引数に持つので、生成したいComponentをこの型に変換するためComponentFactoryResolver
を利用します。
app.component.ts
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private modal: ModalService) {} confirm(): void { this.modal.open(ConfirmationComponent); } complete(): void { this.modal.open(CompleteComponent); } }
app.componentで呼び出すため、ModalServiceをDIします。
confirm
やcomplete
はボタンをクリックしたらモーダルを開くロジックです。
中でthis.modal.open()
を呼ぶことで、好きなComponentを生成できることがわかります。
app.module.ts
@NgModule({ declarations: [ AppComponent, ModalComponent, ConfirmationComponent, CompleteComponent ], imports: [ BrowserModule, FormsModule, HttpModule ], providers: [ModalService], bootstrap: [AppComponent], entryComponents: [ ConfirmationComponent, CompleteComponent ] })
vcr
で作るComponentはentryComponent
に登録する必要があるので追記します。
app.component.html
<app-modal></app-modal> <button (click)="confirm()">Confirm</button> <button (click)="complete()">Complete</button>
app-modal
のセレクタを追記します。
動作確認
ボタンをクリックします。
別のボタンもクリックしてみます。
中のComponentを好きに切り替えられることがわかりますね。