Home  >  Angular

Angular CanDeactivate Guard Example

By Arvind Rai, December 26, 2017
This page will walk through Angular CanDeactivate guard example. CanDeactivate is an interface that is implemented by a class to create a guard which decides if a route can be deactivated. The guard can be added to any component route using canDeactivate attribute of Angular Route interface. Any component which needs to use CanDeactivate guard, has to define canDeactivate method and that will be called by guard class that has been created implementing CanDeactivate interface. We can also create component-specific CanDeactivate guard. The guard will be created as Angular service and need to be added in providers array in main application routing module so that Router can inject it during the navigation process. While deactivating route using CanDeactivate guard, we need to open a Confirmation Dialog Box to take user confirmation if user wants to stay on the page or leave the page.
CanDeactivate guard can be used in the scenario, for example, suppose a user is changing form data and before saving, user tries to navigate away. In this scenario we can use CanDeactivate guard which will deactivate the route and open a Dialog Box to take user confirmation.
Here on this page we will create generic CanDeactivate guard that can be used with any component and we will also create component specific CanDeactivate guard. Now let us discuss complete example step-by-step.

1. Technologies Used

Find the technologies being used in our example.
1. Angular 5.0.0
2. Angular CLI 1.5.4
3. TypeScript 2.4.2
4. Node.js 6.11.0
5. NPM 3.10.10

2. CanDeactivate Interface

CanDeactivate interface is a route guard to decide if a route can deactivated. Find the structure of CanDeactivate interface from Angular doc.
 interface CanDeactivate<T> { 
    canDeactivate(component: T, 
                  currentRoute: ActivatedRouteSnapshot,
                  currentState: RouterStateSnapshot,
                  nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean
} 
CanDeactivate interface has a method canDeactivate that accepts arguments as given below.
a. Component for which route is to be deactivated.
b. ActivatedRouteSnapshot
c. RouterStateSnapshot
d. RouterStateSnapshot

canDeactivate method will return Observable<boolean> or Promise<boolean> or boolean .

3. Steps to Create CanDeactivate Guard for any Component

To use CanDeactivate in our application, we need to create a guard. This will be a service decorated with @Injectable() and will define canDeactivate() method. If a component needs CanDeactivate route guard then that component has also to create a method named as canDeactivate() .
Find the sample output of our application for CanDeactivate guard.
Angular CanDeactivate Guard Example
Now we will discuss step-by-step to use CanDeactivate in our application.

3.1 Create Service for CanDeactivate Guard

First we will create an interface that will declare canDeactivate method and using this interface we will create a service that will act as CanDeactivate guard. This service will define canDeactivate method. Now find our CanDeactivate guard.
can-deactivate-guard.service.ts
export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate, 
           route: ActivatedRouteSnapshot, 
           state: RouterStateSnapshot) {

     let url: string = state.url;
     console.log('Url: '+ url);

     return component.canDeactivate ? component.canDeactivate() : true;
  }
} 
The interface has declared canDeactivate method whose return type is Observable<boolean> or Promise<boolean> or boolean . In the service code, we need to call canDeactivate method using component instance. For any component which has defined canDeactivate method, that method will be called by component instnace otherwise true will be returned by the above guard. We can access current route and current state, too. In this way we have created our CanDeactivate guard .i.e. CanDeactivateGuard service in our example that can be used for any component.

3.2 Configure CanDeactivate Guard Service in Application Routing Module

We will configure our CanDeactivate guard service i.e. CanDeactivateGuard in application routing module using providers attribute of @NgModule decorator. We need to add it in main application routing module so that Router can inject it during the navigation process.
import { CanDeactivateGuard } from './can-deactivate-guard.service';

------
@NgModule({
  ------
  providers: [ 
    CanDeactivateGuard
  ]
})
export class AppRoutingModule { } 

3.3 Create Service for Confirmation Dialog Box

Create a service for Confirmation Dialog Box.
dialog.service.ts
@Injectable()
export class DialogService {
  confirm(message?: string): Observable<boolean> {
    const confirmation = window.confirm(message || 'Are you sure?');

    return Observable.of(confirmation);
  };
} 
We are passing an optional message for Dialog Box. The Boolean result will be returned as Observable. We need to configure the above service in application module using providers attribute of @NgModule decorator.

3.4 Create canDeactivate() method within Component

We have created CanDeactivate guard .i.e. CanDeactivateGuard and it can be used for any component. Suppose we have a component as PersonEditComponent that will perform update operation for person details. If we want to use CanDeactivate guard for this component then we need to create canDeactivate() method within this component.
@Component({
  templateUrl: './person.edit.component.html' 
}) 
export class PersonEditComponent implements OnInit { 
        ------
	constructor( public dialogService: DialogService) { }

	canDeactivate(): Observable<boolean> | boolean {

	    if (!this.isUpdating && this.personForm.dirty) {

        	return this.dialogService.confirm('Discard changes for Person?');
	    }
	    return true;
	}	
} 
The method canDeactivate() of this component will be called by CanDeactivateGuard service. The canDeactivate() method of the above component will open a Dialog Box to ask if we want to stay on the page or leave the page.

3.5 Add CanDeactivate Guard to Component Route in Routing Module

We need to add our CanDeactivate guard .i.e. CanDeactivateGuard to component route in routing module using canDeactivate attribute.
const personRoutes: Routes = [
  { 
    path: 'person',
    component: PersonComponent,
    children: [ 
      {
         path: '',
         component: PersonListComponent,
         children: [
           {
             path: ':id',
             component: PersonEditComponent,
     	     canDeactivate: [CanDeactivateGuard]
           }
         ]			
       }
     ]
  }  
];

@NgModule({
  imports: [ RouterModule.forChild(personRoutes) ],
  exports: [ RouterModule ]
})
export class PersonRoutingModule{ } 
In this way, we can use CanDeactivate guard in our application for any component.

4. Component-Specific CanDeactivate Guard

We can create component specific CanDeactivate guard. Suppose we have a component CountryEditComponent and we want to create CanDeactivate guard for this component. The guard needs to implement Angular CanDeactivate interface and will define canDeactivate() method. Find the code as following.
country-edit-can-deactivate-guard.service.ts
@Injectable()
export class CountryEditCanDeactivateGuard implements CanDeactivate<CountryEditComponent> {
  
  constructor(private dialogService: DialogService) { }

  canDeactivate(
    component: CountryEditComponent,
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    
      let url: string = state.url;
      console.log('Url: '+ url);

      if (!component.isUpdating && component.countryForm.dirty) {
        component.isUpdating = false;
        return this.dialogService.confirm('Discard changes for Country?');
      }
      return true;
  }
} 
Within canDeactivate() method we need to open our Confirmation Dialog Box.

We will configure CountryEditCanDeactivateGuard in main application routing module so that Router can inject it during the navigation process.
import { CanDeactivateGuard } from './can-deactivate-guard.service';
import { CountryEditCanDeactivateGuard } from './country-edit-can-deactivate-guard.service';

------
@NgModule({
  ------
  providers: [ 
    CanDeactivateGuard,
    CountryEditCanDeactivateGuard
  ]
})
export class AppRoutingModule { } 
Now add guard to CountryEditComponent route in routing module using canDeactivate attribute.
const countryRoutes: Routes = [
  { 
      path: 'country',
      component: CountryComponent,
      children: [ 
	{
	   path: 'add',
	   component: AddCountryComponent,
	   canDeactivate: [CanDeactivateGuard]
	},
	{
	   path: 'list',
	   component: CountryListComponent,
	   children: [
	     {
	        path: 'edit/:country-id',
		component: CountryEditComponent,
		canDeactivate: [CountryEditCanDeactivateGuard]
	     }	   
	   ]
	 }	
       ]
    }  
];

@NgModule({
  imports: [ RouterModule.forChild(countryRoutes) ],
  exports: [ RouterModule ]
})
export class CountryRoutingModule { } 
In this way, we can use CanDeactivate guard in our application for a specific component.

5. Complete Example

Find the project structure of our application.
angular-demo
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--can-deactivate-guard.service.ts
|   |   |--country-edit-can-deactivate-guard.service.ts
|   |   |--dialog.service.ts
|   |   |
|   |   |--country
|   |   |    | 
|   |   |    |--country.component.ts
|   |   |    |--country.ts
|   |   |    |--country.module.ts
|   |   |    |--country-routing.module.ts
|   |   |    |
|   |   |    |--add-country
|   |   |    |    |  
|   |   |    |    |--add-country.component.html
|   |   |    |    |--add-country.component.ts
|   |   |    |    
|   |   |    |--country-list
|   |   |    |    |
|   |   |    |    |--country.list.component.html
|   |   |    |    |--country.list.component.ts
|   |   |    |    |
|   |   |    |    |--edit
|   |   |    |    |   |
|   |   |    |    |   |--country.edit.component.html
|   |   |    |    |   |--country.edit.component.ts
|   |   |    |    |
|   |   |    |    
|   |   |    |--services
|   |   |    |    |
|   |   |    |    |--country.service.ts
|   |   |    | 
|   |   |
|   |   |--person
|   |   |    | 
|   |   |    |--person.component.ts
|   |   |    |--person.ts
|   |   |    |--person-routing.module.ts
|   |   |    |--person.module.ts
|   |   |    | 
|   |   |    |--person-list 
|   |   |    |    |
|   |   |    |    |--person.list.component.html 
|   |   |    |    |--person.list.component.ts
|   |   |    |    |
|   |   |    |    |--edit
|   |   |    |    |   |
|   |   |    |    |   |--person.edit.component.html
|   |   |    |    |   |--person.edit.component.ts
|   |   |    | 
|   |   |    |--services
|   |   |    |    |  
|   |   |    |    |--person.service.ts
|   |   |    |
|   |   |
|   |   |--page-not-found.component.ts
|   |   |--app.component.ts
|   |   |--app-routing.module.ts
|   |   |--app.module.ts
|   |   
|   |--main.ts
|   |--index.html
|   |--styles.css
|
|--node_modules
|--package.json
Now find the complete code.
can-deactivate-guard.service.ts
import { Injectable }    from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable }    from 'rxjs/Observable';

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

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate, 
           route: ActivatedRouteSnapshot, 
           state: RouterStateSnapshot) {

     let url: string = state.url;
     console.log('Url: '+ url);

     return component.canDeactivate ? component.canDeactivate() : true;
  }
} 
country-edit-can-deactivate-guard.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot }  from '@angular/router';
import { CountryEditComponent } from './country/country-list/edit/country.edit.component';
import { DialogService } from './dialog.service';

@Injectable()
export class CountryEditCanDeactivateGuard implements CanDeactivate<CountryEditComponent> {
  
  constructor(private dialogService: DialogService) { }

  canDeactivate(
    component: CountryEditComponent,
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    
      let url: string = state.url;
      console.log('Url: '+ url);

      if (!component.isUpdating && component.countryForm.dirty) {
        component.isUpdating = false;
        return this.dialogService.confirm('Discard changes for Country?');
      }
      return true;
  }
} 
dialog.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';

@Injectable()
export class DialogService {
  confirm(message?: string): Observable<boolean> {
    const confirmation = window.confirm(message || 'Are you sure?');

    return Observable.of(confirmation);
  };
} 
country.component.ts
import { Component } from '@angular/core';

@Component({
  template: `<h2>Welcome to Country Home</h2>
      <nav [ngClass] = "'child-menu'">
        <ul>
	    <li><a [routerLink]="['add']" routerLinkActive="active">Add Country</a></li>
	    <li><a [routerLink]="['list']" routerLinkActive="active">Country List</a></li>
        </ul>  
      </nav>  
      <div [ngClass] = "'child-container'">	
	   <router-outlet></router-outlet>	
      </div>
  `
})
export class CountryComponent { 
} 
country.ts
export class Country { 
	constructor(public countryId:number, public name:string,
            	public capital:string, public currency:string) {
	}
} 
country.module.ts
import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { ReactiveFormsModule }    from '@angular/forms';
import { CountryComponent }  from './country.component';
import { AddCountryComponent }  from './add-country/add-country.component';
import { CountryListComponent }  from './country-list/country.list.component';
import { CountryEditComponent }  from './country-list/edit/country.edit.component';
import { CountryService } from './services/country.service';
import { CountryRoutingModule }  from './country-routing.module';

@NgModule({
  imports: [     
         CommonModule,
	 ReactiveFormsModule,
	 CountryRoutingModule
  ], 
  declarations: [
	 CountryComponent,
	 AddCountryComponent,
	 CountryListComponent,
	 CountryEditComponent
  ],
  providers: [ CountryService ]
})
export class CountryModule { } 
country-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CountryComponent }  from './country.component';
import { CountryListComponent }  from './country-list/country.list.component';
import { AddCountryComponent }  from './add-country/add-country.component';
import { CountryEditComponent }  from './country-list/edit/country.edit.component';
import { CanDeactivateGuard }     from '../can-deactivate-guard.service';
import { CountryEditCanDeactivateGuard }     from '../country-edit-can-deactivate-guard.service';

const countryRoutes: Routes = [
   { 
      path: 'country',
      component: CountryComponent,
      children: [ 
	 {
	     path: 'add',
	     component: AddCountryComponent,
	     canDeactivate: [CanDeactivateGuard]
	 },
	 {
	     path: 'list',
	     component: CountryListComponent,
	     children: [
	        {
		   path: 'edit/:country-id',
		   component: CountryEditComponent,
		   canDeactivate: [CountryEditCanDeactivateGuard]
		}	   
	     ]
	 }	
       ]
    }  
];

@NgModule({
  imports: [ RouterModule.forChild(countryRoutes) ],
  exports: [ RouterModule ]
})
export class CountryRoutingModule { } 
add-country.component.html
<h3>Add Country</h3>
<form [formGroup]="countryForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> Capital: <input formControlName="capital"> </p>
   <p> Currency: <input formControlName="currency"> </p>
   <p> <button>Add</button> </p>
</form> 
add-country.component.ts
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CountryService } from '../services/country.service';
import { Country } from '../country';
import { DialogService } from '../../dialog.service';
import { Observable } from 'rxjs/Observable';

@Component({
    templateUrl: './add-country.component.html'
})
export class AddCountryComponent { 
	isAdding = false;	
	constructor(
		private countryService: CountryService,
		private route: ActivatedRoute,
		private router: Router,
		private formBuilder: FormBuilder,
	        private dialogService: DialogService) { }
		
	countryForm = this.formBuilder.group({
	   name: '',
	   capital: '',
	   currency: ''
	});	
	onFormSubmit() {
	   this.isAdding = true;	
	   let name = this.countryForm.get('name').value;
	   let capital = this.countryForm.get('capital').value;
	   let currency = this.countryForm.get('currency').value;
	   
	   let country = new Country(null, name, capital, currency);
	   this.countryService.addCountry(country)
	      .subscribe(() =>
    		  this.router.navigate([ '../list' ], { relativeTo: this.route })
	       );
	}
	canDeactivate(): Observable<boolean> | boolean {
	   if (!this.isAdding && this.countryForm.dirty) {
	      return this.dialogService.confirm('Discard unsaved Country?');
	   }
	   return true;
	} 
} 
country.list.component.html
<h3>Country List</h3>
<div *ngFor="let country of countries | async" [ngClass]= "'sub-child-menu'">
 <p> {{country.countryId}} - {{country.name}} -
     {{country.capital}} - {{country.currency}}
     | <a [routerLink]="['edit', country.countryId]" routerLinkActive="active">Edit</a>
 </p>
</div>
<div [ngClass]= "'sub-child-container'">
   <router-outlet></router-outlet>  
</div> 
country.list.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { CountryService } from '../services/country.service';
import { Country } from '../country';

@Component({
  templateUrl: './country.list.component.html' 
}) 
export class CountryListComponent implements OnInit { 
  countries: Observable<Country[]>
  constructor(private countryService: CountryService) {}
  ngOnInit() {
    this.countries = this.countryService.getCountries();
  }	
}  
country.edit.component.html
<h3>Edit Country</h3>
<p *ngIf="country"><b>Country Id: {{country.countryId }} </b></p>
<form [formGroup]="countryForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> Capital: <input formControlName="capital"> </p>
   <p> Currency: <input formControlName="currency"> </p>
   <p> <button>Update</button> </p>
</form>  
country.edit.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { FormGroup, FormBuilder } from '@angular/forms';
import 'rxjs/add/operator/switchMap';
import { CountryService } from '../../services/country.service';
import { Country } from '../../country';

@Component({
  templateUrl: './country.edit.component.html' 
}) 
export class CountryEditComponent implements OnInit {
	country: Country; 
	countryForm: FormGroup;
	isUpdating = false;
	constructor(
		private countryService: CountryService,
		private route: ActivatedRoute,
		private router: Router,
	        private formBuilder: FormBuilder) { }
		
        ngOnInit() {
             this.route.params
             .switchMap((params: Params) => this.countryService.getCountry(+params['country-id']))
             .subscribe(country => {
	     	  this.country = country;
		  this.createForm(country);
	     });
        }	
	createForm(country: Country) {
	     this.countryForm = this.formBuilder.group({
		 name: country.name,
		 capital: country.capital,
		 currency: country.currency
	     });	
	}
  	onFormSubmit() {
	     this.isUpdating = true;  
	     this.country.name = this.countryForm.get('name').value;
	     this.country.capital = this.countryForm.get('capital').value;
	     this.country.currency = this.countryForm.get('currency').value;

	     this.countryService.updateCountry(this.country)
	     .subscribe(() => 
		    this.router.navigate([ '../../' ], { relativeTo: this.route })
	      );
	}
} 
country.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { Country } from '../country';

const COUNTRIES = [
  new Country(1, 'India', 'New Delhi', 'INR'),
  new Country(2, 'China', 'Beijing', 'RMB')
];
let countriesObservable = of(COUNTRIES);

@Injectable()
export class CountryService { 
  getCountries(): Observable<Country[]> {
	return countriesObservable;
  }
  getCountry(id: number): Observable<Country> {
   	return this.getCountries()
           .map(countries => countries.find(country => country.countryId === id));
  }	
  updateCountry(country: Country): Observable<Country> {
	return this.getCountries()
	   .map(countries => {
	        let countryObj = countries.find(ob => ob.countryId === country.countryId);
                countryObj = country;
		return countryObj;
	    });
  }	
  addCountry(country: Country): Observable<Country> {
	return this.getCountries()
	   .map(countries => {
	       let maxIndex = countries.length - 1;
	       let countryWithMaxIndex = countries[maxIndex];
	       country.countryId = countryWithMaxIndex.countryId + 1;
	       countries.push(country);
	       return country;
	    });
  }	
} 
person.component.ts
import { Component } from '@angular/core';

@Component({
  template: `
        <h2>Welcome to Person Home</h2>
	<div [ngClass] = "'child-container'">	
	  <router-outlet></router-outlet>	
	</div>
  `
})
export class PersonComponent { 
} 
person.ts
export class Person { 
	constructor(public personId:number, public name:string, public city:string) {
	}
} 
person-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PersonComponent }  from './person.component';
import { PersonListComponent }  from './person-list/person.list.component';
import { PersonEditComponent }  from './person-list/edit/person.edit.component';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';

const personRoutes: Routes = [
	{ 
	  path: 'person',
          component: PersonComponent,
	  children: [ 
	    {
	       path: '',
	       component: PersonListComponent,
	       children: [
	         {
	           path: ':id',
	     	   component: PersonEditComponent,
		   canDeactivate: [CanDeactivateGuard]
		 }
	       ]			
	     }
	  ]
	}  
];

@NgModule({
  imports: [ RouterModule.forChild(personRoutes) ],
  exports: [ RouterModule ]
})
export class PersonRoutingModule{ } 
person.module.ts
import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { ReactiveFormsModule }    from '@angular/forms';
import { PersonComponent }  from './person.component';
import { PersonListComponent }  from './person-list/person.list.component';
import { PersonEditComponent }  from './person-list/edit/person.edit.component';
import { PersonService } from './services/person.service';
import { PersonRoutingModule }  from './person-routing.module';

@NgModule({
  imports: [     
        CommonModule,
	ReactiveFormsModule,
	PersonRoutingModule
  ], 
  declarations: [
	PersonComponent,
	PersonListComponent,
	PersonEditComponent
  ],
  providers: [ PersonService ]
})
export class PersonModule { } 
person.list.component.html
<h3>Person List</h3>
<div *ngFor="let person of persons | async" [ngClass]= "'sub-child-menu'">
  <p>{{person.personId}}. {{person.name}}, {{person.city}}
      <button type="button" (click)="goToEdit(person)">Edit</button>
  </p>
</div>
<div [ngClass]= "'sub-child-container'">
   <router-outlet></router-outlet>  
</div>  
person.list.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { PersonService } from '../services/person.service';
import { Person } from '../person';

@Component({
  templateUrl: './person.list.component.html' 
})
export class PersonListComponent implements OnInit { 
  persons: Observable<Person[]>
  constructor(		
        private personService: PersonService,
        private route: ActivatedRoute,
        private router: Router) {}
  ngOnInit() {
      this.persons = this.personService.getPersons();
  }	
  goToEdit(person:Person) {
      this.router.navigate([ person.personId ], { relativeTo: this.route });
  }
} 
person.edit.component.html
<h3>Edit Person</h3>
<p *ngIf="person"><b>Person Id: {{person.personId }} </b></p>
<form [formGroup]="personForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> City: <input formControlName="city"> </p>
   <p> <button>Update</button> </p>
</form> 
person.edit.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import { PersonService } from '../../services/person.service';
import { Person } from '../../person';
import { DialogService } from '../../../dialog.service';

@Component({
  templateUrl: './person.edit.component.html' 
}) 
export class PersonEditComponent implements OnInit { 
	person: Person;
	personForm: FormGroup;
	isUpdating = false;
	constructor(
		private personService: PersonService,
		private route: ActivatedRoute,
		private router: Router,
		private formBuilder: FormBuilder,
		private dialogService: DialogService) { }
		
        ngOnInit() {
            this.route.params
            .switchMap((params: Params) => this.personService.getPerson(+params['id']))
            .subscribe(person => {
		this.person = person;
		this.createForm(person);
	    });
	}	
	createForm(person: Person) {
	    this.personForm = this.formBuilder.group({
	      name: person.name,
	      city: person.city
	    });	
        }
	onFormSubmit() {
	    this.isUpdating = true;	
	    this.person.name = this.personForm.get('name').value;
	    this.person.city = this.personForm.get('city').value;
	    this.personService.updatePerson(this.person)
	      .subscribe(() =>
    		  this.router.navigate([ '../' ], { relativeTo: this.route })
	    );
	}
	canDeactivate(): Observable<boolean> | boolean {
	    if (!this.isUpdating && this.personForm.dirty) {
		  this.isUpdating = false;	
	          return this.dialogService.confirm('Discard changes for Person?');
	    }
	    return true;
	}	
} 
person.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { Person } from '../person';

const PERSONS = [
  new Person(1, 'Mahesh', 'Varanasi'),
  new Person(2, 'Ram', 'Ayodhya'),  
  new Person(3, 'Krishn', 'Mathura')
];
let personsObservable = of(PERSONS);

