Warning unsaved data with help of guard (CanDeactivate guard)


Hey guys, in this tutorial we are going to see how to show a warning to the user if the user edited the data on the page and trying to leave the page without saving the changes. We are going to use CanDeactivate guard to detect the user is leaving the page and then will check for the change in data. So, let’s do it step by step.
We are going to use the same project from our blog to do this task.



Following are the two components from our previous post.

first-page.component.html
<div class="container">
  <form #frm="ngForm" id="frm"><br><br>
    <div class="row">
      <div class="col-lg-4"></div>
      <div class="col-lg-6">
        <h3>Login</h3>
        <table>
          <thead>
            <th>
              Login Name
            </th>
            <th>
              <input class="form-control" type="text" [(ngModel)]="user.userName" name="userName" required>
            </th>
          </thead>
          <tbody>
            <tr>
              <th>
                Password
              </th>
              <td><input class="form-control" type="password" [(ngModel)]="user.password" name="password"></td>
            </tr>
            <tr>
              <td>
              </td>
              <td>
                <button class="btn btn-primary">Login</button>&nbsp;
                <button class="btn btn-light" (click)="registerMe();">Register Me</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </form>
</div>

first-page.component.ts
import { Component, OnInit, HostListener, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { ThrowStmt } from '@angular/compiler';
import { NgForm } from '@angular/forms';
import { user } from '../user/user';
import { BaseComponent } from '../can-deactivate';

@Component({
  selector: 'app-first-page',
  templateUrl: './first-page.component.html',
  styleUrls: ['./first-page.component.css']
})
export class FirstPageComponent implements OnInit {
 

  @ViewChild('frm') public userFrm: NgForm;
  private user = new user();
  constructor(private router: Router) { }

  ngOnInit() {
    console.log("user : " + user.name);
  }

  registerMe() {
    console.log("form touched " + this.userFrm.touched);
    this.router.navigate(['/secondpage']);
  }

}




second-page.component.html
<br><br>
<div class="row">
  <div class="col-lg-4"></div>
  <div class="col-lg-6">
    <form action="" #frm="ngForm">
      <h3>Registration</h3>
      <table>
        <tr>
          <td>User name</td>
          <td><input [(ngModel)]="user.userName" class="form-control" type="text" name="userName"></td>
        </tr>
        <tr>
          <td>password</td>
          <td><input [(ngModel)]="user.password" class="form-control" type="password" name="password"></td>
        </tr>
        <tr>
          <td>confirm password</td>
          <td><input class="form-control" type="password" name="password"></td>
        </tr>
        <tr>
          <td>email</td>
          <td><input [(ngModel)]="user.email" class="form-control" type="text" name="email"></td>
        </tr>
        <tr>
          <td>contact No</td>
          <td><input [(ngModel)]="user.contactNo" class="form-control" type="text" name="contactNo"></td>
        </tr>
        <tr>
          <td>address</td>
          <td><input [(ngModel)]="user.address" class="form-control" type="text" name="address"></td>
        </tr>
        <tr>
          <td></td>
          <td><button class="btn btn-light" (click)="gotoLogin()">Cancel</button>&nbsp;<button class="btn btn-primary">save</button></td>
        </tr>
      </table>
    </form>
  </div>
</div>

second-page.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { BaseComponent } from '../can-deactivate';
import { NgForm } from '@angular/forms';
import { user } from '../user/user';

@Component({
  selector: 'app-second-page',
  templateUrl: './second-page.component.html',
  styleUrls: ['./second-page.component.css']
})
export class SecondPageComponent implements OnInit, BaseComponent {
 

  @ViewChild('frm') public userFrm: NgForm;
  private user = new user();

  constructor(private router: Router) { }

  ngOnInit() {
  }

  gotoLogin() {
    this.router.navigate(['/firstpage']);
  }

  containsEditedData() {
    return this.userFrm.dirty;
  }

}


In the above HTML, we have created login and registration pages respectively.
 We are going to show a warning when the user is going to fill something into the registration form and will going to leave without completing the registration. To achieve that we don’t need many changes in the previous page, we are going to create a new guard and apply it on the registration page.
At very first let’s create an interface and implement it by the component on which we are going to apply the guard. We are creating interface because we need to make wrapper component so we can make guard generic means guard can accept any component and also, we can define some method in it to implement the basic logic.

can-deactivate.ts
export interface BaseComponent {
    containsEditedData();
}


Commands to generate the guard
Command: ng generate guard guardname
Or shortened
Command: ng g g guardname


guard-unsaved-data.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';
import { BaseComponent } from './can-deactivate';

@Injectable({
  providedIn: 'root'
})
export class GuardUnsavedDataGuard implements CanDeactivate<BaseComponent> {
  canDeactivate(component: BaseComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
    if (component.containsEditedData()) {
      if (confirm("please save the changes! If you leave, your changes will be lost. say cancel to stay on page")) {
        return true;
      } else {
        return false;
      }
    }
    return true;

  }
}


Here we have just accepted the component in guard and called containsEditedData() function, which needs to implement by component( that’s why we have created an interface so any component can be passed to the guard with help of interface’s reference and one function in that interface, so implementing class must have that functions implementation)  and it returns true if there is something changed on-page means something is filled in registration form. If something is filled, then show waring of edited data else it can be deactivated.

Now everything is ready but how to apply the guard, here it is

app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstPageComponent } from './first-page/first-page.component';
import { SecondPageComponent } from './second-page/second-page.component';
import { GuardUnsavedDataGuard } from './guard-unsaved-data.guard';

const routes: Routes = [
  { path: '', component: FirstPageComponent },
  { path: 'firstpage', component: FirstPageComponent },
  { path: 'secondpage', component: SecondPageComponent, canDeactivate: [GuardUnsavedDataGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [GuardUnsavedDataGuard]
})

export class AppRoutingModule { }

 In our app.routing file while specifying the routes we can apply a guard for particular component here we have applied the guard on the second component. Here we have applied guard of type deactivate, we have two types of guards one is canActivate, which gets called before loading the component and other is canDeactivate, which gets called before unloading the component.

Here is the output



Yeeha… that’s all we have done to implement the unsaved data guard.

 [ If you are preparing for an interview please click here - angular interview questions and answers ]

Comments