Introduction

The Angular framework has been evolving with a fresh reactivity model in recent releases, centered around signals. With the benefit of experience, it's become clear that certain scenarios weren't fully addressed in the initial rollout. The Angular team, known for their responsiveness, is now stepping up to fill these gaps with purpose-built utilities.

What exactly are the missing pieces? What new tools are on the way, and how will developers put them to work?

Resetting a Signal Based on Another Signal

Let's begin by laying out a concrete example of the problem.

Consider a basket of fruit where each fruit has a quantity. The quantity is managed by a component that receives a fruit as an input.

@Component({
  template: `<button type="button" (click)="updateQuantity()"> 
    {{quantity()}}
    </button>`
})
export class QuantityComponent() {
  fruit = input.required<string>();
  count = signal(1);

  updateQuantity(): void {
    this.count.update(prevCount => prevCount++);
  }
}

The local quantity variable needs to be reinitialized whenever the fruit input changes.

A naive approach would involve using an effect to watch the input and reset the counter.

@Component({
  template: `<button type="button" (click)="updateQuantity()"> 
    {{quantity()}}
    </button>`
})
export class QuantityComponent() {
  fruit = input.required<string>();
  quantity = signal(1);

  countEffect(() => {
    this.fruit();
    this.quantity.set(1);
  }, { allowSignalWrites: true })

  updateQuantity(): void {
    this.quantity.update(prevCount => prevCount++);
  }
}

The code above is an anti-pattern. Why exactly is it problematic?

To modify the quantity signal inside an effect, you're forced to set the allowSignalWrites option to true. This tension stems from a fundamental misreading of the situation.

We're actually trying to keep two variables in sync that should never be out of step in the first place. The counter isn't an independent piece of state; it's a direct derivative of the fruit input. So, what we have is essentially a single component state where the fruit is the source of truth and the rest is downstream of it.

A proper implementation looks like this.

@Component({
  template: `<button type="button" (click)="updateQuantity()"> 
    {{fruitState().quantity()}}
    </button>`
})
export class QuantityComponent() {
  fruit = input.required<string>();

  fruitState = computed(() => ({
    source: fruit(),
    quantity: signal(1),
  }));

  updateQuantity(): void {
    this.fruitState().quantity.update(prevCount => prevCount++);
  }
}

This approach binds the fruit to its quantity tightly. The moment the fruit changes, the fruitState computed signal recalculates, generating an object whose quantity property is a new writable signal, defaulting to 1.

Because the quantity is itself a signal, it can be incremented on user interaction and will automatically reset when a new fruit arrives.

It's a clean pattern, but can we streamline it even further?

Introducing linkedSignal

Angular 19 is rolling out a new utility for creating stateful derived signals.

Up until now, the computed function was the go-to tool for derived state, but it returns a read-only Signal. This is a limitation when you need a writable signal, as our quantity example demands.

This is precisely the gap that linkedSignal fills. As the name implies, it establishes a strong connection between two signals.

Revisiting our previous scenario, linkedSignal simplifies the code dramatically.

@Component({
  template: `<button type="button" (click)="updateQuantity()"> 
    {{quantity()}}
    </button>`
})
export class QuantityComponent() {
  fruit = input.required<string>();
  quantity = linkedSignal({ source: fruit, computation: () => 1 });

  updateQuantity(): void {
    this.quantity.update(prevCount => prevCount++);
  }
}

The function's signature is defined as follows.

