Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Complete] Resource RFC 2: APIs#60121

Locked
Feb 26, 2025· 50 comments· 190 replies
Discussion options

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 APIs

There are a number of APIs that work together and provide a cohesive story for asynchronous reactivity. Those APIs can be categorized into 3 groups:

  • Resource (capital R) and related interfaces - this is the fundamental API, a contract where the framework and Angular developers agree on how asynchronous processing and resulting data are represented.

  • resource and related APIs - represents a generic and flexible resource primitive where data can be loaded asynchronously from a wide variety of storages.

  • httpResource - this is a specialized resource that makes it easy to load data exposed by HTTP endpoints.

The following sections cover APIs from each group in more detail. Note that in some cases, the APIs in this doc differ from the@experimental APIs already in Angular - this reflects planned updates we have not yet landed.

Resource

interfaceResource<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 aSignal since resources can update over time (for example, in response to reactive dependency changes). This is analogous to how the computed signal works - it exposes a signal whose value can change as the application runs and reactive expressions are recomputed.

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 beundefined if the load is in progress or finished successfully).

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:

  • isLoading signal will have thetrue value if the resource is in one of the Loading or Reloading states.

  • hasValue will have thetrue value if thevalue of the resource is not undefined. Note that this does not mean the resource is necessarily resolved. For example, the resource may have adefaultValue other thanundefined, in which casehasValue will always betrue.

    Note thathasValueis reactive, despite not being typed as aSignal.

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.:@if (myRes.status() === ResourceStatus.Error) ) requires exposing ResourceStatus on a component instance - this creates additional authoring friction.

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 (Loading) and the one triggered imperatively (Reloading). Generally, inLoading state the resource has no current value, but inReloading the resource keeps its value while reloading.

Do you consider this distinction to be useful?

resource

Theresource 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:

  • request - a reactive function which determines all required parameters for therequest to be made; you can think about this function as computed that derives request parameters to be used by the actual loading procedure.
  • loader - the actual loading procedure that accepts therequest prepared by the previously mentioned request function. A loader is expected to return a promise that resolves with data to be exposed by a resource instance (or rejected in case of loading problems). The loader function is asynchronous andnot reactive.

RFC question 3: Our intention was to use a name for the parameter (currently calledrequest) indicating that we arepreparing all information necessary to make an actual request. The common point of feedback, though, is that this name might not clearly convey this intention and might be lacking context. What other name would you prefer here? On our side, we’ve also considered:params,data,requestParams,source, …

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 Loading

At the heart of the resource is itsloader. This async function takes the currentrequest of the resource and produces aPromise of the resolved value.

resource will never resolve to a stale value, even if a loader continues to return data after the resource'srequest has changed. Still, it's desirable to be able to cancel pending work if the results are no longer needed. To support this, loaders receive anAbortSignal. This isn't an Angular signal, but a native browser API for responding to cancellation of work streams. A resource loader can use thisAbortSignal to listen for cancellation and abort any pending work.

streaming resource

The 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:

  • it needs to be a promise to handle asynchronous aspect of the data loading, where the first and subsequent chunks are not available immediately;
  • it needs to be a signal so the streaming resource canupdate the resource value as more response chunks are arriving and a richer set of data becomes available.

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?

rxResource

The streaming resource model of returning aPromise of aSignal is a good signals-first model for streamed data, accounting for the initial period of asynchronicity and the changing nature of the resolved value over time. Existing applications often use Observables for this purpose.

To that end, we're adding therxResource utility to@angular/core/rxjs-interop.rxResource is configured similarly to the streaming flavor ofresource, except thestream loader produces Observables:

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.

httpResource

Theresource API is a generic utility for wrapping asynchronous operations in resources. For most applications, the majority of data dependencies come in the form of HTTP operations. To facilitate this, we're introducinghttpResource:

user=httpResource(()=>`/api/user/${currentUserId()}`);

httpResource is afrontend for@angular/common/http. It makes HTTP requests through the Angular HTTP stack, including interceptors and the testing utilities. It differs from theHttpClient interface in several significant ways:

  • Declaring anhttpResource initiates the request eagerly (unlike theHttpClient Observable-based requests which must be subscribed).

  • Likeresource, it configures areactive request. If source signals in the request computation change, a new HTTP request will be made.

  • It returns an instance ofHttpResourceRef, which has additional signals for metadata about the response.

interfaceHttpResourceRef<T>extendsResourceRef<T>{readonlyheaders:Signal<HttpHeaders|undefined>;readonlystatusCode:Signal<number|undefined>;readonlyprogress:Signal<HttpProgressEvent|undefined>;}

