Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2: Close ng-bootstrap modal with browser back event

My Angular2 app uses a ng-bootstrap modal to show some result charts in detail. For this reason i resized the modal to nearly fullscreen (only margin: 20px left). This causes some users to use the browser back button instead of the close button on the top right or bottom of the page.

What i'm trying now is to cancel the default browser back event and close the modal instead when the event is called.

I'm using some code from here as code base to listen to the browser event and extended it with some stuff:

import { PlatformLocation } from '@angular/common'

(...)

modalRef: NgbModalRef;

constructor(location: PlatformLocation) {

    location.onPopState(() => {

        console.log('pressed back!');

        // example for a simple check if modal is opened
        if(this.modalRef !== undefined) 
        {
            console.log('modal is opened - cancel default browser event and close modal');
            // TODO: cancel the default event

            // close modal
            this.modalRef.close();
        } 
        else 
        {
            console.log('modal is not opened - default browser event');
        }
    });
}

(...)
// some code to open the modal and store the reference to this.modalRef

The problem is that i do not know how and whether i can cancel the default back event.

location.onPopState((event) => {

    event.preventDefault();

});

This actually does not work. Same for this solution. Maybe i can insert some "fake" history stack when open up the modal ?!

For AngularJS 1.x it seems actually working: https://stackoverflow.com/a/33454993/3623608

like image 654
Tommy Avatar asked Mar 07 '17 18:03

Tommy


4 Answers

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) 
  }
];
like image 109
kdaShivantha Avatar answered Oct 18 '22 09:10

kdaShivantha


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.

like image 24
Tommy Avatar answered Oct 18 '22 07:10

Tommy


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

like image 1
Aravind Avatar answered Oct 18 '22 08:10

Aravind


As far as I know, I think the answer to the OP's salient point:

"What i'm trying now is to cancel the default browser back event"

... is that you can't cancel the back button event. As far as I know this is a browser useability restriction.

like image 1
Aardvark Avatar answered Oct 18 '22 08:10

Aardvark