10

I have a page that has a form that checks if the user has unsaved changes before navigating away from it.

The problem is that even with a preventDefault() and return false, the user is still able to click away from the component.

Is there a way to prevent the ngOnDestroy or click event from happening?

Note: User is not going to a different route, just another tab from the same component.

ngOnDestroy() {
    if (this.myForm.dirty) {
        let save = confirm('You are about to leave the page with unsaved changes. Do you want to continue?');
        if (!save) {
            window.event.preventDefault();
            return false;
        }
    }
}
Akkusativobjekt
  • 2,005
  • 1
  • 21
  • 26
franco
  • 667
  • 4
  • 7
  • 20
  • you can just disable the button which navigate away without saving changes. ngOnDestroy is not a guard that prevent destroying from happening, it just do some logic before destroying the component, such as unsubscribing for observables or close files...etc. – Raed Khalaf Jul 11 '17 at 07:09
  • you may be looking for auth guards. –  Jul 11 '17 at 07:10
  • @torazaburo I know guards only work with routes. I noted that the user is still in the same route but clicking to a different tab. – franco Jul 11 '17 at 07:14
  • @RaedKhalaf I don't think disabling navigation buttons look good visually. I would rather give the user the option to proceed if they wanted to. – franco Jul 11 '17 at 07:16
  • ok, then don't disable it, when the user click the button which navigate away without saving, just add ' discard = confirm("Do you want to discard changes") ', if discard is true navigate away, other don't navigate away. – Raed Khalaf Jul 11 '17 at 07:18
  • That's my problem, even if the user hits cancel it still navigates away. My code block should show you how I handled it. – franco Jul 11 '17 at 07:25
  • How about a document click listner, if it's not clicked inside the form you check for form validity and show the message? – Vega Jul 11 '17 at 07:49

2 Answers2

9

You would wanna use CanDeactivate guard.
Here is an example:

1. Create a guard service/provider.

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

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

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {

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

}

2. Add your guard service (CanDeactivateGuard) in your app.module providers

providers: [
    CanDeactivateGuard,
]

3. Update your routing, to something like this:

{
    path: "pipeline/:id",
    component: OnePipelineComponent,
    canDeactivate: [CanDeactivateGuard],
  },

4. Implement canDeactivate method in your component where you want to prevent ngOnDestroy. In my case, it was OnePipelineComponent as mentioned in the route above.

canDeactivate() {
    console.log('i am navigating away');

    // you logic goes here, whatever that may be 
    // and it must return either True or False
    if (this.user.name !== this.editName) {
      return window.confirm('Discard changes?');
    }

    return true;
}

Note: Follow steps 1 & 2 only once, obviously, & just repeat steps 3 & 4 for every other component where you want the same behaviour, meaning, to prevent ngOnDestroy or to do something before a component can be destroyed).

Check out these articles for code sample & an explanation for the code written above. CanDeactivate & CanDeactivate Guard Example

Junaid
  • 4,682
  • 1
  • 34
  • 40
5

You are mixing two concepts - navigating away means to a new route. The correct angular solution to this is implementing a CanDeactivateGuard. The docs on that are here.

A stack overflow question with answers is here.

In your situation the user is not navigating to a new page (ie. the route does not change). They are simply clicking on a new tab.

Without seeing more code, it's hard to know if both tabs are part of the same form or in two different forms.

But regardless, you need a click handler on the other tab button and in THAT click handler you need to check if the data for the current tab is unsaved (ie. if that tab is one single form, is that form dirty?).

So basically move the code you posted from ngOnDestroy and into that click handler.

rmcsharry
  • 5,363
  • 6
  • 65
  • 108