-
Notifications
You must be signed in to change notification settings - Fork 26.3k
Experimental Resource API #58255
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
Experimental Resource API #58255
Conversation
Optimizes `PendingTasks` slightly to avoid notifying the scheduler if the disposal function of a pending task is called twice.
Am I wrong for my layman's take being "oh cool, so basically this would be like an official To walk through my understanding of it, here is what I think the syntax would be in a component from how I am reading your explanation and the tests. @Component({
selector: 'app-current-user',
template: `
<p>value: {{ userResource.value() }}</p>
<p>status: {{ userResource.status() }}</p>
<p>error: {{ userResource.error() }}</p>
`,
})
export class CurrentUserComponent {
id = input.required<string>();
// `inject` some service with a method `fetch` that returns a promise.
// Or in the `loader`, perhaps just directly pass in a `fetch`
backend = //..
// Reacts to `id` and its initial value
userResource = resource({
request: () => ({id: id()}),
loader: (params) => backend.fetch(params.request),
});
doSomething() {
// `doSomething` could be a method or `computed` or `effect` etc,
// that can get the `useResource.value()` among other things,
// like the user resource's `status()` and `error()` as well
}
} And in a different flavor of this
Is that all sound? |
loader: ({request, abortSignal}) => { | ||
const cancelled = new Subject<void>(); | ||
abortSignal.addEventListener('abort', () => cancelled.next()); | ||
return firstValueFrom(opts.loader(request).pipe(takeUntil(cancelled))); |
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.
Should it be more clearly documented that this only handles the first emission of an observable stream?
This will work well for handling most HTTPClient calls, which is probably the most significant target for this?
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.
Should it be more clearly documented that this only handles the first emission of an observable stream?
This was the biggest takeaway of the source code for me. Same with your second point. I suppose the refresh
would cover those other HttpClient cases, but imperatively. Hence the clear documentation would be nice.
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.
Note that this is still a draft PR 😊
More docs are coming!
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.
mind elaborating on the reasoning behind this decision to use firstValueFrom? It's not necessarily intuitive from an RxJS perspective and also limits the usage and flexibility to some extent.
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.
mind elaborating on the reasoning behind this decision to use firstValueFrom?
A stream of T values doesn't have enough information to communicate start loading / resolved. So this is geared towards HTTPClient-like usages.
An alternative API would have a stream of value + state but this rather far from the initial API idea.
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.
IMO a status of "loading" only makes sense before the first value returns. After that whenever a new value is emitted from the observable, it's just "there". I work a lot with websockets and the server is pushing values whenever it needs (I connect observables in the backend via websockets to observables in the frontend to propagate changes to all interested clients).
Regarding the name "loading": for me this means it's for fetching data. But couldn't we use it for posting data, too? In my similar implementations I use "busy" as the description of the status.
I just got a quick look at the code, it's definitely something I'm very interested in! Great work!
To me it looks as a conceptual micro-implementation of what a query is in Tanstack Query, but:
IMO an absolutely great move 💪 Question @alxhub: what's your take on potential esource invalidation, whenever the client knows that the resource is already outdated? E.g.
|
Maybe the |
My only concern is that the server caching for SSR won't work for the fetch flavor. You'd need to use firstValueFrom(HttpClient) to still get the benefits of HttpClient or use the rxResource version. It's a gotcha, but perhaps a confusing one for new devs. |
c3b6a75
to
b56f9ce
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.
LGTM
Reviewed-for: public-api
Reviewed-for: fw-core
b56f9ce
to
03cf65d
Compare
03cf65d
to
d36db90
Compare
Caretaker: TAP is failing only due to the batched migration changes (which require a BUILD update) - this PR is passing. |
This PR was merged into the repository by commit 9762b24. The changes were merged into the following branches: main |
) Implement a new experimental API, called `resource()`. Resources are asynchronous dependencies that are managed and delivered through the signal graph. Resources are defined by their reactive request function and their asynchronous loader, which retrieves the value of the resource for a given request value. For example, a "current user" resource may retrieve data for the current user, where the request function derives the API call to make from a signal of the current user id. Resources are represented by the `Resource<T>` type, which includes signals for the resource's current value as well as its state. `WritableResource<T>` extends that type to allow for local mutations of the resource through its `value` signal (which is therefore two-way bindable). PR Close #58255
Implementations of two rxjs-interop APIs which produce `Resource`s from RxJS Observables. `rxResource()` is a flavor of `resource()` which uses a projection to an `Observable` as its loader (like `switchMap`). PR Close #58255
Not sure if this is still being read, but as someone who had previously worked with resources with SolidJS and having implemented a version of Resources a while ago in Angular userland (I was actually working on a PR before leaving for holidays, because I didn't expect resources at all, so I'm really happy you guys did it), I have a few requests/suggestions regarding the API. Resources are nice, but being able to handle error-tolerant "paginated" resource lists without going through hoops is nicer. So in my opinion, either a sort of Quickly going through the code without testing, from what I understand, once the state of the resource changes, the current value is lost. I don't think that is a good thing. In my opinion, the value should still be there while loading and while having errors. For this purpose, I would also suggest passing the current value while executing the resource loader. On top of that (and I know this is something that sucks with Promises and rxjs), I'd love if there was a way to type hint errors. Also a personal preference I had was to be able to use the variable that contains the resource as a signal for shorter and more consistent syntax rather than doing |
@wartab That my be solvable with the new |
@wartab cool to hear the you were toying with the ideas similar to the |
Hey hey folks! Based on the examples I have seen floating in the wild and my own experimentation I would like to ask about some choices that have been made for
Now , what I assume is that My main worry is that this confuses people about RxJs even more as the current way it's used is close to something like
because the Am I missing something? EDIT: Another thing which comes to mind, the could have just accepted both If that's the case, I would still say it would be more fortunate to provide something like a and keep the
|
@tomastrajan I have the impression, rxRessource is currently rather optimized for HTTP requests that exactly return one result. I'm not sure if there are plans to make it more powerful, but currently, I would directly use RxJS for more complex data flows. Regarding combining rxResource and resource: IMHO the reason for this is to separate the RxJS-interop by putting it into a secondary entry point of its own to prepare for a time when RxJS becomes optional (but stays important for more complex data flows). |
@manfredsteyer this makes sense, then again, it looks like main use case is to react to the change in the It feels like this is solved in a very non-idiomatic / confusing ways with the currently proposed approach? |
A typeahead with a debounce was the first thing that I tried with an
This is also the scenario I ran into when trying to initiate a stream with the PS to the team: Overall very excited about this API - looking forward to expanding on the rx observations during the RFC. |
My 5 cents: exhaustMap() behavior of the refresh() is a mistake |
First of all, nice to see this being included in Angular! I have 2 questions: First, you decided against using getters on the resource. I.e. one calls 'myResource.value()', as opposed to 'myResource.value'. In Svelte and SolidJs, often such stores / bundles of signals, are represented using getters. I am not sure, if this would work with the current typeguard on hasValue, that might require Resource to be defined as a union. Anyways, what were the decision for one over the other here? And secondly, I am sure you also looked into Suspense and similar concepts of other frameworks. In react and solidjs, reading data before its loaded throws, to allow propagating loading states automatically. In the case of solidjs, automatically propagating loading states through their signal graph. Is this something that is still being explored, or did you decide against such a mechanism already? |
Hey all! Wow, this PR has 74 comments 😲 I'm happy to join in the discussion here, but I do want to note: There will be a Resource RFCThis has been the plan, but we wanted to get the API into people's hands beforehand, so that we can have more concrete conversations around the API and its semantics. Your feedback / opinions are extremely valuable to us ❤️ |
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. |
Implement a new experimental API, called
resource()
. Resources are asynchronous dependencies that are managed and delivered through the signal graph. Resources are defined by their reactive request function and their asynchronous loader, which retrieves the value of the resource for a given request value. For example, a "current user" resource may retrieve data for the current user, where the request function derives the API call to make from a signal of the current user id.Resources are represented by the
Resource<T>
type, which includes signals for the resource's current value as well as its state.WritableResource<T>
extends that type to allow for local mutations of the resource through itsvalue
signal (which is therefore two-way bindable).Also, implementations of an rxjs-interop APIs which produce
Resource
s from RxJS Observables.rxResource()
is a flavor ofresource()
which uses a projection to anObservable
as its loader (likeswitchMap
).