Use the SweetAlert2 in Angular Common Layout

1. Introduction

SweetAlert2 is a beautiful, responsive, customizable, accessible replacement for javascript’s popup boxes, and there is also a version for Angular, you can find the detail usage in official website. But, the use cases in the official doc are just for use on one page, which means you need to add the same HTML codes on every page you want to use, what if you are using the common layout? The common layout means there is a main layout including Header,Center Body, and Footer, just like this article which I introduced.

In the common layout structure, you don’t need to add the SweetAlert2 HTML code on every page, just adding it to the main layout will be ok! So in this article, I will show you how to do it.

2. Install SweetAlert2 for Angular

Cut of the day I wrote this article, the latest version is 12.2.0, but there is an error for this version, so I suggest installing version 12.1.0

Do you want to be a good trading in cTrader?   >> TRY IT! <<
npm install sweetalert2 @sweetalert2/ngx-sweetalert2@12.1.0

import the SweetAlert2 in app.module

import { SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2';

...

@NgModule({
    ...
    imports:[
        ...
        SweetAlert2Module.forRoot(),
        ...
    ]
    ...
})

Every Subject is an Observable and an Observer. You can subscribe to a Subject, and you can call next to feed values as well as error and complete.

3. Create the SweetAlert2 service

If we want to put the SweetAlert2 in a common main layout, then we need to use the observer pattern, we can use a service to subscribe to an observer and then update or call the SweetAlter2 methods.

So, we create the Subject to do that, we need to subscribe 3 items:

1) SweetAlertOptions : All of the sweet alert options, because we need to dynamic to change these options in difference page, so need to subscribe to it
2) Whether Close the SweetAlter2 : This is a boolean object for detecting whether close the SweetAlter2
3) Confirm Item : This is a custom interface for handling the confirm dialog action

The code snippets as below

private swalSource = new Subject<SweetAlertOptions>();
swalEmitted= this.swalSource.asObservable();

private swalCloseSource = new Subject<boolean>();
swalCloseEmitted = this.swalCloseSource.asObservable();

private swalConfirmSource = new Subject<SwalConfirmItem>();
swalConfirmEmitted$ = this.swalConfirmSource.asObservable();


/**
 * Handle confirm action and data
 */
export interface SwalConfirmItem {
  //the confirm handler function
  fnConfirm: any;
  //The data needs to be passed to the confirm function
  confirmData: any;
  //the current context of the component
  context: any;
}

we can use the next method to execute the subject, so create the show and close method

 // Show swal with options
 // icon: SweetAlertIcon = 'success' | 'error' | 'warning' | 'info' | 'question'
 show(options: SweetAlertOptions) {
   this.swalSource.next(options);
 }

 // Close the swal
 close() {
   this.swalCloseSource.next(true);
 }

 // Set the confirm event
 setConfirm(confirmItem: SwalConfirmItem) {
   this.swalConfirmSource.next(confirmItem);
 }

and we can also create a method for displaying the HttpErrorResponse object

// Handle the HttpErrorResponse and show the error box
showErrors(error: any, options: SweetAlertOptions) {
  console.log('%c [ error ]-37', 'font-size:13px; background:pink; color:#bf2c9f;', error);
  if (error.error && error.error.errors) {
    var errors = '';
    for (var key in error.error.errors) {
      errors += error.error.errors[key] + '<br>';
    }
    options.html = error.error.title + '<br>' + errors;
  } else {
    options.html = error.error;
  }
  options.icon = 'error';
  //show the dialog
  this.swalSource.next(options);
}

maybe the error object will be different from yours (based on the API return values), you can print the error object in the console and find the correct way for looping it.

and don’t forget to destroy the Subject object at the end to void the memory lake

ngOnDestroy() {
  //Complete and release the subject
  this.swalSource.complete();
  this.swalCloseSource.complete();
  this.swalConfirmSource.complete();
}

So, grouping all together will be a SwalService

//MyDemo.Client\src\app\services\swal.service.ts

import { Injectable, OnDestroy } from "@angular/core";
import { Subject } from "rxjs";
import { SweetAlertOptions } from "sweetalert2";

//reference: https://github.com/sweetalert2/ngx-sweetalert2

@Injectable({
  providedIn: 'root',
})
export class SwalService implements OnDestroy {

  private swalSource = new Subject<SweetAlertOptions>();
  swalEmitted= this.swalSource.asObservable();

  private swalCloseSource = new Subject<boolean>();
  swalCloseEmitted = this.swalCloseSource.asObservable();

