# 🔗 Angular's `linkedSignal` and `resource()`: A Practical Guide for 2026

---

## 🎯 Why these two APIs matter right now

Angular's Signals story has been building for three years. With Angular 20, the final pieces clicked into place: `linkedSignal()` and `resource()` both graduated to stable, and together they solve two problems that every Angular developer hits repeatedly.

The first problem is **dependent writable state** — you need a signal whose initial value is derived from another signal, but the user can also change it directly. `computed()` is read-only, so it's no help. Before `linkedSignal()`, developers reached for awkward `effect()` workarounds or manual synchronization logic. 😩

The second problem is **async data in a signals world** — you have a reactive fetch that should re-run automatically when params change, surface loading/error states as signals, and cancel in-flight requests when the params change again. Previously this meant wiring up RxJS observables, managing subscriptions, and handling race conditions by hand. `resource()` handles all of that declaratively. 🙌

Let's look at each one in depth.

---

## 🔗 Part 1: `linkedSignal()`

### 🤔 The problem it solves

Suppose you have a shipping options component. The available options come from a server (or a parent component as an input), and you want to pre-select the first one. But the user can also pick a different option themselves.

Before `linkedSignal()`, the typical approach was to use a `signal()` for the selection and an `effect()` to reset it when the options changed. This works, but `effect()` is meant for side effects — using it for state derivation is a code smell that the Angular team actively warns against. 🚩

`linkedSignal()` is the right tool: it creates a **writable signal whose value resets automatically when its reactive source changes**. ✨

### 🚀 Basic usage

```typescript
import { Component, Signal, linkedSignal } from '@angular/core';

@Component({ /* ... */ })
export class ShippingPickerComponent {
  shippingOptions: Signal<string[]> = getShippingOptions();

  // ✅ Automatically initializes to the first option.
  // If shippingOptions changes (e.g., user switches region),
  // selectedOption resets to the new first element.
  selectedOption = linkedSignal(() => this.shippingOptions()[0]);

  selectOption(index: number) {
    this.selectedOption.set(this.shippingOptions()[index]);
  }
}
```

The key difference from `computed()`: `selectedOption` is a `WritableSignal`. The user can call `.set()` on it, and that change holds — until `shippingOptions` changes, at which point the computation re-runs and resets it to the new first element. 🔄

### 🧠 The advanced form: preserving state across resets

Sometimes you don't want a full reset. Say the user has selected "Air" shipping ✈️, and then the available options refresh with a new list that still includes "Air". Resetting to the first item would be a bad UX — you should keep their selection if it's still valid.

The advanced `linkedSignal()` form gives you access to the **previous value**:

```typescript
interface ShippingMethod {
  id: number;
  name: string;
}

selectedOption = linkedSignal<ShippingMethod[], ShippingMethod>({
  source: () => this.shippingOptions(),
  computation: (newOptions, previous) => {
    // 🔍 If the user had a selection and it still exists in the new list, keep it
    if (previous) {
      const stillAvailable = newOptions.find(
        opt => opt.id === previous.value.id
      );
      if (stillAvailable) return stillAvailable;
    }
    // Otherwise default to the first option
    return newOptions[0];
  }
});
```

The `computation` function receives:
- `source` — the new value of the source signal
- `previous` — an object with `previous.source` (old source value) and `previous.value` (the previous `linkedSignal` value)

> 📝 **Note:** When using the `previous` parameter, you must provide generic type arguments explicitly: `linkedSignal<SourceType, ValueType>`.

### 📝 Using `linkedSignal()` for editable form copies

This is the most common real-world use case. You load data from the server, display it in a form, and let the user edit it. When the user saves, you send the edited copy. If the source data reloads (e.g., another user changed it), the form should refresh. 🔁

