I am using Angular 17 for a tiny app and I want to open different components (with forms) in a dialog window. The dialog itself is a wrapper which has two buttons, one of them should call a method of that component. I found a solution here: https://stackoverflow.com/a/77283310/2776860
The comments says that it's a bit hacky but I cannot manage to solve it on another way? How can I create the component dynamically?
I want to access the component without this statement because it seems to be a bit hacky. this.ngComponentOutlet['_componentRef'].instance;
My code looks like this:
Caller:
export class AppComponent {
constructor(private _dialog: MatDialog) {
}
openDialogOne() {
const data: DialogData<FormComponent> = {
headline: 'Form One',
component: FormOneComponent,
};
this._dialog.open(DialogWrapperComponent, {
width: '500px',
height: '80vh',
data
});
}
...
<button mat-flat-button color="primary" (click)="openDialogOne()">Dialog Form 1</button>
<button mat-flat-button color="primary" (click)="openDialogTwo()">Dialog Form 2</button>
Wrapper:
@Component({
selector: 'app-dialog-wrapper',
standalone: true,
imports: [
MatDialogContent,
MatDialogActions,
MatButton,
MatDialogClose,
MatDialogTitle,
NgComponentOutlet
],
templateUrl: './dialog-wrapper.component.html',
styleUrl: './dialog-wrapper.component.scss'
})
export class DialogWrapperComponent implements AfterViewInit{
@ViewChild(NgComponentOutlet, { static: false })
ngComponentOutlet!: NgComponentOutlet;
constructor(@Inject(MAT_DIALOG_DATA) public data: DialogData<FormComponent>) {
}
submit(): void {
if (this.ngComponentOutlet) {
const componentInstance: FormComponent = this.ngComponentOutlet['_componentRef'].instance;
componentInstance.submit()
}
}
}
Wrapper HTML:
<h2 mat-dialog-title>{{ data.headline }}</h2>
<mat-dialog-content>
<ng-container *ngComponentOutlet="data.component" ></ng-container>
</mat-dialog-content>
<mat-dialog-actions>
<button
mat-flat-button
mat-dialog-close>
Cancel
</button>
<button
mat-flat-button (click)="submit()">
Install
</button>
</mat-dialog-actions>
Many thanks in advance!
I guess the wrapper only use is to call methods from the child component, you can use the EventBus
pattern using rxjs subject
to emit and event from wrapper and receive it on the child! Working code below for your reference!
CHild component listening for event!
import { Component } from '@angular/core';
import { DialogService } from '../dialog.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-dialog-form1',
standalone: true,
imports: [],
templateUrl: './dialog-form1.component.html',
styleUrl: './dialog-form1.component.css',
})
export class DialogForm1Component {
private subscription: Subscription = new Subscription();
asdf: string = '123';
submit() {
alert('hello its me!');
}
constructor(private dialogService: DialogService) {}
ngOnInit() {
this.subscription.add(
this.dialogService.subjectObservable$.subscribe((event: any) => {
if (event.eventType === 'submit') {
this.submit();
}
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
wrapper component emitting the event!
import { CommonModule, NgComponentOutlet } from '@angular/common';
import { Component, Inject, ViewChild } from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogTitle,
} from '@angular/material/dialog';
import { MatButton } from '@angular/material/button';
import { DialogForm1Component } from '../dialog-form1/dialog-form1.component';
import { DialogService } from '../dialog.service';
@Component({
selector: 'app-dialog-wrapper',
standalone: true,
imports: [
MatDialogContent,
MatDialogActions,
MatButton,
MatDialogClose,
MatDialogTitle,
CommonModule,
],
templateUrl: './dialog-wrapper.component.html',
styleUrl: './dialog-wrapper.component.scss',
})
export class DialogWrapperComponent {
@ViewChild(NgComponentOutlet, { static: false })
ngComponentOutlet!: NgComponentOutlet;
constructor(
@Inject(MAT_DIALOG_DATA) public data: any,
private dialogService: DialogService
) {}
submit(): void {
this.dialogService.emit({ eventType: 'submit', data: {} });
}
}
service
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DialogService {
private subject: Subject<any> = new Subject<any>();
subjectObservable$!: Observable<any>;
constructor() {
this.subjectObservable$ = this.subject.asObservable();
}
emit(event: any) {
this.subject.next(event);
}
}