I have an implementation of Angular RouteGuard CanDeactivate. It works for input fields but its not working for drop downs. I also want to display my own modal and I can't get it work correctly.
This is the Html template code:
<div *ngIf="!this.overallStructure" class="page-header">
<div class="col">
<h1>Loading please wait...</h1>
</div>
</div>
<div *ngIf="this.overallStructure" class="content_wrapper">
<div class="container history-tabs">
<div class="page-header">
<div class="row">
<div class="col-md-12">
<h1 class="text-center mx-3 p-3">Account Structure - {{accountInfo?.name}}</h1>
</div>
</div>
</div>
<form #form="ngForm" class="form-group">
<div class="row container-fluid px-5">
<div class="col-md-6">
<mat-form-field class="mx-md-5 w-100" appearance="standard">
<mat-label>Contract Type *</mat-label>
<mat-select [(value)]="overallStructure.contractTypeId" class="product_type required"
(selectionChange)="OnContractTypeSelect($event)" name="contractTypeId" tabindex="1">
<mat-option *ngFor="let item of contractType" [value]="item.id">
{{item.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mx-md-5 w-100" appearance="standard">
<mat-label>Business Unit *</mat-label>
<input [(ngModel)]="businessUnitName" matInput name="businessUnitName" tabindex="3">
</mat-form-field>
<mat-form-field class="mx-md-5 w-100" appearance="standard">
<mat-label>Contract Period *</mat-label>
<mat-select [(value)]="overallStructure.contractPeriod" class="product_type required" name="contractPeriod"
tabindex="5">
<mat-option value="Accident Year">Accident Year</mat-option>
<mat-option value="Policy Year">Policy Year</mat-option>
</mat-select>
</mat-form-field>
</div>
<p>
<br>
</p>
</div>
<div class="row">
<div class="col-12">
<button title="Next" [disabled]="(disableNextButton())"
class="btn btn-sm ptl-button text-center expandOnSmallSize mx-1 btn-right"
(click)="navigateToLobSelection()" type="button" tabindex="16">Next</button>
<button (click)="onClickCancel()" class="btn btn-sm ptl-button text-center expandOnSmallSize mx-1 btn-right"
name="" tabindex="15">
Cancel</button>
<button [disabled]="!allMandatoryFieldsEntered()" (click)="saveOverallStructure()"
class="btn btn-sm ptl-button text-center expandOnSmallSize mx-1 btn-right" tabindex="14">
Save</button>
<button title="Previous" (click)="onClickBackBtn()"
class="btn btn-sm ptl-button text-center expandOnSmallSize mx-1 btn-right" type="button" tabindex="13">
Previous</button>
</div>
</div>
</form>
<P><br></P>
</div>
Html component code:
import { AccountInfo } from './../general/models/accountInfo';
import { MaskService } from './../../services/mask-service.service';
import { Component, OnInit, ViewChild } from '@angular/core';
import {PricingToolService} from '../../services/pricingtoolservice.service'
import { ActivatedRoute, Router } from '@angular/router';
import { OverallStructure } from '../general/models/overallstructure';
import createNumberMask from 'text-mask-addons/dist/createNumberMask';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SnackBarComponent } from '../snackBar/snackBar.component';
import { NgForm } from "@angular/forms";
import { FormCanDeactivate} from '../form-can-deactivate/form-can-deactivate';
@Component({
selector: 'app-overall-structure',
templateUrl: './overall-structure.component.html',
styleUrls: ['./overall-structure.component.scss']
})
export class OverallStructureComponent extends FormCanDeactivate implements OnInit {
@ViewChild('form')
form: NgForm;
constructor(public service: PricingToolService, private ActivatedRoute: ActivatedRoute,
private router: Router){
super();
}
ngOnInit(): void {
}
onClickCancel(){
this.service.openConfirmationDialog("Cancel will remove any edit done on the Account Structure screen, Do you want to continue ?")
.subscribe((response: any) => {
if (response){
this.ngOnInit();
}
});
}
}
CanDeactivte Guard Service:
import { RouterStateSnapshot, ActivatedRouteSnapshot, CanDeactivate, UrlTree } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
}
@Injectable()
export class CanDeactivateGuard implements
CanDeactivate<CanComponentDeactivate> {
canDeactivate(
component: CanComponentDeactivate,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
console.log("component.canDeactivate");
return component.canDeactivate ? component.canDeactivate() :
true;
}
}
Abstract FormComponent
import { Observable, of } from 'rxjs';
import { NgForm } from "@angular/forms";
import { MessageDialog } from '../message-dialog/messageDialog.component';
import { PricingToolService } from '../../services/pricingtoolservice.service';
import { HostListener } from "@angular/core";
import { Injectable } from '@angular/core';
@Injectable()
export abstract class FormCanDeactivate {
abstract get form() : NgForm;
canDeactivate() : boolean {
if(this.form.dirty){
if (confirm("You have unsaved changes! If you leave, your changes will be lost.")) {
return true;
} else {
return false;
}
}
return true;
}
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
if (!this.canDeactivate()) {
$event.returnValue = true;
} else {
console.log("In can deactivate abstract");
}
}
}
I'm not sure what I'm missing but I just need canDeactivate to work when the drop downs change. I am using Angular 13.
You can just keep the logic for deactivation in the component itself.
Simple Component template:
<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<label for="select1">Select</label>
<select
name="select1"
id="select1"
class="form-control"
[(ngModel)]="formData.select1"
>
<option value="1">1</option>
<option value="2">2</option>
</select>
<button>Submit</button>
</form>
In your component Script:
@ViewChild('form')
formElement: NgForm
formData = {
select1: "1"
}
get isDirty() {
return this.formElement.dirty
}
This will however also return true if you changed a value and then change it back to the original value.
If you want to avoid that you can keep a copy of the original data and change up the isDirty
getter to compare the 2 objects:
originalFormData = {
select1: "1"
}
formData = { ...this.originalFormData }
get isDirty() {
return !(JSON.stringify(this.originalFormData) === JSON.stringify(this.formData))
}
Your guard:
@Injectable({
providedIn: 'root'
})
export class FormGuard implements CanDeactivate<FormComponent> {
canDeactivate(
component: FormComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot):
boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
if (component.isDirty) {
return confirm(`Navigate away and lose all changes?`)
}
return true
}
}
Here is the above example in a Stackblitz.