Angular 15 Dynamic Dialog with angular material
Published: 2024-04-18 06:05:49

## Live demo

https://stackblitz.com/github/maag070208/NgDynamicDialog

## Agenda

- Angular installation

- Create a angular project with specific version

- Installation of angular material

- Primeflex installation

- Create dialog atom component

- Create dialog service

- Create the components to be called from the dynamic dialog

- Try dynamic dialog component

## Install angular

```npm install -g @angular/cli```

## Create a angular 15 project

```npx @angular/cli@15 new NgDynamicDialog```

## Install angular material

Step 1: Run installation command
```ng add @angular/material```

Step 2: Choose angular material theme

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6wzsjtfydlptakifjn8.png)


Step 3: Set up typography styles

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju7wdtf2u552jwm5u4wm.png)


Step 4: Include and enable animations

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj83r1ws0u780zy6nlfh.png)

## Install primeflex

```npm i primeflex```

Import styles on styles.scss

```@import 'primeflex/primeflex.scss';```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn04osvxz9fqqbcrgwsb.png)


Create Dialog atom component

```ng g c components/atoms/dialog --standalone```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2ev6wis56jr1tag6smf.png)


Step 1: In dialog.component.ts

```
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ComponentRef,
  Inject,
  OnDestroy,
  ViewChild,
  ViewContainerRef,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';

@Component({
  selector: 'app-dialog',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [CommonModule],
  templateUrl: './dialog.component.html',
  styleUrls: ['./dialog.component.scss'],
})
export class DialogComponent implements OnDestroy, AfterViewInit {
  //this is the target element where component dynamically will be added
  @ViewChild('target', { read: ViewContainerRef }) vcRef!: ViewContainerRef;

  //this will hold the component reference
  private componentRef!: ComponentRef<any>;

  constructor(
    private dialogRef: MatDialogRef<DialogComponent>,
    private cdref: ChangeDetectorRef,
    @Inject(MAT_DIALOG_DATA)
    public data: {
      component: any;
      data: any;
    }
  ) {}

  ngAfterViewInit() {
    //create component dynamically
    this.componentRef = this.vcRef.createComponent(this.data.component);
    //pass some data to component
    this.componentRef.instance.initComponent({ ...this.data.data });
    //subscribe to the event emitter of component
    this.componentRef.instance.onSubmit.subscribe((data: any) => {
      this.dialogRef.close(data);
    });
    //detect changes
    this.cdref.detectChanges();
  }

  ngOnDestroy() {
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }
}
```
Step 2: In dialog.component.html
```
<ng-template #target></ng-template>
```

## Create Dialog service

Step 1: Run command
```
ng g s core/services/dialog
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jd9e3l34f6xrhgv7246h.png)


Step 2: In dialog.service.ts
```
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { DialogComponent } from 'src/app/components/atoms/dialog/dialog.component';

@Injectable({
  providedIn: 'root'
})
export class DialogService {
  constructor(private dialog: MatDialog) {}

  public showDialog<T>(dynamicComponent: any, data?: T): any {
    return this.dialog.open(DialogComponent, {
      data: {
        component: dynamicComponent,
        data: data,
      },
    });
  }
}
```
Step 3: Remember add “MatDialogModule” in app.module.ts
```
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatDialogModule, //<---- import 
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```
## Create the components to be called from the dynamic dialog

Step 1: Create a models folder in path “app/core/models”

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu3nbzupeix9trb67prs.png)

Step 2: Add in models folder the dialog.ts file
```
import { EventEmitter } from "@angular/core";

export interface DynamicDialogComponent<T> {
    //the initComponent method will be called when component is created
    initComponent(data: any): void;
    //the onSubmit event will be emitted when user clicks on submit button
    onSubmit: EventEmitter<T>;
}
```
Step 3: Add in models folder the user.ts file
```
export interface IUser {
    name: string;
    phone: string;
    email: string;
}
```
Step 4: Create Form1 component
```
ng g c components/molecules/form1 --standalone
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fx5ld63s751k7haum558.png)


