Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions adev/src/content/guide/signals/effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,77 @@ Avoid using effects for propagation of state changes. This can result in `Expres
Instead, use `computed` signals to model state that depends on other state.
</docs-callout>

### Common mistakes

#### Avoid using effects for derived state

Effects run **asynchronously**, after Angular's change detection cycle. Setting a signal inside an effect means the new value won't be visible until the next cycle — any code that reads that signal in the same frame will see a stale value.

Avoid:

```ts
const total = signal(0);

effect(() => {
total.set(price() * quantity());
});
```

Here `total` is a writable signal that tries to mirror `price() * quantity()`, but it always lags one cycle behind. It can also get out of sync if anything else writes to `total` directly.

Prefer `computed`, which recalculates synchronously and lazily — the value is always fresh when read and can never drift from its sources:

```ts
const total = computed(() => price() * quantity());
```

As a general rule: if a value can be derived from other signals, it should be a `computed`, not a signal updated by an effect.

#### Avoid using effects to synchronize state

Using an effect to copy one signal's value into another creates two independent pieces of state representing the same data. This makes the relationship between them implicit and easy to break.

Avoid:

```ts
effect(() => {
selectedUserId.set(selectedUser()?.id);
});
```

`selectedUserId` is a separate writable signal that can be set from anywhere else in the code, silently breaking its dependency on `selectedUser`. The reactive graph now has a hidden invariant that nothing enforces.

Prefer deriving the value with `computed`, which makes the relationship explicit and always consistent:

```ts
const selectedUserId = computed(() => selectedUser()?.id);
```

#### Keep effects focused on side effects

Effects are designed for **one-way communication** from Angular's reactive state to external, non-reactive systems. They are the right tool when you need to react to a signal change and call something outside the reactive graph.

Common examples:

- Logging and analytics
- Browser APIs such as `localStorage`, `sessionStorage`, or cookies
- Canvas rendering and charting libraries
- Third-party UI libraries
- Custom DOM integrations

```ts
effect(() => {
localStorage.setItem('theme', theme());
});
```

A useful rule of thumb when choosing between `computed` and `effect`:

- Use `computed` to **calculate a value** — something that other parts of your app will read.
- Use `effect` to **trigger an action** — something that happens outside the reactive graph.

If you can express what you need as a `computed`, that is almost always the better choice.

### Injection context

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`:
Expand Down
90 changes: 90 additions & 0 deletions adev/src/content/guide/signals/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,65 @@ 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`.

## Mental model

Signals are best understood as a reactive state graph.

Each signal represents a piece of state. Computed values derive state from other state. Effects react to state changes and interact with the outside world.

A useful rule of thumb is:

- Use `signal` for state.
- Use `computed` for derived state.
- Use `effect` for side effects.

When working with signals, prefer deriving values over synchronizing values. If one value can be calculated from another, represent that relationship with `computed` instead of manually keeping multiple signals in sync.

### Prefer derivation over synchronization

When a value can be expressed as a function of other state, model it as a derivation instead of maintaining multiple sources of truth.

Prefer:

```ts
const fullName = computed(() => `${firstName()} ${lastName()}`);
```

Over:

```ts
const fullName = signal('');

effect(() => {
fullName.set(`${firstName()} ${lastName()}`);
});
```

Derived state stays consistent automatically and avoids unnecessary mutable state.

### Choosing between computed and effect

Both `computed` and `effect` react to signal changes, but they serve different purposes.

Use `computed` when you need to calculate a value from other signals:

```ts
const total = computed(() => price() * quantity());
```

Use `effect` when you need to update something outside Angular’s signal model, such as browser storage, logging, analytics, or a third-party UI library.

```ts
effect(() => {
localStorage.setItem('theme', theme());
});
```

In general, you'll want to follow the general guidelines:

- If you're calculating a value, use `computed`.
- If you need to synchronize signal state to an external non-reactive system, use `effect`.

## 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_.
Expand Down Expand Up @@ -274,6 +333,37 @@ isWritableSignal(count); // true
isWritableSignal(doubled); // false
```

## Signals and RxJS

Signals and RxJS solve different problems and are often used together.
Signals model state and relationships between values.
RxJS models events and asynchronous streams over time.

A signal answers:

> What is the current value?

An Observable answers:

> What values may be emitted over time?

Signals are typically a good fit for:

- Component state
- UI state
- Derived state
- Relationships between values

RxJS is typically a better fit for:

- Event streams
- WebSocket messages
- Request orchestration
- Cancellation and retry logic
- Complex asynchronous workflows

Signals are not intended to replace RxJS. Instead, they provide a simpler model for representing and deriving application state.

## Using signals with RxJS

See [RxJS interop with Angular signals](ecosystem/rxjs-interop) for details on interoperability between signals and RxJS.
Loading