diff --git a/src/lib/bottom-sheet/bottom-sheet-config.ts b/src/lib/bottom-sheet/bottom-sheet-config.ts index 17a89232fc43..8455f54ee578 100644 --- a/src/lib/bottom-sheet/bottom-sheet-config.ts +++ b/src/lib/bottom-sheet/bottom-sheet-config.ts @@ -39,4 +39,7 @@ export class MatBottomSheetConfig { /** Aria label to assign to the bottom sheet element. */ ariaLabel?: string | null = null; + + /** Whether the bottom sheet should close when the user goes backwards/forwards in history. */ + closeOnNavigation?: boolean = true; } diff --git a/src/lib/bottom-sheet/bottom-sheet-ref.ts b/src/lib/bottom-sheet/bottom-sheet-ref.ts index 9b7765884a31..7e718394aeb0 100644 --- a/src/lib/bottom-sheet/bottom-sheet-ref.ts +++ b/src/lib/bottom-sheet/bottom-sheet-ref.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {Location} from '@angular/common'; import {ESCAPE} from '@angular/cdk/keycodes'; import {OverlayRef} from '@angular/cdk/overlay'; -import {merge, Observable, Subject} from 'rxjs'; +import {merge, Observable, Subject, SubscriptionLike, Subscription} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {MatBottomSheetContainer} from './bottom-sheet-container'; @@ -35,7 +36,13 @@ export class MatBottomSheetRef { /** Result to be passed down to the `afterDismissed` stream. */ private _result: R | undefined; - constructor(containerInstance: MatBottomSheetContainer, private _overlayRef: OverlayRef) { + /** Subscription to changes in the user's location. */ + private _locationChanges: SubscriptionLike = Subscription.EMPTY; + + constructor( + containerInstance: MatBottomSheetContainer, + private _overlayRef: OverlayRef, + location?: Location) { this.containerInstance = containerInstance; // Emit when opening animation completes @@ -54,6 +61,7 @@ export class MatBottomSheetRef { take(1) ) .subscribe(() => { + this._locationChanges.unsubscribe(); this._overlayRef.dispose(); this._afterDismissed.next(this._result); this._afterDismissed.complete(); @@ -65,6 +73,14 @@ export class MatBottomSheetRef { _overlayRef.keydownEvents().pipe(filter(event => event.keyCode === ESCAPE)) ).subscribe(() => this.dismiss()); } + + if (location) { + this._locationChanges = location.subscribe(() => { + if (containerInstance.bottomSheetConfig.closeOnNavigation) { + this.dismiss(); + } + }); + } } /** diff --git a/src/lib/bottom-sheet/bottom-sheet.spec.ts b/src/lib/bottom-sheet/bottom-sheet.spec.ts index abd3cfe9750a..24ea42b96c65 100644 --- a/src/lib/bottom-sheet/bottom-sheet.spec.ts +++ b/src/lib/bottom-sheet/bottom-sheet.spec.ts @@ -21,6 +21,8 @@ import { TestBed, tick, } from '@angular/core/testing'; +import {Location} from '@angular/common'; +import {SpyLocation} from '@angular/common/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MatBottomSheet} from './bottom-sheet'; import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetConfig} from './bottom-sheet-config'; @@ -36,19 +38,24 @@ describe('MatBottomSheet', () => { let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture; + let mockLocation: SpyLocation; beforeEach(fakeAsync(() => { TestBed - .configureTestingModule({imports: [MatBottomSheetModule, BottomSheetTestModule]}) + .configureTestingModule({ + imports: [MatBottomSheetModule, BottomSheetTestModule], + providers: [{provide: Location, useClass: SpyLocation}] + }) .compileComponents(); })); - beforeEach(inject([MatBottomSheet, OverlayContainer, ViewportRuler], - (bs: MatBottomSheet, oc: OverlayContainer, vr: ViewportRuler) => { + beforeEach(inject([MatBottomSheet, OverlayContainer, ViewportRuler, Location], + (bs: MatBottomSheet, oc: OverlayContainer, vr: ViewportRuler, l: Location) => { bottomSheet = bs; overlayContainer = oc; viewportRuler = vr; overlayContainerElement = oc.getContainerElement(); + mockLocation = l as SpyLocation; })); afterEach(() => { @@ -349,6 +356,42 @@ describe('MatBottomSheet', () => { expect(spy).toHaveBeenCalledWith(1337); })); + it('should close the bottom sheet when going forwards/backwards in history', fakeAsync(() => { + bottomSheet.open(PizzaMsg); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeTruthy(); + + mockLocation.simulateUrlPop(''); + viewContainerFixture.detectChanges(); + flush(); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeFalsy(); + })); + + it('should close the bottom sheet when the location hash changes', fakeAsync(() => { + bottomSheet.open(PizzaMsg); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeTruthy(); + + mockLocation.simulateHashChange(''); + viewContainerFixture.detectChanges(); + flush(); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeFalsy(); + })); + + it('should allow the consumer to disable closing a bottom sheet on navigation', fakeAsync(() => { + bottomSheet.open(PizzaMsg, {closeOnNavigation: false}); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeTruthy(); + + mockLocation.simulateUrlPop(''); + viewContainerFixture.detectChanges(); + flush(); + + expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeTruthy(); + })); + describe('passing in data', () => { it('should be able to pass in data', () => { const config = { diff --git a/src/lib/bottom-sheet/bottom-sheet.ts b/src/lib/bottom-sheet/bottom-sheet.ts index 1a92237d87e3..8c6fc9f354fc 100644 --- a/src/lib/bottom-sheet/bottom-sheet.ts +++ b/src/lib/bottom-sheet/bottom-sheet.ts @@ -10,6 +10,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay'; import {ComponentPortal, ComponentType, PortalInjector, TemplatePortal} from '@angular/cdk/portal'; import {ComponentRef, Injectable, Injector, Optional, SkipSelf, TemplateRef} from '@angular/core'; +import {Location} from '@angular/common'; import {of as observableOf} from 'rxjs'; import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetConfig} from './bottom-sheet-config'; import {MatBottomSheetContainer} from './bottom-sheet-container'; @@ -41,7 +42,8 @@ export class MatBottomSheet { constructor( private _overlay: Overlay, private _injector: Injector, - @Optional() @SkipSelf() private _parentBottomSheet: MatBottomSheet) {} + @Optional() @SkipSelf() private _parentBottomSheet: MatBottomSheet, + @Optional() private _location?: Location) {} open(component: ComponentType, config?: MatBottomSheetConfig): MatBottomSheetRef; @@ -54,7 +56,7 @@ export class MatBottomSheet { const _config = _applyConfigDefaults(config); const overlayRef = this._createOverlay(_config); const container = this._attachContainer(overlayRef, _config); - const ref = new MatBottomSheetRef(container, overlayRef); + const ref = new MatBottomSheetRef(container, overlayRef, this._location); if (componentOrTemplateRef instanceof TemplateRef) { container.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null!, {