1

Requirement: Showing a confirmation modal to the user when navigating to different component(UsersComponent) from the current component(ProductsComponent).If user prefers to leave, I have to navigate to some other component(SalesComponent) rather than UsersComponent.

To achieve the above am returning a Subject in canDeactivate() present in ProductsComponent. when user confirms to leave am emitting UrlTree(salesComponent path) in subject but its triggering canDeactivate recursively and ending up showing the modal multiple times.

Any Idea how to fix it ? or by using CanDeactivate guard how can I navigate to Other component than user intended?

app-routing.module.ts

const routes:Routes = [
  {path: 'users', component: UsersComponent},
  {path: 'products', component: ProductsComponent, canDeactivate:[CanExitGuard]},
  {path: 'sales', component: SalesComponent}
];

can-exit.guard.ts

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

export interface CanComponentDeactivate {
  canDeactivate: () => boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>;
}

@Injectable({ providedIn: 'root' })
export class CanExitGuard implements CanDeactivate<CanComponentDeactivate> {


  canDeactivate(component: CanComponentDeactivate,
                currentRoute: ActivatedRouteSnapshot,
                currentState: RouterStateSnapshot,
                nextState?: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {

         return component.canDeactivate ? component.canDeactivate() : true;
  }

}

products.component.ts, Please note I used Timeout and confirm dialog to simulate the asynchronous action.

import { Component, OnInit } from '@angular/core';
import { CanComponentDeactivate } from '../can-exit.guard';
import { Router,RouterStateSnapshot, UrlTree } from '@angular/router';
import { Subject } from 'rxjs';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit, CanComponentDeactivate {

navigationSelection$: Subject<boolean | UrlTree> = new Subject();

  constructor(private router: Router) { }

  ngOnInit() {
  }

  canDeactivate() {
  console.log('Can Deactivate triggered...');
  const isConfirmed: boolean = confirm("Do you want to leave products? Ok - Takes to sales page");
//simulate asynchronous
setTimeout(()=>{
  if(isConfirmed){
    console.log('navigates to sales page');

    const UrlTree: UrlTree = this.router.createUrlTree(['sales']);
    this.navigationSelection$.next(UrlTree);
  } else {
    console.log('navigation Canceled');
    this.navigationSelection$.next(false);
  }
}, 7000);

  return this.navigationSelection$;
}
}

StackBlitz Link

Uzma
  • 11
  • 4

1 Answers1

0

You could solve it by adding an additional check:

canDeactivate(
  component: CanComponentDeactivate,
  currentRoute: ActivatedRouteSnapshot,
  currentState: RouterStateSnapshot,
  nextState?: RouterStateSnapshot
): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
  return component.canDeactivate && nextState.url === '/users' ? component.canDeactivate() : true;
}

You want to make sure that you show the modal only when you navigate from /products to /users.

StackBlitz.

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • thanks @Andrei, firstly this is not the actual project, I just created this example to replicate my scenario. So, in my real project user can leave '/products' route for many other actions. So I can not put check like you mention. and moreover am reusing the same guard for other routes. – Uzma Sep 10 '20 at 23:55
  • @Uzma you could add in the “data” property what URLs to check and access them in the guard – Andrei Gătej Sep 11 '20 at 04:23