-
-
Notifications
You must be signed in to change notification settings - Fork 37
[WIP] feat: Angular Adapter #129
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
base: main
Are you sure you want to change the base?
Conversation
|
|
@theVedanta Thanks for your PR! I just did a first review and I've shared some consideration in some files, but to avoid repeating them 'll summarize them here:
|
| export function useDefaultPacerOptions(): PacerProviderOptions { | ||
| return inject(PACER_OPTIONS) ?? DEFAULT_OPTIONS | ||
| } |
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.
| export function useDefaultPacerOptions(): PacerProviderOptions { | |
| return inject(PACER_OPTIONS) ?? DEFAULT_OPTIONS | |
| } | |
| export function injectPacerOptions(): PacerProviderOptions { | |
| return inject(PACER_OPTIONS) ?? DEFAULT_OPTIONS | |
| } |
As a convention I'd prefer to use inject and remove the default* prefix since with DI you could provide the option in a nested node
| const batcher = new AsyncBatcher<TValue>(fn, mergedOptions) | ||
| const stateSignal = signal<Readonly<TSelected>>( | ||
| selector(batcher.store.state) as Readonly<TSelected>, | ||
| ) | ||
|
|
||
| // Subscribe to store changes and update signal | ||
| const unsubscribe = batcher.store.subscribe((state) => { | ||
| const selected = selector(state) | ||
| stateSignal.set(selected as Readonly<TSelected>) | ||
| }) | ||
|
|
||
| const destroyRef = inject(DestroyRef, { optional: true }) | ||
| if (destroyRef) { | ||
| destroyRef.onDestroy(() => { | ||
| unsubscribe() | ||
| batcher.flush() | ||
| }) | ||
| } |
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.
like the other framework adapters we could use tanstack angular store.
It should be something like this
import {injectStore} from '@tanstack/angular-store';
// your implementation
const state = injectStore(batcher.store, selector);| @@ -0,0 +1,6 @@ | |||
| // re-export everything from the core pacer package, BUT ONLY from the async-batcher module | |||
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 can remove this comment
|
|
||
| const items = computed(() => queuer.state().items as Array<TValue>) | ||
|
|
||
| return [items, queuer.addItem.bind(queuer), queuer] |
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.
In angular we can't really use this fn if we use it through classes. A better approach could be returning the items signal with properties
interface AsyncQueuedSignal<TValue> extends Signal<TValue> {
readonly addItem
readonly queuer
}
const items = computed(() => queuer.state().items as Array<TVAlue>);
return Object.assign(items, {
addItem: queuer.addItem.bind(queuer),
queuer
});
Yo! |
Addressed everything so far! Thank you so much for your review :) I'm fixing the examples & working on adding testing next. |
|
My 2 cents reviewing from my phone:
@Component()
class ExampleComponent {
value = input.required<number>()
// will throw that input signals are not ready
debounced = createDebouncedValue(value)
}You can use |
|
@theVedanta What @benjavicente is saying is that if someone is using a export function injectDebouncedValue<TValue, TSelected = {}>(
value: Signal<TValue>,
initialOptions: DebouncerOptions<Setter<TValue>>,
selector?: (state: DebouncerState<Setter<TValue>>) => TSelected,
): [Signal<TValue>, AngularDebouncer<Setter<TValue>, TSelected>] {
const debounced = injectDebouncedSignal(value(), initialOptions, selector)
// -------------------------------------Λtheoretically an implementation with linkedSignal could allow you to manage everything with the correct timing, but it must be checked, i am not sure. In tanstack table required inputs are supported with proxies, and tantack query implementation changed many times, but they are still using proxies / computeds at the top In case, I would probably use both TValue and a fn<TValue> signature in order to support signal as an argument. or if there are issues with the types, i'll stick only to the callback signature to be consistent with angular primitives export function injectDebouncedSignal<TValue, TSelected = {}>(
- value: TValue,
+ value: TValue | (() => TValue),
initialOptions: DebouncerOptions<Setter<TValue>>,
selector?: (state: DebouncerState<Setter<TValue>>) => TSelected,
): DebouncedSignal<TValue, TSelected> {
- // const debouncedValue = signal<TValue>(value)
+ const debouncedValue = linkedSignal(() => typeof value === 'function' ? value() : value)Also we should probably not return an array within those function but just an object. Destructuring is a little weird in angular classes, and not consistent with all other signal impls |
|
Iβd like to add a note, though. These utilities are effectively things like For example, in the case of the debouncer: if the dependent
Another impl that may work import {linkedSignal, untracked} from '@angular/core';
function debouncedValue<T>(
value: () => T,
initialOptions?,
selector?
) {
const staleValue = {};
const debouncedSignal = createDebouncedSignal(staleValue, initialOptions, selector);
const value = computed(() => {
const val = debouncedSignal.value(); // <--- debouncedSignal() or debouncedSignal.value()? the utility that return a signal probably will just have the accessor
const initialValue = value();
if (val === staleValue) return initialValue;
return val;
})
return ...
} |
|
Iβm also not entirely sure that we should use If we compare this with Angular, they use I remember that some time ago this was being discussed. What do you think about it? @crutchcorn @arnoud-dv (sorry for the tag, but you might be able to help.) |
Oh yeah, for sure! I just ran a simple replace query for now to rename all create* modules to inject* since almost all of them wrap the injectStore from angular-store in some way or the other and run in an injection context. I'll take a dive and find the utils that might need to be more appropriately named. If you've seen some already, feel free to drop them here for a starting point for me! Again, I really appreciate all your help :) |
|
I'd rather remain consistent with the rest of our ecosystem and call it |
π― Changes
β Checklist
pnpm run test:pr.π Release Impact