Displaying confirmation dialog with guarded routes

In Angular 2+, you can protect routes with guards. The most likely used guard types are CanActivate and CanDeactivate. The first guard type decides if a route can be activated, and the second one decides if a route can be deactivated. In this section, we will discuss CanDeactivate. This is an interface having only one method canDeactivate:

export interface CanDeactivate<T> {
canDeactivate(component: T, route: ActivatedRouteSnapshot,
state: RouterStateSnapshot):
Observable<boolean> | Promise<boolean> | boolean;
}

This method can return Observable<boolean>, Promise<boolean>, or boolean. If the value of boolean is true, the user can navigate away from the route. If the value of boolean is false, the user will stay on the same view. If you want to protect your route from navigating away under some circumstances, you have to do three steps:

  1. Create a class that implements the CanDeactivate interface. The class acts as a guard, which will be checked by the router when navigating away from the current view. As you can see, the interface expects a generic component class. This is the current component rendered within the <router-outlet> tag.
  2. Register this guard as provider in a module annotated with @NgModule.
  3. Add this guard to the router configuration. A router configuration has the canDeactivate property where such guards can be added multiple times.
You might want to check out an example from the official Angular documentation
https://angular.io/docs/ts/latest/api/router/index/CanDeactivate-interface.html.

In this book, we would like to implement a typical use case where we will check if there are some unsaved input changes made by user. If the current view has unsaved input values and the user tries to navigate away to another view, a confirmation dialog should be shown. We will use ConfirmDialog from PrimeNG:

Now, hitting the Yes button leads to navigating to another view:

Hitting the No button prevents the the process of navigating from the current route. Let's create the first view with an input element, a submit button, and the <p-confirmDialog> component:

<h1>This is the first view</h1>

<form novalidate (ngSubmit)="onSubmit(f)" #f="ngForm">
<label for="username">Username:</label>
<input id="username" name="username" type="text"
pInputText [(ngModel)]="username"/>
<button type="submit" pButton label="Confirm"></button>
</form>

<p-confirmDialog header="Confirmation" icon="fa fa-question-circle"
width="400">
</p-confirmDialog>

The corresponding component for this template keeps the dirty state of the form, which indicates that the form is being edited:

export class FirstViewComponent {
dirty: boolean;
username: string;

constructor(private router: Router) { }

onSubmit(f: FormGroup) {
this.dirty = f.dirty;
this.router.navigate(['/chapter9/second-view']);
}
}

We will not implement any sophisticated algorithms in order to check if the input value was really changed. We just check the form's dirty state. If the form is not being edited, the navigation on submit should be fine. No need to ask the user about unsaved changes. Now, we have to inject the PrimeNG ConfirmationService into our guard implementation, which is required to display a confirmation dialog, and use it like this within the canDeactivate method:

this.confirmationService.confirm({
message: 'You have unsaved changes.
Are you sure you want to leave this page?',
accept: () => {
// logic to perform a confirmation
},
reject: () => {
// logic to cancel a confirmation
}
});

But there is a problem. The confirm method doesn't return required Observable<boolean>, Promise<boolean>, or boolean. The solution is to create and return an Observable object by invoking Observable.create(). The create method expects a callback with one parameter observer: Observer<boolean>. Now we need to do two steps:

  • Put the call this.confirmationService.confirm() into the callback's body.
  • Pass true or false to the subscriber by invoking observer.next(true) and observer.next(false) respectively. The subscriber is the PrimeNG's component ConfirmDialog which needs to be informed about user's choice.

The full implementation of the UnsavedChangesGuard is shown next:

@Injectable()
export class UnsavedChangesGuard implements
CanDeactivate<FirstViewComponent> {

constructor(private confirmationService: ConfirmationService) { }

canDeactivate(component: FirstViewComponent) {
// Allow navigation if the form is unchanged
if (!component.dirty) { return true; }

return Observable.create((observer: Observer<boolean>) => {
this.confirmationService.confirm({
message: 'You have unsaved changes.
Are you sure you want to leave this page?',
accept: () => {
observer.next(true);
observer.complete();
},
reject: () => {
observer.next(false);
observer.complete();
}
});
});
}
}

As we already said, the guard is registered in the router configuration:

{path: 'chapter9/first-view', component: FirstViewComponent, 
canDeactivate: [UnsavedChangesGuard]}

If you prefer Promise instead of Observable, you can return Promise as follows:

return new Promise((resolve, reject) => {
this.confirmationService.confirm({
message: "You have unsaved changes.
Are you sure you want to leave this page?",
accept: () => {
resolve(true);
},
reject: () => {
resolve(false);
}
});
});
The complete demo application with instructions is available on GitHub at
https://github.com/ova2/angular-development-with-primeng/tree/master/chapter9/guarded-routes.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.93.169