Last active
August 19, 2021 13:42
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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">×</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 { } | |
To work just move confirmstate and confirm service on top before ispristineguard
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
this is excellent. Thanks a lot!