angularangular13angular-route-guardscandeactivate

Angular Can Deactivate - Material Dropdown


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.


Solution

  • A Simple CanDeactivate Guard

    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.