httpResource has two calling patterns: either it can be configured with a URL, or with an object describing a more complex request to make (e.g. a POST request to a query API with a body, headers, etc):

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.

Likeresource,httpResource also supports adefaultValue which overrides the normalundefined value before the resource resolves.

Non-JSON requests

By default,httpResource parses data returned by the backend as JSON. There are three other response types that Angular's HTTP stack supports: text,Blob, andArrayBuffer. To make those requests, sub-functions are available which alter the expected response:

imageBytes=httpResource.arrayBuffer('/image.png');

The other supported sub-functions arehttpResource.text() andhttpResource.blob().

RFC Question 5: We started using this style of API withinput.required and we feel like the ergonomics work well here, with JSON being the default and other response types being accessible as sub-functions. Is there another API we should consider instead, like making each response type a top-level function?

Type-safe HTTP responses

A goal of thehttpResource design was to improve upon the type safety offered byHttpClient, while preserving the convenience of being able to directly declare a response type in cases where more strict validation isn't necessary.

By default,httpResource returns the "raw type" of the response. For JSON resources this isunknown, for the others it's the returned object type (Blob,string, etc).

For JSON resources, you can directly specify a response type, just as withHttpClient:

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.httpResource supports aparse operation which can transform the resource's raw value into a different type, optionally performing validation in the process.

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

httpResource (and the more fundamentalresource) both declare a dependency on data that should be fetched. It's not a suitable primitive for making imperative HTTP requests, such as requests to mutation APIs

Custom Resources

We see lots of desire and benefit for libraries to expose their own async operations via theResource API. While this could be done by wrapping them withresource, this might not be compatible with all potential use cases.

We are considering including base utilities that developers can extend to produce their own implementations ofResource. These APIs are still being researched, and we're not far enough along to solicit feedback on them directly via this RFC. Instead, we would like to ask:

RFC Question 7: if you maintain a library or other utility that could benefit from exposing results asResources, what opportunities and challenges do you see there? Would you be able to useresource() to achieve this, or would you need something lower level to directly implement the resource contract?

You must be logged in to vote

Replies: 50 comments 190 replies

Comment options

question 3: I would definitely renamerequest to something likeparams. I first thoughtrequest was isloader

You must be logged in to vote
15 replies
@griga
Comment options

mayberesolver instead ofloader 🤔

@ThomasWeiser
Comment options

I would suggest naming the parameterneed instead ofrequest.

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,source is not really accurate in my opinion.

@maxKciw
Comment options

Tbh I would go fordependency

@Grauzone
Comment options

@maxKciwdependency is maximal confusing. 👎 👎 👎

@isk0001y
Comment options

'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.

Comment options

Sorry for not responding to any question of the RFC, but I feel that some questions are missing in the discussion.

current value

what about the behavior of the resource regarding the current value when reloading, either imperatively, or because of a request signal change. The RFC says:

Generally, in Loading state the resource has no current value, but in Reloading the resource keeps its value while reloading

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 / unsubscription

This is actually related to Question 7.rxResource andhttpResource both rely on RxJS, which is able to unsubscribe and cancel http requests. This will happen automatically on component destroy if the resource is created by the component. But if we delegate to a service or to a library to create and return the resource, then it forces the component to pass its injector every time to properly unsubscribe (or to provide the service in the component). This isn't something that is easily discoverable, especially for non-expert developers. And it isn't very convenient either. Is there a plan to improve this, or to make it clearer that it should be done?

Testing

