Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

CanDeactivate Guard in Angular With Example

In this post we’ll see an example of CanDeactivate guard in Angular which helps in handling unsaved changes.

CanDeactivate interface in Angular

Any class implementing CanDeactivate interface can act as a guard which decides if a route can be deactivated. If you configure this class to be a canDeactivate guard for any route then if you try to navigate away from that route you can show a dialog box to confirm whether user really want to move away.

CanDeactivate interface has a single method canDectivate and the interface is defined as given below-


interface CanDeactivate {
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable | Promise | boolean | UrlTree
}

Angular CanDeactivate guard example

It is easy to show the required configuration and implementation using an example so let’s go through an example of using CanDeactivate guard in Angular.

In the example we’ll have a Users route that shows a list of users and two nested routes add user and edit user.

What we want to implement is that if you click anywhere else while you are in the middle of adding or editing a user you should get a confirmation dialog box asking whether you really want to navigate away. If you click cancel, you won’t be navigated away, if you click ok then you’ll be navigated away. This handling of unsaved changes is done through implementing a CanDeactivate guard.

Dialog Service

DialogService has a confirm() method to prompt the user to confirm their intent. The window.confirm is a blocking action that displays a modal dialog and waits for user interaction.


import { Observable, of } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class DialogService {
confirm(message?: string): Observable {
const confirmation = window.confirm(message || 'Is it OK?');
return of(confirmation);
}
}

CanDeactivate Guard implementation

Here is an implmentation of canDeactivate guard that checks for the presence of a canDeactivate() method in any component so it can be used as a reusable guard. First thing is to create an interface with a canDeactivate method and then create a Service that implements CanDeactivate interface passing your interface (CanComponentDeactivate) as its generic parameter.


import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';

export interface CanComponentDeactivate {
canDeactivate: () => Observable | Promise | boolean;
}

@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate {
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}

In the canDeactivate() method there is a method call component.canDeactivate(). This generic imlementation can detect if a component has the canDeactivate() method and call it. Any component where a canDeactivate guard is needed can define canDeactivate() method, that method will be invoked by this component.canDeactivate() call otherwise true is returned.

A component where canDeactivate guard is needed can just have a canDeactivate() method and Angular will detect it. If you want to be more correct in your code then your component can also implement the interface created by you. For example if I have a AddUserComponent where I want to have canDeactivate guard then the class can be written as given below.


export class AddUserComponent implements CanComponentDeactivate{
....
....
}

component-specific CanDeactivate guard

You could also make a component-specific CanDeactivate guard. In our example let’s assume that we want a component specific CanDeactivate guard for EditUserComponent. Guard class implements the CanDeactivate interface but its parameter now is the component for which this guard is going to be used.


import { EditUserComponent } from '../users/edituser/edituser.component';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { DialogService } from './dialog.service';

@Injectable({
providedIn: 'root',
})
export class UserEditCanDeactivateGuard implements CanDeactivate {
constructor(private dialogService: DialogService) { }
canDeactivate(component: EditUserComponent, currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot):
boolean | Observable | Promise {
if(!component.isUserEdited && component.userForm.dirty){
return this.dialogService.confirm('Discard changes?');
}
return true;
}
}

User Model (user.model.ts)

There is a User model class with the required fields for the User object.


export class User {
id: number;
name: string;
age: number;
joinDate: Date;
constructor(id: number, name: string, age : number, joinDate : Date) {
this.id = id;
this.name = name;
this.age = age;
this.joinDate = joinDate;
}
}

UserService (user.service.ts)

This is a service class with methods to add or update a user, getting all the users or user by id. Also creates an array of User objects that is displayed through UsersComponent.


