Skip to content

Conversation

@Karnaukhov-kh
Copy link
Member

@Karnaukhov-kh Karnaukhov-kh commented Jul 25, 2020

Scope

This PR will be focusing on setting up strategies on global/feature/component levels and passing these settings to existing directives.

All information below is a very early work in progress and just topics to discuss.

InjectionTokens approach

3 tokens introduced.

  • RX_AVAILABLE_STRATEGIES with all available strategies.
  • RX_ANGULAR_DEFAULT_STRATEGY default strategy for elements in the viewport ('local').
  • RX_ANGULAR_DEFAULT_OUT_OF_VIEWPORT_STRATEGY default strategy for elements out of viewport ('noop').

Changing default strategies.

  • Through providers array on the needed level and useValue.
  • Planned RxRenderer service (will be introduced in separate PR).

Adding a new strategy.

  • Through providers array on the needed level and useValue.
  • Planned RxRenderer service (will be introduced in separate PR).

Consuming strategies

  • By @Inject().

Service approach

StrategySetupService introduced. Implemented with RxState.

State interface

export interface StrategySetupState {
  strategies: AvailableStrategies;
  currentStrategy: StrategyNameType;
  currentOutOfViewportStrategy: StrategyNameType;
}

API

  • setStrategy. Accepts strategy name.
  • setOutOfViewportStrategy. Accepts strategy name.
  • addStrategy. Accepts request object.
export interface AddStrategyRequest {
  name: string;
  constructor: StrategyConstructorType;
  options?: {
    useAsDefault: boolean;
    useAsOutOfViewPort: boolean;
  };
}

This part need to return full strategy function instead of name. Or we should split responsibilities between RxRenderer & SetupService :)

  • strategy$. Observable of currentStrategy.
  • outOfViewportStrategy$. Observable of currentOutOfViewportStrategy.
  • get strategy(). currentStrategy.
  • get outOfViewportStrategy(). currentOutOfViewportStrategy.

Other

Some typing introduced for strategy names, constructor functions, etc.

@github-actions github-actions bot added the </> Template @rx-angular/template related label Jul 25, 2020
@Karnaukhov-kh
Copy link
Member Author

Karnaukhov-kh commented Jul 26, 2020

Further discussion

Use cases

  1. Configuring strategies globally.
  2. Configuring strategies on the feature level. (lazy loaded module or just module)
  3. Configuring strategies on the component level.

Required functionality

  1. Store all strategies in one place.
  2. Define the defaultStrategy globally.
  3. Define the defaultOutOfviewPortStrategy globally.
  4. Define the defaultStrategy on the feature/component level.
  5. Define the default outOfviewPortStrategy on the feature/component level.
  6. Change defaultStrategy at runtime.
  7. Change defaultOutOfviewPortStrategy at runtime.
  8. Register new strategies in the "setup" phase (before App is running or as factory function or forRoot).
  9. Register new strategies at runtime.

Tokens

Separate tokens for default, defaultOutOfVP, and available strategies.

Confiuration

  • In providers section of the entity (module/component/directive).

Consuming strategies

Runtime change

  • Only directive inputs. Impossible to change globally without some kind of additional service.

Registering new strategies

  • Only in providers section of the entity. (useValue)

Requirements satisfaction

  1. ✔️ Store all strategies in one place.
  2. ✔️ Define the defaultStrategy globally.
  3. ✔️ Define the defaultOutOfviewPortStrategy globally.
  4. ✔️ Define the defaultStrategy on the scope level.
  5. ✔️ Define the default outOfviewPortStrategy on the scope level.
  6. ❌ Change defaultStrategy at runtime.
  7. ❌ Change defaultOutOfviewPortStrategy at runtime.
  8. ⚠️ Register new strategies in the "setup" phase. To register new strategy users will need to use useValue and provide original strategies object + own strategy. It might create confusion.
  9. Register new strategies at runtime.

Cons

  • We are not providing any visible in-app hints about how to register new strategy (the only way to get some info is our docs).

Service

Service that will serve as a "store" for strategies config.

Configuration

  • In providers section of the entity (module/component/directive).

Consuming strategies

  • Via currentStrategy observable or get method in the service.

Runtime change

  • Some service method.
  • The change will be applied to the service "scope" (feature/module/component). So the user needs a clear understanding of the dep injection hierarchy.

Registering new strategies

  • Service method.

Requirements satisfaction

  1. ✔️ Store all strategies in one place.
  2. ✔️ Define the defaultStrategy globally.
  3. ✔️ Define the defaultOutOfviewPortStrategy globally.
  4. ✔️ Define the defaultStrategy on the scope level.
  5. ✔️ Define the default outOfviewPortStrategy on the scope level.
  6. ✔️ Change defaultStrategy at runtime.
  7. ✔️ Change defaultOutOfviewPortStrategy at runtime.
  8. ⚠️ Register new strategies in the "setup" phase. Should be done in root component constructor or smth like this.
  9. ✔️ Register new strategies at runtime.

Cons

  • Bloated API. Change, get, add all in one place.

Questions

  • If we stick to services. Should we split responsibilities between different services?
  • If we split :). Should we expose them as a separate module?
  • Should we use @rx-angular/state as a dependency if we stick to services?
  • Registering new strategies. Should we allow feature/component strategies or they always should be defined globally?
  • Can we improve outOfViewPortStrategy naming?
  • Should we introduce "out of viewport" functionality to let?
  • How we can improve typing for strategies. Right now we have an interface with all our current strategies. How we can extend it dynamically so users will get suggestions when they try to consume a custom-defined strategy?
{
    global: StrategyConstructor,
   ....
}

Implementation suggestion

TBD

@Karnaukhov-kh Karnaukhov-kh linked an issue Jul 26, 2020 that may be closed by this pull request
@BioPhoton
Copy link
Member

BioPhoton commented Jul 26, 2020

Should we use @rx-angular/state as a dependency if we stick to services?

No that's not a good idea. Exposed packaged should not depend on each other. The only thing I could think of is that cdk package that could be shared.

Registering new strategies. Should we allow feature/component strategies or they always should be defined globally?

It would be super confusing imho

Can we improve outOfViewPortStrategy naming?

e.g. invisibleStrategy

@BioPhoton
Copy link
Member

How we can improve typing for strategies. Right now we have an interface with all our current strategies. How we can extend it dynamically so users will get suggestions when they try to consume a custom-defined strategy?

@kajetansw any ideas?

@BioPhoton
Copy link
Member

Should we introduce "out of viewport" functionality to let?

Also thought about it. I would say we make it stable as is, restructure it, and then talk again.

@kajetansw
Copy link
Member

@BioPhoton @Karnaukhov-kh

How we can improve typing for strategies. Right now we have an interface with all our current strategies. How we can extend it dynamically so users will get suggestions when they try to consume a custom-defined strategy?

One solution I can think of is for the user to pass custom strategy names when initializing/declaring the StrategySetupService, like:

// 1
const service = new StrategySetupService<'test'>();
const s = service.select().pipe(
  filter(s => s.currentStrategy === 'test')
  //                                 ^^^^
  // OK, TypeScript won't let us use other values than 
  // "global", "native", "local", "detach", "noop" (default strategies) and "test"
);

// 2
const service = new StrategySetupService<'test'>();
const s = service.select().pipe(
  filter(s => s.currentStrategy === 'other')
  //                                 ^^^^
  // ERROR, "other" doesn't match values above
);

// 3
const service = new StrategySetupService();
const s = service.select().pipe(
  filter(s => s.currentStrategy === 'other')
  //                                 ^^^^
  // ERROR, "other" doesn't match default strategy names
);

I can try and write some example implementation on a separate branch - some prototype to try out.

@BioPhoton
Copy link
Member

BioPhoton commented Jul 28, 2020

@kajetansw Thanks for your answer!!

Examples tp poke around sounds good to me!

@Karnaukhov-kh
Copy link
Member Author

@kajetansw Thank you 👍

@kajetansw
Copy link
Member

I did some magic on typings for StrategySetupService and all types around it. You can find it in this commit on branch proto/typings-for-strategy-setup-service:
kajetansw@d07ed51

Play with it please and feed me back on what you think, if you find any caveats or corner cases and if it's of any use altogether 😉

Assumptions and trade-offs of the implementation

For it to work as it is I needed to change two things:

  • every strategy has the same strategy constructor type StrategyConstructorType, you can find it in libs/template/src/lib/render-strategies/interfaces/available-strategies.interface.ts
  • because of the above I proposed dropping of NoopStrategyConstructorType and changing StrategyConstructorType so that the config input is optional

Without the unified constructor across all strategy definitions I wasn't able to create working types for custom strategies. I'm not saying it's impossible though (I don't know that much) but would take some time to investigate further.

What do you think about those assumptions? Are they something worth keeping, or should I keep digging?

Comments along the way

If I may, I'd like to propose two things:

  • to rename AvailableStrategies to DefaultStrategies (or similar) - it may be clearer to interpret that those strategy definitions are the ones, that the user would always have access to,
  • to rename StrategyNameType to DefaultStrategyNames (or similar) - pretty much the same reason as above

@kajetansw
Copy link
Member

About your questions @Karnaukhov-kh:

I think the sole pros/cons ratio showed us that solution with a service is cooler 👍

Bloated API. Change, get, add all in one place.

I personally don't think it's THAT bloated. The service seems to be doing what it is supposed to do - to store and change strategies. Unless of course the implementation is not over and it would be 3 times larger than it is now 😉

If we stick to services. Should we split responsibilities between different services?

How do you propose to split it? Like I wrote above, I don't think the StrategySetupService is that large in terms of functionalities...

@Karnaukhov-kh
Copy link
Member Author

@kajetansw Very cool, thank you! I'll dig into it more and ping you back.

If I may, I'd like to propose two things:

  • to rename AvailableStrategies to DefaultStrategies (or similar) - it may be clearer to interpret that those strategy definitions > are the ones, that the user would always have access to,
  • to rename StrategyNameType to DefaultStrategyNames (or similar) - pretty much the same reason as above

This is a very nice naming 👍.

How do you propose to split it? Like I wrote above, I don't think the StrategySetupService is that large in terms of functionalities...

The main idea was to store strategies separately. Because we want to keep all registered strategies global and this setup service might be provided on multiple levels.

Actually here's a new question 💡. If we need them globally do we need to register new strategies at run-time? We can go with forRoot, configure it once and that's it.

@kajetansw
Copy link
Member

That's a good point... I personally can't think of any use case of registering custom strategies at runtime. (Doesn't mean that there are none 😉)

Registering strategies on module level may also have a very few use cases.

I think it may be a good approach to follow YAGNI and start with what you said - to enable users to register strategies globally, only once, with forRoot (or some other way). Then, if someone would provide us with some use cases in the future we could expand an existing implementation 😉

@Karnaukhov-kh
Copy link
Member Author

Hi again @kajetansw!

As for me, your idea works 👍. Thanks a lot! Would you like to create a PR from your fork into this PR? (otherwise I will just copy-paste 😁)

  • every strategy has the same strategy constructor type StrategyConstructorType, you can find it in libs/template/src/lib/render-strategies/interfaces/available-strategies.interface.ts
  • because of the above I proposed dropping of NoopStrategyConstructorType and changing StrategyConstructorType so that the config input is optional

Maybe instead we can consider adding config to NoopStrategy function?

  • Without config, other strategies will not work.
  • Noop doesn't really care about the presence/absence of the config.

Also, think with should change strategy type in changeStrategy and changeOutOfViewportStrategy to keyof ExtendedStrategies<S>.

@kajetansw
Copy link
Member

kajetansw commented Jul 30, 2020

Sure thing! I'll create a PR within the next hour or so.

Maybe instead we can consider adding config to NoopStrategy function?

I did just that and everything seems fine, so I'll include it in the PR.

Also, think with should change strategy type in changeStrategy and changeOutOfViewportStrategy to keyof ExtendedStrategies<S>

Nice catch, I'll also do that :)

A new question arose in the meantime: is there some reason why files with only type/interface declarations don't have .d.ts extension? I always thought files that hold only declarations should be "declaration files" so that the TypeScript compiler wouldn't emit them, because they would be pretty much empty.

EDIT: Here you go: #196 😉

kajetan.swiatek and others added 2 commits July 30, 2020 09:40
…up-service

Types for extending default strategies with custom ones
@Karnaukhov-kh
Copy link
Member Author

Merged.

A new question arose in the meantime: is there some reason why files with only type/interface declarations don't have .d.ts extension? I always thought files that hold only declarations should be "declaration files" so that the TypeScript compiler wouldn't emit them, because they would be pretty much empty.

Haven't investigated this topic. But will check, thank you 👍.

@BioPhoton BioPhoton changed the title [WIP] Setting up strategies. Setting up strategies. Aug 8, 2020
@BioPhoton BioPhoton added the 🚧 WIP WIP: Work in progress label Aug 8, 2020
@BioPhoton
Copy link
Member

Kirill, could you put the strategy service as poc in the experiments folder? We need it to poc the rxLet with embeddedView rendering.
🙏🙏

@Karnaukhov-kh Karnaukhov-kh mentioned this pull request Oct 25, 2020
1 task
@hoebbelsB hoebbelsB deleted the strategies-setup branch April 13, 2023 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

</> Template @rx-angular/template related 🚧 WIP WIP: Work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Global and local strategies configuration

5 participants