Search code examples
angulartypescriptngx-bootstrapconfirm-dialogangular-guards

Angular use modal dialog in canDeactivate Guard service for unsubmitted changes (Form dirty)


In my Angular 4 application I have some components with a form, like this:

export class MyComponent implements OnInit, FormComponent {

  form: FormGroup;

  ngOnInit() {
    this.form = new FormGroup({...});
  }

they use a Guard service to prevent unsubmitted changes to get lost, so if the user tries to change route before it will ask for a confirmation:

import { CanDeactivate } from '@angular/router';
import { FormGroup } from '@angular/forms';

export interface FormComponent {
  form: FormGroup;
}

export class UnsavedChangesGuardService implements CanDeactivate<FormComponent> {
  canDeactivate(component: FormComponent) {
    if (component.form.dirty) {
      return confirm(
        'The form has not been submitted yet, do you really want to leave page?'
      );
    }

    return true;
  }
}

This is using a simple confirm(...) dialog and it works just fine.

However I would like to replace this simple dialog with a more fancy modal dialog, for example using the ngx-bootstrap Modal.

How can I achieve the same result using a modal instead?


Solution

  • I solved it using ngx-bootstrap Modals and RxJs Subjects.

    First of all I created a Modal Component:

    import { Component } from '@angular/core';
    import { Subject } from 'rxjs/Subject';
    import { BsModalRef } from 'ngx-bootstrap';
    
    @Component({
      selector: 'app-confirm-leave',
      templateUrl: './confirm-leave.component.html',
      styleUrls: ['./confirm-leave.component.scss']
    })
    export class ConfirmLeaveComponent {
    
      subject: Subject<boolean>;
    
      constructor(public bsModalRef: BsModalRef) { }
    
      action(value: boolean) {
        this.bsModalRef.hide();
        this.subject.next(value);
        this.subject.complete();
      }
    }
    

    here's the template:

    <div class="modal-header modal-block-primary">
      <button type="button" class="close" (click)="bsModalRef.hide()">
        <span aria-hidden="true">&times;</span><span class="sr-only">Close</span>
      </button>
      <h4 class="modal-title">Are you sure?</h4>
    </div>
    <div class="modal-body clearfix">
      <div class="modal-icon">
        <i class="fa fa-question-circle"></i>
      </div>
      <div class="modal-text">
        <p>The form has not been submitted yet, do you really want to leave page?</p>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-default" (click)="action(false)">No</button>
      <button class="btn btn-primary right" (click)="action(true)">Yes</button>
    </div>
    

    Then I modified my guard using a Subject, now it look like this:

    import { CanDeactivate } from '@angular/router';
    import { FormGroup } from '@angular/forms';
    import { Injectable } from '@angular/core';
    import { Subject } from 'rxjs/Subject';
    import { BsModalService } from 'ngx-bootstrap';
    
    import { ConfirmLeaveComponent } from '.....';
    
    export interface FormComponent {
      form: FormGroup;
    }
    
    @Injectable()
    export class UnsavedChangesGuardService implements CanDeactivate<FormComponent> {
    
      constructor(private modalService: BsModalService) {}
    
      canDeactivate(component: FormComponent) {
        if (component.form.dirty) {
          const subject = new Subject<boolean>();
    
          const modal = this.modalService.show(ConfirmLeaveComponent, {'class': 'modal-dialog-primary'});
          modal.content.subject = subject;
    
          return subject.asObservable();
        }
    
        return true;
      }
    }
    

    In app.module.ts file go to the @NgModule section and add the ConfirmLeaveComponent component to entryComponents.

    @NgModule({
      entryComponents: [
        ConfirmLeaveComponent,
      ]
    })