docs: add section about reactive contexts

(cherry picked from commit 62ccd64e9f)
This commit is contained in:
SkyZeroZx 2025-12-14 16:48:08 -05:00 committed by Andrew Kushnir
parent 0e59ac795c
commit dc0aae9a59
4 changed files with 65 additions and 37 deletions

View file

@ -230,7 +230,7 @@ export class MediaControls {
In the above example, the `CustomSlider` can write values into its `value` model input, which then propagates those values back to the `volume` signal in `MediaControls`. This binding keeps the values of `value` and `volume` in sync. Notice that the binding passes the `volume` signal instance, not the _value_ of the signal.
In other respects, model inputs work similarly to standard inputs. You can read the value by calling the signal function, including in reactive contexts like `computed` and `effect`.
In other respects, model inputs work similarly to standard inputs. You can read the value by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like `computed` and `effect`.
See [Two-way binding](guide/templates/two-way-binding) for more details on two-way binding in templates.

View file

@ -7,7 +7,7 @@ A component can define **queries** that find child elements and read values from
Developers most commonly use queries to retrieve references to child components, directives, DOM elements, and more.
All query functions return signals that reflect the most up-to-date results. You can read the
result by calling the signal function, including in reactive contexts like `computed` and `effect`.
result by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like `computed` and `effect`.
There are two categories of query: **view queries** and **content queries.**

View file

@ -89,6 +89,68 @@ If you set `showCount` to `true` and then read `conditionalCount` again, the der
Note that dependencies can be removed during a derivation as well as added. If you later set `showCount` back to `false`, then `count` will no longer be considered a dependency of `conditionalCount`.
## Reactive contexts
A **reactive context** is a runtime state where Angular monitors signal reads to establish a dependency. The code reading the signal is the _consumer_, and the signal being read is the _producer_.
Angular automatically enters a reactive context when:
- Executing an `effect`, `afterRenderEffect` callback.
- Evaluating a `computed` signal.
- Evaluating a `linkedSignal`.
- Evaluating a `resource`'s params or loader function.
- Rendering a component template (including bindings in the [host property](guide/components/host-elements#binding-to-the-host-element)).
During these operations, Angular creates a _live_ connection. If a tracked signal changes, Angular will _eventually_ re-run the consumer.
### Asserts the reactive context
Angular provides the `assertNotInReactiveContext` helper function to assert that code is not executing within a reactive context. Pass a reference to the calling function so the error message points to the correct API entry point if the assertion fails. This produces a clearer, more actionable error message than a generic reactive context error.
```ts
import { assertNotInReactiveContext } from '@angular/core';
function subscribeToEvents() {
assertNotInReactiveContext(subscribeToEvents);
// Safe to proceed - subscription logic here
}
```
### Reading without tracking dependencies
Rarely, you may want to execute code which may read signals within a reactive function such as `computed` or `effect` _without_ creating a dependency.
For example, suppose that when `currentUser` changes, the value of a `counter` should be logged. You could create an `effect` which reads both signals:
```ts
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${counter()}`);
});
```
This example will log a message when _either_ `currentUser` or `counter` changes. However, if the effect should only run when `currentUser` changes, then the read of `counter` is only incidental and changes to `counter` shouldn't log a new message.
You can prevent a signal read from being tracked by calling its getter with `untracked`:
```ts
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);
});
```
`untracked` is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency:
```ts
effect(() => {
const user = currentUser();
untracked(() => {
// If the `loggingService` reads signals, they won't be counted as
// dependencies of this effect.
this.loggingService.log(`User set to ${user}`);
});
});
```
## Advanced derivations
While `computed` handles simple readonly derivations, you might find youself needing a writable state that is dependant on other signals.
@ -148,41 +210,6 @@ isWritableSignal(count); // true
isWritableSignal(doubled); // false
```
### Reading without tracking dependencies
Rarely, you may want to execute code which may read signals within a reactive function such as `computed` or `effect` _without_ creating a dependency.
For example, suppose that when `currentUser` changes, the value of a `counter` should be logged. You could create an `effect` which reads both signals:
```ts
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${counter()}`);
});
```
This example will log a message when _either_ `currentUser` or `counter` changes. However, if the effect should only run when `currentUser` changes, then the read of `counter` is only incidental and changes to `counter` shouldn't log a new message.
You can prevent a signal read from being tracked by calling its getter with `untracked`:
```ts
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);
});
```
`untracked` is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency:
```ts
effect(() => {
const user = currentUser();
untracked(() => {
// If the `loggingService` reads signals, they won't be counted as
// dependencies of this effect.
this.loggingService.log(`User set to ${user}`);
});
});
```
## Using signals with RxJS
See [RxJS interop with Angular signals](ecosystem/rxjs-interop) for details on interoperability between signals and RxJS.

View file

@ -15,6 +15,7 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors';
* to disallow certain code from running inside a reactive context (see {@link /api/core/rxjs-interop/toSignal toSignal})
*
* @param debugFn a reference to the function making the assertion (used for the error message).
* @see [Asserts the reactive context](guide/signals#asserts-the-reactive-context)
*
* @publicApi
*/