import { User } from '../users/user.model';
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class UserService {
constructor(){}
// User detail array
users = [new User(1,'Jack', 62, new Date('2005-03-25')),
new User(2, 'Olivia', 26, new Date('2014-05-09')),
new User(3, 'Manish', 34, new Date('2010-10-21'))] ;
// for adding new user
addUser(user: User){
let maxIndex = this.users.length + 1;
user.id = maxIndex;
this.users.push(user);
return user;
}
// for updating existing user
editUser(user: User){
let obj = this.users.find(e => e.id === user.id)
obj = user;
}
// get the users list
getUsers(){
return this.users;
}
// User by Id
getUser(id: Number){
return this.users.find(user => user.id === id)
}
}

Components and templates

We have our CanDeactivate guards, dialog service and UserService ready now let’s go through the components.

HomeComponent (home.component.ts)

This component doesn’t do much just shows a welcome message.


import { OnInit, Component } from '@angular/core';

@Component({
selector: 'app-home',
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
message: string;
constructor() {
this.message = 'Welcome to CanDeactivate guard example';
}
ngOnInit() {
}
}

home.component.html


{{ message }}


UsersComponent (users.component.ts)

This component is used to show users in a tabular form with a edit button in each row to edit any user. There is also an option to add a new user.


import { Component, OnInit, Input } from '@angular/core';
import { User } from './user.model';
import { UserService } from '../services/user.service';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
users : User[];
constructor(private userService: UserService,
private router: Router,
private route: ActivatedRoute){}

ngOnInit(): void {
// get all users
this.users = this.userService.getUsers();
}

onClickNewUser(){
this.router.navigate(['add'], {relativeTo:this.route});
}

editUser(id: number){
this.router.navigate(['edit', id], {relativeTo:this.route});
}
}

users.component.html




User Details

















NameAgeJoining Date
{{i+1}}{{user.name}}{{user.age}}{{user.joinDate | date:'dd/MM/yyyy'}}








AddUserComponent (adduser.commponent.ts)

This is the child component of the UsersComponent and it has the functionality to add new user. In this component canDeactivate() method is added which is called by the CanDeactivateGuard.

Notice that CanComponentDeactivate interface is not explicitly implemented here but it is a good practice to do that. So you can have class as this also- export class AddUserComponent implements CanComponentDeactivate{ .. }


import { Component } from '@angular/core';
import { UserService } from '../../services/user.service';
import { FormBuilder } from '@angular/forms';
import { User } from '../user.model';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { DialogService } from 'src/app/services/dialog.service';

@Component({
selector: 'app-adduser',
templateUrl: './adduser.component.html',
styleUrls: ['./adduser.component.css'],
//providers: [UserService]
})
export class AddUserComponent {
isUserAdded = false;
constructor(private userService: UserService,
private dialogService: DialogService,
private formBuilder: FormBuilder,
private router: Router,
private route: ActivatedRoute){}

// Create a Formgroup instance
userForm = this.formBuilder.group({
name: '',
age: '',
joindate: ''
});
onFormSubmit() {
// Get values from form
let name = this.userForm.get('name').value;
let age = this.userForm.get('age').value;
let joindate = this.userForm.get('joindate').value;
let user = new User(null, name, age, new Date(joindate));
// call adduser to add the new user
this.userService.addUser(user);
//set property to true
this.isUserAdded = true;
// navigate to parent route
this.router.navigate([ '../' ], { relativeTo: this.route })
}
// CanDeactivate guard
canDeactivate(): Observable | boolean {
if (!this.isUserAdded && this.userForm.dirty) {
return this.dialogService.confirm('Discard changes?');
}
return true;
}
}

adduser.component.html


Add User



















EditUserComponent (edituser.commponent.ts)

This is the child component of the UsersComponent and it has the functionality to edit a user. This component has a component specific UserEditCanDeactivateGuard already implemented.


import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core';
import { UserService } from 'src/app/services/user.service';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { User } from '../user.model';
import { FormGroup, FormBuilder } from '@angular/forms';
import { formatDate } from '@angular/common';

@Component({
templateUrl: './edituser.component.html'
})
export class EditUserComponent implements OnInit{
user: User;
userForm: FormGroup;
isUserEdited = false;
constructor(private userService: UserService,
private router: Router,
private route: ActivatedRoute,
private formBuilder: FormBuilder,
@Inject(LOCALE_ID) private locale: string) {}

ngOnInit() {
this.route.params.subscribe((params: Params)=> {
// get the id parameter
let userId = +params['userid'];
//console.log('In edit user-- ' + userId);
this.user = this.userService.getUser(userId);
//console.log('In edit user-- ' + this.user.name);
this.createEditForm(this.user);
});
}

createEditForm(user: User){
this.userForm = this.formBuilder.group({
id: user.id,
name: user.name,
age: user.age,
joindate: formatDate(user.joinDate, 'yyyy-MM-dd',this.locale)
});
}
onFormSubmit() {
this.user.name = this.userForm.get('name').value;
this.user.age = this.userForm.get('age').value;
this.user.joinDate = this.userForm.get('joindate').value;
// call edit user method
this.userService.editUser(this.user);
//set property to true
this.isUserEdited = true;
this.router.navigate([ '../../' ], { relativeTo: this.route })
}
}

edituser.component.html


Edit User



{{user.id}}














AppRoutingModule (app-routing.module.ts)

Now comes the configuration part where we’ll configure the routes and the associated CanDeactivate guards.


import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
import { UsersComponent } from './users/users.component';
import { AddUserComponent } from './users/adduser/adduser.component';
import { EditUserComponent } from './users/edituser/edituser.component';
import { CanDeactivateGuard } from './services/can-deactivate.guard';
import { UserEditCanDeactivateGuard } from './services/useredit-can-deactivate.guard';

const routes: Routes = [
{path: 'home', component: HomeComponent},
{path: 'user', component: UsersComponent, children: [
{path: 'add', component: AddUserComponent, canDeactivate: [CanDeactivateGuard]},
{path: 'edit/:userid', component: EditUserComponent, canDeactivate: [UserEditCanDeactivateGuard]}
]},
{path: '', redirectTo:'/home', pathMatch: 'full'}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [CanDeactivateGuard, UserEditCanDeactivateGuard]
})
export class AppRoutingModule { }

In the module two things to notice are-

1. Configuration of guards in child routes using canDeactivate property.


{path: 'add', component: AddUserComponent, canDeactivate: [CanDeactivateGuard]},
{path: 'edit/:userid', component: EditUserComponent, canDeactivate: [UserEditCanDeactivateGuard]}

2. Providers array with guard implementations.


providers: [CanDeactivateGuard, UserEditCanDeactivateGuard]

AppModule (app.module.ts)

In the AppModule you need to provide the components in the declarations array. In the providers array you need to add DialogService and UserService.


providers: [UserService, DialogService]

Since this example used Forms so also add FormsModule and ReactiveFormsModule in imports array.


imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
AppRoutingModule
]

app.component.html

Code for creating a menu and Route links is in this template.











Add User (Navigating Away)

Edit User (Navigating Away)

That's all for this topic CanDeactivate Guard in Angular With Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Angular Access Control CanActivate Route Guard Example
  2. Angular CanActivateChild Guard to protect Child Routes
  3. Setting Wild Card Route in Angular
  4. Nested Route (Child Route) in Angular
  5. Highlight Currently Selected Menu Item Angular Routing Example

You may also like-

  1. Angular @Input and @Output Example
  2. Angular - Call One Service From Another
  3. Angular Custom Event Binding Using @Output Decorator
  4. Angular Class Binding With Examples
  5. Serialization and Deserialization in Java
  6. Fibonacci Series Program in Java
  7. What is SafeMode in Hadoop
  8. Name Mangling in Python


This post first appeared on Altair Gate - News, please read the originial post: here

Share the post

CanDeactivate Guard in Angular With Example

×

Subscribe to Altair Gate - News

Get updates delivered right to your inbox!

Thank you for your subscription

×