feat(overlay): more flexible scroll strategy API and ability to define/override custom strategies (#4855)

* feat(overlay): more flexible scroll strategy API and ability to define/override custom strategies

* Refactors the overlay setup to allow for scroll strategies to be passed in by name, instead of by instance.
* Handles the scroll strategy dependency injection automatically.
* Adds an API for registering custom scroll strategies and overriding the existing ones.
* Adds a second parameter to the `attach` method, allowing for a config object to be passed in.
* Throws an error if there's an attempt to attach a scroll strategy multiple times. This is mostly a sanity check to ensure that we don't cache the scroll strategy instances.

Relates to #4093.

* refactor: switch to new approach without ReflectiveInjector

* refactor: switch to better approach

* refactor: switch to even simpler approach

* chore: address feedback
This commit is contained in:
Kristiyan Kostadinov
2017-06-05 23:44:34 +02:00
committed by Andrew Seguin
parent 70b31f177a
commit 7e912705c3
21 changed files with 201 additions and 99 deletions
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {BlockScrollStrategy, ViewportRuler} from '@angular/material';
import {Overlay, ScrollStrategy} from '@angular/material';
@Component({
moduleId: module.id,
@@ -8,6 +8,6 @@ import {BlockScrollStrategy, ViewportRuler} from '@angular/material';
styleUrls: ['block-scroll-strategy-e2e.css'],
})
export class BlockScrollStrategyE2E {
constructor(private _viewportRuler: ViewportRuler) { }
scrollStrategy = new BlockScrollStrategy(this._viewportRuler);
constructor(private _overlay: Overlay) { }
scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.block();
}
+2 -4
View File
@@ -13,7 +13,7 @@ import {
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {DOCUMENT} from '@angular/platform-browser';
import {Overlay, OverlayRef, OverlayState, TemplatePortal, RepositionScrollStrategy} from '../core';
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
import {MdAutocomplete} from './autocomplete';
import {PositionStrategy} from '../core/overlay/position/position-strategy';
import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy';
@@ -22,7 +22,6 @@ import {MdOptionSelectionChange, MdOption} from '../core/option/option';
import {ENTER, UP_ARROW, DOWN_ARROW, ESCAPE} from '../core/keyboard/keycodes';
import {Dir} from '../core/rtl/dir';
import {MdInputContainer} from '../input/input-container';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/fromEvent';
@@ -113,7 +112,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
constructor(private _element: ElementRef, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef,
private _changeDetectorRef: ChangeDetectorRef,
private _scrollDispatcher: ScrollDispatcher,
@Optional() private _dir: Dir, private _zone: NgZone,
@Optional() @Host() private _inputContainer: MdInputContainer,
@Optional() @Inject(DOCUMENT) private _document: any) {}
@@ -381,7 +379,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
overlayState.positionStrategy = this._getOverlayPosition();
overlayState.width = this._getHostWidth();
overlayState.direction = this._dir ? this._dir.value : 'ltr';
overlayState.scrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
return overlayState;
}
+1 -4
View File
@@ -25,11 +25,9 @@ import {PortalModule} from '../portal/portal-directives';
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
import {Dir, LayoutDirection} from '../rtl/dir';
import {Scrollable} from './scroll/scrollable';
import {RepositionScrollStrategy} from './scroll/reposition-scroll-strategy';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {coerceBooleanProperty} from '../coercion/boolean-property';
import {ESCAPE} from '../keyboard/keycodes';
import {ScrollDispatcher} from './scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatchModule} from './scroll/index';
@@ -125,7 +123,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
@Input() backdropClass: string;
/** Strategy to be used when handling scroll events while the overlay is open. */
@Input() scrollStrategy: ScrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
@Input() scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.reposition();
/** Whether the overlay is open. */
@Input() open: boolean = false;
@@ -157,7 +155,6 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
constructor(
private _overlay: Overlay,
private _renderer: Renderer2,
private _scrollDispatcher: ScrollDispatcher,
templateRef: TemplateRef<any>,
viewContainerRef: ViewContainerRef,
@Optional() private _dir: Dir) {
+9 -4
View File
@@ -20,9 +20,10 @@ export class OverlayRef implements PortalHost {
private _portalHost: PortalHost,
private _pane: HTMLElement,
private _state: OverlayState,
private _scrollStrategy: ScrollStrategy,
private _ngZone: NgZone) {
this._state.scrollStrategy.attach(this);
_scrollStrategy.attach(this);
}
/** The overlay's HTML element */
@@ -44,7 +45,7 @@ export class OverlayRef implements PortalHost {
this.updateDirection();
this.updatePosition();
this._attachments.next();
this._state.scrollStrategy.enable();
this._scrollStrategy.enable();
// Enable pointer events for the overlay pane element.
this._togglePointerEvents(true);
@@ -71,7 +72,7 @@ export class OverlayRef implements PortalHost {
// This is necessary because otherwise the pane element will cover the page and disable
// pointer events therefore. Depends on the position strategy and the applied pane boundaries.
this._togglePointerEvents(false);
this._state.scrollStrategy.disable();
this._scrollStrategy.disable();
this._detachments.next();
return this._portalHost.detach();
@@ -85,9 +86,13 @@ export class OverlayRef implements PortalHost {
this._state.positionStrategy.dispose();
}
if (this._scrollStrategy) {
this._scrollStrategy.disable();
this._scrollStrategy = null;
}
this.detachBackdrop();
this._portalHost.dispose();
this._state.scrollStrategy.disable();
this._detachments.next();
this._detachments.complete();
this._attachments.complete();
+1 -2
View File
@@ -1,7 +1,6 @@
import {PositionStrategy} from './position/position-strategy';
import {LayoutDirection} from '../rtl/dir';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {NoopScrollStrategy} from './scroll/noop-scroll-strategy';
/**
@@ -13,7 +12,7 @@ export class OverlayState {
positionStrategy: PositionStrategy;
/** Strategy to be used when handling scroll events while the overlay is open. */
scrollStrategy: ScrollStrategy = new NoopScrollStrategy();
scrollStrategy: ScrollStrategy;
/** Custom class to add to the overlay pane. */
panelClass: string = '';
+12 -18
View File
@@ -8,7 +8,8 @@ import {OverlayState} from './overlay-state';
import {OverlayRef} from './overlay-ref';
import {PositionStrategy} from './position/position-strategy';
import {OverlayModule} from './overlay-directives';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {ViewportRuler} from './position/viewport-ruler';
import {ScrollStrategy, ScrollDispatcher} from './scroll/index';
describe('Overlay', () => {
@@ -21,15 +22,14 @@ describe('Overlay', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule],
providers: [
{provide: OverlayContainer, useFactory: () => {
providers: [{
provide: OverlayContainer,
useFactory: () => {
overlayContainerElement = document.createElement('div');
return {getContainerElement: () => overlayContainerElement};
}}
]
});
TestBed.compileComponents();
}
}]
}).compileComponents();
}));
beforeEach(inject([Overlay], (o: Overlay) => {
@@ -354,30 +354,25 @@ describe('Overlay', () => {
describe('scroll strategy', () => {
let fakeScrollStrategy: FakeScrollStrategy;
let config: OverlayState;
let overlayRef: OverlayRef;
beforeEach(() => {
config = new OverlayState();
fakeScrollStrategy = new FakeScrollStrategy();
config.scrollStrategy = fakeScrollStrategy;
fakeScrollStrategy = config.scrollStrategy = new FakeScrollStrategy();
overlayRef = overlay.create(config);
});
it('should attach the overlay ref to the scroll strategy', () => {
let overlayRef = overlay.create(config);
expect(fakeScrollStrategy.overlayRef).toBe(overlayRef,
'Expected scroll strategy to have been attached to the current overlay ref.');
});
it('should enable the scroll strategy when the overlay is attached', () => {
let overlayRef = overlay.create(config);
overlayRef.attach(componentPortal);
expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.');
});
it('should disable the scroll strategy once the overlay is detached', () => {
let overlayRef = overlay.create(config);
overlayRef.attach(componentPortal);
expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.');
@@ -386,8 +381,6 @@ describe('Overlay', () => {
});
it('should disable the scroll strategy when the overlay is destroyed', () => {
let overlayRef = overlay.create(config);
overlayRef.dispose();
expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.');
});
@@ -466,6 +459,7 @@ class FakePositionStrategy implements PositionStrategy {
dispose() {}
}
class FakeScrollStrategy implements ScrollStrategy {
isEnabled = false;
overlayRef: OverlayRef;
+8 -4
View File
@@ -12,6 +12,7 @@ import {OverlayRef} from './overlay-ref';
import {OverlayPositionBuilder} from './position/overlay-position-builder';
import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler';
import {OverlayContainer, OVERLAY_CONTAINER_PROVIDER} from './overlay-container';
import {ScrollStrategy, ScrollStrategyOptions} from './scroll/index';
/** Next overlay unique ID. */
@@ -31,12 +32,13 @@ let defaultState = new OverlayState();
*/
@Injectable()
export class Overlay {
constructor(private _overlayContainer: OverlayContainer,
constructor(public scrollStrategies: ScrollStrategyOptions,
private _overlayContainer: OverlayContainer,
private _componentFactoryResolver: ComponentFactoryResolver,
private _positionBuilder: OverlayPositionBuilder,
private _appRef: ApplicationRef,
private _injector: Injector,
private _ngZone: NgZone) {}
private _ngZone: NgZone) { }
/**
* Creates an overlay.
@@ -61,9 +63,9 @@ export class Overlay {
*/
private _createPaneElement(): HTMLElement {
let pane = document.createElement('div');
pane.id = `cdk-overlay-${nextUniqueId++}`;
pane.classList.add('cdk-overlay-pane');
this._overlayContainer.getContainerElement().appendChild(pane);
return pane;
@@ -84,7 +86,9 @@ export class Overlay {
* @param state
*/
private _createOverlayRef(pane: HTMLElement, state: OverlayState): OverlayRef {
return new OverlayRef(this._createPortalHost(pane), pane, state, this._ngZone);
let scrollStrategy = state.scrollStrategy || this.scrollStrategies.noop();
let portalHost = this._createPortalHost(pane);
return new OverlayRef(portalHost, pane, state, scrollStrategy, this._ngZone);
}
}
@@ -1,29 +1,46 @@
import {NgModule, Component} from '@angular/core';
import {inject, TestBed, async} from '@angular/core/testing';
import {ComponentPortal, OverlayModule, BlockScrollStrategy, Platform} from '../../core';
import {ViewportRuler} from '../position/viewport-ruler';
import {
ComponentPortal,
OverlayModule,
PortalModule,
Platform,
ViewportRuler,
OverlayState,
Overlay,
OverlayRef,
} from '../../core';
describe('BlockScrollStrategy', () => {
let platform = new Platform();
let strategy: BlockScrollStrategy;
let viewport: ViewportRuler;
let overlayRef: OverlayRef;
let componentPortal: ComponentPortal<FocacciaMsg>;
let forceScrollElement: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({imports: [OverlayModule]}).compileComponents();
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule]
}).compileComponents();
}));
beforeEach(inject([ViewportRuler], (viewportRuler: ViewportRuler) => {
strategy = new BlockScrollStrategy(viewportRuler);
viewport = viewportRuler;
forceScrollElement = document.createElement('div');
document.body.appendChild(forceScrollElement);
forceScrollElement.style.width = '100px';
forceScrollElement.style.height = '3000px';
}));
beforeEach(inject([Overlay, ViewportRuler], (overlay: Overlay, viewportRuler: ViewportRuler) => {
let overlayState = new OverlayState();
overlayState.scrollStrategy = overlay.scrollStrategies.block();
overlayRef = overlay.create(overlayState);
componentPortal = new ComponentPortal(FocacciaMsg);
viewport = viewportRuler;
forceScrollElement = document.createElement('div');
document.body.appendChild(forceScrollElement);
forceScrollElement.style.width = '100px';
forceScrollElement.style.height = '3000px';
}));
afterEach(() => {
strategy.disable();
overlayRef.dispose();
document.body.removeChild(forceScrollElement);
setScrollPosition(0, 0);
});
@@ -33,7 +50,7 @@ describe('BlockScrollStrategy', () => {
expect(viewport.getViewportScrollPosition().top)
.toBe(100, 'Expected viewport to be scrollable initially.');
strategy.enable();
overlayRef.attach(componentPortal);
expect(document.documentElement.style.top)
.toBe('-100px', 'Expected <html> element to be offset by the previous scroll amount.');
@@ -41,7 +58,7 @@ describe('BlockScrollStrategy', () => {
expect(viewport.getViewportScrollPosition().top)
.toBe(100, 'Expected the viewport not to scroll.');
strategy.disable();
overlayRef.detach();
expect(viewport.getViewportScrollPosition().top)
.toBe(100, 'Expected old scroll position to have bee restored after disabling.');
@@ -59,7 +76,7 @@ describe('BlockScrollStrategy', () => {
expect(viewport.getViewportScrollPosition().left)
.toBe(100, 'Expected viewport to be scrollable initially.');
strategy.enable();
overlayRef.attach(componentPortal);
expect(document.documentElement.style.left)
.toBe('-100px', 'Expected <html> element to be offset by the previous scroll amount.');
@@ -67,7 +84,7 @@ describe('BlockScrollStrategy', () => {
expect(viewport.getViewportScrollPosition().left)
.toBe(100, 'Expected the viewport not to scroll.');
strategy.disable();
overlayRef.detach();
expect(viewport.getViewportScrollPosition().left)
.toBe(100, 'Expected old scroll position to have bee restored after disabling.');
@@ -80,10 +97,10 @@ describe('BlockScrollStrategy', () => {
it('should toggle the `cdk-global-scrollblock` class', skipIOS(() => {
expect(document.documentElement.classList).not.toContain('cdk-global-scrollblock');
strategy.enable();
overlayRef.attach(componentPortal);
expect(document.documentElement.classList).toContain('cdk-global-scrollblock');
strategy.disable();
overlayRef.detach();
expect(document.documentElement.classList).not.toContain('cdk-global-scrollblock');
}));
@@ -93,12 +110,12 @@ describe('BlockScrollStrategy', () => {
root.style.top = '13px';
root.style.left = '37px';
strategy.enable();
overlayRef.attach(componentPortal);
expect(root.style.top).not.toBe('13px');
expect(root.style.left).not.toBe('37px');
strategy.disable();
overlayRef.detach();
expect(root.style.top).toBe('13px');
expect(root.style.left).toBe('37px');
@@ -106,7 +123,7 @@ describe('BlockScrollStrategy', () => {
it(`should't do anything if the page isn't scrollable`, skipIOS(() => {
forceScrollElement.style.display = 'none';
strategy.enable();
overlayRef.attach(componentPortal);
expect(document.documentElement.classList).not.toContain('cdk-global-scrollblock');
}));
@@ -116,7 +133,7 @@ describe('BlockScrollStrategy', () => {
const previousContentWidth = document.documentElement.getBoundingClientRect().width;
strategy.enable();
overlayRef.attach(componentPortal);
expect(document.documentElement.getBoundingClientRect().width).toBe(previousContentWidth);
});
@@ -151,3 +168,17 @@ describe('BlockScrollStrategy', () => {
}
});
/** Simple component that we can attach to the overlay. */
@Component({template: '<p>Focaccia</p>'})
class FocacciaMsg { }
/** Test module to hold the component. */
@NgModule({
imports: [OverlayModule, PortalModule],
declarations: [FocacciaMsg],
entryComponents: [FocacciaMsg],
})
class OverlayTestModule { }
@@ -10,7 +10,6 @@ import {
OverlayModule,
ScrollStrategy,
ScrollDispatcher,
CloseScrollStrategy,
} from '../../core';
@@ -34,11 +33,9 @@ describe('CloseScrollStrategy', () => {
TestBed.compileComponents();
}));
beforeEach(inject([Overlay, ScrollDispatcher], (overlay: Overlay,
scrollDispatcher: ScrollDispatcher) => {
beforeEach(inject([Overlay], (overlay: Overlay) => {
let overlayState = new OverlayState();
overlayState.scrollStrategy = new CloseScrollStrategy(scrollDispatcher);
overlayState.scrollStrategy = overlay.scrollStrategies.close();
overlayRef = overlay.create(overlayState);
componentPortal = new ComponentPortal(MozarellaMsg);
}));
@@ -1,4 +1,4 @@
import {ScrollStrategy} from './scroll-strategy';
import {ScrollStrategy, getMdScrollStrategyAlreadyAttachedError} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatcher} from './scroll-dispatcher';
@@ -14,6 +14,10 @@ export class CloseScrollStrategy implements ScrollStrategy {
constructor(private _scrollDispatcher: ScrollDispatcher) { }
attach(overlayRef: OverlayRef) {
if (this._overlayRef) {
throw getMdScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
}
+3 -1
View File
@@ -2,12 +2,14 @@ import {NgModule} from '@angular/core';
import {SCROLL_DISPATCHER_PROVIDER} from './scroll-dispatcher';
import {Scrollable} from './scrollable';
import {PlatformModule} from '../../platform/index';
import {ScrollStrategyOptions} from './scroll-strategy-options';
export {Scrollable} from './scrollable';
export {ScrollDispatcher} from './scroll-dispatcher';
// Export pre-defined scroll strategies and interface to build custom ones.
export {ScrollStrategy} from './scroll-strategy';
export {ScrollStrategyOptions} from './scroll-strategy-options';
export {RepositionScrollStrategy} from './reposition-scroll-strategy';
export {CloseScrollStrategy} from './close-scroll-strategy';
export {NoopScrollStrategy} from './noop-scroll-strategy';
@@ -17,6 +19,6 @@ export {BlockScrollStrategy} from './block-scroll-strategy';
imports: [PlatformModule],
exports: [Scrollable],
declarations: [Scrollable],
providers: [SCROLL_DISPATCHER_PROVIDER],
providers: [SCROLL_DISPATCHER_PROVIDER, ScrollStrategyOptions],
})
export class ScrollDispatchModule { }
@@ -10,7 +10,6 @@ import {
OverlayModule,
ScrollStrategy,
ScrollDispatcher,
RepositionScrollStrategy,
} from '../../core';
@@ -34,11 +33,9 @@ describe('RepositionScrollStrategy', () => {
TestBed.compileComponents();
}));
beforeEach(inject([Overlay, ScrollDispatcher], (overlay: Overlay,
scrollDispatcher: ScrollDispatcher) => {
beforeEach(inject([Overlay], (overlay: Overlay) => {
let overlayState = new OverlayState();
overlayState.scrollStrategy = new RepositionScrollStrategy(scrollDispatcher);
overlayState.scrollStrategy = overlay.scrollStrategies.reposition();
overlayRef = overlay.create(overlayState);
componentPortal = new ComponentPortal(PastaMsg);
}));
@@ -1,8 +1,14 @@
import {Subscription} from 'rxjs/Subscription';
import {ScrollStrategy} from './scroll-strategy';
import {ScrollStrategy, getMdScrollStrategyAlreadyAttachedError} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {ScrollDispatcher} from './scroll-dispatcher';
/**
* Config options for the RepositionScrollStrategy.
*/
export interface RepositionScrollStrategyConfig {
scrollThrottle?: number;
}
/**
* Strategy that will update the element position as the user is scrolling.
@@ -11,15 +17,23 @@ export class RepositionScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription|null = null;
private _overlayRef: OverlayRef;
constructor(private _scrollDispatcher: ScrollDispatcher, private _scrollThrottle = 0) { }
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _config: RepositionScrollStrategyConfig) { }
attach(overlayRef: OverlayRef) {
if (this._overlayRef) {
throw getMdScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
}
enable() {
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(this._scrollThrottle, () => {
let throttle = this._config ? this._config.scrollThrottle : 0;
this._scrollSubscription = this._scrollDispatcher.scrolled(throttle, () => {
this._overlayRef.updatePosition();
});
}
@@ -9,7 +9,7 @@ describe('Scroll Dispatcher', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, ScrollTestModule],
imports: [ScrollTestModule],
});
TestBed.compileComponents();
@@ -0,0 +1,42 @@
import {Injectable} from '@angular/core';
import {ScrollStrategy} from './scroll-strategy';
import {CloseScrollStrategy} from './close-scroll-strategy';
import {NoopScrollStrategy} from './noop-scroll-strategy';
import {BlockScrollStrategy} from './block-scroll-strategy';
import {ScrollDispatcher} from './scroll-dispatcher';
import {ViewportRuler} from '../position/viewport-ruler';
import {
RepositionScrollStrategy,
RepositionScrollStrategyConfig,
} from './reposition-scroll-strategy';
/**
* Options for how an overlay will handle scrolling.
*
* Users can provide a custom value for `ScrollStrategyOptions` to replace the default
* behaviors. This class primarily acts as a factory for ScrollStrategy instances.
*/
@Injectable()
export class ScrollStrategyOptions {
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _viewportRuler: ViewportRuler) { }
/** Do nothing on scroll. */
noop = () => new NoopScrollStrategy();
/** Close the overlay as soon as the user scrolls. */
close = () => new CloseScrollStrategy(this._scrollDispatcher);
/** Block scrolling. */
block = () => new BlockScrollStrategy(this._viewportRuler);
/**
* Update the overlay's position on scroll.
* @param config Configuration to be used inside the scroll strategy.
* Allows debouncing the reposition calls.
*/
reposition = (config?: RepositionScrollStrategyConfig) =>
new RepositionScrollStrategy(this._scrollDispatcher, config)
}
+17 -5
View File
@@ -1,19 +1,19 @@
# Scroll strategies
## What is a scroll strategy?
A scroll strategy is a class that describes how an overlay should behave if the user scrolls
A scroll strategy is a way of describing how an overlay should behave if the user scrolls
while the overlay is open. The strategy has a reference to the `OverlayRef`, allowing it to
recalculate the position, close the overlay, block scrolling, etc.
## Usage
To associate an overlay with a scroll strategy, you have to pass in a `ScrollStrategy` instance
to the `OverlayState`. By default, all overlays will use the `NoopScrollStrategy` which doesn't
do anything:
To associate an overlay with a scroll strategy, you have to pass in a function, that returns a
scroll strategy, to the `OverlayState`. By default, all overlays will use the `noop` strategy which
doesn't do anything. The other available strategies are `reposition`, `block` and `close`:
```ts
let overlayState = new OverlayState();
overlayState.scrollStrategy = new BlockScrollStrategy(this._viewportRuler);
overlayState.scrollStrategy = overlay.scrollStrategies.block();
this._overlay.create(overlayState).attach(yourPortal);
```
@@ -25,3 +25,15 @@ interface. There are three stages of a scroll strategy's life cycle:
2. When an overlay is attached to the DOM, it'll call the `enable` method on its scroll strategy,
3. When an overlay is detached from the DOM or destroyed, it'll call the `disable` method on its
scroll strategy, allowing it to clean up after itself.
Afterwards you can pass in the new scroll strategy to your overlay state:
```ts
// Your custom scroll strategy.
export class CustomScrollStrategy implements ScrollStrategy {
// your implementation
}
overlayState.scrollStrategy = new CustomScrollStrategy();
this._overlay.create(overlayState).attach(yourPortal);
```
@@ -9,3 +9,10 @@ export interface ScrollStrategy {
disable: () => void;
attach: (overlayRef: OverlayRef) => void;
}
/**
* Returns an error to be thrown when attempting to attach an already-attached scroll strategy.
*/
export function getMdScrollStrategyAlreadyAttachedError(): Error {
return Error(`Scroll strategy has already been attached.`);
}
+1 -3
View File
@@ -21,7 +21,6 @@ import {Dir} from '../core/rtl/dir';
import {MdDialog} from '../dialog/dialog';
import {MdDialogRef} from '../dialog/dialog-ref';
import {PositionStrategy} from '../core/overlay/position/position-strategy';
import {RepositionScrollStrategy, ScrollDispatcher} from '../core/overlay/index';
import {MdDatepickerInput} from './datepicker-input';
import {Subscription} from 'rxjs/Subscription';
import {MdDialogConfig} from '../dialog/dialog-config';
@@ -157,7 +156,6 @@ export class MdDatepicker<D> implements OnDestroy {
private _overlay: Overlay,
private _ngZone: NgZone,
private _viewContainerRef: ViewContainerRef,
private _scrollDispatcher: ScrollDispatcher,
@Optional() private _dateAdapter: DateAdapter<D>,
@Optional() private _dir: Dir) {
if (!this._dateAdapter) {
@@ -269,7 +267,7 @@ export class MdDatepicker<D> implements OnDestroy {
overlayState.hasBackdrop = true;
overlayState.backdropClass = 'md-overlay-transparent-backdrop';
overlayState.direction = this._dir ? this._dir.value : 'ltr';
overlayState.scrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
this._popupRef = this._overlay.create(overlayState);
}
+8 -5
View File
@@ -2,7 +2,13 @@ import {Injector, ComponentRef, Injectable, Optional, SkipSelf, TemplateRef} fro
import {Location} from '@angular/common';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core';
import {
Overlay,
OverlayRef,
ComponentType,
OverlayState,
ComponentPortal,
} from '../core';
import {extendObject} from '../core/util/object-extend';
import {ESCAPE} from '../core/keyboard/keycodes';
import {DialogInjector} from './dialog-injector';
@@ -10,8 +16,6 @@ import {MdDialogConfig} from './dialog-config';
import {MdDialogRef} from './dialog-ref';
import {MdDialogContainer} from './dialog-container';
import {TemplatePortal} from '../core/portal/portal';
import {BlockScrollStrategy} from '../core/overlay/scroll/block-scroll-strategy';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import 'rxjs/add/operator/first';
@@ -50,7 +54,6 @@ export class MdDialog {
constructor(
private _overlay: Overlay,
private _injector: Injector,
private _viewportRuler: ViewportRuler,
@Optional() private _location: Location,
@Optional() @SkipSelf() private _parentDialog: MdDialog) {
@@ -123,7 +126,7 @@ export class MdDialog {
let overlayState = new OverlayState();
overlayState.panelClass = dialogConfig.panelClass;
overlayState.hasBackdrop = dialogConfig.hasBackdrop;
overlayState.scrollStrategy = new BlockScrollStrategy(this._viewportRuler);
overlayState.scrollStrategy = this._overlay.scrollStrategies.block();
if (dialogConfig.backdropClass) {
overlayState.backdropClass = dialogConfig.backdropClass;
}
+2 -5
View File
@@ -22,8 +22,6 @@ import {
ConnectedPositionStrategy,
HorizontalConnectionPos,
VerticalConnectionPos,
RepositionScrollStrategy,
ScrollDispatcher,
} from '../core';
import {Subscription} from 'rxjs/Subscription';
import {MenuPositionX, MenuPositionY} from './menu-positions';
@@ -80,8 +78,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
@Output() onMenuClose = new EventEmitter<void>();
constructor(private _overlay: Overlay, private _element: ElementRef,
private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir,
private _scrollDispatcher: ScrollDispatcher) { }
private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir) { }
ngAfterViewInit() {
this._checkMenu();
@@ -219,7 +216,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
overlayState.hasBackdrop = true;
overlayState.backdropClass = 'cdk-overlay-transparent-backdrop';
overlayState.direction = this.dir;
overlayState.scrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
return overlayState;
}
+4 -3
View File
@@ -25,7 +25,6 @@ import {
ComponentPortal,
OverlayConnectionPosition,
OriginConnectionPosition,
RepositionScrollStrategy,
} from '../core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -237,10 +236,12 @@ export class MdTooltip implements OnDestroy {
});
let config = new OverlayState();
config.direction = this._dir ? this._dir.value : 'ltr';
config.positionStrategy = strategy;
config.scrollStrategy =
new RepositionScrollStrategy(this._scrollDispatcher, SCROLL_THROTTLE_MS);
config.scrollStrategy = this._overlay.scrollStrategies.reposition({
scrollThrottle: SCROLL_THROTTLE_MS
});
this._overlayRef = this._overlay.create(config);
}