Simple Unsaved Data Changes Guard in Angular 17+

Danioropezasoria
2 min readNov 12, 2023

--

In this article, we are going to learn how to take advantages of the features of the new versions of Angular for the use-case, “when a user wants to navigate to other routes having pending data changes in the current page”. To understand it better, a picture is worth a thousand words, a gif is worth a thousand images.

We will achieve this flow in a very easy way through a simple, elegant and reusable guard.

Let’s go for it! 😎🚀 (You can checkout the full code at the end of the articule).

Technologies and its versions

First of all, I want to tell you the technologies and versions that I am using:

  • Angular: 17.0.2
  • Angular CLI: 17.0.0
  • Node: 20.9.0
  • npm: 10.1.0
  • bootstrap: 5.3.2
  • @ng-bootstrap/ng-bootstrap: 16.0.0-rc.0

Implementation

Create an interface called DirtyComponent to force all the the classes that implement this interface to define the isDirty() method.

export interface DirtyComponent {
isDirty: () => boolean
}

Then we add this interface to the component we want to check whether or not it has pending changes in the forms before moving to another route in the navigation.

export class ProjectComponent implements OnInit, DirtyComponent {
public projectForm!: FormGroup;

constructor(private formBuilder: FormBuilder) { }

ngOnInit() {
this.projectForm = this.formBuilder.group({
name: new FormControl('The Water is Gold'),
country: new FormControl('Bolivia'),
isImportant: new FormControl(true)
});
}

isDirty(): boolean {
// Here you have to define what "dirty" means for your application.
return this.projectForm.dirty;
}
}

All right, let’s define the hasUnsavedChangesGuard guard.

export const hasUnsavedChangesGuard = (async (component: DirtyComponent) => {
const isDirty = component.isDirty();
let shouldNavigate = true;
if (isDirty) {
const modalService = inject(NgbModal);
const modalRef = modalService.open(UnsavedDataConfirmModal);
shouldNavigate = await modalRef.result;
}
return shouldNavigate;
});

In its implementation we call the isDirty() method and depending on its value we show the modal or not. If the response in the modal is positive, we allow navigation, otherwise we do not continue.

In the new versions of Angular, we support functional router guards, we can take advantage of this enhancement and inject() to create lightweight, ergonomic, and more composable guards compared with class-based guards [1].

It helps us reduce boilerplate code and have simpler components like the one we just created.

Perfect, now we add the guard to the routes, and that’s it!

export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{
path: 'projects',
component: ProjectComponent,
canDeactivate: [hasUnsavedChangesGuard]
},
{ path: 'about', component: AboutComponent },
];

As simple as that, isn’t it?

See the full code on GitHub

References

[1] Advancements in the Angular Router

https://blog.angular.io/advancements-in-the-angular-router-5d69ec4c032

--

--