Angular Deep Dive into CanDeactivate Route Guard
What is CanDeactivate
In this article, we’ll learn about the CanDeactivate
router guard. Router guard is a browser event that's used to warn users that they're about to leave a page.
You can use the CanDeactivate guard to prevent users from accidentally leaving a route/page in your application. For example if such page contains a text editor with unsaved changes or an unsubmitted form. You can let the user know they can’t leave the component yet by using a confirmation or alert before the navigation to the other route takes place using the Angular CanDeactivate route guard.
In this article, you’ll see how to use the Angular CanDeactivate guard by example. CanDeactivate is a TypeScript interface that needs to be implemented by a component to create a route guard. This guard will be used by the router to decide if the route can be deactivated. It can be implemented in any Angular component using the canDeactivate
method of the interface.
Let’s see how to implement CanDeactivate step by step with Angular 10.
Step 1 — Creating the CanDeactivate Route Guard Service
ng generate guard guards/can-deactivate
Now your default can-deactivate.guard.ts
will look like below.
@Injectable({
providedIn: 'root'
})export class CanDeactivateGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}
We will modify this code.
firstly add CanComponentDeactive
interface and type
type CanDeactivateType = Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;export interface CanComponentDeactivate {
canDeactivate: () => CanDeactivateType;
}
We simply implement the CanDeactivate
interface with a generic component type that defines a canDeactivate
method. The CanDeactivate
interface contains one canDeactivate()
method that you need to override and pass the generic component as parameter so we can call the canDeactivate
method of of the component.
Step 2: Implement the canDeactivate()
Method in your Component
Next, we need to implement the canDeactivate()
method in our component, so first let's create the component.
Head back to your terminal and run the following command:
ng generate component components/user-details
add given code in your user-details.component.ts
In this code we are creating a simple form which has 2 field for “name” and “email”.
Created canDeactivate
method which will be executed whenever user will try to leave page.
export class UserDetailsComponent implements OnInit {
isSaved = false;
userDetailsForm: FormGroup; constructor() {
this.userDetailsForm = new FormGroup({
name: new FormControl(''),
email: new FormControl(''),
});
} ngOnInit(): void {
} canDeactivate(): Observable<boolean> {
if (!this.isSaved) {
const result = window.confirm('There are unsaved changes! Are you sure?');
return of(result);
} return of(true);
} onSubmit() {
this.isSaved = true;
}
}
Add below code in your user-details.component.html
. In this code we are have created a simple reactive form.
<h2>User Details</h2><form [formGroup]="userDetailsForm" novalidate (submit)="onSubmit()">
<div>
<label>Name</label>
<input type="text" [formControlName]="'name'" />
</div> <div>
<label>Email</label>
<input type="email" [formControlName]="'email'" />
</div> <div>
<input type="submit" value="Submit" />
</div>
</form>
Now add routing in your app-routing.module.ts
under routes
array.
{
path: '',
component: UserDetailsComponent,
canDeactivate: [CanDeactivateGuard]
},
Add this in app.component.html
<router-outlet></router-outlet>
Create another component using given command.
ng generate component components/page-one
Add new route in app-routing.module.ts
under routes
array
{
path: 'page-one',
component: PageOneComponent
}
Add link for page one in app.component.html
<a [routerLink]="['/page-one']">Page One</a>
If everything goes fine your page should look like below.
Now if you enter data in field and try to move on another page by clicking on page-one link without saving / submitting, it will show a confirmation message like below.
Note: CanDeactivate won’t work if user refreshes page, try to move back/forward using browser’s next and previous button or closes browser window/tab.
Solution is you can listen to window beforeunload event using @HostListener('window:beforeunload')
.
The window
beforeunload
event activates (due to the @HostListener('window:beforeunload')
) and handles the confirm
dialog on its own, without the CanDeactivate
guard. In this case, Chrome and Firefox display their own pending changes warning message.
@HostListener('window:beforeunload', ['$event'])
public onBeforeUnload(event: BeforeUnloadEvent): void {
this.isAllowedNavigation(true).subscribe(isAllowedNavigation =>
{
if (event && !isAllowedNavigation) {
event.preventDefault();
event.returnValue = false;
}
});
}canDeactivate(): Observable<boolean> {
return this.isAllowedNavigation();
}private isAllowedNavigation(beforeunloadEvent = false): Observable<boolean> {
if (!this.isSaved || beforeunloadEvent) {
const result = window.confirm('There are unsaved changes! Are you sure?');
return of(result);
} return of(true);
}
Source Code: https://github.com/piyush303/angular-candeactivate-guard-example
You can play with code here — https://stackblitz.com/edit/angular-ivy-nntfkd
If you liked this please give a clap.
Also let us know how you are doing this in your project. Do share if you know any other approach.