The 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 withof(), or aSubject. When using anhttpResource directly inside the component, we lose this ability, and are forced, AFAICT, to use theHttpTestingController to test the component. This isn't very convenient. And if the resource is
And if we keep delegating to a service to create and return resources, then is there a plan to provide a fake resource that can be used in tests, and which guarantees the contract ofResourceRef (i.e. there souldn't be an error and a value at the same time, for example)?

You must be logged in to vote
18 replies
@jnizet
Comment options

Agreed. Naming is hard.

@robertIsaac
Comment options

I actually agree with@alxhub that if we have userId as signal and user as a resource that ifuser is defined thenuser.id should always equaluserId
havinguser.id equal to the olduserId can introduce hard to debug errors, while if user become undefined debugging will be easier

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
I know there might be cases where we'd like to keep the old value but as suggested here we can used linkedSignal to show the old value

another potential solution (but more effort from the Angular team) is to add another variantresource.keepOldValue or other better name, same asinput.required andformBuilder.nonNullable that make it quiet different

@csvn
Comment options

and in reality I think showing loading is better than showing the old value

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 can avoid abrupt height changes to show loading state
  • DOM elements can possibly be re-used instead of discarded and re-created
  • That the list is loading new items can still be shown without discarding existing table/list-view

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 forresource() to opt-in to keeping the value until we get the new response. Something like:resource({ ..., clearStaleValue: false }) (but with better naming).

@robertIsaac
Comment options

So we agree that the default behavior should be to clear the current value
And it's good to have the option to opt out of this

@gabynevada
Comment options

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.

Comment options

Note that hasValue is reactive, despite not being typed as a Signal.

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.

You must be logged in to vote
5 replies
@JeanMeche
Comment options

It's a predicate ! You use it as a signal but you don't it to be a signal 😄

@jnizet
Comment options

@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.

@alxhub
Comment options

alxhubFeb 26, 2025
Collaborator

hasValue is a TS type guard which makes an assertion about the resource's type. This is a specific syntax for the return type that doesn't let us type the function as aSignal. If we did, we'd lose the type narrowing.

Our hope is that TS ultimately adds support for signals to be declared as narrowable (microsoft/TypeScript#60948), in which casehasValue can potentially be removed in favor of explicitly checking the value.

@crutchcorn
Comment options

With this context, I'd like to withdraw my comment. This makes a lot of sense to me

@nartc
Comment options

@crutchcorn just to clear this up further, I have something similar in my library
image
image

Comment options

What would be your preference when it comes to the status type?

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"error" | "loading" as another step towards enabling erasable syntax only on my TSConfig

You must be logged in to vote
7 replies
@saithis
Comment options

If they would make using enums in templates easy, would you still prefer union strings?

@joshdales
Comment options

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

@flensrocker
Comment options

+1 for string literals! When debugging, the value is more readable then to remember what status 3 is about... 🥸

@Stonepaw
Comment options

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.

@msmallest
Comment options

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".

Comment options

Generally, in Loading state the resource has no current value, but in Reloading the resource keeps its value while reloading.

Do you consider this distinction to be useful?

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:

  • Loading state
  • Error state
  • Data state
  • Refetching state
You must be logged in to vote
0 replies
Comment options

Does theType-safe HTTP responses work withStandard Schema? It might be an "easy" way to support adding in support for many community validation libraries such as ArkType, Valibot, and soon even Yup.

Ultimately I like the idea of not having to use TypeScript generics to cast type-safe values at all.

You must be logged in to vote
1 reply
@alxhub
Comment options

alxhubFeb 26, 2025
Collaborator

This is a very interesting idea and something we can definitely consider.

Comment options

One thing that I also find quite strange in the API is thereload() method. It's in the baseResource interface, so all implementations must define the method. But it can returnfalse to indicate that it's not supported. So the API recognizes that all resources should not be reloadable, but there's no way to know if they are or not. Shouldn't the API have aReloadableResource interface that extendsResource?

You must be logged in to vote
4 replies
@alxhub
Comment options

alxhubFeb 26, 2025
Collaborator

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.reload()'s boolean return indicates whether it actually started a reload or not.

A related question is "why isreload() on the read-onlyResource type?" I think that point resonates - reloading is a kind of mutation, and if you give someone a read-only handle to your resource state, they shouldn't be able to manipulate it.

@jnizet
Comment options

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.
In what situations can a resource decide to ignore the reload. Is it just when is it inLoading orReloading state already, and the reload would thus cancel the current request to redo the exact same one?

@wartab
Comment options

reload() returns false when the status is Idle or Loading (or Reloading - Reloading actually isn't used under the hood; there's just a reload counter and if it's 0, you get Loading and if it's > 0, you get Reloading)

@jnizet
Comment options

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.
Please say Hi to the Torê for me :-)

Comment options

My feedback comes after having used resources in both SolidJS and now Angular.
For background, I actually had implemented my own resources in userland that worked a bit more like SolidJS's shortly before resources were announced in Angular. I hadn't expected their release, so this caused a bit of friction, because they don't work exactly the same as mine. But given this background, I think I have a quite wide perspective on what can be achieved and what is still lacking in my opinion.

Regarding the questions:

Question 1: (enum vs. string literal)
I think that it doesn't really matter. But if the enums are kept, I believe that there need to be signals exposed for every possible state and its corresponding typing narrowing to avoid having to import the Status enum. (so for exampleisReloading() should be in there, for example).
Besides the enum import issue, I have a slight preference for string literals, as when you are quick and dirty debugging and printing status() in a template, the numbers aren't very meaningful, while a good old "loading" gets its meaning across much better.

Question 2: (usefulness of Reloading status)
I think exposing Reloading isn't problematic in its current state.
Reloading is in my opinion the part that needs the most work in resources. The loader should get sent the previous stored value, as well as the information of what caused the function to get called (source changed? manual reload?)
Besides that, the way things are currently implemented, resources are fairly useless for the purpose of fetch paginated lists of resources or resource lists (depending on what your definition of a resource is). I will come back to this at the end, because it's really the biggest thing that I am currently missing native support for.

Question 3: (name of resource options)
SolidJS calls the equivalent ofrequestsource. The issue with the namerequest is that it can give the impression that it is meant as the function that does the request. So I think doing it like SolidJS would be good. The other suggested names are either too long or a bit too generic (likedata).

Question 4: (streaming API)
This usage seems quite advanced; I haven't really been in the position to need this. That said the API seems to be robust and given its advanced use, I'd expect people that need it to know what they're doing. I wouldn't really have an alternative to suggest.

Question 5: (more uncommonly requested types)
I think this is a good idea. Small suggestion would be to allow XML.

Question 6: (type-safety in httpResource)
I think this is a good idea. However, there is one thing that isn't type safe: errors.I don't know if this is my Rust background talking, but I want resources and httpResources to not consider errors as less important than data.. Errors should also get an (optional) typing and an optionalparseError option! And yes, I realise, this goes against the JavaScript ecosystem with RXJS and Promises not handling this, but there is no reason to perpetuate this.

There is always much more boiler plate to write to extract errors and there just isn't a good reason for it.

Question 7:
No opinion

Now my two cents regarding resources:

  • We need a way to easily handleresource lists orlist resources. I won't get into the philosophical part about what it should be called, but paginated lists based on filters is currently very hard to implement with 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):
I do not think rxResource was a good idea. Resources should be able to accept both promises and Observables. I also think that the source of a resource should be allowed to be a Signal or an Observable (or even hotter take: a Promise).

Edit: I had mentioned this in the original resource-PR, but I would also like it to be possible to accessmyResource.value() with the shorthand ofmyResource() like regular signals (SolidJS does this), but that's obviously just preference. I just like having things be used the same way everywhere.

You must be logged in to vote
3 replies
@Cjameek
Comment options

Going to co-sign thatresource should be able to accept an Observable.

@alxhub
Comment options

alxhubFeb 26, 2025
Collaborator

As far as acceptingObservable, I think this is something we plan to revisit once theObservable spec lands upstream in the browser.

@laurenzfiala
Comment options

I agree on optional typing for errors in httpResource, that seems like a great idea!

Comment options

Question 5:
Using sub-function APIs looks nice, but I’m worried about tree shaking. Will the unusedhttpResource.X() methods be removed from the final bundle?

You must be logged in to vote
2 replies
@JeanMeche
Comment options

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.

@alxhub
Comment options

alxhubFeb 27, 2025
Collaborator

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).

Comment options

Probably a dumb request, but can there be anInitialized ResourceStatus when theresource is first initialized? I’ve run into several use cases where I want to delay (or essentially skip) the initial resource load because therequest value is tied to an input Signal where the initial value is an empty array or null value.

I knowundefined prevents the loader from running, but our template is dictating our choice of default values. How I’ve been working around this is using an RxJsfilter() operator on therxResource loader Observable.

EDIT:

Regarding RFC question 2, my vote goes to a string union type.

You must be logged in to vote
1 reply
@pkozlowski-opensource
Comment options

Probably a dumb request, but can there be an Initialized ResourceStatus when the resource is first initialized? I’ve run into several use cases where I want to delay (or essentially skip) the initial resource load because the request value is tied to an input Signal where the initial value is an empty array or null value.

WouldIdle work for your use-cases? If not - why?

Comment options

Mutations
httpResource (and the more fundamental resource) both declare a dependency on data that should be fetched. It's not a suitable primitive for making imperative HTTP requests, such as requests to mutation APIs

Apologies if the expectation is to only answer the prompts. I'm primarily concerned with the decision to discourage mutations inhttpResource.

It's name implies that it's designed to interact with HTTP resources. The clearest definition of an HTTP resource is:

The target of an HTTP request is called a "resource". HTTP does not limit the nature of a resource; it merely defines an interface that might be used to interact with resources.

RFC-9110 - 3.1 Resources

