10

I am successfully using canDeactivate to provide a warning message if a user navigates from a page with a dirty form:

The code that I am using is here:

    is_submit = false;
   canDeactivate() {
       //https://scotch.io/courses/routing-angular-2-applications/candeactivate
        if (this.myForm.dirty == true && this.is_submit==false){
            return window.confirm('Discard changes?');
        } //end if
        return true
    } // end  canDeactivate

This is where I got the code:

https://scotch.io/courses/routing-angular-2-applications/candeactivate

However I would like to use a angular2 Dialog. Here is my code:

    //ts for the main component

    is_submit = false;
   canDeactivate() {
        if (this.myForm.dirty == true && this.is_submit==false){
            const config = new MdDialogConfig();
            config.disableClose=true;
            let dialogRef = this.dialog.open(DialogCanDeactive,config);
            dialogRef.afterClosed().subscribe(result => {
                if (result=='cancel'){
                    return false;
                } 
                if (result=='save'){
                    return true;
                } 
                if (result=='discard'){
                    return true;
                } 
            }); //end dialogRef
        } //end if
        return true
    }

 ///Code for the dialog

@Component({
  selector: 'can_deactive_dialog',
  template: `
<div>
    <button md-raised-button (click)="dialogRef.close('cancel')">Cancel</button>
    <button md-raised-button (click)="dialogRef.close('save')">Save Changes</button>
    <button md-raised-button (click)="dialogRef.close('discard')">Discard Changes</button>
</div>
`,
})
export class DialogCanDeactive {


  constructor(public dialogRef: MdDialogRef<DialogCanDeactive>) {} //end constructor

}

What happens when i navigate away is this:

1) I go to the page where a navigate

2) the Dialog then show..

How to have the Dialog block like the below code?

    window.confirm('Discard changes?')
FAISAL
  • 33,618
  • 10
  • 97
  • 105
Tampa
  • 75,446
  • 119
  • 278
  • 425

3 Answers3

13

canDeactivate method can also return a Promise or Observable. You should return that and resolve the promise or emit a value on the observable with the result that you want.

In your specific example you can return the observable from the afterClosed method instead of subscribing to it, and just map it to a boolean:

return dialogRef.afterClosed().map(result => {
                if (result=='cancel'){
                    return false;
                } 
                if (result=='save'){
                    return true;
                } 
                if (result=='discard'){
                    return true;
                } 
            }).first();

Also I would move out this logic from the guard, for example in the component and just call a method from there.

Adrian Fâciu
  • 12,414
  • 3
  • 53
  • 68
  • I tried your code... when I click cancel it stays on the page. Same thing if a click discard which should navigate away from the the page since it returns true – Tampa Jul 22 '17 at 13:17
  • Might need to close the observable so the router does not wait for further values. Have a look here: https://stackoverflow.com/questions/38524161/return-observable-in-candeactivate-not-working I'll update my answer with that. – Adrian Fâciu Jul 22 '17 at 15:03
  • only has add remove and unscubcribe as methods... first does not work since no method – Tampa Jul 23 '17 at 05:02
  • You need to import first in order to use it: import 'rxjs/add/operator/first'; – Adrian Fâciu Jul 23 '17 at 06:26
  • I did the import -- (159,16): Property 'first' does not exist on type 'Subscription'. Thats when I run..in vcode I get a red underline – Tampa Jul 23 '17 at 07:36
  • [ts] Property 'first' does not exist on type 'Subscription'. any – Tampa Jul 23 '17 at 07:44
  • I tried this ..I did not get an error but still stayed on page when I clicked discard.. dialogRef.afterClosed().first().subscribe(..... – Tampa Jul 23 '17 at 07:47
  • I also tried dialogRef.afterClosed().take(1) same results – Tampa Jul 23 '17 at 07:51
  • You don't need to subscribe to the observable anymore (this is why you get the error with no first method), just use map instead where you return true/false and chain first() or take(1) after that. – Adrian Fâciu Jul 23 '17 at 08:10
  • Just a clean up in code: `return result === 'discard' || result === 'save';` – developer033 Aug 25 '17 at 02:18
4

Updated version for RXJS 6+ :

return dialogRef.afterClosed().pipe(map(result => {
    if (result === 'cancel') {
        return false;
    }
    if (result === 'save') {
        return true;
    }
    if (result === 'discard') {
        return true;
    }
}), first());

see https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md

Here in particular : https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md#howto-convert-to-pipe-syntax

Florian Motteau
  • 3,467
  • 1
  • 22
  • 42
1
 Nothing more just do it

**Guard**

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } 
from '@angular/router';
import { Observable} from 'rxjs';
import { ExampleComponent } from '../components/example.component';
import { MatDialog } from '@angular/material/dialog'; 

@Injectable({
  providedIn: 'root'
})
export class ConfirmationDeactivateGuard implements CanDeactivate<ExampleComponent> {
  constructor(private dialog: MatDialog,
    private router: Router){}
    canDeactivate(
      component: ExampleComponent,
      route: ActivatedRouteSnapshot,
      state: RouterStateSnapshot
    ): Observable<boolean> {
      return component.confirmationOnRouteChange();
  }
}

**In Your Component** //ExampleComponent.component.ts

confirmationOnRouteChange() {
        const message = "Do you want to Leave ?"
        const dialogRef = this.matDialog.open(ConfirmationComponent,{
            width: '400px',
            data: { message }
        })
        return dialogRef.afterClosed();
    }
  • 4
    While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained. – borchvm Jan 22 '21 at 07:19
  • Thanks for sharing! This works much better than subscribing to confirmationOnRouteChange and returning of(true) or of(false) – Ben Petersen May 28 '21 at 22:57