Display Component Dynamically As A Modal Popup Using Material Dialog In Angular 6

Introduction

Usually, in our web applications, we need to display a confirmation popup to the user before allowing him/her to delete any record from the table. When the user allows the application to delete the record by clicking on the yes button, the logic behind deletion executes and deletes the record. Just think about if your application does not display any confirmation popup to the user and he/she deletes the records accidentally, what will happen? It’s always a best practice to show a confirmation popup while developing an application.

Through this article, we will cover the following topics,

  • What are entry Components in Angular?
  • How to show Confirmation Dialog before deleting a record from the table?
  • How to pass a data object to pop up in case we need to show any data over it?

Output

Application with Modal popup will look like below,

Display Component Dynamically As A Modal Popup Using Material Dialog In Angular 6 
 
Implementation

Before implementation, you need to know the prerequisites. First, you need to setup Angular Material with your Angular application. Just go to this official guide, install necessary packages for Angular Material and set up the application in order to use Angular Material Components.

Import and export the necessary modules from material.module.ts and add this material module in imports array of main module; i.e. app.module.ts in my case. For your reference, I have attached the material module file with this article.

Now we will build an Angular component (user-posts.component.ts) and a service (post.service.ts) where we will call JSONPlaceHolder API to get posts data and will show the data as a list in user-posts component view. A list displaying records will have two more buttons (Delete & Update). On clicking on Delete button, the user will see a confirmation popup with two options (YES & NO) just to allow application whether the selected record will be deleted from the list or not.

Using the code

Component Template (user-posts.component.html)

  1. <ul class="list-group">  
  2.   <li *ngFor="let post of userPosts" class="list-group-item">  
  3.     <button (click)="updatePost(post)" class="btn btn-default btn-sm">Update</button>  
  4.     <button (click)="deletePostConfirm(post)" class="btn btn-default btn-sm">Delete</button>  
  5.     <a [routerLink]="['/userposts',post.id]">{{post.id}}</a> - {{post.title}}  
  6.   </li>  
  7. </ul>  

Post Service (post.service.ts)

  1. import { Injectable } from "@angular/core";  
  2. import { HttpClient } from "@angular/common/http";  
  3. import { Observable, throwError } from "rxjs";  
  4. import { delay, catchError } from "rxjs/operators";  
  5. import { IPost } from "../Resolver/user-posts.resolve";  
  6.   
  7. import { AppError } from "./../common/app-error";  
  8. import { NotFoundError } from "../common/not-found-error";  
  9.   
  10. @Injectable({  
  11.   providedIn: "root"  
  12. })  
  13. export class PostService {  
  14.   private url = "http://jsonplaceholder.typicode.com/posts";  
  15.   
  16.   constructor(private http: HttpClient) {}  
  17.   
  18.   getPosts(): Observable<IPost[]> {  
  19.     return this.http.get<IPost[]>(this.url).pipe(delay(1000));  
  20.   }  
  21.     
  22.   deletePost(id) {  
  23.      return this.http.delete(this.url + "/" + id).pipe(  
  24.        catchError((err: Response) => {  
  25.          if (err.status === 404) return throwError(new NotFoundError(err));  
  26.          return throwError(new AppError(err));  
  27.        })  
  28.      );  
  29.    }  
  30. }  

In the above service, I have configured the injector with provider at the service level itself using the provided metadata options of Injectable decorator. However we can configure injectors with providers at three different levels of the Angular application. For more details, read here.

Now we will create a confirmation component that will display as a Modal popup when we will click on Delete button.

Delete Confirmation Component Code (detete-confirmation.component.ts)

  1. import { Component, Inject } from '@angular/core';  
  2.   
  3. @Component({  
  4.     selector: 'app-delete-confirmation',  
  5.     template: `  
  6.         <h1 mat-dialog-title>Are you want to delete?</h1>    
  7.         <mat-dialog-actions>  
  8.             <button mat-raised-button [mat-dialog-close]="true">YES</button>  
  9.             <button mat-raised-button [mat-dialog-close]="false">NO</button>  
  10.         </mat-dialog-actions>  
  11.     `  
  12. })  
  13. export class DeleteConfirmationComponent{  
  14.     constructor(){  
  15.     }  
  16. }  