linkedSignal(computation: () => D, options?: { equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>;

linkedSignal(options: { source: () => S; computation: (source: NoInfer<S>, previous?: { source: NoInfer<S>; value: NoInfer<D>; }) => D; equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>;

In its simplest form, linkedSignal accepts a computation function and an optional configuration object.

const quantity = input.required<number>();
const price = linkedSignal(() => quantity() * 0);

In this first example, the computation function directly references the quantity signal itself, which causes it to be re-evaluated whenever that quantity changes.

The second definition takes a more structured object with three distinct properties.

  • source: The signal that the computation function observes for changes.
  • computation: The logic that computes the new value.
  • equal: A configuration object for custom equality checks.

Unlike the shorthand form, this version of the computation function passes two arguments: the current source value and the previously computed value.

const fruits = signal(['apple', 'orange']);
const choice = linkedSignal({
  source: fruits,
  computation: (source, previous) => {
    if(!Boolean(previous)) {
      return 'apple';
    }
    return previous.source.find(fruit => fruit === previous.value) ||   
      'apple';
  }
})

The New resource API

Angular 19 is also set to introduce a dedicated API designed to simplify asynchronous data fetching. This new utility elegantly bundles together the request, its status, the returned data, and any errors.

For those familiar with other frameworks, it shares conceptual similarities with React's use hook for data handling.

Let's examine a practical illustration.


import { resource } from "@angular/core";

@Component()
export class FruitComponent {
  fruitId = input.required<string>();
  fruitRessource = resource({
    loader: () => {
      return fetch(`https://myFruit.com/${this.fruitId()}`).then(response => 
      response.json());
    },
  });

  fruitRessourceEffect(() => {
    console.log("Status: ", this.todoResource.status());
    console.log("Value: ", this.todoResource.value());
    console.log("Error: ", this.todoResource.error());
  })
}

There are several key observations to make about this code sample.

  • By default, the loader expects a function that returns a Promise.
  • The resulting fruitResource is of type WritableResource, which permits direct local mutations of its value if needed.
  • The HTTP request for the fruit details is initiated automatically and immediately upon the creation of the resource.
  • The this.fruitId() call inside the loader is untracked, meaning the loader won't be re-invoked simply because the ID changes.
  • The WritableResource also exposes a reload method to manually trigger a data refresh.

This effect will log the reactive values.

Status: 'pending'
Value: undefined,
Error: undefined,

// When the promise is resolved, we enter again in the effect, because the status and value are signals and they changed

Status: 'resolved'
Value: { name: 'apple', count: 2 },
Error: undefined,

As noted above, the resource API doesn't automatically track signal dependencies by default within the loader. This raises the question: how do you restart the request when the fruitId changes, and critically, how do you manage concurrent requests to prevent a race condition?

The solution lies in an additional property the resource function accepts, called request.

This property expects a function that reads the source signals and returns a value, which is then passed to the loader.


import { resource } from "@angular/core";

@Component()
export class FruitComponent {
  fruitId = input.required<string>();
  fruitRessource = resource({
    request: this.fruitId
    loader: (request, abortSignal) => {
      return fetch(`https://myFruit.com/${request}`}, { signal: abortSignal }).then(response => 
      response.json());
    },
  });

  fruitRessourceEffect(() => {
    console.log("Status: ", this.todoResource.status());
    console.log("Value: ", this.todoResource.value());
    console.log("Error: ", this.todoResource.error());
  })
}

As demonstrated above, the loader function now accepts a second parameter. This parameter is an AbortSignal from the provided AbortController, which is used to cancel the active HTTP request.

Consequently, if the fruitId signal changes while the previous fruit's details are still loading, the in-flight request is cancelled before the new one begins.

Finally, recognizing the ecosystem's investment in RxJS, Angular has ensured interoperability with this new API. This is achieved through the rxResource function.

Its signature mirrors that of resource almost exactly. The only significant difference is that the loader function is expected to return an Observable instead of a promise.


import { resource } from "@angular/core";

@Component()
export class FruitComponent {
  fruitId = input.required<string>();
  fruitRessource = rxResource({
    request: this.fruitId
    loader: request => {
      return this.httpClient.get<any>(`https://myFruit.com/${request}`)
    }
  });

  fruitRessourceEffect(() => {
    console.log("Status: ", this.todoResource.status());
    console.log("Value: ", this.todoResource.value());
    console.log("Error: ", this.todoResource.error());
  })
}

In this context, you don't need to explicitly use an AbortSignal. The cancellation of the previous subscription is inherent to how rxResource functions, matching the behavior of the RxJS switchMap operator perfectly.