As an example, a resource could behttps://api.contoso.com/storage that hasPOST endpoint for adding items and aGET endpoint for retrieving items. (Keeping it simple to make the point)

Suggestions: ifhttpResource is meant to be considered an entirely different "resource" from the general HTTP resources, the name should be reconsidered. If not, mutations should be considered valid use-cases and supported.

You must be logged in to vote
2 replies
@alxhub
Comment options

alxhubFeb 27, 2025
Collaborator

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.

@dapperdandev
Comment options

Gotcha. What about calling ithttpQuery instead? The other two may be something likequery andrxQuery.

I see solidjs uses the naming convention ofresource, but I can't think of any other frameworks that do.

The better question might be: if mutations aren't meant forresource, is anything on the roadmap that is?

I know I'm using it wrong, but I love the elegance of disabling a form's submit button whenmyResource.loading()

Comment options

When I introduce thehttpResource function to new people, what almost all get wrong at first:

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 (string andHttpResourceRequest) really needed? Is the overhead of calling the provided "computed" function that bad?

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 thehttpResource in anAfterViewInit hook or similar and then things get even worse... 🙈

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.

You must be logged in to vote
1 reply
@alxhub
Comment options

alxhubFeb 28, 2025
Collaborator

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...

Comment options

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.:@if (myRes.status() === ResourceStatus.Error) ) requires exposing ResourceStatus on a component instance - this creates additional authoring friction.

I would like to see string literals as status values (likeFormControlStatus). "erasable syntax only" is one reason, but the other is, when debugging, the values are easier to read, because you don't have to remember what2 means.

You must be logged in to vote
0 replies
Comment options

RFC question 2: the experimental resource implementation distinguishes between the loading operation triggered by change to the reactive dependencies (Loading) and the one triggered imperatively (Reloading). Generally, in Loading state the resource has no current value, but in Reloading the resource keeps its value while reloading.

Yes, distinguishing between those two states is valuable.

You must be logged in to vote
0 replies
Comment options

RFC question 3: Our intention was to use a name for the parameter (currently called request) indicating that we are preparing all information necessary to make an actual request. The common point of feedback, though, is that this name might not clearly convey this intention and might be lacking context. What other name would you prefer here? On our side, we’ve also considered: params, data, requestParams, source, …

I prefersource, because that aligns withlinkedSignal. The source provides the request, it isn't the request.

And naming the functionloader/stream is inconsistent - why notstreamer? Or justload?

I would prefer (likeloadChildren orloadComponent in the routes) naming them

  • loadValue
  • streamValues (I'm not sure about the plural here)

RegardingrxResource the naming is harder. It don't neccessary loads only one value, it can emit multiple values (likestream). It's more like a creation function likeof orfrom or even more like aswitchMap.
Since this is an integration point to rxjs and it behaves likeswitchMap, it should be named like that?

That would open up to future overloads with something likeconcatMap etc.

You must be logged in to vote
0 replies
Comment options

I've been playing around withhttpResource today and I'm surprised to see that unlike inHttpClient, there is no way to add anHttpContext. Is that intentional? We rely heavily onHttpContext in our interceptors and without that, there is really no way for us to usehttpResource directly.

You must be logged in to vote
13 replies
@JeanMeche
Comment options

@ameckl Is that an assumption or a test feedback ?

@ameckl
Comment options

I quickly updated to 19.2.1 and tested it, and while nowHttpResourceRequest accepts thecontext parameter, it seems that is still not working. Now digging the Angular code to find evidence.

functionnormalizeRequest(

Shouldn't the context be passed here?

@JeanMeche
Comment options

Good catch, That's what happen when you ship without a unit test.

@wartab
Comment options

The funny thing is that when I saw the PR, I was wondering about this, checked the code, got interrupted, and forgot about it

@JeanMeche
Comment options

#60228 fixes this.

Comment options

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{}
You must be logged in to vote
2 replies
@JeanMeche
Comment options

This should be doable in user land. Ngxtension has ainjectQueryParams. The implementation would need to change to always return the same resource though.

@tomalaforge
Comment options

yes tanstack query is already doing it. but I thought that would be nice to do it.

Comment options

About Q2:
It is a little bit strange that value is set to something empty when reloading is triggered reactivly.
For example I'm trying to filter a list, but when I change the filter value the list is for a moment empty until it is reloaded. I believe it would be better to keep the old value in place during loading and just replace it with the new one. It would also be less DOM changes, maybe only a loading sign, not the entire table.

You must be logged in to vote
5 replies
@flensrocker
Comment options

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 fromusers/1 tousers/2 (which is the same component, just the inputuserId = input.required<string>() changes, which is used in the request of the "userResource").

"showing the last known value" is a needed functionality, I agree, but should it be part ofresource as a low-level building block?

@ProfEibe
Comment options

@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.

@flensrocker
Comment options

There are two sides of this "coin" for me:

  • How to display it in the template.
  • How to keep the resource "in sync".

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 theisLoading() signal.

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 ofResource<T> creating resource function is easy and a nice composition. The same is valid for caching capabilities (which I don't need in my apps).

@mauriziocescon
Comment options

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!

@flensrocker
Comment options

Yes, you have to put "status" into that equation. There exist different equally valid solutions to this.
Most of the time I type such states with discriminated unions, where, for example, thevalue doesn't exist, whenstatus isIdle. But that was expressed with an observable (or one signal containing the whole state{ status; request; value }.
When you break out and provide signals for each property, you have to define, how to express "missing". Andundefined is not the worst way to do.

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;};
Comment options

RFC question 1: in the proposed API design resource status is of type ResourceStatus enum.

In all honesty, I'd say the best approach would be to have both a status and aisX signal for every possible status. It feels like a very minor thing to add and it would allow us to have the best of both worlds.

RFC question 3: Our intention was to use a name for the parameter (currently called request) indicating that we are preparing all information necessary to make an actual request.

I tend to agree thatrequest isn't a good option here because it's not the actual request so it shouldn't be called that as such.requestParams seems to come closest to what it actually is, a set of params used to make a request.

You must be logged in to vote
0 replies
Comment options

Question 3: what abouttrigger orloaderTrigger? Would make more sense to me, at least in an example like this:

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});
You must be logged in to vote
4 replies
@flensrocker
Comment options

