- Notifications
You must be signed in to change notification settings - Fork27k
[Complete] Resource RFC 2: APIs#60121
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Welcome to part 2 of the Resource RFC, covering the API design of Angular's initial resource integrations. Also check out theResource RFC: Part 1 covering the Resource Architecture. Resource APIsThere are a number of APIs that work together and provide a cohesive story for asynchronous reactivity. Those APIs can be categorized into 3 groups:
The following sections cover APIs from each group in more detail. Note that in some cases, the APIs in this doc differ from the ResourceinterfaceResource<T>{readonlyvalue:Signal<T>;readonlystatus:Signal<ResourceStatus>;readonlyerror:Signal<Error|undefined>;readonlyisLoading:Signal<boolean>;hasValue():boolean;} TheResource interface exposes, first and foremost, data loaded asynchronously. Thevalue represents data as a In addition to the value itself we also expose additional properties indicating if a value is ready, being loaded - or maybe is not available due to some processing errors. Theerror signal will hold an error instance if data load failed (and will be More generally, resources transition through different states in response to changes in its reactive dependencies. Those states are enumerated in the ResourceStatus: enumResourceStatus{Idle,Error,Loading,Reloading,Resolved,Local,} The current state is exposed as thestatus signal. As a convenience, we expose 2 additional signals that makes it possible to quickly and conveniently check for common states:
RFC question 1: in the proposed API design resource status is of type ResourceStatus enum. ResourceStatus is a convenient namespace that collects all the statuses and makes them easily discoverable. At the same time using enums in Angular templates (ex.: We do expect this friction to be alleviated over time, either through better support for enums and other imported symbols in templates, or a dedicated template syntax for working with resources. However, it's clear this friction will be felt early on. We’ve been considering exposing statuses as an union type of string literals (see#58920 for additional context). What would be your preference when it comes to the status type? RFC question 2: the experimental resource implementation distinguishes between the loading operation triggered by change to the reactive dependencies ( Do you consider this distinction to be useful? resourceTheresource function is a factory that creates instances ofResourceRef (an owned flavor of theResource interface). Itprojects a reactive state to an asynchronous operation defined by a loader function and finally exposes the result of loading as aResource. This is a rather generic and lower-level building block that is adequate for interacting with a wide variety of asynchronous data storages: functionresource<T,R>({request?:(()=>R)|undefined; loader:(param:ResourceLoaderParams<R>)=>PromiseLike<T>;defaultValue?:T|undefined;equal?:ValueEqualityFn<T>|undefined;injector?:Injector|undefined;}):ResourceRef<T|undefined>; Therequest andloader functions (provided as options to theresource creation) work together to derive reactive, asynchronous data based on changes to the reactive state:
RFC question 3: Our intention was to use a name for the parameter (currently called Like effects,resources are owned by the component which creates them and thus require aninjection context. Alternatively, you can provide a specificinjector instance. The optionalequal function is passed to the signal representing avalue. Resource LoadingAt the heart of the resource is its
streaming resourceThe APIs described so far are well suited for the cases where an asynchronous data storage returns values in one response. There are other cases, though, where a server might respond in multiple chunks (for a given set of request parameters). As an example, a server might return the first n items of a list as soon as possible and follow up with more items in subsequent chunks / responses. To accommodate those use cases the resource comes with thestream option (alternative to theloader): interfaceStreamingResourceOptions<T,R>{request?:(()=>R)|undefined; stream:(param:ResourceLoaderParams<R>)=>PromiseLike<Signal<ResourceStreamItem<T>>>defaultValue?:NoInfer<T>|undefined;equal?:ValueEqualityFn<T>|undefined;injector?:Injector|undefined;} Thestreaming resource uses the same creation function discussed previously. Most of the options remain the same, but there is one important difference - thestream function is equipped to handle multiple responses for the same set of request parameters. To do so, the stream function returns apromise of a signal:
RFC question 4: the proposed design uses one resource function with a different set of parameters. Is this API making sufficient distinction between the streaming and non-streaming usage patterns? rxResourceThe streaming resource model of returning a To that end, we're adding the interfaceRxResourceOptions<T,R>{request?:(()=>R)|undefined; stream:(param:ResourceLoaderParams<R>)=>Observable<T>,defaultValue?:NoInfer<T>|undefined;equal?:ValueEqualityFn<T>|undefined;injector?:Injector|undefined;} The resulting resource tracks the value of the Observable over time, including if the Observable stream ends with an error. httpResourceThe user=httpResource(()=>`/api/user/${currentUserId()}`);
interfaceHttpResourceRef<T>extendsResourceRef<T>{readonlyheaders:Signal<HttpHeaders|undefined>;readonlystatusCode:Signal<number|undefined>;readonlyprogress:Signal<HttpProgressEvent|undefined>;}
results=httpResource({url:'/api/query',method:'POST',body:{...},params:{'rowLimit':100,},}); In either case, the request can be configured as a static value or by passing a reactive function. Like Non-JSON requestsBy default, imageBytes=httpResource.arrayBuffer('/image.png'); The other supported sub-functions are RFC Question 5: We started using this style of API with Type-safe HTTP responsesA goal of the By default, For JSON resources, you can directly specify a response type, just as with constuser=httpResource<User>(...);// HttpResourceRef<User|undefined> This is equivalent to a type cast. However, sometimes more strict type-checking is desirable. For example, often developers use runtime validation libraries likeZod to enforce type safety when dealing with external data sources. For example, this snippet uses Zod to validate the shape of the backend response at runtime: constUser=zod.object({id:zod.number(),name:zod.string(),// ... other fields ...});constuser=httpResource(...,{parse:User.parse}); RFC Question 6: how do you feel about this approach to type safety? Have you seen alternative approaches in other HTTP integrations that we should consider instead? Mutations
Custom ResourcesWe see lots of desire and benefit for libraries to expose their own async operations via the We are considering including base utilities that developers can extend to produce their own implementations of RFC Question 7: if you maintain a library or other utility that could benefit from exposing results as |
BetaWas this translation helpful?Give feedback.
All reactions
👍 40❤️ 43🚀 27
Replies: 50 comments 190 replies
-
question 3: I would definitely rename |
BetaWas this translation helpful?Give feedback.
All reactions
-
maybe |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I would suggest naming the parameter It's a reactive value that declaratively states the specificdata-need to be satisfied/resolved by this resource. The other suggestions are a bit generic IMHO. Also, |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 4
-
Tbh I would go for |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@maxKciw |
BetaWas this translation helpful?Give feedback.
All reactions
-
'source' is IMHO a very confusing expression. It is not the source of anything that happens in the loader. It is maybe the 'cause', but not the source. I strongly suggest using 'need'. It is a signal that shows that the resource is needed. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1👎 1
-
Sorry for not responding to any question of the RFC, but I feel that some questions are missing in the discussion. current valuewhat about the behavior of the resource regarding the current value when reloading, either imperatively, or because of a request signal change. The RFC says:
This looks wrong to me. I think that depending on the UI needs, either we want to switch to a loading spinner every time there is a reload, or we want to keep the current value (and maybe display a loading spinner in addition to the value) every time there is a reload. I find it weird to force the loss of the current value when loading, which makes it impossible to keep the current value displayed. Cancellation / unsubscriptionThis is actually related to Question 7. TestingThe common and best practice today, when not using resources, is to delegate to services to make HTTP requests. This makes it very convenient when testing the component because it's easy to mock/fake the service and make it return an observable created with |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
Agreed. Naming is hard. |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
-
I actually agree with@alxhub that if we have userId as signal and user as a resource that if and in reality I think showing loading is better than showing the old value, because if there is delay the user might thing his action was ignored and might try to try again, imagine in a page click next page but nothing happen you might think you clicked outside the button (specially on touch screens) and can try to click again which could cancel the ongoing request and create a new one which makes the delay even longer another potential solution (but more effort from the Angular team) is to add another variant |
BetaWas this translation helpful?Give feedback.
All reactions
-
When using a list of items with pagination, I much prefer not to show loading states when swapping pages. It would be much better to still show "Page 1" even after the signal state has changed to "Page 2".
It's likely a good default to be pure, and discard current value when state changes. But it would IMO be great if there was an option for |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
So we agree that the default behavior should be to clear the current value |
BetaWas this translation helpful?Give feedback.
All reactions
👍 6
-
Might be obvious but if keeping the current value, it should also keep the last response headers and status code as they're inherently tied to the state of the current value. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Why is this the case? As an educator and mentor of many junior engineers, it's much easier to help enforce the new standards of Signals (especially in a Zoneless app) that most reactive values are marked as signals for quick grok-ability. |
BetaWas this translation helpful?Give feedback.
All reactions
-
It's a predicate ! You use it as a signal but you don't it to be a signal 😄 |
BetaWas this translation helpful?Give feedback.
All reactions
-
@JeanMeche the fact that the RFC felt the need to specify the fact that it is reactive is quite a sign that it isn't entirely obvious. Making it a signal makes it obvious. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
-
Our hope is that TS ultimately adds support for signals to be declared as narrowable (microsoft/TypeScript#60948), in which case |
BetaWas this translation helpful?Give feedback.
All reactions
👍 18🚀 9
-
With this context, I'd like to withdraw my comment. This makes a lot of sense to me |
BetaWas this translation helpful?Give feedback.
All reactions
-
@crutchcorn just to clear this up further, I have something similar in my library |
BetaWas this translation helpful?Give feedback.
All reactions
🎉 3
-
I may be in the minority here, but I'd actually much rather not use a TypeScript enum here and rather just use a TS union of string constants |
BetaWas this translation helpful?Give feedback.
All reactions
👍 49
-
If they would make using enums in templates easy, would you still prefer union strings? |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Yeah - because even when not in the template generally you have to import the enum to use it's value but with a string literal you don't have to import the type in order to check it and can just infer its value |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
-
+1 for string literals! When debugging, the value is more readable then to remember what status 3 is about... 🥸 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 6
-
I would rather have a const object style enum where you get the best of both worlds of being able to use the object as it if was an enum or with a string literal. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Yeah, union type > enum IMO. The arguments for the enum I have been told make sense: primarily for my sake, that the helper functions for value/status do the bulk of the lifting and prevent people from overthinking/getting too granular. But personally in my experimentation with resource as an overthinking-maxxer, I alias the enum as a component variable to be used in the template for various edge cases half the time. This is probably a good speed bump for writing application code, but when I am experimenting with library type logic I'm like "ugh". |
BetaWas this translation helpful?Give feedback.
All reactions
-
Not only do I find this distinction useful, it's incredibly important for what, IMO, is the correct loading pattern of having cached results from the previous call still display in the UI while displaying a mini loading spinner indicating the refetching behavior. All apps should have:
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 9
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Does the Ultimately I like the idea of not having to use TypeScript generics to cast type-safe values at all. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 12
-
This is a very interesting idea and something we can definitely consider. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 14
-
One thing that I also find quite strange in the API is the |
BetaWas this translation helpful?Give feedback.
All reactions
-
Conceptually all resourcesshould be able to support reloading - it's basically the same as running the loader again with the same inputs, as if the resource were freshly created. A related question is "why is |
BetaWas this translation helpful?Give feedback.
All reactions
👍 6
-
Thanks@alxhub. The documentation currently says "@returns true if a reload was initiated, false if a reload was unnecessary or unsupported". So maybe it should drop the "or unsupported" part. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
|
BetaWas this translation helpful?Give feedback.
All reactions
-
Thanks@wartab. That's how the specific implementation used currently works. But the interface defines a contract that this implementation and other implementations are supposed to respect. So I think it would be a good idea to make the contract as clear as possible. |
BetaWas this translation helpful?Give feedback.
All reactions
😄 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
My feedback comes after having used resources in both SolidJS and now Angular. Regarding the questions: Question 1: (enum vs. string literal) Question 2: (usefulness of Reloading status) Question 3: (name of resource options) Question 4: (streaming API) Question 5: (more uncommonly requested types) Question 6: (type-safety in httpResource) There is always much more boiler plate to write to extract errors and there just isn't a good reason for it. Question 7: Now my two cents regarding resources:
In my limited time, I have worked on a function called listResource that I quite like (it doesn't have type narrowing, cause I'm bad at TS type juggling, but the idea is as follows). This is how it is used: combinedFilters=computed(()=>({startDate:this.startDate(),endDate:this.endDate(),sortBy:this.sortBy(),}));myList=listResource({filterSource:this.combinedFilters,fetcher:async(filters,pagination,notifyEndReached)=>{constnewRows=awaitfetchMore(filters,pagination);notifyEndReached(newRows.length<50);returnnewRows;},// Optional options here (this is actually the default behaviour):initPaginationValue:0,getNextPagination:(currentItemList,lastPage)=>{returnlastPage+1;},});// Some readonly signals:myList.data();myList.isLoading();myList.isLoadingMore();myList.isError();myList.isEndReached();// Some methods to reset and load more data:myList.reset();myList.loadMore(); Type definitions for those who care: typeResourceListState="loading"|"loading-more"|"loaded"|"end-reached"|"error";interfaceListResource<Type,FilterType,PaginationType>{readonlystate:Signal<ResourceListState>;readonlyhasData:Signal<boolean>;readonlydata:Signal<Type[]|null>;readonlyisLoading:Signal<boolean>;readonlyisLoadingMore:Signal<boolean>;readonlyisError:Signal<boolean>;readonlyisEndReached:Signal<boolean>;loadMore():boolean;reset():void;mutateList(mutationOperation?:(list:Type[])=>void):void;}typeResourceListFetcher<Type,FilterType,PaginationType>=(filters:FilterType,pagination:PaginationType,notifyEndReached:(endReached:boolean)=>void,abortSignal:AbortSignal,)=>Promise<Type[]>|Observable<Type[]>;typeResourceListOptions<Type,FilterType,PaginationType>={filterSource:Signal<FilterType>,fetcher:ResourceListFetcher<Type,FilterType,PaginationType>,initPaginationValue:PaginationType,getNextPagination?:(currentItems:Type[],previousPagination:number)=>number,injector?:Injector;};functionlistResource<Type,FilterType,PaginationType>(options:ResourceListOptions<Type,FilterType,PaginationType>,):ListResource<Type,FilterType,PaginationType>; Something like this should exist natively, because it's easy to get this wrong, between cancelation of requests when filters change, etc. Maybe the most controversial take I have about resources (that is because my own resource implementation did this, so I may be biased): Edit: I had mentioned this in the original resource-PR, but I would also like it to be possible to access |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
Going to co-sign that |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
As far as accepting |
BetaWas this translation helpful?Give feedback.
All reactions
👍 6
-
I agree on optional typing for errors in httpResource, that seems like a great idea! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Question 5: |
BetaWas this translation helpful?Give feedback.
All reactions
-
It won't treeshake with current optimizer (esbuild), but there isn't much to treeshake it's only a different argument getting passed to a mutual function. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yeah, tree-shaking isn't a concern here - in this case it's just a different way of passing another parameter (butso much cleaner in terms of typing). |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Probably a dumb request, but can there be an I know EDIT: Regarding RFC question 2, my vote goes to a string union type. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Would |
BetaWas this translation helpful?Give feedback.
All reactions
😕 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Apologies if the expectation is to only answer the prompts. I'm primarily concerned with the decision to discourage mutations in It's name implies that it's designed to interact with HTTP resources. The clearest definition of an HTTP resource is:
As an example, a resource could be Suggestions: if |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes, the name "resource" is not related to the HTTP / RESTful concept of "resources". It's a name that a few different reactive frameworks use to refer to async dependencies in the reactive graph. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Gotcha. What about calling it I see solidjs uses the naming convention of The better question might be: if mutations aren't meant for I know I'm using it wrong, but I love the elegance of disabling a form's submit button when |
BetaWas this translation helpful?Give feedback.
All reactions
-
When I introduce the userId:Signal<string>= ...;// e.g. inputuser=httpResource(`/api/user/${this.userId()}`); instead of doing userId:Signal<string>= ...;// e.g. inputuser=httpResource(()=>`/api/user/${this.userId()}`); Are the overloads with static values ( Doing it wrong does work sometimes, but of course doesn't react to changes. And if you have a required input signal, you get the error, that a value is not provided yet (of course). And that leads the junior devs on the wrong path, because then they start to setup the In my application I have only a handful of data requests, that don't use any filter parameters (from query etc.). Almost every fetch needs some parameter. So I won't miss the static overloads. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 10👎 1🚀 1
-
This is a good point - our thinking was that "well, notall data requests need to be reactive" but you're right that it's a footgun. I think we can consider removing the non-reactive flavors... |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2❤️ 7
-
I would like to see string literals as status values (like |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes, distinguishing between those two states is valuable. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I prefer And naming the function I would prefer (like
Regarding That would open up to future overloads with something like |
BetaWas this translation helpful?Give feedback.
All reactions
-
I've been playing around with |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
@ameckl Is that an assumption or a test feedback ? |
BetaWas this translation helpful?Give feedback.
All reactions
-
I quickly updated to 19.2.1 and tested it, and while now
Shouldn't the context be passed here? |
BetaWas this translation helpful?Give feedback.
All reactions
-
Good catch, That's what happen when you ship without a unit test. |
BetaWas this translation helpful?Give feedback.
All reactions
👀 1
-
The funny thing is that when I saw the PR, I was wondering about this, checked the code, got interrupted, and forgot about it |
BetaWas this translation helpful?Give feedback.
All reactions
-
#60228 fixes this. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
I would love to see a pattern like in AngularQuery where you can create a custom injector and inject your data when needed but without refetching the backend if the data is fresh. exportconstinjectTodos=()=>{assertInInjectionContext(injectTodos);constapiUrl=injectApiUrl();constdataId=injectRouteQueryParams<string>('dataId');constTODO_API='todo';returnhttpResource(()=>`${apiUrl}/${TODO_API}?dataId=${dataId()}`,);};@Component({selector:'todos-list',template:` <div>{{ todos.value() }}</div>`,changeDetection:ChangeDetectionStrategy.OnPush,})exportclassTestComponent{// I expect to have only one requestpublictodos=injectTodos();}@Component({selector:'todos-list',imports:[TestComponent],template:` <todos-list/> <todos-list/> `})exportclassParentComponent{} |
BetaWas this translation helpful?Give feedback.
All reactions
-
This should be doable in user land. Ngxtension has a |
BetaWas this translation helpful?Give feedback.
All reactions
-
yes tanstack query is already doing it. but I thought that would be nice to do it. |
BetaWas this translation helpful?Give feedback.
All reactions
-
About Q2: |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
But if I'm on a route which show not a list but a specific item (e.g. a user), then I don't want to see the last user, while navigating from "showing the last known value" is a needed functionality, I agree, but should it be part of |
BetaWas this translation helpful?Give feedback.
All reactions
-
@flensrocker So you think it would be better that all userdata is set to undefined or null when clicking on the next route? Wouldn't it be enough to have a loading animation with the isLoading() signal, because in this scenario too, it would flicker twise first removing the old user and than adding the new one instead of replacing the first with the second. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
There are two sides of this "coin" for me:
Yes, you should avoid flickering, layout shift etc. in the template when changing state. But that highly depends on the usecase. What I do, when showing a list of items (think search results) and I modify the filter, I do show the old results grayed out and a progress bar to visualize, that the current list is reevaluated. The template can that reuse the components (track $index) and it#s a nicer experience. When navigating from one entity to another, I would rather not display the old entity while fetching the new one. "Disabling" the view of the entity highly depends on how it's actually done. I often have forms, and when I set a new value to the existing form, I have to really think about, if I want to "emitEvent" or not, because the form may react to changes in fields. And distinguishing between "the field has changed because of user input" or "the field has changed because of a whole new form value is loaded" is difficult. So I rather destroy the form and create a new one, because then I have the "switchMap(form => form.ValueChanges)" kind of thing where I'm able to interrupt the "valueChanges" (it does not emit on subscribe) in a controlled way. Both scenarios can be accomplished regardless if the resource resets to the default value (or undefined) while loading or preserving the old value, because you can visualize that depending on the The other thing is "keeping the resource in sync". Signals are for synchronous state. And when the request signal represents "userId 2", but the resource (the request is part of the resource) still represents "user with userId 1", then the resource in itself is out of sync. That's why the resource should reset to the default value (which is undefined on default) while the value is not in sync with the request. But that's just my opinion, we can agree to disagree. 😀 I would like to have less "built in" in the low-level resource, so it can be extended with whatever I need. And putting a "memoizeResource" around every kind of |
BetaWas this translation helpful?Give feedback.
All reactions
-
I don't think there is any notion of "resource in sync" when you just consider "request" and "value": // IMO equally wrong while loadinguserId='2'withresource.value=user1userId='2'withresource.value=undefineduserId='2'withresource.value=defaultValue As I see it, It's mostly a matter of picking the smartest approximation... so it would be nice to see examples where "undefined" or "defaultValue" are better than "user1" (for which clear examples have been provided). My 2 cents! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Yes, you have to put "status" into that equation. There exist different equally valid solutions to this. Simplified example: typeResource<R,T>={status:ResourceStatus.Idle;}|{status:ResourceStatus.Loading;request:R;}|{status:ResourceStatus.Error;request:R;error:unknown;}|{status:ResourceStatus.Reloading;request:R;value:T|undefined;// in this case it can be the last resolved value or "undefined" if it has never been resolved}|{status:ResourceStatus.Resolved;request:R;value:T;}; |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
In all honesty, I'd say the best approach would be to have both a status and a
I tend to agree that |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Question 3: what about searchTerm=signal<string>('');// used as an [(ngModel)] for an inputdata=rxResource({loader:()=>this.myService.find(this.searchTerm()),request:this.searchTerm}); VS. searchTerm=signal<string>('');// used as an [(ngModel)] for an inputdata=rxResource({loader:()=>this.myService.find(this.searchTerm()),trigger:this.searchTerm}); |
BetaWas this translation helpful?Give feedback.
All reactions
-
Sidenote (because of "wrong" code): Don't use the signal inside the searchTerm=signal<string>('');// used as an [(ngModel)] for an inputdata=rxResource({loader:({request:searchTerm})=>this.myService.find(searchTerm),request:this.searchTerm}); |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
ok, when we use it like this, |
BetaWas this translation helpful?Give feedback.
All reactions
-
The
|
BetaWas this translation helpful?Give feedback.
All reactions
-
yes, i know - that's why for me the name |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Not sure if anyone has mentioned this here regarding the statuses topic - but these statuses are a great use case (in my opinion) for an as const POJO (Plain Old JavaScript Object) that you can then also create a matching type for that exposes some additional type-safety, like an enum. That way you get that same enum behavior but without the enum itself (which has been proven many times by many people to be a part of the TS language that is not ideal both in its construction and runtime behavior behind the scenes). Check out@mattpocock's video here if you haven't seen it already:https://www.youtube.com/watch?v=jjMbPt_H3RQ |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
My initial thought is that yes, the numbers be explicitly defined in the POJO. However, I'm not sure exactly how a migration from the current enum to this approach would be handled. In any case, I will say that, in my experience in my job, we use a pattern where statuses span a broad range (i.e. from 0 to 1000 or similar) and we categorize groups of statuses within that overall range, kind of like http status codes. So errors might be from 500 to 600, success might be from 200 to 300, etc. And that way, you can insert new statuses into the system very easily and still have room for more and not have to "shift" things around. That approach may be overkill for this scenario, but the concept of the POJO still makes more sense and is a much better solution than the current enum. |
BetaWas this translation helpful?Give feedback.
All reactions
-
It's an experimental API, so breaking it is "allowed" I guess. Reminds me of good old C64 BASIC line numbers time... 😀 But I agree, that may be overkill here. |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
-
Yeah so maybe some reduced version of that or something. Who knows! |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@johnsonav1992 there is a trick to move away with full compatibility is to name the type as the object e.g. now everything works same as before and now typescript will use the type when you use it as a type and the object when you use it as a value :) |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@robertIsaac yep! This is exactly what I'm proposing. I think the above discussion was mostly revolving around when new statuses get added but not really sure this would happen often for this scenario. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Would love to see a clear and simple way to defer requests based on some reactive trigger. I have a set of Resources defined in a component with multiple |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
That's a fair point, I guess all I was trying to say is that I understand why this behavior is the way that it is, and that it is probably better to opt into a waterfall rather than opt out |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I probably differ on that point of view. Resource being a derivation (an asynchronous one), the concept of lazyness is somehow bizarre. Either you can derivate (= trigger the loader) or you can't (the state is somehow invalid and your request returns undefined). It the possibility of derivating that should drive that if the resource triggers or not. And if there is no derivation at play, then probably resource isn't the right pattern for that and concrete usescases could help shape future apis ! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Well if it's going to stay this way then the only thing I can think of other than using some arbitrary signal to manage whether the loader returns undefined or not is to use route parameters or something like that. But still seems like that might be a bit much in some cases where you just straight up don't want to trigger it until you are ready. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@JeanMeche a simple example. State management and loading is encapsulated in a service to keep state and related logic en encapsulated. How do you control data loading? The trigger could be a route load (guard) or as required by the UI in a component. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
In general, I'd appreciate more real examples of how this code can benefit big sized apps. The example of using a resource in component is very trivial... actually, for a long time loading data in a component is understood as something to avoid, data fetching is a different responsibility. I feel the example could be misleading in some contexts. As a primitive for derived asynchronous state, great. I can definehow data is loaded. Thewhen.. should be responsibility of the app. I struggle to see why eager execution is better than being lazy, as computed is. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
-
Kinda late to the party😅 I haven't been able to go through all the comments here (a bit overwhelming I must say), so pardon me if any of my points have already been discussed and answered. My first reaction is actually more about feature parity with the existing RxJS-based HTTP client, which isn't mentioned much in the RFC. I guess it all boils down to whether there will be declarative equivalents (in the form of some configuration parameters) of the following RxJS operators in this new resource API:
You know, the usual stuff. They are pretty handy, so I'd rather we still have them with the new API. Q1: Maybe template literals? That'd be cleaner in my book. Or expose all the available states via signal getters/methods, like Q2: Is it really necessary to empty the values when the resource is loading due to a reactive signal update? I seriously doubt it. For example, when I update the search box for a list of to-do items, why should the to-do list be emptied while the backend search and data fetching are happening? Q3: Q4: At first glance, yes. Q5: N/A Q6: Appreciate the additional parser option. Would it be limited to synchronous parsers? Many validation/schema libraries allow async validation on their own. Would that cause (timing/scheduling) problems? And what would happen when the parsing/validation fails? Can we specify error handlers for that? Q7: N/A |
BetaWas this translation helpful?Give feedback.
All reactions
-
As I understand the reasoning behind that is, that the resource itself is "in sync" with the request. So when the request changes, then the value of the resource is invalidated (and reset to the |
BetaWas this translation helpful?Give feedback.
All reactions
-
Can I specify a fallback value with the error handler, though? Similar to what |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I haven't tried it in detail, but I would say yes, when the loader throws an error, the value is reset to the
So you don't specify a fallback value in the error handler but in the resource itself. |
BetaWas this translation helpful?Give feedback.
All reactions
-
OK I see... I was hoping for a more dynamic error handler so I can provide fallback values based on the actual error, but I guess this will fit the bill in many cases. The |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
You could always feed the |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
i would say i love the idea of the httpResource but for consistency keep the JSON option as other options like text / blob .... this.#router.navigate([],{queryParams:params,queryParamsHandling:'replace',}); to keep the URL sync with the data filtered by the user exportfunctiongetStablePaginationHttpResource<T>(url:string,paramsSignal:IPaginatedRequestSignal,syncWithUrl:boolean):TStableResource<IPaginatedResponse<T>>{constqueryParamsService=inject(QueryParamsUtilityClass,{optional:true,});constresource=httpResource<IPaginatedResponse<T>>(()=>{constparams=getPaginationResourceRequest(paramsSignal);if(syncWithUrl)queryParamsService?.updateQueryParams(params);return{url:url,params:{ ...params},};},{defaultValue:PAGINATION_RESPONSE_DEFAULT});conststableValue=linkedSignal<HttpResourceRef<IPaginatedResponse<T>>,IPaginatedResponse<T>>({source:()=>resource,computation:(value,prevValue)=>value.isLoading()&&prevValue ?prevValue.value :value.value(),});return{resource:resource,stableValue:stableValue,};}constPAGINATION_RESPONSE_DEFAULT:IPaginatedResponse<never>={current_page:1,data:[],per_page:10,total:0,}; |
BetaWas this translation helpful?Give feedback.
All reactions
-
(Apologies for not following the format of the RFC questions) Firstly, I fully support a) moving to a string union to represent the I'd like to discuss the modelling ofstreams using a (potentially) better In particular: I prefer to think of streams as beingconnected ordisconnected (or inconnecting orerror states). I find that you often want to explicitly manage this connection (as touched on in previous comments in this RFC, when folks have talked about adding an (Aside: for token-based auth, you want to treat it like a stream because a refresh token call may fail if e.g. a user is banned). Below are a couple of examples of how I'm modelling state for two different NgRx SignalStores that use RxJS streams under the hood: typeDisconnectedState={status:'disconnected';user:null;error:null;};typeConnectingState={status:'connecting';user:null;error:null;};typeConnectedState={status:'connected';user:User|null;error:null;};typeErrorState={status:'error';user:null;error:string;};typeAuthState=DisconnectedState|ConnectingState|ConnectedState|ErrorState;constinitialState:AuthState={status:'disconnected',user:null,error:null,}; typeDisconnectedState={status:'disconnected';categories:[];error:null;};typeConnectingState={status:'connecting';categories:[];error:null;};typeConnectedState={status:'connected';categories:string[];error:null;};typeErrorState={status:'error';categories:[];error:string;};typeConfigState=DisconnectedState|ConnectingState|ConnectedState|ErrorState;constinitialState:ConfigState={status:'disconnected',categories:[],error:null,}; (I give two very similar examples just to show how two different use cases can be modelled in the same fashion). Then, the relevant stores expose a For reference, here are the two store implementations in full:
I'm not yet sure how my thoughts above could be incorporated into this proposal, or how I would use the new resource-based APIs to replace this, but I'll share some off-the-cuff thoughts:
Overall, I think that trying to fit stream-based resources into the same model as the other resources may be detrimental, and seems to move us further away from the semantics of RxJS and stream-based processing. |
BetaWas this translation helpful?Give feedback.
All reactions
👀 2
-
Does it make sense that
I guess designing it like that could have the advantage that each dedicate type does not have to deal with According to thedocs
Does this mean that I guess the approach using a union type based on a discriminator could have disadvantages too, or at least I did not think it through in detail yet, but I just wanted to share the thought and concerns about API’s including If the resource is in a state of error I would assume that a value for this error is available, hence that it can’t be undefined. The same is true for being in a success state. I would assume that a value is available, which again can’t be undefined, hence adheres to the value retuned by the loader. This semantics should be reflected in the type system as well. If I know that the state is “resolved”, then in the best case I don’t want to be forced checking if a value is available. I hope this makes sense. I did not fiddle around with Resources in detail yet, so don’t blame me if this concerns are already resolved. I just wanted to share the thought. The work regarding this topic is highly appreciated. Thanks. 🙏 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
From my experimentation, resource clearing the value when request change IS logically correct but is really NOT ideal for UX Imagine following example
Current "workround" is to use products=rxResource({defaultValue:[],request:this.debouncedQuery,loader:({ request})=>this.#productService.find(request),});// workaround to NOT lose items when reloading to prevent jumpy UX ( UX concern)productsList=linkedSignal<Product[],Product[]>({// needs explicit generic typesource:()=>this.products.value(),computation:(source,previous)=>this.products.status()===ResourceStatus.Loading&&previous ?previous.source :source,}); it's not an IDE issue, compiler complains about it as well Desired behavior OrderApp.-.Google.Chrome.2025-03-18.10-47-43.mp4 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Looking at this example, I wonder if it's desirable to have Let's assume the data for the ui is fetched in 2 chunks and (current) // ui_products = chunk_of_products_1 + chunk_of_products_2products=rxResource({defaultValue:[],request:this.debouncedQuery,loader:({ request, value})=>{if(request.chunk===1){returnthis.#productService.find(request),}else{returnthis.#productService.find(request).pipe(map(items=>[...value, ...items])),}},}); The question is: should Some objections / reasonings:
playground:https://stackblitz.com/edit/stackblitz-starters-nlqwjbcn?file=src%2Fmain.ts All of this just to say: adding Just some thoughts! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Note, |
BetaWas this translation helpful?Give feedback.
All reactions
-
Q1: I personally won't use enums (unless they are implemented natively in JavaScript directly). If it is a const object instead, why not. But as you first need to reference it in the class first, right now, I would prefer the union type Q2: As far as I've been using it, I never had any use case where the reloading state was relevant. I would always expect the loading one and that's it. There may be some specific cases where reloading could be useful but in that case I would expect that:
Q3: Q4: having an overload for stream and another one for loader is clear enough for me. My concern here is more about signals going "too far" in what they should achieve in my opinion as I don't think they are good for streams... Q5: I can't find any at the moment Q6: I think it's great like this! Q7: I don't know if I would need something lower level to implement the resource contract for any external lib I would make BUT I would most probably need it if I created some fake services, using real resources in the Http instances and fake ones in Fake instances like InMemory for example! |
BetaWas this translation helpful?Give feedback.
All reactions
-
RFC answer 1: RFC answer 2: RFC answer 3: RFC answer 4: RFC answer 6: Thanks for this RFC! <3 And guys, have a question (maybe, a stupid one): so, using a resource that make a fetch call to an api, will not trigger angular interceptor, right? I think this is a big difference between the two, if my understanding is correct. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes, |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
We would like to thank everyone who commented on, or otherwise engaged in the discussion on this RFC.The API section alone got over 200 comments: responses to the RFC questions, new use-cases and many, many insights. The level of details and depth of the technical discussion was outstanding - the Angular community at its best! We went over every single comment, responded to many of them and discussed several patterns internally, in the team. Generally speaking the feedback can be broadly categorized into: “Resource as a concept” and “API design”. When it comes to the conceptual side of the new primitive we could see several main themes:
As for the API design, there was a lot of consensus around RFC questions and API shape in general. Based on the feedback you’ve shared we’ve decided to make the following changes:
Some of those changes landed already, some have open PRs or will have PRs opened. In any case we will make all the mentioned changes before releasing Angular v20. The Resource story in Angular is just starting and will be evolving. To reflect this, the resource APIs will remain experimental in Angular v20. We will persuade the stabilization process and embark on designing additional APIs and functionality in the subsequent releases. Your continued feedback is crucial: play with Angular v20, build on top of it and open GitHub issues for any problems or enhancements that should be considered. Thank you |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5🎉 19❤️ 19🚀 6



