diff --git a/src/lib/autocomplete/index.ts b/src/lib/autocomplete/index.ts index a9c4eec768fc..9ed105eb737a 100644 --- a/src/lib/autocomplete/index.ts +++ b/src/lib/autocomplete/index.ts @@ -15,6 +15,7 @@ export * from './autocomplete-trigger'; declarations: [MdAutocomplete, MdAutocompleteTrigger], }) export class MdAutocompleteModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdAutocompleteModule, diff --git a/src/lib/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle.ts index 330da2677a82..a253eb45a01a 100644 --- a/src/lib/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle.ts @@ -24,6 +24,7 @@ import { UniqueSelectionDispatcher, coerceBooleanProperty, DefaultStyleCompatibilityModeModule, + UNIQUE_SELECTION_DISPATCHER_PROVIDER, } from '../core'; /** Acceptable types for a button toggle. */ @@ -471,12 +472,14 @@ export class MdButtonToggle implements OnInit { DefaultStyleCompatibilityModeModule, ], declarations: [MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle], + providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER] }) export class MdButtonToggleModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdButtonToggleModule, - providers: [UniqueSelectionDispatcher] + providers: [] }; } } diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index 3de8265c0aef..91600e588f04 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -11,7 +11,6 @@ import { } from '@angular/core'; import {CommonModule} from '@angular/common'; import {MdRippleModule, coerceBooleanProperty, DefaultStyleCompatibilityModeModule} from '../core'; -import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; // TODO(jelbourn): Make the `isMouseDown` stuff done with one global listener. @@ -167,10 +166,11 @@ export class MdAnchor extends MdButton { declarations: [MdButton, MdAnchor], }) export class MdButtonModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdButtonModule, - providers: [ViewportRuler] + providers: [] }; } } diff --git a/src/lib/card/card.ts b/src/lib/card/card.ts index 2540804cf116..f7dbad0cb149 100644 --- a/src/lib/card/card.ts +++ b/src/lib/card/card.ts @@ -119,6 +119,7 @@ export class MdCardTitleGroup {} ], }) export class MdCardModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdCardModule, diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 94836ece0188..f89f3146df3a 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -17,7 +17,6 @@ import {CommonModule} from '@angular/common'; import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {MdRippleModule, DefaultStyleCompatibilityModeModule} from '../core'; -import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; /** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */ @@ -399,10 +398,11 @@ export class MdCheckbox implements ControlValueAccessor { declarations: [MdCheckbox], }) export class MdCheckboxModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdCheckboxModule, - providers: [ViewportRuler] + providers: [] }; } } diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index 0d3ff0b91261..3b4dea73c7d5 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -215,6 +215,7 @@ export class MdChipList implements AfterContentInit { declarations: [MdChipList, MdChip] }) export class MdChipsModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdChipsModule, diff --git a/src/lib/core/a11y/index.ts b/src/lib/core/a11y/index.ts index 14fb20375323..be37c92085c5 100644 --- a/src/lib/core/a11y/index.ts +++ b/src/lib/core/a11y/index.ts @@ -1,25 +1,22 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import {FocusTrap} from './focus-trap'; -import {LiveAnnouncer} from './live-announcer'; +import {LIVE_ANNOUNCER_PROVIDER} from './live-announcer'; import {InteractivityChecker} from './interactivity-checker'; import {CommonModule} from '@angular/common'; import {PlatformModule} from '../platform/index'; -export const A11Y_PROVIDERS = [LiveAnnouncer, InteractivityChecker]; - @NgModule({ imports: [CommonModule, PlatformModule], declarations: [FocusTrap], exports: [FocusTrap], + providers: [InteractivityChecker, LIVE_ANNOUNCER_PROVIDER] }) export class A11yModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: A11yModule, - providers: [ - PlatformModule.forRoot().providers, - A11Y_PROVIDERS, - ], + providers: [], }; } } diff --git a/src/lib/core/a11y/live-announcer.spec.ts b/src/lib/core/a11y/live-announcer.spec.ts index 7c9169af8761..a3c9f506f6c4 100644 --- a/src/lib/core/a11y/live-announcer.spec.ts +++ b/src/lib/core/a11y/live-announcer.spec.ts @@ -115,4 +115,3 @@ class TestApp { this.live.announce(message); } } - diff --git a/src/lib/core/a11y/live-announcer.ts b/src/lib/core/a11y/live-announcer.ts index 1cb134bdf2d2..a9dad6db3093 100644 --- a/src/lib/core/a11y/live-announcer.ts +++ b/src/lib/core/a11y/live-announcer.ts @@ -2,7 +2,8 @@ import { Injectable, OpaqueToken, Optional, - Inject + Inject, + SkipSelf, } from '@angular/core'; export const LIVE_ANNOUNCER_ELEMENT_TOKEN = new OpaqueToken('liveAnnouncerElement'); @@ -62,3 +63,17 @@ export class LiveAnnouncer { } } + +export function LIVE_ANNOUNCER_PROVIDER_FACTORY(parentDispatcher: LiveAnnouncer, liveElement: any) { + return parentDispatcher || new LiveAnnouncer(liveElement); +}; + +export const LIVE_ANNOUNCER_PROVIDER = { + // If there is already a LiveAnnouncer available, use that. Otherwise, provide a new one. + provide: LiveAnnouncer, + deps: [ + [new Optional(), new SkipSelf(), LiveAnnouncer], + [new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)] + ], + useFactory: LIVE_ANNOUNCER_PROVIDER_FACTORY +}; diff --git a/src/lib/core/compatibility/default-mode.ts b/src/lib/core/compatibility/default-mode.ts index 16596318c21b..cf1d4eba8237 100644 --- a/src/lib/core/compatibility/default-mode.ts +++ b/src/lib/core/compatibility/default-mode.ts @@ -71,6 +71,7 @@ export class MatPrefixEnforcer { }] }) export class DefaultStyleCompatibilityModeModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: DefaultStyleCompatibilityModeModule, diff --git a/src/lib/core/compatibility/no-conflict-mode.ts b/src/lib/core/compatibility/no-conflict-mode.ts index 2aa116f71fbb..75df64e22bf6 100644 --- a/src/lib/core/compatibility/no-conflict-mode.ts +++ b/src/lib/core/compatibility/no-conflict-mode.ts @@ -60,6 +60,7 @@ export class MdPrefixEnforcer { }], }) export class NoConflictStyleCompatibilityMode { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: NoConflictStyleCompatibilityMode, diff --git a/src/lib/core/coordination/unique-selection-dispatcher.ts b/src/lib/core/coordination/unique-selection-dispatcher.ts index 7aa15e8f4ac4..7816a424e5dd 100644 --- a/src/lib/core/coordination/unique-selection-dispatcher.ts +++ b/src/lib/core/coordination/unique-selection-dispatcher.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {Injectable, Optional, SkipSelf} from '@angular/core'; // Users of the Dispatcher never need to see this type, but TypeScript requires it to be exported. @@ -33,3 +33,15 @@ export class UniqueSelectionDispatcher { this._listeners.push(listener); } } + +export function UNIQUE_SELECTION_DISPATCHER_PROVIDER_FACTORY( + parentDispatcher: UniqueSelectionDispatcher) { + return parentDispatcher || new UniqueSelectionDispatcher(); +} + +export const UNIQUE_SELECTION_DISPATCHER_PROVIDER = { + // If there is already a dispatcher available, use that. Otherwise, provide a new one. + provide: UniqueSelectionDispatcher, + deps: [[new Optional(), new SkipSelf(), UniqueSelectionDispatcher]], + useFactory: UNIQUE_SELECTION_DISPATCHER_PROVIDER_FACTORY +}; diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 00ca0edfa45d..52c9cec19928 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -6,8 +6,7 @@ import {MdOptionModule} from './option/option'; import {MdRippleModule} from './ripple/ripple'; import {PortalModule} from './portal/portal-directives'; import {OverlayModule} from './overlay/overlay-directives'; -import {A11yModule, A11Y_PROVIDERS} from './a11y/index'; -import {OVERLAY_PROVIDERS} from './overlay/overlay'; +import {A11yModule} from './a11y/index'; // RTL @@ -71,8 +70,8 @@ export { AriaLivePoliteness, LiveAnnouncer, LIVE_ANNOUNCER_ELEMENT_TOKEN, + LIVE_ANNOUNCER_PROVIDER, } from './a11y/live-announcer'; - /** @deprecated */ export {LiveAnnouncer as MdLiveAnnouncer} from './a11y/live-announcer'; @@ -84,9 +83,9 @@ export {A11yModule} from './a11y/index'; export { UniqueSelectionDispatcher, - UniqueSelectionDispatcherListener + UniqueSelectionDispatcherListener, + UNIQUE_SELECTION_DISPATCHER_PROVIDER, } from './coordination/unique-selection-dispatcher'; - /** @deprecated */ export { UniqueSelectionDispatcher as MdUniqueSelectionDispatcher @@ -143,10 +142,11 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode ], }) export class MdCoreModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdCoreModule, - providers: [A11Y_PROVIDERS, OVERLAY_PROVIDERS], + providers: [], }; } } diff --git a/src/lib/core/observe-content/observe-content.ts b/src/lib/core/observe-content/observe-content.ts index efa57db98462..217416d41bdd 100644 --- a/src/lib/core/observe-content/observe-content.ts +++ b/src/lib/core/observe-content/observe-content.ts @@ -46,6 +46,7 @@ export class ObserveContent implements AfterContentInit, OnDestroy { declarations: [ObserveContent] }) export class ObserveContentModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: ObserveContentModule, diff --git a/src/lib/core/overlay/overlay-container.ts b/src/lib/core/overlay/overlay-container.ts index c227961a23ce..4e54646c8f4b 100644 --- a/src/lib/core/overlay/overlay-container.ts +++ b/src/lib/core/overlay/overlay-container.ts @@ -1,4 +1,5 @@ -import {Injectable} from '@angular/core'; +import {Injectable, Optional, SkipSelf} from '@angular/core'; + /** * The OverlayContainer is the container in which all overlays will load. @@ -30,3 +31,14 @@ export class OverlayContainer { this._containerElement = container; } } + +export function OVERLAY_CONTAINER_PROVIDER_FACTORY(parentContainer: OverlayContainer) { + return parentContainer || new OverlayContainer(); +}; + +export const OVERLAY_CONTAINER_PROVIDER = { + // If there is already an OverlayContainer available, use that. Otherwise, provide a new one. + provide: OverlayContainer, + deps: [[new Optional(), new SkipSelf(), OverlayContainer]], + useFactory: OVERLAY_CONTAINER_PROVIDER_FACTORY +}; diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index ff4b43772572..5f3232e14f26 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -295,12 +295,14 @@ export class ConnectedOverlayDirective implements OnDestroy { imports: [PortalModule], exports: [ConnectedOverlayDirective, OverlayOrigin, Scrollable], declarations: [ConnectedOverlayDirective, OverlayOrigin, Scrollable], + providers: [OVERLAY_PROVIDERS], }) export class OverlayModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: OverlayModule, - providers: OVERLAY_PROVIDERS, + providers: [], }; } } diff --git a/src/lib/core/overlay/overlay.ts b/src/lib/core/overlay/overlay.ts index 1e702d2b2d41..1105a869c52e 100644 --- a/src/lib/core/overlay/overlay.ts +++ b/src/lib/core/overlay/overlay.ts @@ -4,14 +4,16 @@ import { ApplicationRef, Injector, NgZone, + Provider, } from '@angular/core'; import {OverlayState} from './overlay-state'; import {DomPortalHost} from '../portal/dom-portal-host'; import {OverlayRef} from './overlay-ref'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; -import {ViewportRuler} from './position/viewport-ruler'; -import {OverlayContainer} from './overlay-container'; -import {ScrollDispatcher} from './scroll/scroll-dispatcher'; +import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler'; +import {OverlayContainer, OVERLAY_CONTAINER_PROVIDER} from './overlay-container'; +import {SCROLL_DISPATCHER_PROVIDER} from './scroll/scroll-dispatcher'; + /** Next overlay unique ID. */ let nextUniqueId = 0; @@ -88,10 +90,10 @@ export class Overlay { } /** Providers for Overlay and its related injectables. */ -export const OVERLAY_PROVIDERS = [ - ViewportRuler, - OverlayPositionBuilder, +export const OVERLAY_PROVIDERS: Provider[] = [ Overlay, - OverlayContainer, - ScrollDispatcher, + OverlayPositionBuilder, + VIEWPORT_RULER_PROVIDER, + SCROLL_DISPATCHER_PROVIDER, + OVERLAY_CONTAINER_PROVIDER, ]; diff --git a/src/lib/core/overlay/position/viewport-ruler.ts b/src/lib/core/overlay/position/viewport-ruler.ts index b4f3c82a5310..91c21a18a541 100644 --- a/src/lib/core/overlay/position/viewport-ruler.ts +++ b/src/lib/core/overlay/position/viewport-ruler.ts @@ -1,5 +1,4 @@ -import {Injectable} from '@angular/core'; - +import {Injectable, Optional, SkipSelf} from '@angular/core'; /** @@ -56,3 +55,14 @@ export class ViewportRuler { return {top, left}; } } + +export function VIEWPORT_RULER_PROVIDER_FACTORY(parentDispatcher: ViewportRuler) { + return parentDispatcher || new ViewportRuler(); +}; + +export const VIEWPORT_RULER_PROVIDER = { + // If there is already a ViewportRuler available, use that. Otherwise, provide a new one. + provide: ViewportRuler, + deps: [[new Optional(), new SkipSelf(), ViewportRuler]], + useFactory: VIEWPORT_RULER_PROVIDER_FACTORY +}; diff --git a/src/lib/core/overlay/scroll/scroll-dispatcher.ts b/src/lib/core/overlay/scroll/scroll-dispatcher.ts index d9f2c25d860f..e4f8b3864713 100644 --- a/src/lib/core/overlay/scroll/scroll-dispatcher.ts +++ b/src/lib/core/overlay/scroll/scroll-dispatcher.ts @@ -1,4 +1,4 @@ -import {Injectable, ElementRef} from '@angular/core'; +import {Injectable, ElementRef, Optional, SkipSelf} from '@angular/core'; import {Scrollable} from './scrollable'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; @@ -88,3 +88,13 @@ export class ScrollDispatcher { } } +export function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher: ScrollDispatcher) { + return parentDispatcher || new ScrollDispatcher(); +}; + +export const SCROLL_DISPATCHER_PROVIDER = { + // If there is already a ScrollDispatcher available, use that. Otherwise, provide a new one. + provide: ScrollDispatcher, + deps: [[new Optional(), new SkipSelf(), ScrollDispatcher]], + useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY +}; diff --git a/src/lib/core/platform/index.ts b/src/lib/core/platform/index.ts index 82ed0453ac7a..edb7dae39703 100644 --- a/src/lib/core/platform/index.ts +++ b/src/lib/core/platform/index.ts @@ -5,12 +5,15 @@ export * from './platform'; export * from './features'; -@NgModule({}) +@NgModule({ + providers: [Platform] +}) export class PlatformModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: PlatformModule, - providers: [Platform], + providers: [], }; } } diff --git a/src/lib/core/portal/portal-directives.ts b/src/lib/core/portal/portal-directives.ts index e58422c88c92..1502239211d6 100644 --- a/src/lib/core/portal/portal-directives.ts +++ b/src/lib/core/portal/portal-directives.ts @@ -130,6 +130,7 @@ export class PortalHostDirective extends BasePortalHost implements OnDestroy { declarations: [TemplatePortalDirective, PortalHostDirective], }) export class PortalModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: PortalModule, diff --git a/src/lib/core/projection/projection.ts b/src/lib/core/projection/projection.ts index 868fed354510..fb22b8672185 100644 --- a/src/lib/core/projection/projection.ts +++ b/src/lib/core/projection/projection.ts @@ -81,12 +81,13 @@ export class DomProjection { @NgModule({ exports: [DomProjectionHost], declarations: [DomProjectionHost], + providers: [DomProjection], }) export class ProjectionModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: ProjectionModule, - providers: [DomProjection] }; } } diff --git a/src/lib/core/ripple/ripple.ts b/src/lib/core/ripple/ripple.ts index 2f778fb1d856..6b1780364ae2 100644 --- a/src/lib/core/ripple/ripple.ts +++ b/src/lib/core/ripple/ripple.ts @@ -17,7 +17,7 @@ import { ForegroundRippleState, } from './ripple-renderer'; import {DefaultStyleCompatibilityModeModule} from '../compatibility/default-mode'; -import {ViewportRuler} from '../overlay/position/viewport-ruler'; +import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from '../overlay/position/viewport-ruler'; @Directive({ @@ -238,12 +238,14 @@ export class MdRipple implements OnInit, OnDestroy, OnChanges { imports: [DefaultStyleCompatibilityModeModule], exports: [MdRipple, DefaultStyleCompatibilityModeModule], declarations: [MdRipple], + providers: [VIEWPORT_RULER_PROVIDER], }) export class MdRippleModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdRippleModule, - providers: [ViewportRuler] + providers: [] }; } } diff --git a/src/lib/core/rtl/dir.ts b/src/lib/core/rtl/dir.ts index 92bce595ee75..05eaef60c2d2 100644 --- a/src/lib/core/rtl/dir.ts +++ b/src/lib/core/rtl/dir.ts @@ -52,6 +52,7 @@ export class Dir { declarations: [Dir] }) export class RtlModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: RtlModule, diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index 7983385e0131..38243742b63c 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -346,6 +346,68 @@ describe('MdDialog', () => { }); }); +describe('MdDialog with a parent MdDialog', () => { + let parentDialog: MdDialog; + let childDialog: MdDialog; + let overlayContainerElement: HTMLElement; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdDialogModule.forRoot(), DialogTestModule], + declarations: [ComponentThatProvidesMdDialog], + providers: [ + {provide: OverlayContainer, useFactory: () => { + overlayContainerElement = document.createElement('div'); + return {getContainerElement: () => overlayContainerElement}; + }} + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(inject([MdDialog], (d: MdDialog) => { + parentDialog = d; + + fixture = TestBed.createComponent(ComponentThatProvidesMdDialog); + childDialog = fixture.componentInstance.dialog; + fixture.detectChanges(); + })); + + afterEach(() => { + overlayContainerElement.innerHTML = ''; + }); + + it('should close dialogs opened by a parent when calling closeAll on a child MdDialog', () => { + parentDialog.open(PizzaMsg); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent) + .toContain('Pizza', 'Expected a dialog to be opened'); + + childDialog.closeAll(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent.trim()) + .toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent'); + }); + + it('should close dialogs opened by a child when calling closeAll on a parent MdDialog', () => { + childDialog.open(PizzaMsg); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent) + .toContain('Pizza', 'Expected a dialog to be opened'); + + parentDialog.closeAll(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent.trim()) + .toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child'); + }); +}); + @Directive({selector: 'dir-with-view-container'}) class DirectiveWithViewContainer { @@ -384,6 +446,14 @@ class ContentElementDialog { closeButtonAriaLabel: string; } +@Component({ + template: '', + providers: [MdDialog] +}) +class ComponentThatProvidesMdDialog { + constructor(public dialog: MdDialog) {} +} + // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_DIRECTIVES = [ diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index 496b972b8891..38c51ef6be59 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -1,4 +1,4 @@ -import {Injector, ComponentRef, Injectable} from '@angular/core'; +import {Injector, ComponentRef, Injectable, Optional, SkipSelf} from '@angular/core'; import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core'; import {extendObject} from '../core/util/object-extend'; @@ -19,10 +19,17 @@ import {MdDialogContainer} from './dialog-container'; */ @Injectable() export class MdDialog { + private _openDialogsAtThisLevel: MdDialogRef[] = []; + /** Keeps track of the currently-open dialogs. */ - private _openDialogs: MdDialogRef[] = []; + get _openDialogs(): MdDialogRef[] { + return this._parentDialog ? this._parentDialog._openDialogs : this._openDialogsAtThisLevel; + } - constructor(private _overlay: Overlay, private _injector: Injector) { } + constructor( + private _overlay: Overlay, + private _injector: Injector, + @Optional() @SkipSelf() private _parentDialog: MdDialog) { } /** * Opens a modal dialog containing the given component. diff --git a/src/lib/dialog/index.ts b/src/lib/dialog/index.ts index 1180255bd710..5db15376a443 100644 --- a/src/lib/dialog/index.ts +++ b/src/lib/dialog/index.ts @@ -2,13 +2,9 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import { OverlayModule, PortalModule, - OVERLAY_PROVIDERS, A11yModule, - InteractivityChecker, - Platform, - DefaultStyleCompatibilityModeModule, + DefaultStyleCompatibilityModeModule } from '../core'; - import {MdDialog} from './dialog'; import {MdDialogContainer} from './dialog-container'; import { @@ -41,13 +37,17 @@ import { MdDialogActions, MdDialogContent ], + providers: [ + MdDialog, + ], entryComponents: [MdDialogContainer], }) export class MdDialogModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdDialogModule, - providers: [MdDialog, OVERLAY_PROVIDERS, InteractivityChecker, Platform], + providers: [], }; } } diff --git a/src/lib/grid-list/grid-list.ts b/src/lib/grid-list/grid-list.ts index 6e974664ef76..1d04fd4f092e 100644 --- a/src/lib/grid-list/grid-list.ts +++ b/src/lib/grid-list/grid-list.ts @@ -156,6 +156,7 @@ export class MdGridList implements OnInit, AfterContentChecked { declarations: [MdGridList, MdGridTile, MdGridTileText], }) export class MdGridListModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdGridListModule, diff --git a/src/lib/icon/icon.ts b/src/lib/icon/icon.ts index 174d921cc2d2..47a47717e7d5 100644 --- a/src/lib/icon/icon.ts +++ b/src/lib/icon/icon.ts @@ -11,8 +11,11 @@ import { SimpleChange, ViewEncapsulation, AfterViewChecked, + Optional, + SkipSelf, } from '@angular/core'; -import {HttpModule} from '@angular/http'; +import {HttpModule, Http} from '@angular/http'; +import {DomSanitizer} from '@angular/platform-browser'; import {MdError, DefaultStyleCompatibilityModeModule} from '../core'; import {MdIconRegistry} from './icon-registry'; export {MdIconRegistry} from './icon-registry'; @@ -244,17 +247,30 @@ export class MdIcon implements OnChanges, OnInit, AfterViewChecked { } } +export function ICON_REGISTRY_PROVIDER_FACTORY( + parentRegistry: MdIconRegistry, http: Http, sanitizer: DomSanitizer) { + return parentRegistry || new MdIconRegistry(http, sanitizer); +}; + +export const ICON_REGISTRY_PROVIDER = { + // If there is already an MdIconRegistry available, use that. Otherwise, provide a new one. + provide: MdIconRegistry, + deps: [[new Optional(), new SkipSelf(), MdIconRegistry], Http, DomSanitizer], + useFactory: ICON_REGISTRY_PROVIDER_FACTORY, +}; @NgModule({ imports: [HttpModule, DefaultStyleCompatibilityModeModule], exports: [MdIcon, DefaultStyleCompatibilityModeModule], declarations: [MdIcon], + providers: [ICON_REGISTRY_PROVIDER], }) export class MdIconModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdIconModule, - providers: [MdIconRegistry], + providers: [], }; } } diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index adbe5cccf132..b3ee85047710 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -430,10 +430,11 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange ], }) export class MdInputModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdInputModule, - providers: PlatformModule.forRoot().providers, + providers: [], }; } } diff --git a/src/lib/list/list.ts b/src/lib/list/list.ts index 38abb30f6ec1..c6f2a5270c4d 100644 --- a/src/lib/list/list.ts +++ b/src/lib/list/list.ts @@ -84,6 +84,7 @@ export class MdListItem implements AfterContentInit { declarations: [MdList, MdListItem, MdListDivider, MdListAvatar], }) export class MdListModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdListModule, diff --git a/src/lib/menu/menu.ts b/src/lib/menu/menu.ts index fa2ed66c9ecb..468aaa691136 100644 --- a/src/lib/menu/menu.ts +++ b/src/lib/menu/menu.ts @@ -1,6 +1,6 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import {CommonModule} from '@angular/common'; -import {OverlayModule, OVERLAY_PROVIDERS, DefaultStyleCompatibilityModeModule} from '../core'; +import {OverlayModule, DefaultStyleCompatibilityModeModule} from '../core'; import {MdMenu} from './menu-directive'; import {MdMenuItem} from './menu-item'; import {MdMenuTrigger} from './menu-trigger'; @@ -18,10 +18,11 @@ export {MenuPositionX, MenuPositionY} from './menu-positions'; declarations: [MdMenu, MdMenuItem, MdMenuTrigger], }) export class MdMenuModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdMenuModule, - providers: OVERLAY_PROVIDERS, + providers: [], }; } } diff --git a/src/lib/module.ts b/src/lib/module.ts index c2480e6fb32d..eb0ebbebbcaf 100644 --- a/src/lib/module.ts +++ b/src/lib/module.ts @@ -118,6 +118,7 @@ export class MaterialRootModule { } exports: MATERIAL_MODULES, }) export class MaterialModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return {ngModule: MaterialRootModule}; } diff --git a/src/lib/progress-bar/progress-bar.ts b/src/lib/progress-bar/progress-bar.ts index 3bffe76deedb..34c286547560 100644 --- a/src/lib/progress-bar/progress-bar.ts +++ b/src/lib/progress-bar/progress-bar.ts @@ -91,6 +91,7 @@ function clamp(v: number, min = 0, max = 100) { declarations: [MdProgressBar], }) export class MdProgressBarModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdProgressBarModule, diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index ad5fa2a866b4..bba128b6c6b7 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -367,6 +367,7 @@ function getSvgArc(currentValue: number, rotation: number) { declarations: [MdProgressSpinner, MdSpinner], }) export class MdProgressSpinnerModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdProgressSpinnerModule, diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index ae2bbcc378bd..234190916d55 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -24,9 +24,10 @@ import { MdRippleModule, UniqueSelectionDispatcher, DefaultStyleCompatibilityModeModule, + UNIQUE_SELECTION_DISPATCHER_PROVIDER, } from '../core'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; -import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; +import {VIEWPORT_RULER_PROVIDER} from '../core/overlay/position/viewport-ruler'; /** @@ -508,13 +509,15 @@ export class MdRadioButton implements OnInit { @NgModule({ imports: [CommonModule, MdRippleModule, DefaultStyleCompatibilityModeModule], exports: [MdRadioGroup, MdRadioButton, DefaultStyleCompatibilityModeModule], + providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER, VIEWPORT_RULER_PROVIDER], declarations: [MdRadioGroup, MdRadioButton], }) export class MdRadioModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdRadioModule, - providers: [UniqueSelectionDispatcher, ViewportRuler], + providers: [], }; } } diff --git a/src/lib/select/index.ts b/src/lib/select/index.ts index a709f6792ead..5ebec6b9721f 100644 --- a/src/lib/select/index.ts +++ b/src/lib/select/index.ts @@ -4,7 +4,6 @@ import {MdSelect} from './select'; import {MdOptionModule} from '../core/option/option'; import { DefaultStyleCompatibilityModeModule, - OVERLAY_PROVIDERS, OverlayModule, } from '../core'; export * from './select'; @@ -17,10 +16,11 @@ export {fadeInContent, transformPanel, transformPlaceholder} from './select-anim declarations: [MdSelect], }) export class MdSelectModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdSelectModule, - providers: [OVERLAY_PROVIDERS] + providers: [] }; } } diff --git a/src/lib/sidenav/sidenav.ts b/src/lib/sidenav/sidenav.ts index f12e3c3c0f74..2425eeaeb2b6 100644 --- a/src/lib/sidenav/sidenav.ts +++ b/src/lib/sidenav/sidenav.ts @@ -21,7 +21,6 @@ import {A11yModule} from '../core/a11y/index'; import {FocusTrap} from '../core/a11y/focus-trap'; import {ESCAPE} from '../core/keyboard/keycodes'; import {OverlayModule} from '../core/overlay/overlay-directives'; -import {InteractivityChecker} from '../core/a11y/interactivity-checker'; /** Exception thrown when two MdSidenav are matching the same side. */ @@ -512,10 +511,11 @@ export class MdSidenavContainer implements AfterContentInit { declarations: [MdSidenavContainer, MdSidenav], }) export class MdSidenavModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdSidenavModule, - providers: [InteractivityChecker] + providers: [] }; } } diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 4afb8445bb64..41900bcc3000 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -337,12 +337,14 @@ class SlideToggleRenderer { imports: [FormsModule, DefaultStyleCompatibilityModeModule], exports: [MdSlideToggle, DefaultStyleCompatibilityModeModule], declarations: [MdSlideToggle], + providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}], }) export class MdSlideToggleModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdSlideToggleModule, - providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}] + providers: [] }; } } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index bf5d6c42b414..420c21cd4dc0 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -579,15 +579,14 @@ export class SliderRenderer { imports: [CommonModule, FormsModule, DefaultStyleCompatibilityModeModule], exports: [MdSlider, DefaultStyleCompatibilityModeModule], declarations: [MdSlider], - providers: [ - {provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}, - ], + providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}] }) export class MdSliderModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdSliderModule, - providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}] + providers: [] }; } } diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index 7156212b7f09..94ea75cc161b 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -5,7 +5,7 @@ import { TestBed, fakeAsync, flushMicrotasks, - tick, + tick } from '@angular/core/testing'; import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -307,7 +307,7 @@ describe('MdSnackBar', () => { let config = new MdSnackBarConfig(); config.duration = 250; let snackBarRef = snackBar.open('content', 'test', config); - snackBarRef.afterDismissed().subscribe(null, null, () => { + snackBarRef.afterDismissed().subscribe(() => { dismissObservableCompleted = true; }); @@ -322,6 +322,72 @@ describe('MdSnackBar', () => { })); }); +describe('MdSbackBar with parent MdSnackBar', () => { + let parentSnackBar: MdSnackBar; + let childSnackBar: MdSnackBar; + let overlayContainerElement: HTMLElement; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdSnackBarModule.forRoot(), SnackBarTestModule], + declarations: [ComponentThatProvidesMdSnackBar], + providers: [ + {provide: OverlayContainer, useFactory: () => { + overlayContainerElement = document.createElement('div'); + return {getContainerElement: () => overlayContainerElement}; + }} + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(inject([MdSnackBar], (sb: MdSnackBar) => { + parentSnackBar = sb; + + fixture = TestBed.createComponent(ComponentThatProvidesMdSnackBar); + childSnackBar = fixture.componentInstance.snackBar; + fixture.detectChanges(); + })); + + afterEach(() => { + overlayContainerElement.innerHTML = ''; + }); + + it('should close snackBars opened by parent when opening from child MdSnackBar', fakeAsync(() => { + parentSnackBar.open('Pizza'); + fixture.detectChanges(); + tick(1000); + + expect(overlayContainerElement.textContent) + .toContain('Pizza', 'Expected a snackBar to be opened'); + + childSnackBar.open('Taco'); + fixture.detectChanges(); + tick(1000); + + expect(overlayContainerElement.textContent) + .toContain('Taco', 'Expected parent snackbar msg to be dismissed by opening from child'); + })); + + it('should close snackBars opened by child when opening from parent MdSnackBar', fakeAsync(() => { + childSnackBar.open('Pizza'); + fixture.detectChanges(); + tick(1000); + + expect(overlayContainerElement.textContent) + .toContain('Pizza', 'Expected a snackBar to be opened'); + + parentSnackBar.open('Taco'); + fixture.detectChanges(); + tick(1000); + + expect(overlayContainerElement.textContent) + .toContain('Taco', 'Expected child snackbar msg to be dismissed by opening from parent'); + })); +}); + @Directive({selector: 'dir-with-view-container'}) class DirectiveWithViewContainer { constructor(public viewContainerRef: ViewContainerRef) { } @@ -346,6 +412,15 @@ class ComponentWithChildViewContainer { class BurritosNotification {} +@Component({ + template: '', + providers: [MdSnackBar] +}) +class ComponentThatProvidesMdSnackBar { + constructor(public snackBar: MdSnackBar) {} +} + + /** Simple component to open snack bars from. * Create a real (non-test) NgModule as a workaround forRoot * https://github.com/angular/angular/issues/10760 diff --git a/src/lib/snack-bar/snack-bar.ts b/src/lib/snack-bar/snack-bar.ts index f83c19ac28f0..24c746e24bcb 100644 --- a/src/lib/snack-bar/snack-bar.ts +++ b/src/lib/snack-bar/snack-bar.ts @@ -3,6 +3,8 @@ import { ModuleWithProviders, Injectable, ComponentRef, + Optional, + SkipSelf, } from '@angular/core'; import { ComponentType, @@ -12,9 +14,9 @@ import { OverlayRef, OverlayState, PortalModule, - OVERLAY_PROVIDERS, LiveAnnouncer, DefaultStyleCompatibilityModeModule, + LIVE_ANNOUNCER_PROVIDER, } from '../core'; import {CommonModule} from '@angular/common'; import {MdSnackBarConfig} from './snack-bar-config'; @@ -29,10 +31,31 @@ import {extendObject} from '../core/util/object-extend'; */ @Injectable() export class MdSnackBar { - /** A reference to the current snack bar in the view. */ - private _snackBarRef: MdSnackBarRef; + /** + * Reference to the current snack bar in the view *at this level* (in the Angular injector tree). + * If there is a parent snack-bar service, all operations should delegate to that parent + * via `_openedSnackBarRef`. + */ + private _snackBarRefAtThisLevel: MdSnackBarRef; + + /** Reference to the currently opened snackbar at *any* level. */ + get _openedSnackBarRef(): MdSnackBarRef { + return this._parentSnackBar ? + this._parentSnackBar._openedSnackBarRef : this._snackBarRefAtThisLevel; + } + + set _openedSnackBarRef(value: MdSnackBarRef) { + if (this._parentSnackBar) { + this._parentSnackBar._openedSnackBarRef = value; + } else { + this._snackBarRefAtThisLevel = value; + } + } - constructor(private _overlay: Overlay, private _live: LiveAnnouncer) {} + constructor( + private _overlay: Overlay, + private _live: LiveAnnouncer, + @Optional() @SkipSelf() private _parentSnackBar: MdSnackBar) {} /** * Creates and dispatches a snack bar with a custom component for the content, removing any @@ -50,18 +73,18 @@ export class MdSnackBar { // When the snackbar is dismissed, clear the reference to it. snackBarRef.afterDismissed().subscribe(() => { // Clear the snackbar ref if it hasn't already been replaced by a newer snackbar. - if (this._snackBarRef == snackBarRef) { - this._snackBarRef = null; + if (this._openedSnackBarRef == snackBarRef) { + this._openedSnackBarRef = null; } }); // If a snack bar is already in view, dismiss it and enter the new snack bar after exit // animation is complete. - if (this._snackBarRef) { - this._snackBarRef.afterDismissed().subscribe(() => { + if (this._openedSnackBarRef) { + this._openedSnackBarRef.afterDismissed().subscribe(() => { snackBarRef.containerInstance.enter(); }); - this._snackBarRef.dismiss(); + this._openedSnackBarRef.dismiss(); // If no snack bar is in view, enter the new snack bar. } else { snackBarRef.containerInstance.enter(); @@ -75,8 +98,8 @@ export class MdSnackBar { } this._live.announce(config.announcementMessage, config.politeness); - this._snackBarRef = snackBarRef; - return this._snackBarRef; + this._openedSnackBarRef = snackBarRef; + return this._openedSnackBarRef; } /** @@ -144,12 +167,14 @@ function _applyConfigDefaults(config: MdSnackBarConfig): MdSnackBarConfig { exports: [MdSnackBarContainer, DefaultStyleCompatibilityModeModule], declarations: [MdSnackBarContainer, SimpleSnackBar], entryComponents: [MdSnackBarContainer, SimpleSnackBar], + providers: [MdSnackBar, LIVE_ANNOUNCER_PROVIDER] }) export class MdSnackBarModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdSnackBarModule, - providers: [MdSnackBar, OVERLAY_PROVIDERS, LiveAnnouncer] + providers: [] }; } } diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts index f229aaf5b195..2e07efbe3e84 100644 --- a/src/lib/tabs/tab-group.ts +++ b/src/lib/tabs/tab-group.ts @@ -26,7 +26,7 @@ import {MdRippleModule} from '../core/ripple/ripple'; import {ObserveContentModule} from '../core/observe-content/observe-content'; import {MdTab} from './tab'; import {MdTabBody} from './tab-body'; -import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; +import {VIEWPORT_RULER_PROVIDER} from '../core/overlay/position/viewport-ruler'; import {MdTabHeader} from './tab-header'; @@ -204,12 +204,14 @@ export class MdTabGroup { exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink, MdTabLinkRipple], declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper, MdTabNavBar, MdTabLink, MdTabBody, MdTabLinkRipple, MdTabHeader], + providers: [VIEWPORT_RULER_PROVIDER], }) export class MdTabsModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdTabsModule, - providers: [ViewportRuler] + providers: [] }; } } diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts index 87a1ef0d907b..8ba16668afda 100644 --- a/src/lib/toolbar/toolbar.ts +++ b/src/lib/toolbar/toolbar.ts @@ -62,6 +62,7 @@ export class MdToolbar { declarations: [MdToolbar, MdToolbarRow], }) export class MdToolbarModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdToolbarModule, diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts index e4c1849452d8..7c0571f182b9 100644 --- a/src/lib/tooltip/tooltip.ts +++ b/src/lib/tooltip/tooltip.ts @@ -30,7 +30,6 @@ import {MdTooltipInvalidPositionError} from './tooltip-errors'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; import {Dir} from '../core/rtl/dir'; -import {OVERLAY_PROVIDERS} from '../core/overlay/overlay'; import 'rxjs/add/operator/first'; export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after'; @@ -384,10 +383,11 @@ export class TooltipComponent { entryComponents: [TooltipComponent], }) export class MdTooltipModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdTooltipModule, - providers: [OVERLAY_PROVIDERS] + providers: [] }; } }