Skip to content

Instantly share code, notes, and snippets.

@whisher
Last active August 19, 2021 13:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save whisher/c5726e30ea40a4d5caf8b77ab8b0d48a to your computer and use it in GitHub Desktop.
Save whisher/c5726e30ea40a4d5caf8b77ab8b0d48a to your computer and use it in GitHub Desktop.
How to create a reusable service allowing to open a confirmation modal to use with canDeactivate using ng-bootstrap
/*
Credit to: https://gist.github.com/jnizet/15c7a0ab4188c9ce6c79ca9840c71c4e
Credit to: Giuseppe Luca Lo Re
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Component, Injectable, Directive, TemplateRef } from '@angular/core';
import { NgbModal, NgbModalRef, NgbModalOptions } from '@ng-bootstrap/ng-bootstrap';
/**
* The guarded Component should implement this interface
*/
export interface IsPristineAware {
isPristine(): boolean;
}
/**
* Guard to use with the guarded Component
* component: GuardedComponent,
* canActivate: [IsPristineGuard],
**/
@Injectable()
export class IsPristineGuard implements CanDeactivate<IsPristineAware> {
constructor(private confirmService: ConfirmService) {}
canDeactivate(component: IsPristineAware): Promise<boolean> {
if (!component.isPristine()) {
return this.confirmService.confirm({ title:'Confirm', message: 'Are you sure you want to leave?' });
}
return Promise.resolve(true);
}
}
/**
* Options passed when opening a confirmation modal
*/
interface ConfirmOptions {
/**
* The title of the confirmation modal
*/
title: string,
/**
* The message in the confirmation modal
*/
message: string
}
/**
* An internal service allowing to access, from the confirm modal component,
* the options and the modal reference.
*/
@Injectable()
export class ConfirmState {
/**
* The last options passed ConfirmService.confirm()
*/
options: ConfirmOptions;
/**
* The last opened confirmation modal
*/
modal: NgbModalRef;
}
/**
* A confirmation service, allowing to open a confirmation modal from anywhere and get back a promise.
*/
@Injectable()
export class ConfirmService {
constructor(private modalService: NgbModal, private state: ConfirmState) {}
/**
* Opens a confirmation modal
* @param options the options for the modal (title and message)
* @returns {Promise<boolean>} a promise that is fulfilled when the user chooses to confirm
* or closes the modal
*/
confirm(options: ConfirmOptions): Promise<boolean> {
this.state.options = options;
this.state.modal = this.modalService.open(UIConfirmComponent, { size: 'sm' });
return this.state.modal.result;
}
}
/**
* The component displayed in the confirmation modal opened by the ConfirmService.
*/
@Component({
selector: 'ui-confirm',
template: `<div class="modal-header">
<h4 class="modal-title">
{{ options.title}}
</h4>
<button type="button" class="close" aria-label="Close" (click)="no()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ options.message }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="yes()">Yes</button>
<button type="button" class="btn btn-secondary" (click)="no()">No</button>
</div>`
})
export class UIConfirmComponent {
options: ConfirmOptions;
constructor(private state: ConfirmState) {
this.options = state.options;
}
yes() {
this.state.modal.close(true);
}
no() {
this.state.modal.close(false);
}
}
// MODULE
const UI_CONFIRM_COMPONENTS = [UIConfirmComponent];
const UI_CONFIRM_PROVIDERS = [ConfirmState, ConfirmService, IsPristineGuard];
@NgModule({
imports: [
CommonModule
],
declarations: UI_CONFIRM_COMPONENTS,
providers: UI_CONFIRM_PROVIDERS,
exports: UI_CONFIRM_COMPONENTS,
entryComponents: [
UIConfirmComponent
]
})
export class UiConfirmModule { }
@forfrossen
Copy link

this is excellent. Thanks a lot!

@tviliescu
Copy link

To work just move confirmstate and confirm service on top before ispristineguard

@pavanesh2021
Copy link

Thank you for creating the useful code.
While integrating when we press Back button following error is shown:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for 'ng-untouched': 'true'. Current value: 'false'.

please could you suggest if there is any additional change Detetction has to be integrated here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment