Skip to content

Commit

Permalink
fix(dialog): close all dialogs on popstate/hashchange
Browse files Browse the repository at this point in the history
Closes all of the open dialogs when the user goes forwards/backwards in history.

Fixes angular#2601.
  • Loading branch information
crisbeto committed Jan 31, 2017
1 parent 08e9d70 commit 3e7423d
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
31 changes: 29 additions & 2 deletions src/lib/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {NgModule, Component, Directive, ViewChild, ViewContainerRef, Injector} from '@angular/core';
import {Location} from '@angular/common';
import {SpyLocation} from '@angular/common/testing';
import {MdDialogModule} from './index';
import {MdDialog} from './dialog';
import {OverlayContainer} from '../core';
Expand All @@ -22,6 +24,7 @@ describe('MdDialog', () => {

let testViewContainerRef: ViewContainerRef;
let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>;
let mockLocation: SpyLocation;

beforeEach(async(() => {
TestBed.configureTestingModule({
Expand All @@ -30,15 +33,17 @@ describe('MdDialog', () => {
{provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div');
return {getContainerElement: () => overlayContainerElement};
}}
}},
{provide: Location, useClass: SpyLocation}
],
});

TestBed.compileComponents();
}));

beforeEach(inject([MdDialog], (d: MdDialog) => {
beforeEach(inject([MdDialog, Location], (d: MdDialog, l: Location) => {
dialog = d;
mockLocation = l as SpyLocation;
}));

beforeEach(() => {
Expand Down Expand Up @@ -271,6 +276,28 @@ describe('MdDialog', () => {
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
});

it('should close all open dialogs when the user goes forwards/backwards in history', () => {
dialog.open(PizzaMsg);
dialog.open(PizzaMsg);

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);

mockLocation.simulateUrlPop('');

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
});

it('should close all open dialogs when the location hash changes', () => {
dialog.open(PizzaMsg);
dialog.open(PizzaMsg);

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);

mockLocation.simulateHashChange('');

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
});

describe('disableClose option', () => {
it('should prevent closing via clicks on the backdrop', () => {
dialog.open(PizzaMsg, {
Expand Down
26 changes: 18 additions & 8 deletions src/lib/dialog/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Injector, ComponentRef, Injectable, Optional, SkipSelf} from '@angular/core';
import {Location} from '@angular/common';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';

Expand Down Expand Up @@ -49,7 +50,14 @@ export class MdDialog {
constructor(
private _overlay: Overlay,
private _injector: Injector,
@Optional() @SkipSelf() private _parentDialog: MdDialog) { }
private _location: Location,
@Optional() @SkipSelf() private _parentDialog: MdDialog) {

// Close all of the dialogs when the user goes forwards/backwards in history or when the
// location hash changes. Note that this usually doesn't include clicking on links (unless
// the user is using the `HashLocationStrategy`).
_location.subscribe(() => this.closeAll());
}

/**
* Opens a modal dialog containing the given component.
Expand Down Expand Up @@ -77,12 +85,14 @@ export class MdDialog {
closeAll(): void {
let i = this._openDialogs.length;

while (i--) {
// The `_openDialogs` property isn't updated after close until the rxjs subscription
// runs on the next microtask, in addition to modifying the array as we're going
// through it. We loop through all of them and call close without assuming that
// they'll be removed from the list instantaneously.
this._openDialogs[i].close();
if (i > 0) {
while (i--) {
// The `_openDialogs` property isn't updated after close until the rxjs subscription
// runs on the next microtask, in addition to modifying the array as we're going
// through it. We loop through all of them and call close without assuming that
// they'll be removed from the list instantaneously.
this._openDialogs[i].close();
}
}
}

Expand Down Expand Up @@ -127,7 +137,7 @@ export class MdDialog {
config?: MdDialogConfig): MdDialogRef<T> {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
let dialogRef = <MdDialogRef<T>> new MdDialogRef(overlayRef);
let dialogRef = new MdDialogRef(overlayRef) as MdDialogRef<T>;

if (!dialogContainer.dialogConfig.disableClose) {
// When the dialog backdrop is clicked, we want to close it.
Expand Down
2 changes: 2 additions & 0 deletions src/lib/dialog/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';
import {
OverlayModule,
PortalModule,
Expand All @@ -17,6 +18,7 @@ import {

@NgModule({
imports: [
CommonModule,
OverlayModule,
PortalModule,
A11yModule,
Expand Down

0 comments on commit 3e7423d

Please sign in to comment.