Sidenote (because of "wrong" code): Don't use the signal inside theloader, but use the value from therequest object passed to theloader.

searchTerm=signal<string>('');// used as an [(ngModel)] for an inputdata=rxResource({loader:({request:searchTerm})=>this.myService.find(searchTerm),request:this.searchTerm});
@patricsteiner
Comment options

ok, when we use it like this,params makes more sense. So far i always just used the signal directly, so it felt like atrigger 🤔

@flensrocker
Comment options

Theloader function is not executed in a reactive context, so it won't track the signal. It only works, because you use the same signal on therequest and therefore it is tracked.

conststream=awaituntracked(()=>{

@patricsteiner
Comment options

yes, i know - that's why for me the nametrigger made sense at first^^ but you're right, if the request is passed to the loader function, then a name likeparams orrequest makes more sense.

Comment options

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

You must be logged in to vote
6 replies
@johnsonav1992
Comment options

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.

@flensrocker
Comment options

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.

@johnsonav1992
Comment options

Yeah so maybe some reduced version of that or something. Who knows!

@robertIsaac
Comment options

@johnsonav1992 there is a trick to move away with full compatibility is to name the type as the object e.g.

const LogLevel = {  WARNING: 'warning',  ERROR: 'error',} as const;type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];function log(msg: string, level: LogLevel) {  console.log(level, msg);}log('error message', LogLevel.ERROR);log('error message 2', 'error');

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 :)

@johnsonav1992
Comment options

@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.

Comment options

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@defer blocks. I don't want requests made until after the defer blocks are loaded. Is there currently a recommended way of handling this? I have a condition in my loader function that returns empty untilrequest provides expected params after defer block is loaded. Would prefer to not even call the loader initially though.

You must be logged in to vote
11 replies
@joshdales
Comment options

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

@JeanMeche
Comment options

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 !

@johnsonav1992
Comment options

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.

@samuelfernandez
Comment options

@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.

@samuelfernandez
Comment options

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.

Comment options

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:

  • retry
  • delay
  • debounceTime
  • throttle
  • startsWith
  • distinctUntilChanged
  • catchError

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, likeisIdel,hasError,isLocal, etc.

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:params is good enough, I think. Or maybeparamSource?

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

You must be logged in to vote
7 replies
@flensrocker
Comment options

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?

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 thedefaultValue if given orundefined).
You can cache the "last good value" with alinkedSignal operating on theresource.value(), there are examples here in the discussion.

@HymanZHAN
Comment options

Can I specify a fallback value with the error handler, though? Similar to whatcatchError provides?

@flensrocker
Comment options

I haven't tried it in detail, but I would say yes, when the loader throws an error, the value is reset to thedefaultValue.

returnstreamValue&&isResolved(streamValue) ?streamValue.value :this.defaultValue;

