どんな時に起こるか
あるDialogを表示したいとする。DialogのComponentの中でAcomponent,Bcomponentの表示を動的に変化させたい。
@Component({
})
export class Acomponent implements OnInit {
constructor(private dialog: Dialog) {
}
open() {
/// AcomponentでDialogを経由してBcomponentを呼び出す
this.dialog.open(Bcomponent)
}
}
@Component({
})
export class Bcomponent implements OnInit {
constructor(private dialog: Dialog) {
}
open() {
/// BcomponentでDialogを経由してAcomponentを呼び出す
this.dialog.open(Acomponent)
}
}
この時、compile時に以下のようなwarningが表示される。
WARNING in Circular dependency detected:
a-component.ts -> dialog.ts -> b-component.ts
WARNING in Circular dependency detected:
b-component.ts -> dialog.ts -> a-component.ts
解決方法
- ParentDialogにAcomponentとBcomponentを仕込む。
- 子Componentの切り替えは、Outputを通して行う。
- Dialog自体のcloseはService経由で行う
- enumでParentDialogComponentのtsFileから分離することで循環参照を防ぐ。
DialogNameEnum
export enum DialogName {
A,
B
}
ParentComponent
<app-a-component *ngIf="componentList[0].active" (toDialog)="openDialog($event)"></app-a-component>
<app-b-component *ngIf="componentList[1].active" (toDialog)="openDialog($event)"></app-b-component>
interface DialogList {
name: DialogName;
component: any;
active: boolean;
}
export interface ParentDialogData {
dialogName: DialogName;
}
@Component({
selector: 'app--parent-dialog',
templateUrl: './parent-dialog.component.html',
styleUrls: ['./parent-dialog.component.scss'],
})
export class ParentDialogComponent implements OnInit, OnDestroy {
loading$: Observable<boolean>;
private dialogCloseSubscription: Subscription;
componentList: Array<DialogList> = [
{
active: true,
name: DialogName.A,
component: AComponent,
},
{
active: false,
name: DialogName.B,
component: BComponent,
},
];
constructor(
@Inject(MAT_DIALOG_DATA) public data: ParentDialogData,
public readonly matDialogRef: MatDialogRef<ParentDialogComponent>,
private dialogService: DialogCloseService,
) {
this.query.selectLoading();
}
ngOnInit() {
this.openDialog(this.data.dialogName);
this.dialogCloseSubscription = this.dialogService.sharedDataSource$.subscribe((msg) => {
if (msg === 'close') {
this.matDialogRef.close();
}
});
}
openDialog(name: DialogName) {
this.componentList.forEach((c) => (c.active = false));
this.componentList.find((c) => c.name === name).active = true;
}
close() {
this.matDialogRef.close();
}
ngOnDestroy(): void {
this.dialogCloseSubscription.unsubscribe();
}
}
Acomponent
@Component({
selector: 'app-a',
templateUrl: './a.component.html',
styleUrls: ['./a.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
})
export class AComponent implements OnInit {
@Output() toDialog: EventEmitter<DialogName> = new EventEmitter();
constructor(
private readonly dialog: DialogCloseService,
) {}
ngOnInit() {
}
toBDialog() {
this.toDialog.emit(DialogName.B);
}
closeDialog() {
this.dialog.close();
}
Bcomponent
@Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
})
export class BComponent implements OnInit {
@Output() toDialog: EventEmitter<DialogName> = new EventEmitter();
constructor(
private readonly dialog: DialogCloseService,
) {}
ngOnInit() {
}
toADialog() {
this.toDialog.emit(DialogName.A);
}
closeDialog() {
this.dialog.close();
}
CloseService
Subject経由でcloseの検知
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DialogCloseService {
constructor() {}
private sharedDataSource = new Subject<string>();
public sharedDataSource$ = this.sharedDataSource.asObservable();
close() {
this.sharedDataSource.next('close');
}
}
まとめ
デメリットとしては切り替えるcomponentが増えるたびにParentComponentを修正しなきゃいけないところ。
ngComponentOutletなどを駆使してスマートにやるための案待ってます。