Skip to content

feat(overlay): allow for scroll strategy to be swapped out #15067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions src/cdk/overlay/overlay-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {OverlayConfig} from './overlay-config';
import {coerceCssPixelValue, coerceArray} from '@angular/cdk/coercion';
import {OverlayReference} from './overlay-reference';
import {PositionStrategy} from './position/position-strategy';
import {ScrollStrategy} from './scroll';


/** An object where all of its properties cannot be written. */
Expand All @@ -34,6 +35,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
private _attachments = new Subject<void>();
private _detachments = new Subject<void>();
private _positionStrategy: PositionStrategy | undefined;
private _scrollStrategy: ScrollStrategy | undefined;
private _locationChanges: SubscriptionLike = Subscription.EMPTY;

/**
Expand Down Expand Up @@ -71,7 +73,8 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
private _location?: Location) {

if (_config.scrollStrategy) {
_config.scrollStrategy.attach(this);
this._scrollStrategy = _config.scrollStrategy;
this._scrollStrategy.attach(this);
}

this._positionStrategy = _config.positionStrategy;
Expand Down Expand Up @@ -123,8 +126,8 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
this._updateElementSize();
this._updateElementDirection();

if (this._config.scrollStrategy) {
this._config.scrollStrategy.enable();
if (this._scrollStrategy) {
this._scrollStrategy.enable();
}

// Update the position once the zone is stable so that the overlay will be fully rendered
Expand Down Expand Up @@ -186,8 +189,8 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
this._positionStrategy.detach();
}

if (this._config.scrollStrategy) {
this._config.scrollStrategy.disable();
if (this._scrollStrategy) {
this._scrollStrategy.disable();
}

const detachmentResult = this._portalOutlet.detach();
Expand Down Expand Up @@ -216,10 +219,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
this._positionStrategy.dispose();
}

if (this._config.scrollStrategy) {
this._config.scrollStrategy.disable();
}

this._disposeScrollStrategy();
this.detachBackdrop();
this._locationChanges.unsubscribe();
this._keyboardDispatcher.remove(this);
Expand Down Expand Up @@ -336,6 +336,21 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
return typeof direction === 'string' ? direction : direction.value;
}

/** Switches to a new scroll strategy. */
updateScrollStrategy(strategy: ScrollStrategy): void {
if (strategy === this._scrollStrategy) {
return;
}

this._disposeScrollStrategy();
this._scrollStrategy = strategy;

if (this.hasAttached()) {
strategy.attach(this);
strategy.enable();
}
}

/** Updates the text direction of the overlay panel. */
private _updateElementDirection() {
this._host.setAttribute('dir', this.getDirection());
Expand Down Expand Up @@ -490,6 +505,19 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
});
});
}

/** Disposes of a scroll strategy. */
private _disposeScrollStrategy() {
const scrollStrategy = this._scrollStrategy;

if (scrollStrategy) {
scrollStrategy.disable();

if (scrollStrategy.detach) {
scrollStrategy.detach();
}
}
}
}


Expand Down
110 changes: 100 additions & 10 deletions src/cdk/overlay/overlay.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,27 +816,29 @@ describe('Overlay', () => {
});

describe('scroll strategy', () => {
let fakeScrollStrategy: FakeScrollStrategy;
let config: OverlayConfig;
let overlayRef: OverlayRef;

beforeEach(() => {
fakeScrollStrategy = new FakeScrollStrategy();
config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
overlayRef = overlay.create(config);
});

it('should attach the overlay ref to the scroll strategy', () => {
const fakeScrollStrategy = new FakeScrollStrategy();
const config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
const 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', () => {
const fakeScrollStrategy = new FakeScrollStrategy();
const config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
const 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', () => {
const fakeScrollStrategy = new FakeScrollStrategy();
const config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
const overlayRef = overlay.create(config);

overlayRef.attach(componentPortal);
expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.');

Expand All @@ -845,9 +847,93 @@ describe('Overlay', () => {
});

it('should disable the scroll strategy when the overlay is destroyed', () => {
const fakeScrollStrategy = new FakeScrollStrategy();
const config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
const overlayRef = overlay.create(config);

overlayRef.dispose();
expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.');
});

it('should detach the scroll strategy when the overlay is destroyed', () => {
const fakeScrollStrategy = new FakeScrollStrategy();
const config = new OverlayConfig({scrollStrategy: fakeScrollStrategy});
const overlayRef = overlay.create(config);

expect(fakeScrollStrategy.overlayRef).toBe(overlayRef);

overlayRef.dispose();

expect(fakeScrollStrategy.overlayRef).toBeNull();
});

it('should be able to swap scroll strategies', fakeAsync(() => {
const firstStrategy = new FakeScrollStrategy();
const secondStrategy = new FakeScrollStrategy();

[firstStrategy, secondStrategy].forEach(strategy => {
spyOn(strategy, 'attach');
spyOn(strategy, 'enable');
spyOn(strategy, 'disable');
spyOn(strategy, 'detach');
});

const overlayRef = overlay.create({scrollStrategy: firstStrategy});

overlayRef.attach(componentPortal);
viewContainerFixture.detectChanges();
zone.simulateZoneExit();
tick();

expect(firstStrategy.attach).toHaveBeenCalledTimes(1);
expect(firstStrategy.enable).toHaveBeenCalledTimes(1);

expect(secondStrategy.attach).not.toHaveBeenCalled();
expect(secondStrategy.enable).not.toHaveBeenCalled();

overlayRef.updateScrollStrategy(secondStrategy);
viewContainerFixture.detectChanges();
tick();

expect(firstStrategy.attach).toHaveBeenCalledTimes(1);
expect(firstStrategy.enable).toHaveBeenCalledTimes(1);
expect(firstStrategy.disable).toHaveBeenCalledTimes(1);
expect(firstStrategy.detach).toHaveBeenCalledTimes(1);

expect(secondStrategy.attach).toHaveBeenCalledTimes(1);
expect(secondStrategy.enable).toHaveBeenCalledTimes(1);
}));

it('should not do anything when trying to swap a strategy with itself', fakeAsync(() => {
const strategy = new FakeScrollStrategy();

spyOn(strategy, 'attach');
spyOn(strategy, 'enable');
spyOn(strategy, 'disable');
spyOn(strategy, 'detach');

const overlayRef = overlay.create({scrollStrategy: strategy});

overlayRef.attach(componentPortal);
viewContainerFixture.detectChanges();
zone.simulateZoneExit();
tick();

expect(strategy.attach).toHaveBeenCalledTimes(1);
expect(strategy.enable).toHaveBeenCalledTimes(1);
expect(strategy.disable).not.toHaveBeenCalled();
expect(strategy.detach).not.toHaveBeenCalled();

overlayRef.updateScrollStrategy(strategy);
viewContainerFixture.detectChanges();
tick();

expect(strategy.attach).toHaveBeenCalledTimes(1);
expect(strategy.enable).toHaveBeenCalledTimes(1);
expect(strategy.disable).not.toHaveBeenCalled();
expect(strategy.detach).not.toHaveBeenCalled();
}));

});
});

