-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
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 #4855
Conversation
82d632f
to
b13aff8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment that may be relevant even w/ changing the approach
src/lib/core/overlay/overlay.ts
Outdated
@@ -31,12 +37,22 @@ let defaultState = new OverlayState(); | |||
*/ | |||
@Injectable() | |||
export class Overlay { | |||
// Create a child ReflectiveInjector, allowing us to instantiate scroll | |||
// strategies without going throught the injector cache. | |||
private _reflectiveInjector = ReflectiveInjector.resolveAndCreate([], this._injector); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReflectiveInjector
is meant to be JIT-only and will be going away soon.
1207c42
to
3fe7dbf
Compare
@jelbourn updated the approach as discussed. |
case 'reposition': return new RepositionScrollStrategy(this._scrollDispatcher); | ||
case 'close': return new CloseScrollStrategy(this._scrollDispatcher); | ||
case 'noop': return new NoopScrollStrategy(); | ||
case 'block': return new BlockScrollStrategy(this._viewportRuler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way is still string-based; you can't get IDE completion or correction for the strategies available, and they can't be minified.
What I had been thinking was having each of these be properties. What's the value for those properties? Doing something like this would work....
export type ScrollStrategyOption = () => ScrollStrategy;
export class ScrollStrategyOptions {
// The class symbol acts as a unique token for the strategy option.
noop: ScrollStrategyOption = () => new NoopScrollStrategy();
close: ScrollStrategyOption = () => new CloseScrollStrategy(this._scrollDispatcher);
block: ScrollStrategyOption = () => new BlockScrollStrategy(this._viewportRuler);
reposition: ScrollStrategyOption = () => new RepositionScrollStrategy(this._scrollDispatcher);
getStrategyInstance(option: ScrollStrategyOption) {
return option();
}
}
// When using the options
overlayState.scrollStrategy = overlay.scrollStratgies.block;
I originally had them as the classes (BlockScrollStrategy
) with a switch
, but realized you could cut out that step completely if you treat the properties as the map to the creation directly. I like this approach because, to the user, each option is some opaque thing called ScrollStrategyOption
, but on the inside we know it's a factory function.
When someone wants to customize the strategies, they would do this:
export class SuperScrollStrategyOptions extends ScrollStrategyOptions {
// overwrite block strategy
block: ScrollStrategyOption = () => new SuperBlockScrollStrategy(/* ... */);
// define a custom strategy
explode: ScrollStrategyOption = () => new SuperExploseScrollStrategy(/* ... */);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, that's even better. I've updated the PR.
src/lib/core/overlay/overlay.ts
Outdated
@@ -34,9 +35,10 @@ export class Overlay { | |||
constructor(private _overlayContainer: OverlayContainer, | |||
private _componentFactoryResolver: ComponentFactoryResolver, | |||
private _positionBuilder: OverlayPositionBuilder, | |||
private _scrollStrategyOptions: ScrollStrategyOptions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expose scrollStrategies
as a public property on Overlay
so each component won't have to inject it separately?
|
||
export type OverlayStateScrollStrategy = {strategy: ScrollStrategyOption; config: any} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that we lose out on having config types specific to the strategy with this approach. So......
What if we tweak the options setup slightly so that it's something like...
// in scroll-strategy-option.ts
export interface ScrollStrategyOption {
_create: () => ScrollStrategy;
}
// in block-scroll-strategy.ts
export class BlockScrollStrategyOption {
_config = new BlockScrollStrategyConfig();
_create() {
return new BlockScrollStrategy(..., config)
}
withExtraOption(value) {
this._config.extraOption = value;
return this;
}
}
// in scroll-strategy-options.ts
export class ScrollStrategyOptions {
constructor(private _scrollDispatcher: ScrollDispatcher, private _viewportRuler: ViewportRuler) { }
noop: ScrollStrategyOption = basicOption(() => new NoopScrollStrategy());
close: ScrollStrategyOption = basicOption(() => new CloseScrollStrategy(this._scrollDispatcher));
block = new BlockScrollStrategyOption(...);
reposition: ScrollStrategyOption = basicOption(() => new RepositionScrollStrategy(this._scrollDispatcher));
basicOption(factoryFn): ScrollStrategyOption {
return {_create: factoryFn};
}
}
For someone just consuming the options, this is identical but with a nicer interface for the configuration. For people creating a custom strategy, they just have to create this new builder option class, which I think is worth it when capturing the configuration.
Sorry for the churn- I keep thinking of new things.
…e/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 angular#4093.
9b79ef6
to
accf559
Compare
@jelbourn added the discussed changes. It cut down ~100 LoC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just a few last comments
block = () => new BlockScrollStrategy(this._viewportRuler); | ||
reposition = (config?: RepositionScrollStrategyConfig) => { | ||
return new RepositionScrollStrategy(this._scrollDispatcher, config); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Omit the return?
reposition = (config?: RepositionScrollStrategyConfig) =>
new RepositionScrollStrategy(this._scrollDispatcher, config);
|
||
/** | ||
* Factory that instantiates scroll strategies. Provides the built-in `reposition`, `close`, | ||
* `noop` and `block` strategies by default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
/**
* 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.
*/
private _scrollDispatcher: ScrollDispatcher, | ||
private _viewportRuler: ViewportRuler) { } | ||
|
||
noop = () => new NoopScrollStrategy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a public JsDoc for each for these, e.g.
/** Do nothing on scroll. */
/** Close the overlay on scroll. */
* Returns an error to be thrown when attempting to attach an already-attached scroll strategy. | ||
*/ | ||
export function getMdScrollStrategyAlreadyAttachedError(): Error { | ||
return new Error(`Scroll strategy has already been attached.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quirk: the new
isn't actually necessary for Error
Feedback has been addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
attach
method, allowing for a config object to be passed in.Relates to #4093.