```typescript
@Component({ /* ... */ })
export class UserProfileComponent {
  loadedUser = this.store.user; // a Signal<User>

  // ✏️ Editable local copies — reset if loadedUser changes
  name    = linkedSignal(() => this.loadedUser().name);
  email   = linkedSignal(() => this.loadedUser().email);
  role    = linkedSignal(() => this.loadedUser().role);

  save() {
    this.store.update({
      ...this.loadedUser(),
      name:  this.name(),
      email: this.email(),
      role:  this.role(),
    });
  }
}
```

The template uses two-way binding directly on the linked signals:

```html
<input [(ngModel)]="name" />
<input [(ngModel)]="email" />
<select [(ngModel)]="role"> ... </select>
<button (click)="save()">Save</button>
```

The user can freely edit the form. If the server pushes an update to `loadedUser`, all three fields reset to reflect the new data. Clean, and zero `effect()` calls. 💪

### 🚫 When *not* to use `linkedSignal()`

Use `computed()` when the derived value should be **strictly read-only** — for example, a total price derived from quantity and unit price. There's no scenario where you'd want the user to override that value directly. `linkedSignal()` is specifically for cases where manual overrides are part of the intended UX.

---

## 📡 Part 2: `resource()`

### 😩 The problem it solves

Before `resource()`, fetching data reactively in Angular typically meant:

1. 💉 Injecting `HttpClient`
2. 🔀 Using `switchMap` in an RxJS pipeline to react to param changes
3. 📊 Manually tracking loading and error states
4. 🧹 Unsubscribing on destroy
5. 🏁 Handling race conditions manually (usually via `switchMap`)

That's a lot of boilerplate for something as universal as "fetch data when this signal changes." `resource()` replaces the whole pattern. 🎉

### ⚙️ How `resource()` works

A `resource` has two parts:

- **`params`** 🎛️ — a reactive function (like `computed()`) that returns the parameters for the async operation. When the params change, the resource automatically re-fetches.
- **`loader`** 🚚 — an async function that receives the current params and returns a `Promise`.

```typescript
import { resource, signal } from '@angular/core';

userId = signal<string>('user-1');

userResource = resource({
  params: () => ({ id: this.userId() }),
  loader: ({ params }) => fetch(`/api/users/${params.id}`)
    .then(res => res.json())
});
```

The resource automatically exposes the following signals:

| Signal | Type | Description |
|--------|------|-------------|
| `.value()` ✅ | `T \| undefined` | The resolved data |
| `.isLoading()` ⏳ | `boolean` | True while the loader is running |
| `.error()` ❌ | `unknown` | The thrown error, if any |
| `.hasValue()` 🔍 | `boolean` | True when data is available |
| `.status()` 📊 | `ResourceStatus` | Full status string |

### 🖼️ Using resource signals in templates

```html
@if (userResource.isLoading()) {
  <p>⏳ Loading...</p>
} @else if (userResource.error()) {
  <p>❌ Error: could not load user.</p>
} @else if (userResource.hasValue()) {
  <user-profile [user]="userResource.value()" />
}
```

> ⚠️ **Always guard `.value()` with `hasValue()`** — reading `.value()` when the resource is in an error state throws at runtime.

### 🔄 `rxResource()` — the RxJS-friendly variant

If your data layer uses `HttpClient` (which returns `Observable`), use `rxResource()` from `@angular/core/rxjs-interop`. It's identical to `resource()` except the loader function returns an `Observable` instead of a `Promise`. As of Angular 20, the loader key is called `stream` in `rxResource()`.

```typescript
import { inject, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';

@Component({ /* ... */ })
export class ProductListComponent {
  private http = inject(HttpClient);
  page = signal(1);

  productsResource = rxResource({
    params: () => this.page(),
    stream: ({ params: page }) =>
      this.http.get<Product[]>(`/api/products?page=${page}`)
  });
}
```

`rxResource()` automatically unsubscribes and cancels in-flight requests when params change — no `switchMap`, no `takeUntilDestroyed`. 🧘

### ⚡ `httpResource()` — the simplest option for plain HTTP GET

For the most common case — a reactive HTTP GET with a dynamic URL — `httpResource()` is the most concise API:

```typescript
import { httpResource } from '@angular/core';

@Component({ /* ... */ })
export class UserComponent {
  userId = input.required<string>();

  // 🔁 Automatically re-fetches whenever userId changes
  user = httpResource(() => `/api/users/${this.userId()}`);
}
```

`httpResource()` runs through Angular's `HttpClient` stack, including interceptors, and returns the response as signals. It also supports response type variants (`.text()`, `.blob()`, `.arrayBuffer()`) and schema validation via Zod or Valibot via a `parse` option.

> 🧪 **Note:** `httpResource()` is still experimental as of Angular 21. `resource()` and `rxResource()` are stable.

### 🤝 Combining `resource()` with `linkedSignal()`

This is where the two APIs shine together. ✨ Say you fetch a list of posts and want to track the selected post — defaulting to the first one, but letting the user pick:

```typescript
@Component({ /* ... */ })
export class PostBrowserComponent {
  private http = inject(HttpClient);
  category = signal('angular');

  postsResource = rxResource({
    params: () => this.category(),
    stream: ({ params: cat }) =>
      this.http.get<Post[]>(`/api/posts?category=${cat}`)
  });

  // 🎯 Defaults to first post, resets when posts reload,
  // but user can select any post manually
  selectedPost = linkedSignal(
    () => this.postsResource.value()?.[0] ?? null
  );

  selectPost(post: Post) {
    this.selectedPost.set(post);
  }
}
```

When `category` changes, `postsResource` re-fetches, and `selectedPost` automatically resets to the new first post. The user can select any post in between, and that selection holds until the next category change. 🔄

---

## 🛠️ Part 3: Common patterns and pitfalls

### 🏁 Race conditions are handled for you

One of the most important things `resource()` does behind the scenes: if params change while a load is already in flight, the previous request is aborted (for `resource()` via `AbortSignal`, and for `rxResource()` via automatic unsubscription). You never need to think about stale responses overwriting fresh ones. 🛡️

### 🔗 Chained resources

Resources can depend on other resources via `computed()`:

```typescript
userResource = resource({
  params: () => ({ id: this.userId() }),
  loader: ({ params }) => fetchUser(params)
});

// ⛓️ Only triggers when userResource has a value
profilePicResource = httpResource(
  () => this.userResource.hasValue()
    ? `/api/images/${this.userResource.value()!.profilePicId}`
    : undefined
);
```

When the `params` function returns `undefined`, the resource stays in idle state and doesn't fire a request. 😴

### 🔃 Manual reload

Resources expose a `.reload()` method for cases like "pull to refresh":

```html
<button (click)="postsResource.reload()">🔄 Refresh</button>
```

### 🚫 Avoid using `resource()` for mutations

`resource()` is designed for **read operations** only. If params change while a POST or PUT is in flight, the request gets cancelled — meaning your mutation might never reach the server. ⚠️ Use `HttpClient` directly (or a dedicated mutations pattern) for writes.

---

## 📋 Summary: which API to reach for

| Scenario | Use |
|----------|-----|
| Derive a value reactively, user can override it 🖊️ | `linkedSignal()` |
| Editable form copy synced with server data 📝 | `linkedSignal()` |
| Fetch data reactively using Promise 🤝 | `resource()` |
| Fetch data reactively using HttpClient/Observable 🔄 | `rxResource()` |
| Simple reactive HTTP GET, don't need full control ⚡ | `httpResource()` |
| Strict read-only derived value 🔒 | `computed()` |
| Side effects (logging, DOM, non-Angular APIs) ⚙️ | `effect()` |

---

## 📚 Further reading

- 🔗 [Angular docs: Dependent state with `linkedSignal`](https://angular.dev/guide/signals/linked-signal)
- 📡 [Angular docs: Async reactivity with resources](https://angular.dev/guide/signals/resource)
- ⚡ [Angular docs: `httpResource`](https://angular.dev/guide/http/http-resource)
- 📖 [Angular API reference: `linkedSignal`](https://angular.dev/api/core/linkedSignal)
