Angular2: Close ng-bootstrap modal with browser back event

ng-bootstrap modal, now has dismissAll()

https://ng-bootstrap.github.io/#/components/modal/api#NgbModal

u can close all open modals by using a route-guard when ng-router detects a change (including browser-back)

export class NgbModalDismissAllGuard implements CanActivateChild {
  constructor(private modalService: NgbModal) {}

  canActivateChild(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    if (this.modalService.hasOpenModals()) this.modalService.dismissAll();
    return true;
  }
}

and in router-config..

const routes: Routes = [
  { 
    path: '', 
    canActivateChild: [NgbModalDismissAllGuard], 
    loadChildren: () => import('./Custom.Module').then(m => m.CustomModule) 
  }
];

On Destroy Lifecycle hook will fix your problem.

export class AppComponent implements OnDestroy{
  @ViewChild('childModal') childModal :CommonModalComponent;
  constructor(private viewContainerRef: ViewContainerRef) {
  }
   ngOnDestroy() {
    this.childModal.hide();
  }

}

LIVE DEMO


I actually solved my problem by inserting some "fake" history state when open up the modal. The function for modal opening and modal closing looks like the following:

modalRef: NgbModalRef;

open(content: NgbModal) {
    this.modalRef = this.modalService.open(content);

    // push new state to history
    history.pushState(null, null, 'modalOpened');

    this.modalRef.result.then((result) => {
        this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
        this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
    });
}

private getDismissReason(reason: any): string {
    // go back in history if the modal is closed normal (ESC, backdrop click, cross click, close click)
    history.back();

    if (reason === ModalDismissReasons.ESC) {
        return 'by pressing ESC';
    } else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
        return 'by clicking on a backdrop';
    } else {
        return  `with: ${reason}`;
    }
}

The open() and getDismissReason() functions are copied from https://ng-bootstrap.github.io/#/components/modal "Modal with default options". The important parts i added are pushing the new history state on modal open and go back in history on "normal" modal close. When we go back with the browser back button this function is not called and we go back in history automatically.

To ensure that the modal is closed on history back event you need the following lines:

constructor(private location: PlatformLocation, private modalService: NgbModal)
{
    location.onPopState((event) => {
        // ensure that modal is opened
        if(this.modalRef !== undefined) {
            this.modalRef.close();
        }
});

Conclusion: When the modal is opened we push a new history state (e.g. with the current page). If the modal is closed normally (using ESC, close button, ...) the history back event is manually triggered (we do not want to stack history states). If the history back event is triggered by browser we just need to close the modal if it is opened. The pushed history stack ensure that we stay on the same page.

Limitations: Adding a new history stack and going back in history also provide the opportunity to go forward in history (after closing the modal). This is not the desired behavior.