* Define `ResourceSnapshot<T>` as a type union of possible states for a
`Resource<T>`.
* Add `Resource.snapshot()` to convert a `Resource` to a signal of its
snapshot.
* Add `resourceFromSnapshots` to convert a reactive snapshot back into a
`Resource`.
By converting resources from/to `Signal<ResourceSnapshot>`s, full
composition of resources is now possible on top of signal composition APIs
like `computed` and `linkedSignal`.
For example, a common feature request is to have a `Resource` which retains
its value when its reactive source (params) changes. This can now be built
as a utility, leveraging `linkedSignal`'s previous value capability:
```ts
function withPreviousValue<T>(input: Resource<T>): Resource<T> {
const derived = linkedSignal({
source: input.snapshot,
computation: (snap, previous) => {
if (snap.status === 'loading' && previous?.value) {
// When the input resource enters loading state, we keep the value
// from its previous state, if any.
return {status: 'loading', value: previous.value.value};
}
// Otherwise we simply forward the state of the input resource.
return snap;
},
});
return resourceFromSnapshots(derived);
}
// In application code:
userId = input.required<number>();
user = withPreviousValue(httpResource(() => `/user/{this.userId()}`));
// if `userId()` switches, `user.value()` will keep the old value until
// the new one is ready!
```
* Define `ResourceSnapshot<T>` as a type union of possible states for a
`Resource<T>`.
* Add `Resource.snapshot()` to convert a `Resource` to a signal of its
snapshot.
* Add `resourceFromSnapshots` to convert a reactive snapshot back into a
`Resource`.
By converting resources from/to `Signal<ResourceSnapshot>`s, full
composition of resources is now possible on top of signal composition APIs
like `computed` and `linkedSignal`.
For example, a common feature request is to have a `Resource` which retains
its value when its reactive source (params) changes. This can now be built
as a utility, leveraging `linkedSignal`'s previous value capability:
```ts
function withPreviousValue<T>(input: Resource<T>): Resource<T> {
const derived = linkedSignal({
source: input.snapshot,
computation: (snap, previous) => {
if (snap.status === 'loading' && previous?.value) {
// When the input resource enters loading state, we keep the value
// from its previous state, if any.
return {status: 'loading', value: previous.value.value};
}
// Otherwise we simply forward the state of the input resource.
return snap;
},
});
return resourceFromSnapshots(derived);
}
// In application code:
userId = input.required<number>();
user = withPreviousValue(httpResource(() => `/user/{this.userId()}`));
// if `userId()` switches, `user.value()` will keep the old value until
// the new one is ready!
```
This commit changes `Resource.hasValue()` and its derived types to improve narrowing
of resources whose generic type either does not include `undefined` (i.e. when a default
value has been provided) or when the generic type is `unknown`. This fixes the undesirable
behavior where `hasValue()` would cause the `else` branch of an `hasValue()` conditional
to have a narrowed type of `never`, given that the `hasValue()`'s type guard covers the
entire type range already (meaning that the type in the else-branch cannot be inhabited
in the type system, yielding the `never` type).
By making the `hasValue()` method only a type guard when the generic type includes `undefined`
these problems are avoided.
Fixes#60766Fixes#63545Fixes#63982
PR Close#63994
Change direct deps in bazel targets and import specifiers within files to maintain strict deps requirements ahead of enabling strict deps tests in the repo
PR Close#63323
When using `hasValue()` I would expect it to behave like any other
reactive value such that changes to the internal `value()` that do not
cause `hasValue()` to return anything different do not trigger change
detection, but this was not the case. This change wraps the value
checking in a `computed` such that it behaves as expected again while
still preserving the type narrowing.
PR Close#62595
When the resource is loading after reloading from the error state reading `Resource.value()` would return the default value instead of throwing an error.
This change prevents `Resource.hasValue()` from throwing an error in such a case.
PR Close#61441
`Resource.error` used to return `unknown`. Now it's `Error | undefined`.
For non-`Error` types they are encapsulated with the `Error` type.
PR Close#61441
As decided in the resource RFC, this commit renames the `request` option of
a resource to `params`, including the subsequent argument passed to the
loader. It also corrects the type in the process to properly allow narrowing
of the `undefined` value.
Fixes#58871
PR Close#60919
An outcome of the Resource RFC was that we should use string constants for
communicating the resource status instead of an enum. This commit converts
`ResourceStatus` accordingly.
PR Close#60919
Instead of stabilizing the TestBed.flushEffects() API we intend to
replace it with the tick() method (equivalent of ApplicationRef.tick().
The reasoning here is that we prefer tests running the entire
synchronization process (as in production apps) instead of invoking
parts of the synchronization process in a way that would naver happen
in a running application.
PR Close#60959
The refactoring of `resource()` to use `linkedSignal()` introduced the
potential for a race condition where resources would get stuck and not update
in response to a request change. This occurred under a specific condition:
1. The request changes while the resource is still in loading state
2. The resource resolves the previous load before its `effect()` reacts to the
request change.
In practice, the window for this race is small, because the request change in
(1) will schedule the effect in (2) immediately. However, it's easier to
trigger this sequencing in tests, especially when one resource depends on the
output of another.
To fix the race condition, the resource impl is refactored to track the request
in its state, and ignore resolved values or streams for stale requests. This
refactoring actually makes the resource code simpler and easier to follow as
well.
Fixes#59842
PR Close#59851
When a resource first starts up, even if it transitions immediately to
`Loading` it should report a `previous.state` of `Idle`. It was reporting
`Loading` as the previous state in such a case because of an oversight in
the migration to `linkedSignal` which this commit addresses.
PR Close#59708
Before `resource()` resolves, its value is in an unknown state. By default
it returns `undefined` in these scenarios, so the type of `.value()`
includes `undefined`.
This commit adds a `defaultValue` option to `resource()` and `rxResource()`
which overrides this default. When provided, an unresolved resource will
return this value instead of `undefined`, which simplifies the typing of
`.value()`.
PR Close#59655
This commit adds support for creating `resource()`s with streaming response
data. A streaming resource is defined by a `stream` option instead of a
`loader`, with `stream` being a function returning
`Promise<Signal<{value: T}|{error: unknown}>>`. Once the streaming loader
resolves to a `Signal`, it can continue to update that signal over time, and
the values (or errors) will be delivered to via the resource's state.
`rxResource()` is updated to leverage this new functionality to handle
multiple responses from the underlying Observable.
PR Close#59573
When the reactive `request` of a resource() notifies, it transitions to the
Loading state, fires the loader, and eventually transitions to Resolved.
With the prior implementation, a change of the `request` will queue the
effect, but the state remains unchanged until the effect actually runs. For
a brief period, the resource is in a state where the request has changed,
but the state has yet to update.
This is problematic if we want to use resources in certain contexts where we
care about the state of the resource in a synchronous way. For example, an
async validator backed by a resource might be checked after an update:
```
value.set(123);
if (validator.value()) {
// value is still valid, even though the resource is dirty and will soon
// flip to loading state (returning value(): undefined) while revalidating
}
```
To address this timing concern, `linkedSignal()` is used within the
`resource()` to synchronously transition the state in response to the
request changing. This ensures any followup reads see a consistent view of
the resource regardless of whether the effect has run.
This also addresses a race condition where `.set()` behaves differently on a
`resource()` depending on whether or not the effect has run.
PR Close#59024
Before this commit, a resource with a previous value wouldn't set the error state correctly.
This commit fixes this. A resource will set its status to error even when there was a previous valid value.
PR Close#58855
Implement a new experimental API, called `resource()`. Resources are
asynchronous dependencies that are managed and delivered through the signal
graph. Resources are defined by their reactive request function and their
asynchronous loader, which retrieves the value of the resource for a given
request value. For example, a "current user" resource may retrieve data for
the current user, where the request function derives the API call to make
from a signal of the current user id.
Resources are represented by the `Resource<T>` type, which includes signals
for the resource's current value as well as its state. `WritableResource<T>`
extends that type to allow for local mutations of the resource through its
`value` signal (which is therefore two-way bindable).
PR Close#58255