So you don't specify a fallback value in the error handler but in the resource itself.

@HymanZHAN
Comment options

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.

ThelinkedSignal withresource.value() seems a bit of a hassle for something I consider default. TBF, I would say that a manual reload should empty the resource value, not the other way round. But that could just be me...

@flensrocker
Comment options

You could always feed theresource.error() andresource.value() into acomputed to do whatever you need. The composability is great with signals.

Comment options

RFC Question 5: We started using this style of API with input.required and we feel like the ergonomics work well here, with JSON being the default and other response types being accessible as sub-functions. Is there another API we should consider instead, like making each response type a top-level function?

i would say i love the idea of the httpResource but for consistency keep the JSON option as other options like text / blob ....
and one more thing if there is a way to hold and keep the old data internally, that would be awesome, also if there is a way to handle URL params instead of doing it manually will be also awesome (( by url params i mean

this.#router.navigate([],{queryParams:params,queryParamsHandling:'replace',});

to keep the URL sync with the data filtered by the user
i had to make a generic function that handles everything above

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,};
You must be logged in to vote
0 replies
Comment options

(Apologies for not following the format of the RFC questions)

Firstly, I fully support a) moving to a string union to represent thestatus field, instead of a TS enum, and b) incorporating more of RxJS into therxResource function, as suggested by other commentators.

I'd like to discuss the modelling ofstreams using a (potentially) betterstatus lifecycle, and with discriminated type unions:

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 anenabled flag). Especially for global and route-level state, like token-based auth and WebSocket-based data streams (e.g. Firestore), where it all depends on your application's logic and the state of other things (e.g. if the user has completed onboarding). And even if connected implicitly, then I think you still want to talk about streams in terms of being "connected" and "disconnected" as their overall status.

(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.manageStream('connect' | 'disconnect') function to allow the consumer of the store to control when the stream should be connected and disconnected (or, in some cases, will automatically connect the stream on initialisation of the store).

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:

  • The discriminated unions ensure that the state of the store is as consistent as possible, and also signals (no pun intended) intent to the reader of the code.
  • AnisLoading helper function would then check forstatus === 'connecting'.
  • An intermediate state for'emitting' could also be useful to have (and could be used within theisLoading check). Though may be overkill.
  • Another potential intermediate state is'disconnecting' (though also could be overkill).
  • I'm not sure how the concept of "reloading" fits in with streams.
  • Knowing that, for example, auth is in adisconnected state is important as that means you can't rely on the value of theuser object to determine if the user is logged in or not — you have towait until the resource is connected first.

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.

You must be logged in to vote
0 replies
Comment options

Does it make sense thatResource contains all the available states? Could it make more sense thatResource is a union type discriminated by status? I would have something like the following in mind:

type Resource = IdleResource | PendingResource | ResolvedResource | ErroneousResource.

I guess designing it like that could have the advantage that each dedicate type does not have to deal withundefined for its value. E.g. theErroneousResource could have a propertyerror of typeT, instead ofT | undefined. The same would be true for thevalue ofResolvedResource.

According to thedocsvalue is already of typeT, but I am wondering how this is possible as it has to beundefined having the data not loaded yet. The referenced documentation confirms this assumption, stating the following:

value: The current value of the Resource, or undefined if there is no current value.

Does this mean thatvalue is a cast toT and thathasValue has to be called first to safely access it?

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 includingundefined. 😇

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. 🙏

You must be logged in to vote
0 replies
Comment options

From my experimentation, resource clearing the value when request change IS logically correct but is really NOT ideal for UX

Imagine following example

  1. initially loading a list of items, show skeleton items
  2. items loaded, then runreload(), items are preserved by resource (because request is the same), show small spinner
  3. items loaded as a result of the change in search query, items are lost, so we show skeleton again, but we would like to show just a small spinner to prevent jumpy UI, a valid UX concern

Current "workround" is to uselinkedSignal, but this is quite some extra logic AND currently, it seems to not be able to infer the type correctly so we have to provide "redundant" generic

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,});

image

it's not an IDE issue, compiler complains about it as well

image

Desired behavior

OrderApp.-.Google.Chrome.2025-03-18.10-47-43.mp4
You must be logged in to vote
2 replies
@mauriziocescon
Comment options

Looking at this example, I wonder if it's desirable to havevalue provided in the loader as well (IMO on top of keeping it while loading or in case of errors).

Let's assume the data for the ui is fetched in 2 chunks and (current)value is available in the loader. Then

// 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: shouldproducts be the combination of chunk1 / chunk2 or just represent one fetch? I'm inclined to think the former might be better.

Some objections / reasonings:

  • Need cache? => I'd cache [...products_1, ...products_2], not just one of the two!
  • Infinite Scrolling? => I'd render [...products1, ...products2].
  • You're mixing things! => Maybe, but you can already use any signal defined within the class inside the loader (untracked). But you can't usethis.products.value().
  • Same requests would produce different values! => Well, the relationship betweenrequest andvalue is not "pure"!
  • Resource is not an accumulator! => Well, resource is a mechanism to set values asynchronously: local db fetches, expensive operations in a WebWorkers, ... not just http traffic. Having value might make sense in other cases!
  • It's anyway strange! => TSQ has the notion ofinfinite-queries... albeit not precisely the same!

playground:https://stackblitz.com/edit/stackblitz-starters-nlqwjbcn?file=src%2Fmain.ts

All of this just to say: addingvalue to the loader is a small change with nice potential and likely no logical side effects (at least I hope so... but stupidity is always around the corner! 😅).

Just some thoughts!
Cheers

@JeanMeche
Comment options

Note,ResourceLoaderParams already exposes aprevious property but today it only has the previousstatus.

Comment options

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:

  • if reloading, loading WOULD also be true
  • if loading, reloading COULD be true

Q3:request is fine by me but if you made any of the changes you've proposed, it would still be fine to me!

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...
For this, I would always choose observables instead since they are pushed based, while signals are pulled based. Plus I don't really see how the cb would be made tbh. Returning a Promise like of a signal feels really hacky to me.

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!
I would then provide the real instance in production and I would use the fake one for tests, without needing to explicitly mock everything in every tests + I could also use the fake one until the backend is ready (or if I need more flexibility during development). In that regard, yes, I would like some lower level tools to implement the resource contract!

You must be logged in to vote
0 replies
Comment options

RFC answer 1:
Yeah, i think that using union type could be useful

RFC answer 2:
imho it could be useful to mantain this separation between Loading and Reloading

RFC answer 3:
sure, the name needs to be changed: params, requestParams i think that suits well

RFC answer 4:
yes, pretty clear for me

RFC answer 6:
nice idea, also we can think about adding a parseError handler.

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?
But, using httpResource will, is that right?

I think this is a big difference between the two, if my understanding is correct.
Thanks in advance to those who clearify my doubts :)

You must be logged in to vote
1 reply
@JeanMeche
Comment options

Yes,httpResource can be seen as a frontend for theHttpClient, and as such, it exhibits the same behavior.

Comment options

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:

  • modifying data on a backend - the resource family of the APIs presented in this RFC was designed as theread-only view of the data from a backend. While many applications and use-cases need just that - we absolutely recognize that most real-life applications will need to change data. We will continue to extend Resource capabilities in a separate design and a RFC.
  • persisting values while reloading data - this is one of the most frequent requests. The resource was designed around the idea of “async derivation” where change in the reactive params result in data fetch from a backend. In this scenario it is very important to make sure that UI is not showing “stale” data. At the same time we absolutely do see several UI patterns where it is important to preserve previous state. In all honesty we are still trying to decide on the best design here.
  • loading triggers - as of today, resource loads data in response to reactive params. But a number of comments pointed at other scenarios where more control over data fetching is needed:
    • loading triggered by events (or more practically - supporting Observables as a source, in rxResource): this is something we are considering but don’t have final decision / design;
    • “lazy resource” - we believe that this direction is fundamentally incompatible with the ideas presented in the architecture discussion;
    • enabling / disabling resource loading: any new API would be a syntactic sugar over returning undefined from the source function and we don’t think that there is enough added value to justify a larger API / concept surface.

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:

  • resource :
    • rename therequest function argument. Our best candidate is**params**;
    • represent resource’s state as an union of string constants instead of an enum;
    • move thereload() operation to theWritableResource;
  • httpResource:
    • remove the non-reactive argument and always require an arrow function as it was acommon source of confusion;
    • rename themap operation toparse;
    • add thecontext andtransferCache optionsPR #60228

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♥️

You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
RFCs
Labels
None yet
64 participants
@pkozlowski-opensource@jits@alfaproject@bmayen@griga@ProfEibe@e-oz@BojanKogoj@flensrocker@Lonli-Lokli@javiermarinros@navix@JeanMeche@saithis@manfredsteyer@jnizet@mauriziocescon@tomastrajan@ThomasWeiser@alxhub@ameckland others

[8]ページ先頭

©2009-2025 Movatter.jp