  private swalConfirmSource = new Subject<SwalConfirmItem>();
  swalConfirmEmitted$ = this.swalConfirmSource.asObservable();


  // Show swal with options
  // icon: SweetAlertIcon = 'success' | 'error' | 'warning' | 'info' | 'question'
  show(options: SweetAlertOptions) {
    this.swalSource.next(options);
  }

  // Close the swal
  close() {
    this.swalCloseSource.next(true);
  }

  // Handle the HttpErrorResponse and show the error box
  showErrors(error: any, options: SweetAlertOptions) {
    console.log('%c [ error ]-37', 'font-size:13px; background:pink; color:#bf2c9f;', error);

    if (error.error && error.error.errors) {
      var errors = '';
      for (var key in error.error.errors) {
        errors += error.error.errors[key] + '<br>';
      }
      options.html = error.error.title + '<br>' + errors;
    } else {
      options.html = error.error;
    }

    options.icon = 'error';
    this.swalSource.next(options);
  }

  // Set the confirm event
  setConfirm(confirmItem: SwalConfirmItem) {
    this.swalConfirmSource.next(confirmItem);
  }

  ngOnDestroy() {
    // complete and release the subject
    this.swalSource.complete();
    this.swalCloseSource.complete();
    this.swalConfirmSource.complete();
  }
}

/**
 * Handle confirm action and data
 */
export interface SwalConfirmItem {
  //the confirm handler function
  fnConfirm: any;
  //the data need to be pass to the confirm function
  confirmData: any;
  //the current context of the component
  context: any;
}

4. Create the Main layout

Create the main layout component with the below command

ng g c MainLayout --skip-tests

For demonstrate, we just use the simple layout below, add the SweetAlert2 in the main layout, we need to dynamic to control the visible status and the confirm action

<!-- MyDemo.Client\src\app\main-layout\main-layout.component.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Demo Testing</title>

</head>

<body>
<div>Header content</div>

<div id="wrapper">
  <router-outlet></router-outlet>
</div>

<div>Footer content</div>

<swal #swalBox  [swalVisible]="isSwalVisible" (confirm)="handleConfirm($event, swalConfirmData, swalComponentContext)"></swal>

</body>
</html>

And define the variables in the backend ts file, we use the @ViewChild to show the swal object in frontend

isSwalVisible = false;
swalConfirmData : any;
swalComponentContext: any;

@ViewChild(SwalComponent) swalBox!: SwalComponent;
swalOptions: SweetAlertOptions = {};

It need to subscribe and fire the swal from the child component, so use the swal service in the constructor

constructor(private swalService: SwalService){
  //fire the swal from child component
  this.swalService.swalEmitted.subscribe(options => {
    if(!this.swalBox){
      //just wait for the time to load the object if can't find it
      setTimeout(()=>{
        if(this.swalBox){
          this.isSwalVisible = true;
          this.swalBox.update(options);
          this.swalBox.fire();
        }
      }, 500);
    }
    else {
      this.isSwalVisible = true;
      this.swalBox.update(options);
      this.swalBox.fire();
    }
  });

  //set the confirm function and execute the login in the child component
  this.swalService.swalConfirmEmitted.subscribe(confirmItem => {
    this.handleConfirm = confirmItem.fnConfirm == null ? this.resetHandleConfirm : confirmItem.fnConfirm;
    this.swalConfirmData = confirmItem.confirmData;
    this.swalComponentContext = confirmItem.context;
  });

  //handle close the swal event
  this.swalService.swalCloseEmitted$.subscribe(item => {
    this.swalBox.close();
  });
}

Here we need to create the handle confirm method and it will be override by this.swalService.swalConfirmEmitted$, that’s mean will be process the logic in each child component, so just keep empty in here

handleConfirm(item: string, data: any, context: any): void {
   //this will be overwrite by this.swalService.swalConfirmEmitted$
}

Another handle method is resetHandleConfirm, this is only an empty method, but why do we need to use this?

When you call the confirm dialog once time, it will keep the handle method for next time to use, this will cause the handler will be called again and again. To avoid this case, we need to reset the handler method, just setting an empty method will be ok!

//just for reset(remove) the existing handle confirm function
resetHandleConfirm(item: string, data: any, context: any): void {
  //this will be overwrite by this.swalService.swalConfirmEmitted$
}

If the confirm function is null, then use this empty function

this.handleConfirm = confirmItem.fnConfirm == null ? this.resetHandleConfirm : confirmItem.fnConfirm;

In the end, update the app-routing.module.ts, and set all the components under the main layout, so that can be used the main layout structure

const routes: Routes = [
  {
    path: '',
    component: MainLayoutComponent,
    children: [
      { path: 'user-management', component: UserManagementComponent, canActivate: [AuthGuard] },
      { path: 'form-layout', component: FormLayoutTestingComponent, canActivate: [AuthGuard] },
      { path: 'login', component: LoginComponent }
    ]
  }
];

5. Usage

Now, we can try to use the SweetAlter2 in the child component.

5.1. Show the message box

We will show the error if login failed on login page:

1) First, inject the SwalService in the constructor

constructor(private fb: FormBuilder, 
    private router: Router, 
    private auth: AuthService,
    private swalService: SwalService) {
  }

2) Define the SweetAlertOptions and set the default icon to info

swalOptions: SweetAlertOptions = { icon : 'info' };

3) Show the error after login failed

login() {
    console.log('user name:' + this.username.value);
    this.auth
      .login(this.username.value, this.password.value)
      .pipe(filter(authenticated => authenticated))
      .subscribe(
        () => {
          console.log('after logined and redirect');
          this.router.navigateByUrl('/user-management');
        },
        (errorRes: HttpErrorResponse) => {
          if(errorRes.status == 401){

            // set the swal icon to 'error'
            this.swalOptions.icon = 'error';
            // set the message need to be show 
            this.swalOptions.html =  'User name or password is not valid!';
            // show the swal box
            this.swalService.show(this.swalOptions);
          }

          console.log('Error', errorRes);
        }
      );
  }

The result as below:

5.2. Show the confirm dialog

We will show the confirm dialog when try to delete a user.

To support deleting users, we need to add the button for each row in MtxGridColumn. You can find the details for how to use it in this article.

{
  header: 'Operation', field: 'operation' ,
  minWidth: 140,
  pinned: 'left',
  type: 'button',
  buttons: [
    {
      color: 'warn',
      icon: 'delete',
      text: 'Delete',
      tooltip: 'Delete',
      click: record => this.delete(record),
    },
  ],
},

Then you will find the delete icon in each row

If you can’t see the icon, just take a look at whether add it below in your index.html file

<!-- MyDemo.Client\src\index.html -->
<link href="assets/fonts/Material_Icons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Ok, we need to implement the this.delete() function, this is the delete confirm function, so we can use the SwalService to show the confirm dialog

public delete(value: any) {
  this.swalOptions.icon = 'question';
  this.swalOptions.title = 'Delete User';
  this.swalOptions.html = `Do you confirm to delete the user ${value.name}?`;
  this.swalOptions.showConfirmButton = true;
  this.swalOptions.showCancelButton = true;

  this.confirmItem.confirmData = value; //pass the data to delete confirm function
  this.confirmItem.fnConfirm = this.deleteConfirm;
  this.swalService.setConfirm(this.confirmItem);
  this.swalService.show(this.swalOptions);
}

As you can see, the this.swalOptions is the SweetAlert2 native setting value, we need to pass the current value (should be user ID in this case) to confirm data, and pass the delete confirm function to override the main layout function, the delete confirm function need to handle the confirm event, that’s mean if user confirmed to delete, will be execute the delete function and call the API to delete user

public deleteConfirm(isConfirm: string, data: any, context: any): void {
  //console.log('delete item', data);
  // call the service to delete user
  context.userManagementService.delete(data.id).subscribe((result: { success: any; message: string | HTMLElement | JQuery | undefined; }) => {
    this.swalOptions.showCancelButton = false; //just need to show the OK button
    context.confirmItem.fnConfirm = null;//reset the confirm function to avoid call this function again and again.
    this.swalService.setConfirm(context.confirmItem);
    if(result.success){
      this.swalOptions.icon = 'success'; //set the icon
      this.swalOptions.html = `The user ${data.name} has been deleted!`;
    } else {
      this.swalOptions.icon = 'error';
      this.swalOptions.html = result.message;
    }
    this.swalService.show(this.swalOptions);
    context.loadData();
  }, (error: { error: any; }) => {
    console.error(error);
    this.swalService.showErrors(error.error, this.swalOptions);
  });
}

Let’s try and see the result:

6. Conclusion

SweetAlert2 is a very nice popup message box, and I also use it in other projects, but it will be a little complex in Angular with main/children component structure, that’s why I wrote this article, and hope this can help more people!

In the end, I will provide the full source code below:

https://github.com/coderblog-winson/MyDemo

Loading

Views: 59
Total Views: 750 ,

2 thoughts on “Use the SweetAlert2 in Angular Common Layout

Leave a Reply

Your email address will not be published. Required fields are marked *