Expand Down Expand Up @@ -908,4 +994,8 @@ class FakeScrollStrategy implements ScrollStrategy {
disable() {
this.isEnabled = false;
}

detach() {
this.overlayRef = null!;
}
}
5 changes: 5 additions & 0 deletions src/cdk/overlay/scroll/close-scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export class CloseScrollStrategy implements ScrollStrategy {
}
}

detach() {
this.disable();
this._overlayRef = null!;
}

/** Detaches the overlay ref and disables the scroll strategy. */
private _detach = () => {
this.disable();
Expand Down
5 changes: 5 additions & 0 deletions src/cdk/overlay/scroll/reposition-scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ export class RepositionScrollStrategy implements ScrollStrategy {
this._scrollSubscription = null;
}
}

detach() {
this.disable();
this._overlayRef = null!;
}
}
3 changes: 3 additions & 0 deletions src/cdk/overlay/scroll/scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export interface ScrollStrategy {

/** Attaches this `ScrollStrategy` to an overlay. */
attach: (overlayRef: OverlayReference) => void;

/** Detaches the scroll strategy from the current overlay. */
detach?: () => void;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion tools/public_api_guard/cdk/overlay.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export declare class CdkOverlayOrigin {
export declare class CloseScrollStrategy implements ScrollStrategy {
constructor(_scrollDispatcher: ScrollDispatcher, _ngZone: NgZone, _viewportRuler: ViewportRuler, _config?: CloseScrollStrategyConfig | undefined);
attach(overlayRef: OverlayReference): void;
detach(): void;
disable(): void;
enable(): void;
}
Expand Down Expand Up @@ -228,9 +229,9 @@ export declare class OverlayRef implements PortalOutlet, OverlayReference {
readonly overlayElement: HTMLElement;
constructor(_portalOutlet: PortalOutlet, _host: HTMLElement, _pane: HTMLElement, _config: ImmutableObject<OverlayConfig>, _ngZone: NgZone, _keyboardDispatcher: OverlayKeyboardDispatcher, _document: Document, _location?: Location | undefined);
addPanelClass(classes: string | string[]): void;
attach(portal: any): any;
attach<T>(portal: ComponentPortal<T>): ComponentRef<T>;
attach<T>(portal: TemplatePortal<T>): EmbeddedViewRef<T>;
attach(portal: any): any;
attachments(): Observable<void>;
backdropClick(): Observable<MouseEvent>;
detach(): any;
Expand All @@ -245,6 +246,7 @@ export declare class OverlayRef implements PortalOutlet, OverlayReference {
setDirection(dir: Direction | Directionality): void;
updatePosition(): void;
updatePositionStrategy(strategy: PositionStrategy): void;
updateScrollStrategy(strategy: ScrollStrategy): void;
updateSize(sizeConfig: OverlaySizeConfig): void;
}

Expand All @@ -267,6 +269,7 @@ export interface PositionStrategy {
export declare class RepositionScrollStrategy implements ScrollStrategy {
constructor(_scrollDispatcher: ScrollDispatcher, _viewportRuler: ViewportRuler, _ngZone: NgZone, _config?: RepositionScrollStrategyConfig | undefined);
attach(overlayRef: OverlayReference): void;
detach(): void;
disable(): void;
enable(): void;
}
Expand All @@ -285,6 +288,7 @@ export declare class ScrollingVisibility {

export interface ScrollStrategy {
attach: (overlayRef: OverlayReference) => void;
detach?: () => void;
disable: () => void;
enable: () => void;
}
Expand Down