Component Code (user-posts.component.ts)

  1. import { Component, OnInit } from "@angular/core";  
  2. import { ActivatedRoute } from "@angular/router";  
  3. import { MatDialog } from '@angular/material';  
  4.   
  5. import { PostService } from "./../services/post.service";  
  6. import { DeleteConfirmationComponent } from './detete-confirmation.component';  
  7. import { AppError } from "./../common/app-error";  
  8. import { NotFoundError } from "../common/not-found-error";  
  9. import { IPost } from "./../Resolver/user-posts.resolve";  
  10.   
  11. @Component({  
  12.   selector: "app-user-posts",  
  13.   templateUrl: "./user-posts.component.html",  
  14.   styleUrls: ["./user-posts.component.css"]  
  15. })  
  16. export class UserPostsComponent implements OnInit {  
  17.   userPosts: IPost[] = [];  
  18.   
  19.   constructor(  
  20.     private postService: PostService,  
  21.     private route: ActivatedRoute,  
  22.     private dialog: MatDialog  
  23.   ) {}  
  24.   
  25.   ngOnInit() {  
  26.     this.userPosts = this.route.snapshot.data.userposts;  
  27.     console.log(this.userPosts.length);  
  28.   }  
  29.   
  30.   deletePostConfirm(post) {  
  31.     const dialogRef=this.dialog.open(DeleteConfirmationComponent);  //Open MatDialog and load component dynamically  
  32.   
  33.     //Need to subscribe afterClosed event of MatDialog   
  34.     dialogRef.afterClosed().subscribe(confirmresult=>{  
  35.       console.log(confirmresult);  
  36.       if(confirmresult){            //if dialog result is yes, delete post  
  37.         this.deletePost(post);  
  38.         console.log("Delete confirm is approved by user.");  
  39.       }  
  40.       else{                        //if dialog result is no, DO NOT delete post  
  41.         console.log("Delete confirm is cancelled by user.");  
  42.       }  
  43.     })      
  44.   }  
  45.   
  46.   private deletePost(post){  
  47.     this.postService  
  48.       .deletePost(post.id)  
  49.       //this.postService.deletePost(300)  
  50.       .subscribe(  
  51.         response => {  
  52.           let indexOfItem = this.userPosts.indexOf(post);  
  53.           this.userPosts.splice(indexOfItem, 1);  
  54.           console.log(this.userPosts.length);  
  55.         },  
  56.         (error: AppError) => {  
  57.           if (error instanceof NotFoundError) {  
  58.             console.log("This post has already been deleted.", error);  
  59.           } else {  
  60.             console.log("Unexpected Exception occurred: ", error);  
  61.           }  
  62.         }  
  63.       );  
  64.   }  
  65. }  

In above component code, I have imported MatDialog service from @angular/material package and injected it into constructor to get its instance. Now when user clicks on the Delete button, previously created component (delete-confirmation.component.html) will be displayed as Modal popup with two options (yes & no) with the help of open method of MatDialog service.

Run Application (ng serve)

Now run the application to see the desired output using Angular CLI ng serve command. Click on the delete button to display the confirmation popup.

Display Component Dynamically As A Modal Popup Using Material Dialog In Angular 6

What happens when you click on Delete button? Nothing displays on the screen. Let’s go inside the console window (press F12) and see the error.

Display Component Dynamically As A Modal Popup Using Material Dialog In Angular 6 

Before resolving the above error, let’s understand the Entry Components in Angular.

Entry Components in Angular

Entry Component in an Angular application is any component that is either a bootstrapped root component (listed in the bootstrap array of @NgModule) or a component you include in a route definition and Angular loads these type of components imperatively.

There are mainly two main types of entry components,

  • A component that is bootstrapped by Angular inside @NgModule decorator
    1. @NgModule({  
    2.   declarations: [  
    3.     AppComponent  
    4.   ],  
    5.   imports: [  
    6.     BrowserModule  
    7.     BrowserAnimationsModule,  
    8.     MaterialModule  
    9.   ],  
    10.   providers: [],  
    11.   bootstrap: [  
    12.     AppComponent                    //A bootstrapped entry component  
    13.   ]  
    14. })  
    15. export class AppModule { }  
  • A component that you include in a route definition
    1. @NgModule({  
    2.   imports: [  
    3.     BrowserModule  
    4.     BrowserAnimationsModule,  
    5.     MaterialModule,  
    6.     RouterModule.forRoot([  
    7.       {   
    8.         path: 'userposts',          
    9.         component: UserPostsComponent       // Another bootstrapped component  
    10.       }  
    11.     ])      
    12.   ],    
    13.   bootstrap: [  
    14.     AppComponent                // A bootstrapped root component  
    15.   ]  
    16. })  
    17. export class AppModule { }  

