Home > Software engineering >  How to Make the location.back() trigger CanDeactivate? ( in Angular 13)
How to Make the location.back() trigger CanDeactivate? ( in Angular 13)

Time:06-04

I saw a similar question created about five years ago, but my question is a little different:

I wrote a breadcrumb for whole app component, as a part of my outermost layout.

In breadcrumb, I used 'location.back()' to make a back button:

    <i
      *ngIf="this.urlLevels > 1"
      nz-icon
      nzType="arrow-left"
      nzTheme="outline"
      (click)="back()"
    ></i>
import { Location } from '@angular/common';
 
constructor(private location: Location) {}

  back() {
    this.location.back();
  }

My App Routes like:

  {
    path: '',
    redirectTo: 'blog',
    pathMatch: 'full',
  },
  {
    path: 'login',
    component: LoginComponent,
  },
  {
    path: '',
    canActivateChild: [RouteGuard],
    children: [
      {
        path: 'blog',
        component: LayoutComponent, // breadcrumb is a part of this component
        children: [
          {
            path: '',
            loadChildren: () => import('src/app/blog/blog.module').then((m) => m. BlogModule),
          },
        ],
        data: {
          icon: '',
          breadcrumb: 'Blog'
        },
      },
    ],
  },
  {
    path: '**',
    component: NotFoundComponent,
  },

The routes of BlogModule like:

  { path: '', redirectTo: 'bloglist', pathMatch: 'full' },
  {
    path: 'bloglist',
    component: BlogListComponent,
    data: {
      breadcrumb: '',
    },
  },
  {
    path: 'createblog',
    component: CreateBlogComponent,
    data: {
      breadcrumb: 'CreateBlog',
    },
    canDeactivate: [BeforeLeaveGuard],
  },

The BeforeLeaveGuard:

import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';

import { CreateBlogComponent } from './create-blog.component';


@Injectable()
export class BeforeLeaveGuard implements CanDeactivate< CreateBlogComponent > {
  canDeactivate(component: CreateBlogComponent): Observable<boolean> | boolean {
    component.openDialog(); // I open a leave-confirm dialog here
    return component.confirmed; // result of confirm dialog
  }
}

The question is : When I hit the back button, my CanDeactivate doesn't work, my page changed just like there isn't a guard.

I saw two methods of router in http://angular.io/api/router/Router, they are: initialNavigation(), setUpLocationChangeListener()

It seems they can help me to solve my problem. I tried to use them in my BlogModule, but they both didn't work (nothing happened, my page still changed successfully, ignored all my settings).

What is the problem? T_T

I need your help.

CodePudding user response:

There are a few things going on here, but what you're trying to do is possible.

1. Waiting for a dialog is an asynchronous operation

In your CanDeactivate code here:

    canDeactivate(component: CreateBlogComponent): Observable<boolean> | boolean {
      component.openDialog(); // I open a leave-confirm dialog here
      return component.confirmed; // result of confirm dialog
    }

you open the dialog, but all waiting for user response (or network response, etc) in Javascript (and Typescript) is async. You need to use a Promise, Observable, or some other callback to wait for and handle the result of the dialog being presented to the user. As written, your code will immediately execute the next line (return component.confirmed), which will return whatever the initial value of confirmed is.

You haven't included the implementation of openDialog(), so it's not clear what that looks like, but any dialog component will return a Promise or Observable to the eventual value, which you can return from canDeactivate directly (if it happens to be a Promise<boolean> or Observable<boolean>) or map it to a boolean via a map operation (Promise.then() for Promises, or pipe(map()) for Observable). For example, the Angular Material mat-dialog component returns an Obervable<boolean> from its open() method, and you could return that directly from canDeactivate.

2. There's a bug in Angular's browser history management

See here for a related StackOverflow question and here for the bug report. Basically, even if the navigation is denied by canDeactivate, it still updates the browser history and it gets into a bad state (where repeated attempts to go back will eventually navigate you past the previous page and probably away from the app completely). According to the report this is fixed, however I found that I still needed to use this "hidden" option to avoid the bug:

router.canceledNavigationResolution = 'computed';

I've written a Stackblitz that does what you've described, by waiting on the confirm dialog response (and implementing the router history fix) here: https://stackblitz.com/edit/angular-ivy-gaa2bh?file=src/app/before-leave-guard.ts

  • Related