In this tutorial you will be learning how to cancel navigation with confrimation pop-up dialog using can CanDeactivate
interface and a guard for that matter. At the end I will show you as well how to make it with generics so you can use it on all of you components that need that.
Finished Live Example - Generic Template
ng new cancel-navigation-tutorial
cd cancel-navigation-tutorial
ng add @angular/material
Now let’s generate two component which we are gonna use for navigation
ng g c page1
ng g c page2
Add to your project angular material if you want at least that is what I did to show better looking buttons if you wanna see how just see the stackblitz link above.
After that just put those two link in your app.component.html
file, so we can navigate to each one of them.
<a mat-raised-button color="primary" routerLink="page1">Page 1</a>
<a mat-raised-button color="primary" routerLink="page2">Page 2</a>
Let’s make as well the confirmation dialog component
ng g c dialog
dialog.component.html
<div mat-dialog-content>
<p>{{ text }}</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onYesClick()">Yes</button>
<button mat-button (click)="onCancelClick()">Cancel</button>
</div>
dialog.component.ts
export class DialogComponent {
@Input() text: string;
subject: Subject<boolean>;
constructor(private dialogRef: MatDialogRef<DialogComponent>) { }
onYesClick() {
if (this.subject) {
this.subject.next(true);
this.subject.complete();
}
this.dialogRef.close(true);
}
onCancelClick() {
if (this.subject) {
this.subject.next(false);
this.subject.complete();
}
this.dialogRef.close(false);
}
}
Also make sure you put the DialogComponent in entry components
array in your NgModule. Next we would need to make some condition based on which we want to cancel the navigation, usually this is some input change.
page2.component.html
<mat-form-field>
<input matInput placeholder="Your Name" (change)="isInput = true">
</mat-form-field>
Don’t forget to define the isInput variable in the ts file.
Make a new file and call it navigation.guard.ts
in it paste this:
@Injectable({
providedIn: 'root'
})
export class CustomConfirmGuard implements CanDeactivate<Page2Component> {
confirmDlg: MatDialogRef<DialogComponent>;
constructor(
private dialog: MatDialog
) {}
canDeactivate(component: Page2Component) {
const subject = new Subject<boolean>();
if (component.isInput) {
this.confirmDlg = this.dialog.open(DialogComponent, { disableClose: true });
this.confirmDlg.componentInstance.subject = subject;
this.confirmDlg.componentInstance.text = 'Are you sure?';
return subject.asObservable();
}
return true;
}
}
Finally make sure you guard your page2 route with our guard;
app-routing.module.ts
const routes: Routes = [
{ path: 'page1', component: Page1Component },
{ path: 'page2', component: Page2Component, canDeactivate: [CustomConfirmGuard] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
And that’s about it at this you have anything you need to use it as it is, but in the next part of this tutorial I will show you how to make a generic teplate out of this and possibly use it in bunch of other places without hesitation.
Generic Template
Edit your navigation.guard.ts
like that
export interface ComponentGuard {
isInput: boolean;
}
export class CustomConfirmGuard<T extends ComponentGuard> implements CanDeactivate<T> {
confirmDlg: MatDialogRef<DialogComponent>;
subject = new Subject<boolean>();
text: string;
constructor(protected dialog: MatDialog) {
}
canDeactivate(component: T) {
if (component.isInput) {
this.confirmDlg = this.dialog.open(DialogComponent, { disableClose: true });
this.confirmDlg.componentInstance.subject = this.subject;
this.confirmDlg.componentInstance.text = this.text;
return this.subject.asObservable();
}
return true;
}
}
@Injectable({
providedIn: 'root'
})
export class Page2ConfirmGuard extends CustomConfirmGuard<Page2Component> {
constructor(dialog: MatDialog) {
super(dialog);
this.text = 'Are you sure leaving Page 2?';
}
}
@Injectable({
providedIn: 'root'
})
export class Page1ConfirmGuard extends CustomConfirmGuard<Page1Component> {
constructor(dialog: MatDialog) {
super(dialog);
this.text = 'Are you sure leaving Page 1?';
}
}
Your app-routing.module.ts
const routes: Routes = [
{ path: 'page1', component: Page1Component, canDeactivate: [Page1ConfirmGuard] },
{ path: 'page2', component: Page2Component, canDeactivate: [Page2ConfirmGuard] },
];
Your page1.component.ts
export class Page1Component implements ComponentGuard {
isInput: boolean = true;
}
Your page2.component.ts
export class Page2Component implements ComponentGuard {
isInput: boolean = false;
}
And finally you need to comment this.subject.complete();
line in both functions of dialog.component.ts
. With this template you will be able with minimal effort to use this in every component you like.