Here is my canDeactivate guard and it works. But I dont want to call the guard when I use the submit button. Only when I navigate by any other means. How?
import { Injectable } from '@angular/core';
import { Router, CanDeactivate } from '@angular/router';
import { FormGroup } from '@angular/forms';
export interface FormComponent {
myForm: FormGroup;
}
@Injectable()
export class DirtyGuard implements CanDeactivate<FormComponent> {
constructor(private router: Router) {}
canDeactivate(component: FormComponent) {
console.log(component.myForm)
if (component.myForm.dirty ){
return confirm('You have unsaved changes. Are you sure you want to navigate away?');
}
return true;
}
}
<button md-raised-button [disabled]="!myForm.valid" type="submit" color="primary">
<i class="material-icons">arrow_forward</i>
Exposures: Currencies
</button>
[angular v.4 - untested on v.2, but should work]
The guard 'canDeactivate' is correctly getting called, but you just want to return true for the scenario where you've submitted, so how can we determine that? You already have a handle to the FormGroup, however this does not appear to have a property relating to submitted. As an alternative, you can obtain a handle to your form using the @ViewChild decorator within your component class, like so:
@ViewChild('myForm') myForm;
In order for this to work you'll have to add a local variable for your form, like so:
<form #myForm="ngForm" (ngSubmit)="onSubmit()">
you will then see a property on the myForm object called _submitted. This allows you to update your if condition to only show the confirm message if dirty && !submitted. e.g.:
if (this.myForm.form.dirty && !this.myForm._submitted ){
return confirm('You have unsaved changes. Are you sure you want to navigate away?');
}
return true;
I'm assuming you've already worked round this issue, based on the date posted, but this might serve to explain what was going on at least.
In our component we can trick alert according to our need by using canDeactivate method.
Component:
import { ComponentCanDeactivate } from './pending-changes.guard';
import { HostListener } from '@angular/core';
import { Observable } from 'rxjs/Observable';
export class MyComponent implements ComponentCanDeactivate {
@ViewChild('RegisterForm')
form: NgForm;
// @HostListener allows us to also guard against browser refresh, close, etc.
@HostListener('window:beforeunload')
canDeactivate(): Observable<boolean> | boolean {
// insert logic to check if there are pending changes here;
// returning true will navigate without confirmation
// returning false will show a confirm dialog before navigating away
return this.form.submitted || !this.form.dirty; // insert your code here for submit event
}
}
Guard:
import { CanDeactivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
export interface ComponentCanDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}
@Injectable()
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
return component.canDeactivate() ?
true :
// NOTE: this warning message will only be shown when navigating elsewhere within your angular app;
// when navigating away from your angular app, the browser will show a generic warning message
// see https://stackoverflow.com/questions/52044306/how-to-add-candeactivate-functionality-in-component
confirm('WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
}
}
Routes:
import { PendingChangesGuard } from './pending-changes.guard';
import { MyComponent } from './my.component';
import { Routes } from '@angular/router';
export const MY_ROUTES: Routes = [
{ path: '', component: MyComponent, canDeactivate: [PendingChangesGuard] },
];
Module:
import { PendingChangesGuard } from './pending-changes.guard';
import { NgModule } from '@angular/core';
@NgModule({
// ...
providers: [PendingChangesGuard],
// ...
})
export class AppModule {}
I assume that you are navigating away after the user hits the submit button. The problem with disabling the can deactivate guard when the form is valid, is that the user could accidentally hit cancel/back button, etc., after the form is completely filled out losing all of their work. In other words, just because the form is valid does not mean you would want to deactivate the can-deactivate guard.
If you are navigating away, you can set a property to "true" in the onSubmit function before the route navigation and use it in your guard logic.
Component (reactive forms)
export class MyComponent {
// ..
submitSuccess: boolean;
canDeactivate(): boolean | Observable<boolean> | Promise<boolean> {
if (!this.inputForm.dirty || this.submitSuccess ) {
return true;
}
return this.confirmDialogService.confirm('Discard changes?');
}
// ...
onSubmit() {
this.submitSuccess = true;
this.router.navigate(['my-page']);
}
}
Confirm Dialog Service
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ConfirmationDialogService {
/**
* Ask user to confirm an action. `message` explains the action and choices.
* Returns observable resolving to `true`=confirm or `false`=cancel
*/
confirm(message?: string): Observable<boolean> {
const confirmation = window.confirm(message || 'Are you sure? Your changes
will be lost.');
return of(confirmation);
}
}
Can Deactivate Guard
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements
CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
Routes
\\ ...
children: [
{ path: 'my-path', component: MyComponent, canDeactivate:
[CanDeactivateGuard] },
]},
// ..
Hope it helps!