We don't need this tooling anymore because we are already validating
that there are no circular dependencies via the `ng-dev` tooling that
checks `.ts` files directly.
Also these tests never actually failed to my knowledge.
PR Close#61209
With the changes in #59573, `resource` can now define a `stream` rather than a `loader`.
In the same PR, `rxResource` was updated to leverage this new functionality to handle multiple responses from the underlying observable,
rather than just the first one as it was previously.
This commit renames the `loader` option of `rxResource` into `stream` to be better aligned with its new behavior.
The previous version is temporarily kept and marked as deprecated to help migrating the current usage.
Before
```
usersResource = rxResource({
request: () => ...,
loader: ({ request }) => ...
});
```
After
```
usersResource = rxResource({
request: () => ...,
stream: ({ request }) => ...
});
```
PR Close#59910
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
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
Originally the `T` in `Resource<T>` represented the resolved type of the
resource, and `undefined` was explicitly added to this type in the `.value`
signal. This turned out to be problematic, as it wasn't possible to write a
type for a resource which didn't return `undefined` values. Such a type is
useful for 2 reasons:
1. to support narrowing of the resource type when `Resource.hasValue()`
returns `true`.
2. for resources which use a different value instead of `undefined` to
represent not having a value (for example, array resources which want to
use `[]` as their default).
Instead, this commit changes `resource()` and `rxResource()` to return an
explicit `ResourceRef<T|undefined>`, and removes the union with `undefined`
from all types related to the resource's value. This way, it's trivially
possible to write `Resource<T>` to represent resources where `.value` only
returns `T`.
`hasValue()` then actually works to perform narrowing, by narrowing the
resource type to `Exclude<T, undefined>`.
PR Close#59024
The `rxResource` was using `firstValueFrom` which isn't supported in rxjs 6.x. `@angular/core` currently supports rxjs 6 so we need this to be backwards compatible. This came up when trying to deploy the Material docs site which is still on rxjs 6 ([see](https://github.com/angular/components/actions/runs/11487971079/job/31973721563)).
PR Close#58341
This commit adds an operator to help rxjs observables important for rendering
keep the application unstable (and prevent serialization) until there is
an event (observable emits, completes, or errors, or the subscription is
unsubscribed). This helps with SSR for zoneless and also helps for when
operations are intentionally executed outside the Angular Zone but are
still important for SSR (i.e. angularfire and the zoneWrap helper hacks).
PR Close#56533
Implementations of two rxjs-interop APIs which produce `Resource`s from
RxJS Observables. `rxResource()` is a flavor of `resource()` which uses a
projection to an `Observable` as its loader (like `switchMap`).
PR Close#58255
A unit test has been added to check that `EventEmitter` properly completes upon component/directive destrouction when used with `outputToObservable`.
It explains why `destroyRef` has to be injected in `EventEmitter` in the first place.
PR Close#58239
The original effect design for Angular had one "bucket" of effects, which
are scheduled on the microtask queue. This approach got us pretty far, but
as developers have built more complex reactive systems, we've hit the
limitations of this design.
This commit changes the nature of effects significantly. In particular,
effects created in components have a completely new scheduling system, which
executes them as a part of the change detection cycle. This results in
behavior similar to that of nested effects in other reactive frameworks. The
scheduling behavior here uses the "mark for traversal" flag
(`HasChildViewsToRefresh`). This has really nice behavior:
* if the component is dirty already, effects run following preorder hooks
(ngOnInit, etc).
* if the component isn't dirty, it doesn't get change detected only because
of the dirty effect.
This is not a breaking change, since `effect()` is in developer preview (and
it remains so).
As a part of this redesigned `effect()` behavior, the `allowSignalWrites`
flag was removed. Effects no longer prohibit writing to signals at all. This
decision was taken in response to feedback / observations of usage patterns,
which showed the benefit of the restriction did not justify the DX cost.
The new effect timing is not yet enabled - a future PR will flip the flag.
PR Close#56501
The option introduced in 5df3e78c99 has been named `equals` whereas the existing option in `signal` is named `equal`.
This commit renames the new option to `equal` as well to keep the naming coherent across these APIs.
PR Close#56769
`toSignal` predates the decision to allow a more flexible equality check in
signals, and thus doesn't support a custom equality function. This commit
adds the ability to pass a custom value equality function. As a side effect,
it now adds the default equality check where it wasn't used before.
Fixes#55573
PR Close#56447
An `EventEmitter` is a construct owned by Angular that should be
used for outputs as of right now.
As we are introducing the new `OutputRef` interface for the new output
function APIs, we also think `EventEmitter` should implement
`OutputRef`— ensuring all "known" outputs follow the same contract.
This commit ensures `EventEmitter` implements an `OutputRef`
Note: An output ref captures the destroy ref from the current injection
context for clean-up purposes. This is also done for `EventEmitter` in a
backwards compatible way:
- not requiring an injection context. EventEmitter may be used
elsewhere.
- not cleaning up subscriptions/completing the emitter when the
directive/component is destroyed. This would be a change in behavior.
Note 2: The dependency on `DestroyRef` causes it to be retained in all
bundling examples because ironically `NgZone` uses `EventEmitter`- not
for outputs. The code is pretty minimal though, so that should be
acceptable.
`EventEmitter` will now always retain `NgZone. This increases the
payload size slightly around 800b for AIO. Note that the other increases
were coming from previous changes. This commit just pushed it over the
threshold.
PR Close#54650
This commit introduces an addition to `output()` and
`outputFromObservable`()` —called `outputToObservable()`.
The helper lives in the RxJS interop package and allows agnostic
programmatic subscriptions to `OutputRef`s by converting the output
to an observable with `.pipe` etc.
The function is ideally used in all places where you subscribe to an
output programmatically. Those outputs in the future, with the new APIs,
may not be actual RxJS constructs, but abstract `OutputRef`'s that
simply expose a `.subscribe` method. The helper allows you to
agnostically convert outputs to RxJS observables that you can safely
interact with.
The observables are also completed automatically, if possible, when the
owning directive/component is destroyed— Something that is not
guaranteed right now.
PR Close#54650
Introduces a second API in addition to the new `output()` function.
The new function `outputFromObservable()` can be used to declare outputs
using the new `OutputRef` API and `output()` API, while using a custom
RxJS observable as data source.
This is something that is currently possible in Angular and we would
like to keep possible- even though we never intended to support custom
observables aside from RxJS-based `EventEmitter`.
The interop bridges the gap and allows you to continue using
`Subject`, `ReplaySubject`, `BehaivorSubjct,` - or cold custom
observables for outputs. You can still trigger logic only when
the output is subscribed- unlike with imperative `emit`s of
`EventEmitter` or the new `OutputEmitterRef`.
A notable difference is that you need two class members where you
previously could access the `Subject` directly. This is an intentional
trade-off we've made to ensure that all new outputs implement the
`OutputRef` interface and we are exposing a minimal API surface to
consumers of components that currently access the output
programmatically.
PR Close#54650
By default, `toSignal` transforms an `Observable` into a `Signal`, including
the error channel of the Observable. When an error is received, the signal
begins throwing the error.
`toSignal` is intended to serve the same purpose as the `async` pipe, but
the async pipe has a different behavior with errors: it rejects them
outright, throwing them back into RxJS. Rx then propagates the error into
the browser's uncaught error handling logic. In the case of Angular, the
error is then caught by zone.js and reported via the application's
`ErrorHandler`.
This commit introduces a new option for `toSignal` called `rejectErrors`.
With that flag set, `toSignal` copies the async pipe's behavior, allowing
for easier migrations.
Fixes#51949
PR Close#52474
This adds `generate_api_docs` targets to all of the packages for which we publish api reference docs. One known issue here is that any type information that comes from another package (e.g. router depending on core) currently resolve to `any` because the other sources are not available in the program. This can be tackled in a follow-up commit.
This commit also updates the install patch for `@angular/build-tools` to use the local version of compiler-cli.
PR Close#52034
This commit cleans up the signatures of `toSignal` to better handle the
types of situations that it might be used in, and produce better type
inference results.
Fixes#50687Fixes#50591
Co-authored-by: Andrew Scott <atscott@google.com>
PR Close#51991
This commit reorganizes the Angular code a bit, and moves signals into a
newly defined `@angular/core/primitives` location. This will be used inside
g3 to allow non-Angular targets to depend on the signals core without
incurring a dependency on the whole framework.
PR Close#51986
Revert (with improvements of): dcf18dc74c
We recently landed a change that allows `toSignal` to be called
from within reactive contexts (e.g. `effect`/`computed`). After
more thorough investigatio and consideration with the team, we
feel like allowing `toSignal` to be called in such contexts is
encouraging non-ideal / hard-to-notice code patterns.
e.g. a new subscription to an observable is made every time `toSignal`
is invoked. There is no caching done here. Additionally, multiple new
subscriptions can trigger unintended side-effects- that may slow down
the app, result in incorrect/unexpected behavior or perform unnecessary
work.
Users should instead move the `toSignal` call outside of the `computed`
or `effect` and then read the signal values from within their `computed`. e.g.
```ts
computed(() => {
const smth = toSignal(coldObservable$)
return smth() + 2;
}
```
--> should instead be:
```ts
const smth = toSignal(coldObsverable$);
computed(() => smth() + 2);
```
In cases where a new subscription for each invocation is actually intended, a manual
subscription can be made. That way it's also much more obvious to users
that they are triggering side-effects every time, or causing new
subscriptions.
PR Close#52049
This PR moves the Observable subscription of toSignal outside of the
reactive context. As the result the toSignal calls are allowed in the
computed, effect and all other reactive consumers.
This is based on the reasoning that we already allow signals creation
in a reactive context. Plus a similar change was done to the async pipe
in the https://github.com/angular/angular/pull/50522Fixes#51027
PR Close#51831
Fixes that there was code duplication between the primary entry-point,
the testing entry-point and the rxjs-interop entry-point.
This code duplication resulted in additional code size (really
neglibible here because rxjs-interop did not duplicate large parts of
core, and `testing` is not used in production).
On the other hand though, the duplication resulted in a subtle JIT
dependency tracking issue due to the `depsTracker` no longer being a
singleton. This caused test failures as in:
https://github.com/angular/angular/pull/51415.
PR Close#51500
Extend toSignal to accept any Subscribable (instead of only Observable)
for consistency with AsyncPipe and for broader compatibility with any
observable library (that is compatible with the Subscribable interface).
This is only a type change as the implementation does not use anything
else than the Subscribable interface anyway.
PR Close#50162
This commit introduces an interface for `toSignal` options to mirror that of
`toObservable`, and adjusts docs for both symbols. It also adds the ability
for `toSignal` to manually specify `DestroyRef` (similarly to
`toObservable` accepting an injector) or for `toSignal` automatic cleanup to
be disabled (in which case the subscription persists until the Observable
completes). Either option allows `toSignal` to be used outside of a DI
context, like `toObservable`.
PR Close#50071
As described in
https://github.com/angular/angular/discussions/49681#discussioncomment-5628930,
if an `Observable` created from a signal with `toObservable` is
subscribed to in a template, it will initially have `null` as the value.
Immediately after the template is done executing, effects are flushed
and this results in the `AsyncPipe` getting a new value before the
`checkNoChanges` pass, resulting in `ExpressionChanged` error.
```
template: '{{obs$ | async}}'
...
obs$ = toObservable(signal(0));
```
Instead, this commit updates the `toObservable` to synchronously emit
the initial value to the Observable stream.
Side note here: We don't exactly encourage this pattern. Instead of
using `AsyncPipe`, the template should just read signals.
PR Close#49894
This commit updates the code to avoid deep links into the `@angular/core`, which triggers a build issue in apps when a code is referenced.
PR Close#49823