Contents
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
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