entryComponents array in @NgModule decorator

When we need to bootstrap or dynamically load a component by type imperatively, we must add that component to entryComponents array of @NgModule decorator explicitly. Behind the scene, Angular also automatically adds all the components listed in the @NgModule bootstrap array and the components those in route definitions to the entryComponents array.

Let’s come to the above error in the console now…

What is explained about entry components above, also indicated by the error in the console window? The error is –

ERROR Error - No component factory found for DeleteConfirmationComponent. Did you add it to @NgModule.entryComponents?

Since the component DeleteConfimationComponent is bootstrapped or loaded dynamically inside the Material Dialog, so we need to add it to entryComponents array of the @NgModule decorator to resolve the above error. Let’s add it and run the application again.

  1. @NgModule({  
  2.   declarations: [  
  3.     AppComponent,  
  4.     DeleteConfirmationComponent       
  5.     ...  
  6.   ],  
  7.   imports: [  
  8.     BrowserModule,  
  9.     HttpClientModule  
  10.     ...  
  11.   ],    
  12.   bootstrap: [  
  13.     AppComponent  
  14.   ],  
  15.   entryComponents:[DeleteConfirmationComponent]      //Dynamically loaded component added to entryComponents array  
  16. })  
  17. export class AppModule { }  

Now application with Modal Popup works fine as shown above output.

Passing Data to Modal Popup

What if you want to pass any data to your Modal Popup and display on it? You can pass a data object to your popup easily. In this case, I will pass the record user id to pop up and display it on the popup with a confirmation message. This is beneficial when we dynamically load a component with multiple controls for displaying a lot of dynamic information in a modal popup.

Let’s pass the data object to modal popup and display record user id there with the help of the following steps –

  1. Import MAT_DIALOG_DATA from @angular/material, inject in the constructor of DeleteConfirmationComponent and access data inside the component template.
    1. import { Component, Inject } from '@angular/core';  
    2. import {MAT_DIALOG_DATA} from '@angular/material';  
    3.   
    4. @Component({  
    5.     selector: 'app-delete-confirmation',  
    6.     template: `  
    7.         <h1 mat-dialog-title>Are you want to delete?</h1>  
    8.         <mat-dialog-content>User ID is {{data.record_id}}.</mat-dialog-content>      
    9.         <mat-dialog-actions>  
    10.             <button mat-raised-button [mat-dialog-close]="true">YES</button>  
    11.             <button mat-raised-button [mat-dialog-close]="false">NO</button>  
    12.         </mat-dialog-actions>  
    13.     `  
    14. })  
    15. export class DeleteConfirmationComponent{  
    16.     constructor(@Inject(MAT_DIALOG_DATA) public data:any){  
    17.     }  
    18. }  
  1. Pass data object as second parameter of open method of MatDialog service in the user-posts.component.ts.
    1. deletePostConfirm(post) {  
    2.     console.log(post.userId);  
    3.     //Open MatDialog and load component dynamically  
    4.     const dialogRef = this.dialog.open(DeleteConfirmationComponent, {               //Pass data object as a second parameter  
    5.       data: {  
    6.         record_id: post.userId  
    7.       }  
    8.     });  
    9.     //Need to subscribe afterClosed event of MatDialog  
    10.     dialogRef.afterClosed().subscribe(confirmresult => {  
    11.       console.log(confirmresult);  
    12.       if (confirmresult) {  
    13.         //if dialog result is yes, delete post  
    14.         this.deletePost(post);  
    15.         console.log("Delete confirm is approved by user.");  
    16.       } else {  
    17.         //if dialog result is no, DO NOT delete post  
    18.         console.log("Delete confirm is cancelled by user.");  
    19.       }  
    20.     });  
    21.   }  

Now run the application to see the expected result.

Display Component Dynamically As A Modal Popup Using Material Dialog In Angular 6

Other Posts on Angular

Check the below post to help out with how Page Loader is implemented in an Angular application.

Check the below post to help out with how to load data before component load using Router Resolve API in an Angular application.

Summary

Through this article, we learned what entry components in Angular are and how to load components dynamically in the material dialog.

Thanks a lot for reading. I hope you liked this article. Please share your valuable suggestions and feedback. Write in the comment box in case you have any questions. Have a good day!