Skip to content
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

Merged

Conversation

crisbeto
Copy link
Member

  • 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.

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label May 28, 2017
@crisbeto crisbeto force-pushed the scroll-strategy-override-provider-again branch from 82d632f to b13aff8 Compare May 28, 2017 16:50
Copy link
Member

@jelbourn jelbourn left a 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

@@ -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);
Copy link
Member

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.

@crisbeto crisbeto force-pushed the scroll-strategy-override-provider-again branch from 1207c42 to 3fe7dbf Compare June 1, 2017 18:46
@crisbeto
Copy link
Member Author

crisbeto commented Jun 1, 2017

@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);
Copy link
Member

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(/* ... */);
}

Copy link
Member Author

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.

@@ -34,9 +35,10 @@ export class Overlay {
constructor(private _overlayContainer: OverlayContainer,
private _componentFactoryResolver: ComponentFactoryResolver,
private _positionBuilder: OverlayPositionBuilder,
private _scrollStrategyOptions: ScrollStrategyOptions,
Copy link
Member

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} |
Copy link
Member

@jelbourn jelbourn Jun 2, 2017

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.
@crisbeto crisbeto force-pushed the scroll-strategy-override-provider-again branch from 9b79ef6 to accf559 Compare June 2, 2017 19:11
@crisbeto
Copy link
Member Author

crisbeto commented Jun 2, 2017

@jelbourn added the discussed changes. It cut down ~100 LoC.

Copy link
Member

@jelbourn jelbourn left a 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);
}
Copy link
Member

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.
Copy link
Member

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();
Copy link
Member

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.`);
Copy link
Member

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

@crisbeto
Copy link
Member Author

crisbeto commented Jun 2, 2017

Feedback has been addressed.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker and removed pr: needs review labels Jun 2, 2017
@andrewseguin andrewseguin merged commit 7e91270 into angular:master Jun 5, 2017
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants