DEV Community

netsi1964 🙏🏻
netsi1964 🙏🏻

Posted on

Create a Guard in Angular to Handle Navigation Away from a Component with Unsaved Changes

Have you ever found yourself in a situation where you are about to leave a component and want to prevent navigation away from the component if there are unsaved changes? The solution to this issue is to create a guard in Angular that can handle navigation away from a component with unsaved changes. In this blog post, we will look at how to create a guard in Angular that uses an observable value and can be used in the router to handle navigation away from a component with unsaved changes.

TLDR:

In this blog post, we will create a guard in Angular which uses an observable value and can be used in the router to handle navigation away from a component with unsaved changes. We will also create an interface that components can implement to provide the guard with the necessary information it needs to determine if navigation should be blocked.

Introduction

When a user navigates away from a component, there could be situations where they have made some changes to the component, and these changes have not been saved. To prevent the loss of these changes, it's best to prompt the user to save these changes or lose them before navigating away from the component. To achieve this, we can create a guard in Angular that will handle the navigation away from the component.

Creating the Guard

To create the guard, we need to create a class that implements the CanDeactivate interface from the @angular/router package. The class will have a method canDeactivate, which will receive the component that the guard is protecting, the ActivatedRouteSnapshot, and the RouterStateSnapshot. The method will return a boolean or an observable of a boolean, indicating whether navigation away from the component should be allowed.

import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot } from '@angular/router';
import { Observable, take } from 'rxjs';

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CanDeactivateBlockNavigationIfChange<T extends BlockNavigationIfChange> implements CanDeactivate<T> {
  canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
    return new Observable<boolean>((observer) => {
      component.hasChanges$.pipe(take(1)).subscribe((hasChanges: boolean) => {
        const result = hasChanges ? confirm('You have changes which will be lost') : true;
        observer.next(result);
        observer.complete();
      });
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Creating the Interface

We also need to create an interface that components can implement to provide the guard with the necessary information it needs to determine if navigation should be blocked. The interface will define an observable property called hasChanges$, which will indicate whether there are unsaved changes in the component.

export interface BlockNavigationIfChange {
  hasChanges$: Observable<boolean>;
}
Enter fullscreen mode Exit fullscreen mode

Using the guard in the routing module

In the routing module, you can use the guard by adding it as a property in the route configuration for the component it should protect. To use the guard, you need to import it from the file you defined it in, and then add it to the canDeactivate property in the route configuration.

import { CanDeactivateBlockNavigationIfChange } from './guards/block-navigation-if-change.guard.ts';

const routes: Routes = [
  {
    path: 'edit',
    component: EditComponent,
    canDeactivate: [CanDeactivateBlockNavigationIfChange]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
Enter fullscreen mode Exit fullscreen mode

To use the guard in a component, you need to implement the BlockNavigationIfChange interface, and provide an Observable which emits the current state of the component's unsaved changes. In this example, we will create an EditComponent which implements this interface.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Subject } from 'rxjs';

import { BlockNavigationIfChange } from './can-deactivate-block-navigation-if-change.guard.ts';

@Component({
  selector: 'app-edit',
  template: `
    <form [formGroup]="editForm">
      <input type="text" formControlName="name">
    </form>
  `,
})
export class EditComponent implements OnInit, BlockNavigationIfChange {
  editForm = new FormGroup({
    name: new FormControl('')
  });
  hasChanges$ = new Subject<boolean>();

  ngOnInit(): void {
    this.editForm.valueChanges.subscribe(() => {
      this.hasChanges$.next(this.editForm.dirty);
    });
  }
}


Enter fullscreen mode Exit fullscreen mode

The EditComponent implements the BlockNavigationIfChange interface, which means that it must have a property called hasChanges$ of type Observable<boolean>. The component will then subscribe to changes in the form, and if the form becomes dirty, it will use the .next() method to emit a new value to the hasChanges$ subject, which will trigger an update in the guard. This allows the guard to stay updated with the latest changes made to the form and make an informed decision on whether navigation away from the form should be blocked or not.

How will it seem to the user?

When the user navigates to the EditComponent, the guard CanDeactivateBlockNavigationIfChange will be activated and start monitoring the form for changes. If the user makes any changes to the form and then tries to navigate away from the component, the guard will check the hasChanges$ subject and see if there are any pending changes. If there are changes, the guard will present a confirmation prompt to the user asking them if they are sure they want to leave the page and lose the changes.

This experience provides a seamless and intuitive way for the user to understand when they have unsaved changes and protects their data by preventing accidental navigation away from the page. The guard allows the user to either stay on the page and continue making changes, or confirm that they want to leave and discard the changes. This experience ensures that the user is always in control of their data and reduces the risk of losing any important information.

Conclusion

By using the CanDeactivate interface and the Observable type, you can create a guard in Angular which can be used in your router to handle navigation away from a component which has unsaved changes. This is a useful pattern for preventing the loss of data when a user navigates away from a component.

Disclaimer: This blog was generated using OpenAI chat robot, guided by Netsi1964 and with a few edits by him.

Top comments (1)

Collapse
 
andriesn profile image
AndriesN

Hi, thanks for creating this! I have a question though, because I cannot seem to understand how you can access the component in the canDeactivate method. component seems always to be null in my case. Any suggestions?