diff --git a/adev/src/content/guide/components/inputs.md b/adev/src/content/guide/components/inputs.md index d3f8e1e6925..0b2d8236f96 100644 --- a/adev/src/content/guide/components/inputs.md +++ b/adev/src/content/guide/components/inputs.md @@ -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. diff --git a/adev/src/content/guide/components/queries.md b/adev/src/content/guide/components/queries.md index e7b1dc6eb35..1404ffee902 100644 --- a/adev/src/content/guide/components/queries.md +++ b/adev/src/content/guide/components/queries.md @@ -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.** diff --git a/adev/src/content/guide/signals/overview.md b/adev/src/content/guide/signals/overview.md index cd914c6b423..127d63e22b3 100644 --- a/adev/src/content/guide/signals/overview.md +++ b/adev/src/content/guide/signals/overview.md @@ -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. diff --git a/packages/core/src/render3/reactivity/asserts.ts b/packages/core/src/render3/reactivity/asserts.ts index e28fc23021d..bc3cee73eff 100644 --- a/packages/core/src/render3/reactivity/asserts.ts +++ b/packages/core/src/render3/reactivity/asserts.ts @@ -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 */