Skip to content

Commit 39376c7

Browse files
authored
translate: signals debounced and effect guides (Angular 22.1) (#210)
Translate guide/signals/debounced.md and guide/signals/effect.md into Spanish and keep the English originals as .en.md backups. Fixes #186
1 parent 541b174 commit 39376c7

4 files changed

Lines changed: 379 additions & 87 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Debouncing signals with `debounced`
2+
3+
IMPORTANT: `debounced` is [experimental](reference/releases#experimental). It's ready for you to try, but it might change before it is stable.
4+
5+
Use `debounced` to delay reacting to a signal's value until it stops changing. It returns a `Resource` whose value reflects the debounced value of the source signal.
6+
7+
```angular-ts
8+
import {debounced, resource, signal} from '@angular/core';
9+
10+
@Component({
11+
template: `
12+
<input (input)="query.set($event.target.value)" />
13+
14+
@if (results.isLoading()) {
15+
<p>Searching…</p>
16+
}
17+
@for (item of results.value(); track item.id) {
18+
<li>{{ item.name }}</li>
19+
}
20+
`,
21+
})
22+
export class Search {
23+
query = signal('');
24+
25+
debouncedQuery = debounced(this.query, 300);
26+
27+
results = resource({
28+
params: () => this.debouncedQuery.value(),
29+
loader: ({params}) => fetchResults(params),
30+
});
31+
}
32+
```
33+
34+
`debounced` takes the source signal and a wait duration in milliseconds. The returned resource's `value()` always contains the last settled value, and `status()` tells you whether a new value is still pending.
35+
36+
## Status during debounce
37+
38+
While the debounce timer is counting down, `status()` is `'loading'` and `value()` returns the previously resolved value. When the timer expires, the resource settles to `'resolved'`. If the source signal throws, the resource enters `'error'` immediately; no timer runs.
39+
40+
See [Resource status](/guide/signals/resource#resource-status) for the full list of statuses and their `value()` behavior.
41+
42+
## Custom wait function
43+
44+
Instead of a millisecond duration, you can pass a function that returns a `Promise<void>`. The resource resolves when the promise resolves. If the source signal changes before the promise settles, Angular discards the previous promise and starts a new one.
45+
46+
```ts
47+
debouncedQuery = debounced(query, (value, lastSnapshot) => {
48+
// Retry immediately after an error rather than making the user wait again.
49+
if (lastSnapshot.status === 'error') return;
50+
// Short queries get a longer delay—the user is likely still typing.
51+
const ms = value.length < 3 ? 500 : 200;
52+
return new Promise<void>((resolve) => setTimeout(resolve, ms));
53+
});
54+
```
55+
56+
See the `DebounceTimer` type in the API reference for details.
57+
58+
## Equality
59+
60+
By default, `debounced` uses `Object.is` to compare values.
61+
62+
Provide a custom equality function with the `equal` option when the default identity check is too strict:
63+
64+
```ts
65+
debouncedFilter = debounced(filter, 200, {
66+
equal: (a, b) => a.category === b.category && a.minPrice === b.minPrice,
67+
});
68+
```
69+
70+
## Injection context
71+
72+
`debounced` must be called inside an [injection context](guide/di/dependency-injection-context). Angular automatically destroys the debounced resource and cancels any pending timer when the injector is destroyed.
73+
74+
To use `debounced` outside of an injection context, pass an explicit `Injector` via the options:
75+
76+
```ts
77+
@Service()
78+
export class SearchService {
79+
private injector = inject(Injector);
80+
81+
createDebouncedQuery(query: Signal<string>): Resource<string> {
82+
return debounced(query, 300, {injector: this.injector});
83+
}
84+
}
85+
```

adev-es/src/content/guide/signals/debounced.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Debouncing signals with `debounced`
1+
# Aplicar debounce a signals con `debounced`
22

3-
IMPORTANT: `debounced` is [experimental](reference/releases#experimental). It's ready for you to try, but it might change before it is stable.
3+
IMPORTANT: `debounced` es [experimental](reference/releases#experimental). Está listo para que lo pruebes, pero podría cambiar antes de ser estable.
44

5-
Use `debounced` to delay reacting to a signal's value until it stops changing. It returns a `Resource` whose value reflects the debounced value of the source signal.
5+
Usa `debounced` para retrasar la reacción al valor de una signal hasta que deje de cambiar. Devuelve un `Resource` cuyo valor refleja el valor con debounce de la signal fuente.
66

77
```angular-ts
88
import {debounced, resource, signal} from '@angular/core';
@@ -12,7 +12,7 @@ import {debounced, resource, signal} from '@angular/core';
1212
<input (input)="query.set($event.target.value)" />
1313
1414
@if (results.isLoading()) {
15-
<p>Searching…</p>
15+
<p>Buscando…</p>
1616
}
1717
@for (item of results.value(); track item.id) {
1818
<li>{{ item.name }}</li>
@@ -31,47 +31,47 @@ export class Search {
3131
}
3232
```
3333

34-
`debounced` takes the source signal and a wait duration in milliseconds. The returned resource's `value()` always contains the last settled value, and `status()` tells you whether a new value is still pending.
34+
`debounced` recibe la signal fuente y una duración de espera en milisegundos. El `value()` del resource devuelto siempre contiene el último valor asentado, y `status()` te indica si un nuevo valor sigue pendiente.
3535

36-
## Status during debounce
36+
## Estado durante el debounce {#status-during-debounce}
3737

38-
While the debounce timer is counting down, `status()` is `'loading'` and `value()` returns the previously resolved value. When the timer expires, the resource settles to `'resolved'`. If the source signal throws, the resource enters `'error'` immediately; no timer runs.
38+
Mientras el temporizador del debounce está en cuenta regresiva, `status()` es `'loading'` y `value()` devuelve el valor resuelto anteriormente. Cuando el temporizador expira, el resource se asienta en `'resolved'`. Si la signal fuente lanza un error, el resource entra en `'error'` de inmediato; no se ejecuta ningún temporizador.
3939

40-
See [Resource status](/guide/signals/resource#resource-status) for the full list of statuses and their `value()` behavior.
40+
Consulta [Estado del resource](/guide/signals/resource#resource-status) para ver la lista completa de estados y el comportamiento de `value()` en cada uno.
4141

42-
## Custom wait function
42+
## Función de espera personalizada {#custom-wait-function}
4343

44-
Instead of a millisecond duration, you can pass a function that returns a `Promise<void>`. The resource resolves when the promise resolves. If the source signal changes before the promise settles, Angular discards the previous promise and starts a new one.
44+
En lugar de una duración en milisegundos, puedes pasar una función que devuelva un `Promise<void>`. El resource se resuelve cuando la promesa se resuelve. Si la signal fuente cambia antes de que la promesa se asiente, Angular descarta la promesa anterior e inicia una nueva.
4545

4646
```ts
4747
debouncedQuery = debounced(query, (value, lastSnapshot) => {
48-
// Retry immediately after an error rather than making the user wait again.
48+
// Reintenta de inmediato tras un error en lugar de hacer que el usuario espere de nuevo.
4949
if (lastSnapshot.status === 'error') return;
50-
// Short queries get a longer delay—the user is likely still typing.
50+
// Las consultas cortas reciben un retraso mayor: probablemente el usuario sigue escribiendo.
5151
const ms = value.length < 3 ? 500 : 200;
5252
return new Promise<void>((resolve) => setTimeout(resolve, ms));
5353
});
5454
```
5555

56-
See the `DebounceTimer` type in the API reference for details.
56+
Consulta el tipo `DebounceTimer` en la referencia de la API para más detalles.
5757

58-
## Equality
58+
## Igualdad {#equality}
5959

60-
By default, `debounced` uses `Object.is` to compare values.
60+
Por defecto, `debounced` usa `Object.is` para comparar valores.
6161

62-
Provide a custom equality function with the `equal` option when the default identity check is too strict:
62+
Proporciona una función de igualdad personalizada con la opción `equal` cuando la comprobación de identidad predeterminada sea demasiado estricta:
6363

6464
```ts
6565
debouncedFilter = debounced(filter, 200, {
6666
equal: (a, b) => a.category === b.category && a.minPrice === b.minPrice,
6767
});
6868
```
6969

70-
## Injection context
70+
## Contexto de inyección {#injection-context}
7171

72-
`debounced` must be called inside an [injection context](guide/di/dependency-injection-context). Angular automatically destroys the debounced resource and cancels any pending timer when the injector is destroyed.
72+
`debounced` debe llamarse dentro de un [contexto de inyección](guide/di/dependency-injection-context). Angular destruye automáticamente el resource con debounce y cancela cualquier temporizador pendiente cuando se destruye el inyector.
7373

74-
To use `debounced` outside of an injection context, pass an explicit `Injector` via the options:
74+
Para usar `debounced` fuera de un contexto de inyección, pasa un `Injector` explícito a través de las opciones:
7575

7676
```ts
7777
@Service()
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
## Effects
2+
3+
Signals are useful because they notify interested consumers when they change. An **effect** is an operation that runs whenever one or more signal values change. You can create an effect with the `effect` function:
4+
5+
```ts
6+
import {effect} from '@angular/core';
7+
8+
effect(() => {
9+
console.log(`The current count is: ${count()}`);
10+
});
11+
```
12+
13+
Effects always run **at least once.** When an effect runs, it tracks any signal value reads. Whenever any of these signal values change, the effect runs again. Similar to computed signals, effects keep track of their dependencies dynamically, and only track signals which were read in the most recent execution.
14+
15+
Effects always execute **asynchronously**, during the change detection process.
16+
17+
### Use cases for effects
18+
19+
Effects should be the last API you reach for. Always prefer `computed()` for derived values and `linkedSignal()` for values that can be both derived and manually set. If you find yourself copying data from one signal to another with an effect, it's a sign you should move your source-of-truth higher up and use `computed()` or `linkedSignal()` instead. Effects are best for syncing signal state to imperative, non-signal APIs.
20+
21+
TIP: There are no situations where effect is good, only situations where it is appropriate.
22+
23+
- Logging signal values, either for analytics or as a debugging tool.
24+
- Keeping data in sync with different kinds of storage: `window.localStorage`, session storage, cookies, etc.
25+
- Adding custom DOM behavior that can't be expressed with template syntax.
26+
- Performing custom rendering to a `<canvas>` element, charting library, or other third party UI library.
27+
28+
<docs-callout critical title="When not to use effects">
29+
Avoid using effects for propagation of state changes. This can result in `ExpressionChangedAfterItHasBeenChecked` errors, infinite circular updates, or unnecessary change detection cycles.
30+
31+
Instead, use `computed` signals to model state that depends on other state.
32+
</docs-callout>
33+
34+
### Injection context
35+
36+
By default, you can only create an `effect()` within an [injection context](guide/di/dependency-injection-context) (where you have access to the `inject` function). The easiest way to satisfy this requirement is to call `effect` within a component, directive, or service `constructor`:
37+
38+
```ts
39+
@Component(/* ... */)
40+
export class EffectiveCounter {
41+
readonly count = signal(0);
42+
43+
constructor() {
44+
// Register a new effect.
45+
effect(() => {
46+
console.log(`The count is: ${this.count()}`);
47+
});
48+
}
49+
}
50+
```
51+
52+
To create an effect outside the constructor, you can pass an `Injector` to `effect` via its options:
53+
54+
```ts
55+
@Component(/* ... */)
56+
export class EffectiveCounter {
57+
readonly count = signal(0);
58+
private injector = inject(Injector);
59+
60+
initializeLogging(): void {
61+
effect(
62+
() => {
63+
console.log(`The count is: ${this.count()}`);
64+
},
65+
{injector: this.injector},
66+
);
67+
}
68+
}
69+
```
70+
71+
### Execution of effects
72+
73+
Angular implicitly defines two implicit behaviors for its effects depending on the context they were created in.
74+
75+
A "View Effect" is an `effect` created in the context of a component instantiation. This includes effects created by services that are tied to component injectors.<br>
76+
A "Root Effect" is created in the context of a root provided service instantiation.
77+
78+
The execution of both kinds of `effect` are tied to the change detection process.
79+
80+
- "View effects" are executed _before_ their corresponding component is checked by the change detection process.
81+
- "Root effects" are executed prior to all components being checked by the change detection process.
82+
83+
In both cases, if at least one of the effect dependencies changed during the effect execution, the effect will re-run before moving ahead on the change detection process.
84+
85+
### Destroying effects
86+
87+
When a component or directive is destroyed, Angular automatically cleans up any associated effects.
88+
89+
An `effect` can be created in two different contexts that will affect when it's destroyed:
90+
91+
- A "View effect" is destroyed when the component is destroyed.
92+
- A "Root effect" is destroyed when the application is destroyed.
93+
94+
Effects return an `EffectRef`. You can use the ref's `destroy` method to manually dispose of an effect. You can combine this with the `manualCleanup` option when creating an effect to disable automatic cleanup. Be careful to actually destroy such effects when they're no longer required.
95+
96+
### Effect cleanup functions
97+
98+
When a component or directive is destroyed, Angular automatically cleans up any associated effects.
99+
Effects might start long-running operations, which you should cancel if the effect is destroyed or runs again before the first operation finished. When you create an effect, your function can optionally accept an `onCleanup` function as its first parameter. This `onCleanup` function lets you register a callback that is invoked before the next run of the effect begins, or when the effect is destroyed.
100+
101+
```ts
102+
effect((onCleanup) => {
103+
const user = currentUser();
104+
105+
const timer = setTimeout(() => {
106+
console.log(`1 second ago, the user became ${user}`);
107+
}, 1000);
108+
109+
onCleanup(() => {
110+
clearTimeout(timer);
111+
});
112+
});
113+
```
114+
115+
## Side effects on DOM elements
116+
117+
The `effect` function is a general-purpose tool for running code in reaction to signal changes. However, it runs _before_ the Angular updates the DOM. In some situations, you may need to manually inspect or modify the DOM, or integrate a 3rd-party library that requires direct DOM access.
118+
119+
For these situations, you can use `afterRenderEffect`. It functions like `effect`, but runs after Angular has finished rendering and committed its changes to the DOM.
120+
121+
```ts
122+
@Component(/* ... */)
123+
export class MyFancyChart {
124+
chartData = input.required<ChartData>();
125+
canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
126+
chart: ChartInstance;
127+
128+
constructor() {
129+
// Run a single time to create the chart instance
130+
afterNextRender({
131+
write: () => {
132+
this.chart = initializeChart(this.canvas().nativeElement(), this.chartData());
133+
},
134+
});
135+
136+
// Re-run after DOM has been updated whenever `chartData` changes
137+
afterRenderEffect(() => {
138+
this.chart.updateData(this.chartData());
139+
});
140+
}
141+
}
142+
```
143+
144+
In this example `afterRenderEffect` is used to update a chart created by a 3rd party library.
145+
146+
TIP: You often don't need `afterRenderEffect` to check for DOM changes. APIs like `ResizeObserver`, `MutationObserver` and `IntersectionObserver` are preferred to `effect` or `afterRenderEffect` when possible.
147+
148+
### Render phases
149+
150+
Accessing the DOM and mutating it can impact the performance of your application, for example by triggering too many unnecessary [reflows](https://developer.mozilla.org/en-US/docs/Glossary/Reflow).
151+
152+
To optimize those operations, `afterRenderEffect` offers four phases to group the callbacks and execute them in an optimized order.
153+
154+
The phases are:
155+
156+
| Phase | Description |
157+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158+
| `earlyRead` | Use this phase to read from the DOM before a subsequent write callback, for example to perform custom layout that the browser doesn't natively support. Prefer the read phase if reading can wait. |
159+
| `write` | Use this phase to write to the DOM. **Never** read from the DOM in this phase. |
160+
| `mixedReadWrite` | Use this phase to read from and write to the DOM simultaneously. Never use this phase if it is possible to divide the work among the other phases instead. |
161+
| `read` | Use this phase to read from the DOM. **Never** write to the DOM in this phase. |
162+
163+
Using these phases helps prevent layout thrashing and ensures that your DOM operations are performed in a safe and efficient manner.
164+
165+
You can specify the phase by passing an object with a `phase` property to `afterRender` or `afterNextRender`:
166+
167+
```ts
168+
afterRenderEffect({
169+
earlyRead: (cleanupFn) => {
170+
/* ... */
171+
},
172+
write: (previousPhaseValue, cleanupFn) => {
173+
/* ... */
174+
},
175+
mixedReadWrite: (previousPhaseValue, cleanupFn) => {
176+
/* ... */
177+
},
178+
read: (previousPhaseValue, cleanupFn) => {
179+
/* ... */
180+
},
181+
});
182+
```
183+
184+
CRITICAL: If you don't specify the phase, `afterRenderEffect` runs callbacks during the `mixedReadWrite` phase. This may worsen application performance by causing additional DOM reflows.
185+
186+
#### Phase executions
187+
188+
The `earlyRead` phase callback receives no parameters. Each subsequent phase receives the return value of the previous phase's callback as a Signal. You can use this to coordinate work across phases.
189+
190+
Effects run in the following phase order:
191+
192+
1. `earlyRead`
193+
2. `write`
194+
3. `mixedReadWrite`
195+
4. `read`
196+
197+
If one of the phases modifies a signal value tracked by `afterRenderEffect`, the affected phases execute again.
198+
199+
#### Cleanup
200+
201+
Each phase provides a cleanup callback function as argument. The cleanup callbacks are executed when the `afterRenderEffect` is destroyed or before re-running phase effects.
202+
203+
### Server-side rendering caveats
204+
205+
`afterRenderEffect`, similarly to `afterNextRender`/`afterEveryRender`, only runs on the client.
206+
207+
NOTE: Components are not guaranteed to be [hydrated](/guide/hydration) before the callback runs. You must use caution when directly reading or writing the DOM and layout.

0 commit comments

Comments
 (0)