Step 5: In form1.component.ts file
```
import { Component, EventEmitter, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatInputModule } from '@angular/material/input';
import {
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';
import { DynamicDialogComponent } from 'src/app/core/models/dialog';
import { IUser } from 'src/app/core/models/user';
import { MatButtonModule } from '@angular/material/button';

@Component({
  selector: 'app-form1',
  standalone: true,
  imports: [CommonModule, MatInputModule,MatButtonModule, ReactiveFormsModule],
  templateUrl: './form1.component.html',
  styleUrls: ['./form1.component.scss'],
})
export class Form1Component implements DynamicDialogComponent<IUser> {
  //this is the output event that you need to emit when the form is submitted
  @Output() onSubmit: EventEmitter<IUser> = new EventEmitter<IUser>();

  //this is the form that you need to create
  public form1!: FormGroup;
  //this is the user that you need to pass to the form
  public user!: IUser;
  //this method replace the ngOnInit() method
  initComponent(data: IUser): void {
    //this is the data that you pass from the parent component
    this.user = data;
    //this is the method that initialize the form
    this.initForm();
  }
  //if you have a form, you need to create a method to initialize it
  initForm(): void {
    this.form1 = new FormGroup({
      name: new FormControl(this.user.name, Validators.required),
      phone: new FormControl(this.user.phone, Validators.required),
      email: new FormControl(this.user.email, Validators.required),
    });
  }
}
```
Step 6: In form1.component.html file
```
<form [formGroup]="form1" class="flex flex-column m-5">
  <mat-form-field appearance="outline">
    <mat-label>Name</mat-label>
    <input matInput formControlName="name" placeholder="maag" />
  </mat-form-field>

  <mat-form-field appearance="outline">
    <mat-label>Phone</mat-label>
    <input matInput formControlName="phone" placeholder="1231231231" />
  </mat-form-field>

  <mat-form-field appearance="outline">
    <mat-label>Email</mat-label>
    <input matInput formControlName="email" placeholder="maag@gmail.com" />
  </mat-form-field>

  <button
    mat-raised-button
    color="primary"
    [disabled]="form1.invalid"
    (click)="onSubmit.emit(form1.value)"
  >
    Submit
  </button>
</form>
```
Step 7: Create a delete dialog component
```
ng g c components/molecules/delete-dialog --standalone
```
Step 8: In delete-dialog.component.ts file
```
import { Component, EventEmitter, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DynamicDialogComponent } from 'src/app/core/models/dialog';
import { MatButtonModule } from '@angular/material/button';

@Component({
  selector: 'app-delete-dialog',
  standalone: true,
  imports: [CommonModule, MatButtonModule],
  templateUrl: './delete-dialog.component.html',
  styleUrls: ['./delete-dialog.component.scss']
})
export class DeleteDialogComponent implements DynamicDialogComponent<boolean> {
  @Output() onSubmit: EventEmitter<boolean> = new EventEmitter<boolean>();

  initComponent(): void {}

}
```
Step 9: In delete-dialog.component.ts file
```
<section class="flex flex-column">
    <p class="text-center">Are you shure to delete this register?</p>
  
    <div class="flex justify-content-between">
      <button  mat-raised-button 
      color="warn"
      (click)="onSubmit.emit(true)">Delete</button>
      <button  mat-raised-button 
      color="accent"
      (click)="onSubmit.emit(false)">
        Cancel
      </button>

    </div>
  </section>
```
## Try dynamic dialog component

Step 1: in app.component.ts file
```
import { Component } from '@angular/core';
import { DialogService } from './core/services/dialog.service';
import { Form1Component } from './components/molecules/form1/form1.component';
import { IUser } from './core/models/user';
import { DeleteDialogComponent } from './components/molecules/delete-dialog/delete-dialog.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  constructor(private _dialogService: DialogService) {}

  public async showForm1(): Promise<void> {
    const dialogRef = this._dialogService.showDialog<IUser>(
      Form1Component,
      {} as IUser
    );
    const result = await dialogRef.afterClosed().toPromise();
    console.log(result);
  }

  public async showForm1WithInitialData(): Promise<void> {
    const user: IUser = {
      name: 'maag',
      phone: '1234567890',
      email: 'maag070208@gmail.com',
    };

    const dialogRef = this._dialogService.showDialog<IUser>(
      Form1Component,
      user
    );
    const result = await dialogRef.afterClosed().toPromise();
    console.log(result);
  }

  public async showDeleteDialog(): Promise<void> {
    const dialogRef = this._dialogService.showDialog<boolean>(
      DeleteDialogComponent
    );
    const result = await dialogRef.afterClosed().toPromise();
    console.log(result);
  }
}
```
Step 1: in app.component.html file
```
<div class="flex flex-column gap-4 m-4">
    <button mat-raised-button  (click)="showForm1()">Show Form 1</button>
    <button mat-raised-button color="primary" (click)="showForm1WithInitialData()">Show Form 1 With Initial Data</button>
    <button mat-raised-button color="accent" (click)="showDeleteDialog()">Show confirm dialog</button>
</div>
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zszhce4kldotlda8ipig.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpdtbpogirl9bakap4tv.png)