Description
Information
Currently debounce can be used by passing to select { debounce: true }
. That means a selector must be either debounced or not.
This creates a few problems:
- Any debounced selector cannot be used easily in effects
- Any selector based on debounced selector is actually kind of debounced too - but not always how you would expect. That makes even one debounced selector a dangerous thing.
After working with component store for some time I realized what I was missing is having the ability to both debounce and not debounce the same selector. Any selector used in a component should be debounced for performance while selectors used in effects shouldn't. That creates a contradiction with the current implementation as one selector can be used in a component as well as in an effect.
The proposed solution is allowing to debounce every selector while also allow immediate access to any selector. I came up with a solution that wraps component store and I believe it's a good addition. The idea is returning an object from select containing two properties debouned$
and immediate$
allowing the user to decide which strategy to use solving the above problems.
The implementation is pretty straightforward:
First selector:
{
immediate$: this.select(projectionFn),
debounced$: this.select(projectionFn, { debounce: true }),
};
Selectors based on other selectors:
{
immediate$: this.select(
...selectors.map((selector) => selector.immediate$),
projectionFn
),
debounced$: this.select(
...selectors.map((selector) => selector.debounced$),
projectionFn,
{ debounce: true }
),
};
Considering we can memoize projectionFn
once for both selectors returned this solution has many benefits:
- No need anymore to think if
debounce
can be passed and what effect will it have on other selectors and effects. - Debounce performance boost to any selector used by a component
- Easy effects access to immediate store value
- Due to memoization,
projectionFn
will always evaluate once although there are two selectors. - Due to select internal
shareReplay
usingrefCount
if you don't use any of the selectors in the object there will be no ghost subscriptions.
The only thing lost here is the ability to pass any observable to select
. This can be solved anyway. One option is having this functionality as a new method called for example createSelector
and retaining the select functionality. This will also make this change non breaking.
Describe any alternatives/workarounds you're currently using
- Carefully use debounce as it is now - from my experience it can lead to a lot of weird results especially when combining debounced and non debounced selectors.
- Don't use debounce at all and pay the performance penalty (and actually the memoization penalty when using async pipe inside ngIf due to refCount).
- Wrap component store by yourself with this functionality
I would be willing to submit a PR to fix this issue
- Yes
- No