DEV Community

Martin Amaro
Martin Amaro

Posted on

Angular 15 Dynamic Dialog with angular material

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

Step 3: Set up typography styles

Image description

Step 4: Include and enable animations

Image description

Install primeflex

npm i primeflex

Import styles on styles.scss

@import 'primeflex/primeflex.scss';

Image description

Create Dialog atom component

ng g c components/atoms/dialog --standalone

Image description

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();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: In dialog.component.html

<ng-template #target></ng-template>
Enter fullscreen mode Exit fullscreen mode

Create Dialog service

Step 1: Run command

ng g s core/services/dialog
Enter fullscreen mode Exit fullscreen mode

Image description

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,
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

Create the components to be called from the dynamic dialog

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

Image description

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>;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Add in models folder the user.ts file

export interface IUser {
    name: string;
    phone: string;
    email: string;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create Form1 component

ng g c components/molecules/form1 --standalone
Enter fullscreen mode Exit fullscreen mode

Image description

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),
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Step 7: Create a delete dialog component

ng g c components/molecules/delete-dialog --standalone
Enter fullscreen mode Exit fullscreen mode

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 {}

}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

Top comments (0)