Skip to content

Commit 3e7423d

Browse files
committed
fix(dialog): close all dialogs on popstate/hashchange
Closes all of the open dialogs when the user goes forwards/backwards in history. Fixes #2601.
1 parent 08e9d70 commit 3e7423d

File tree

3 files changed

+49
-10
lines changed

3 files changed

+49
-10
lines changed

src/lib/dialog/dialog.spec.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from '@angular/core/testing';
1010
import {By} from '@angular/platform-browser';
1111
import {NgModule, Component, Directive, ViewChild, ViewContainerRef, Injector} from '@angular/core';
12+
import {Location} from '@angular/common';
13+
import {SpyLocation} from '@angular/common/testing';
1214
import {MdDialogModule} from './index';
1315
import {MdDialog} from './dialog';
1416
import {OverlayContainer} from '../core';
@@ -22,6 +24,7 @@ describe('MdDialog', () => {
2224

2325
let testViewContainerRef: ViewContainerRef;
2426
let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>;
27+
let mockLocation: SpyLocation;
2528

2629
beforeEach(async(() => {
2730
TestBed.configureTestingModule({
@@ -30,15 +33,17 @@ describe('MdDialog', () => {
3033
{provide: OverlayContainer, useFactory: () => {
3134
overlayContainerElement = document.createElement('div');
3235
return {getContainerElement: () => overlayContainerElement};
33-
}}
36+
}},
37+
{provide: Location, useClass: SpyLocation}
3438
],
3539
});
3640

3741
TestBed.compileComponents();
3842
}));
3943

40-
beforeEach(inject([MdDialog], (d: MdDialog) => {
44+
beforeEach(inject([MdDialog, Location], (d: MdDialog, l: Location) => {
4145
dialog = d;
46+
mockLocation = l as SpyLocation;
4247
}));
4348

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

279+
it('should close all open dialogs when the user goes forwards/backwards in history', () => {
280+
dialog.open(PizzaMsg);
281+
dialog.open(PizzaMsg);
282+
283+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);
284+
285+
mockLocation.simulateUrlPop('');
286+
287+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
288+
});
289+
290+
it('should close all open dialogs when the location hash changes', () => {
291+
dialog.open(PizzaMsg);
292+
dialog.open(PizzaMsg);
293+
294+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);
295+
296+
mockLocation.simulateHashChange('');
297+
298+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
299+
});
300+
274301
describe('disableClose option', () => {
275302
it('should prevent closing via clicks on the backdrop', () => {
276303
dialog.open(PizzaMsg, {

src/lib/dialog/dialog.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Injector, ComponentRef, Injectable, Optional, SkipSelf} from '@angular/core';
2+
import {Location} from '@angular/common';
23
import {Observable} from 'rxjs/Observable';
34
import {Subject} from 'rxjs/Subject';
45

@@ -49,7 +50,14 @@ export class MdDialog {
4950
constructor(
5051
private _overlay: Overlay,
5152
private _injector: Injector,
52-
@Optional() @SkipSelf() private _parentDialog: MdDialog) { }
53+
private _location: Location,
54+
@Optional() @SkipSelf() private _parentDialog: MdDialog) {
55+
56+
// Close all of the dialogs when the user goes forwards/backwards in history or when the
57+
// location hash changes. Note that this usually doesn't include clicking on links (unless
58+
// the user is using the `HashLocationStrategy`).
59+
_location.subscribe(() => this.closeAll());
60+
}
5361

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

80-
while (i--) {
81-
// The `_openDialogs` property isn't updated after close until the rxjs subscription
82-
// runs on the next microtask, in addition to modifying the array as we're going
83-
// through it. We loop through all of them and call close without assuming that
84-
// they'll be removed from the list instantaneously.
85-
this._openDialogs[i].close();
88+
if (i > 0) {
89+
while (i--) {
90+
// The `_openDialogs` property isn't updated after close until the rxjs subscription
91+
// runs on the next microtask, in addition to modifying the array as we're going
92+
// through it. We loop through all of them and call close without assuming that
93+
// they'll be removed from the list instantaneously.
94+
this._openDialogs[i].close();
95+
}
8696
}
8797
}
8898

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

132142
if (!dialogContainer.dialogConfig.disableClose) {
133143
// When the dialog backdrop is clicked, we want to close it.

src/lib/dialog/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {NgModule, ModuleWithProviders} from '@angular/core';
2+
import {CommonModule} from '@angular/common';
23
import {
34
OverlayModule,
45
PortalModule,
@@ -17,6 +18,7 @@ import {
1718

1819
@NgModule({
1920
imports: [
21+
CommonModule,
2022
OverlayModule,
2123
PortalModule,
2224
A11yModule,

0 commit comments

Comments
 (0)