@Injectable()
export class PersonService { 
    getPersons(): Observable<Person[]> {
	return personsObservable;
    }
    getPerson(id: number): Observable<Person> {
        return this.getPersons()
          .map(persons => persons.find(person => person.personId === id));
    }	
    updatePerson(person: Person): Observable<Person> {
	return this.getPersons()
	  .map(persons => {
	      let personObj = persons.find(ob => ob.personId === person.personId);
              personObj = person;
	      return personObj;
	  });
    }	
} 
page-not-found.component.ts
import { Component } from '@angular/core';
import { Location } from '@angular/common';

@Component({
  template: `<h2>Page Not Found.</h2>
             <div>
                <button (click)="goBack()">Go Back</button>
	     </div>
            `
})
export class PageNotFoundComponent {
	constructor(private location: Location) { }
	goBack(): void {
           this.location.back();
        }
} 
app.component.ts
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
		<nav [ngClass] = "'parent-menu'">
		   <ul>
		     <li><a routerLink="/country" routerLinkActive="active">Country</a></li>
		     <li><a routerLink="/person" routerLinkActive="active">Person</a></li>
		   </ul> 
		</nav>  
		
		<router-outlet></router-outlet>	
  `
})
export class AppComponent { 
} 
app-routing.module.ts
import { NgModule }      from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent }  from './page-not-found.component';
import { CanDeactivateGuard } from './can-deactivate-guard.service';
import { CountryEditCanDeactivateGuard } from './country-edit-can-deactivate-guard.service';

const routes: Routes = [
	{
	   path: 'country',
           loadChildren: 'app/country/country.module#CountryModule',
           data: { preload: true }
	},
	{
	   path: 'person',
           loadChildren: 'app/person/person.module#PersonModule'
	},
	{
	   path: '',
	   redirectTo: '/country',
	   pathMatch: 'full'
	},
	{
	   path: '**',
	   component: PageNotFoundComponent 
	}
];

@NgModule({
  imports: [ 
      RouterModule.forRoot(routes) 
  ],
  exports: [ 
      RouterModule 
  ],
  providers: [ 
      CanDeactivateGuard,
      CountryEditCanDeactivateGuard,
  ],
})
export class AppRoutingModule { }  
app.module.ts
import { NgModule }   from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';
import { PageNotFoundComponent }  from './page-not-found.component';
import { CountryModule }  from './country/country.module';
import { PersonModule }  from './person/person.module';
import { AppRoutingModule }  from './app-routing.module';
import { DialogService } from './dialog.service';

@NgModule({
  imports: [     
    BrowserModule,
    CountryModule,
    PersonModule,
    AppRoutingModule,
  ],
  declarations: [
    AppComponent,
    PageNotFoundComponent
  ],
  providers: [ 
    DialogService 
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { } 
styles.css
.parent-menu ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333;
}
.parent-menu li {
    float: left;
}
.parent-menu li a {
    display: block;
    color: white;
    text-align: center;
    padding: 15px 15px;
    text-decoration: none;
}
.parent-menu li a:hover:not(.active) {
    background-color: #111;
}
.parent-menu .active{
    background-color: #4CAF50;
}
.child-container {
    padding-left: 10px;
}
.sub-child-container {
    padding-left: 10px;
}
.child-menu  {
    padding-left: 25px;
}
.child-menu .active{
    color: #4CAF50;
}
.sub-child-menu {
    background-color: #f1f1f1;  
    width: 275px;
    list-style-type: none;	
    margin: 0;
    padding: 0;
}
.sub-child-menu .active{
    color: #4CAF50;
}
button {
    background-color: #008CBA;
    color: white;
} 

6. Run Application

To run the application, find following steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Now access the URL http://localhost:4200
Click on the country and go to add country. Fill data and try to navigate away. We will get Confirmation Dialog Box to ask if we want to discard unsaved data.
Angular CanDeactivate Guard Example

7. References

Angular Doc: CanDeactivate
Routing & Navigation
Angular 2/4 Child Routes and Relative Navigation Example

8. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
FIND MORE TUTORILAS


©2018 concretepage.com | Privacy Policy | Contact Us