Angular Router has this awesome feature called Route Guards. They let us stop a route transition. One of the most common use cases for a feature like this would be preventing unauthorized users from accessing a page with permissions checking.
While that is an awesome use case, there is a number of use cases that this can be helpful for too. One use case I use guards for is preventing a user from navigating away from a page if they made changes to a form and didn’t save. For this, we want to look at the CanDeactivate guard.
Let’s get started by defining an interface for our component to implement:
export interface ComponentCanDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}
now in our component we implement this interface like so:
@Component({
selector: 'my-app',
template: `
<h1>Whats your name?</h1>
<input type="text" (change)="isDirty = true" />
`
})
export class App implements ComponentCanDeactivate {
isDirty: boolean = false; canDeactivate(): boolean {
return !this.isDirty;
}
}
in this component we have a simple form that sets the isDirty
flag to true when the input value changes. Next we implement the ComponentCanDeactivate
method in our component that will return a boolean whether we can transition to the next page.
Ok now that we have a interface and a component that know how to tell us if they are dirty, lets implement the guard.
import { CanDeactivate } from '@angular/router';export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
if(component.canDeactivate()) {
return true;
} else {
confirm('You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
}
}
}
In the guard, we get a reference to the component and invoke our newly created canDeactivate
method. If the value returns false, we will prompt the user with a confirm box. Pretty slick, eh?
Next, what if a user refreshes the browser? Our router guard won’t prevent against that. To fix this, let’s add a HostListener
to listen for the window’s beforeunload
.
@Component({
selector: 'my-app',
template: `
<h1>Whats your name?</h1>
<input type="text" (change)="isDirty = true" />
`
})
export class App implements ComponentCanDeactivate {
isDirty: boolean = false; @HostListener('window:beforeunload')
canDeactivate(): boolean {
return !this.isDirty;
}
}
Pretty sweet huh? Special thanks to this StackOverflow post that helped me write this article!
I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!