Angular 19 Signals

Angular 19: Signals, Linked Signals, and Resource API

Introduction Angular’s reactive capabilities continue to evolve, and with the release of Angular 19, signals have become even more powerful and versatile. For a refresher…

   

Introduction

Angular’s reactive capabilities continue to evolve, and with the release of Angular 19, signals have become even more powerful and versatile. For a refresher on Angular 18, check out our previous guide. This update brings several significant enhancements aimed at improving developer productivity and simplifying state management. In this article, we will explore the key new features in Angular Signals, such as the useEffect API for handling reactive side effects, linked signals for better composability, and the resource() API for asynchronous operations. We will also provide a detailed comparison between Angular Signals and RxJS to help you determine the most suitable approach for your use case.

Angular Signals in Angular 18 (Recap):

Angular Signals introduced a way to manage reactivity with a fine-grained change detection system. Using signals, developers can define reactive state variables that automatically update dependent computations when changed. Signals simplify reactivity and reduce the need for manual subscriptions, but Angular 19 takes it to a new level.

import { signal, computed } from '@angular/core';
const count = signal(0);
const double = computed(() => count() * 2);
count.set(2);
console.log(double()); // Output: 4

This example creates a reactive value count starting at 0 and a derived value double that’s always twice count. When count is updated to 2, double automatically updates to 4. It shows how Angular’s signals let values react to changes automatically.

Top Angular Signals Enhancements in Angular 19

1. LinkedSignals

In Angular 19, linked signals extend the concept of reactivity by allowing multiple signals to be interconnected, creating a more powerful and flexible reactive state management system. While the computed signal was introduced in Angular 18 to derive values from other signals, linked signals allow for more dynamic interdependencies between signals, simplifying complex reactive logic. A linked signal derives its value from other signals in a more explicit and fluid manner than the computed signal. Unlike computed, which is typically used to derive a value from one or more signals, linked signals can establish dependencies that are updated automatically whenever the source signals change. The key difference lies in the ease of composition and the ability to manage complex relationships between multiple signals.

home.component.ts file
userSignals = signal([
  {
    name: 'John Doe',
    age: 30,
    isAdmin: true,
  },
  {
    name: 'Mark Smith',
    age: 20,
    isAdmin: true,
  },
  {
    name: 'Dominic',
    age: 10,
    isAdmin: true,
  }
]);

selectedLinkedUser = linkedSignal(() => this.userSignals()[0]);
home.component.html file
<div class = "col-3">
  Total Users = {{userSignals().length}}
  <div>
      SelectedUser: {{selectedLinkedUser().name}}
  </div>
</div>

Output:

Angular 19 Signals

2. Preserve Previous State:

In some cases, the computation logic for a linkedSignal needs to consider its previously selected value. For example, when a source signal changes like a list of options, it may be preferable to retain the previously selected item, provided it still exists in the updated list. Consider a scenario where a user’s selection resets to the first option every time the list of options changes. This behavior can be undesirable in real-world applications where user context should be preserved. To address this, Angular’s linkedSignal allows us to access the previous value during computation, enabling more intelligent state transitions. Here’s how this works: you define a linkedSignal with both a source (the signal that triggers updates) and a computation function. Inside that function, you can use the previous parameter to compare or re-select values based on prior state. This pattern is particularly useful for preserving user selections or reactively maintaining state consistency across updates.

import { signal, linkedSignal } from '@angular/core';
interface User {
  name: string;
  age: number;
  isAdmin: boolean;
}

@Component({
  selector: 'app-user-selector',
  template: `<div>{{ selectedUser() | json }}</div>`,
})
export class UserSelectorComponent {
  constructor() {
    this.selectUser(1); // Select Mark Smith
    this.updateUsers();
    console.log(this.selectedUser()); 
    // Output: { name: 'Mark Smith', age: 20, isAdmin: true }
  }

  // Initial list of users
  userSignals = signal<User[]>([
    { name: 'John Doe', age: 30, isAdmin: true },
    { name: 'Mark Smith', age: 20, isAdmin: true },
    { name: 'Dominic', age: 10, isAdmin: true },
  ]);

  // Linked signal that preserves the selected user if they still exist
  selectedUser = linkedSignal<User[], User>({
    source: this.userSignals,
    computation: (newUsers, previous) => {
      return (
        newUsers.find(u => u.name === previous?.value.name) ?? newUsers[0]
      );
    }
  });

  // Change selected user by index
  selectUser(index: number) {
    this.selectedUser.set(this.userSignals()[index]);
  }
  // Update the user list
  updateUsers() {
    this.userSignals.set([
      { name: 'Mark Smith', age: 20, isAdmin: true },
      { name: 'Alice', age: 25, isAdmin: false },
      { name: 'Bob', age: 40, isAdmin: false },
    ]);
  }}

What Does an MCP Server Actually Do?

3. Improved effect():

In Angular 19, the effect() function has been significantly improved based on community input. A key change is the removal of the allowSignalWrites flag. This flag previously restricted setting Signal values inside effect(), encouraging the use of computed() instead. However, this was found to be more restrictive than helpful, hindering the effective use of effect() in appropriate situations. Therefore, Angular 19 now allows setting Signals within effect() by default, simplifying its use and emphasizing better coding practices through features like linkedSignal and the Resource API. Furthermore, the timing of effect() execution has been fundamentally changed. Instead of being queued as microtasks, effects will now run as part of the component’s change detection cycle, following the component tree hierarchy. This adjustment aims to resolve issues where effects run at unexpected times, ensuring a more predictable and logical execution order aligned with the component structure. These enhancements are intended to make the effect() function more powerful and user-friendly, better meeting the needs of developers. While effect() will remain in developer preview in version 19, this allows for continued refinement based on developer feedback regarding these new capabilities.

4. Tracking vs. Untracked Dependencies

Default Behavior (Track)

By default, effect() tracks all signals it reads, so it runs when any of them change. If you only want the effect to react to a specific signal (like currentUser), and treat other signal reads (like counter) as incidental, use untracked().

Key Points:
  • Without untracked, changes to any signal inside the effect will trigger it.

  • Example: If your effect reads both currentUser and counter, it will run whenever either signal updates.

Code Example:

Angular 19 Signals

Screenshot:

Angular 19 Signals

Controlling Dependencies with untracked()

Use untracked() to make an effect depend only on specific signals.

Key Points:
  • untracked(counter) ensures the effect only depends on currentUser.

  • Ideal for ignoring incidental signals.

Code Example:

Angular 19 Signals

Screenshot:

Angular 19 Signals

Untracking External Functions:

untracked() also works for external functions that read signals.

Key Points:
  • Prevents accidental tracking of signals read inside helper functions.

  • Wrap the function call with untracked().

Code Example:

Angular 19 Signals

Screenshot:

Angular 19 Signals

Demo: Full Component Code

app-effect-component.component.html file
import { CommonModule } from '@angular/common';
import { Component, signal, effect, untracked } from '@angular/core';
@Component({
  selector: 'app-effect-component',
  imports: [CommonModule],
  styleUrl: './effect-component.component.css',
  templateUrl: './effect-component.component.html',

})
export class EffectComponentComponent {
  currentUser = signal('Guest');
  counter = signal(0);
  logEntries = signal<string[]>([]);
  regularEffect = effect(() => {
    const entry = `[Regular Effect] User: ${this.currentUser()}, Counter: ${this.counter()}`;
    this.log(entry);
  });
  constructor() {
    this.log('Constructor: Initial state');
  }

  incrementCounter(): void {
    const newValue = this.counter() + 1;
    this.log(`Button click: Setting counter to ${newValue}`);
    this.counter.set(newValue);
  }

  changeUser(): void {
    const newUser = this.currentUser() === 'Guest' ? 'Alice' :
                    this.currentUser() === 'Alice' ? 'Bob' : 'Guest';
    this.log(`Button click: Setting user to ${newUser}`);
    this.currentUser.set(newUser);
  }
  resetDemo(): void {
    this.log('Button click: Resetting demo');
    this.counter.set(0);
    this.currentUser.set('Guest');
    this.logEntries.set(['Demo reset']);
  }
  private log(message: string): void {
    const timestamp = new Date().toLocaleTimeString();
    this.logEntries.update(entries => [...entries, `${timestamp} - ${message}`]);
  }
}


5. Angular Signals vs. RxJS: Choosing Your Reactive Weapon

In the ever-evolving landscape of Angular development, managing asynchronous and reactive data is paramount. Angular 19 offers two primary tools for this: Signals and RxJS. While both aim to handle data that changes over time, they operate on fundamentally different principles and excel in distinct scenarios. Furthermore, Angular 19 introduces the Resource API, a new approach to managing asynchronous resources with Signals. Understanding these nuances is crucial for building efficient and maintainable Angular applications.

RxJS: The Powerhouse of Asynchronous Streams

RxJS (Reactive Extensions for JavaScript) is a robust library built around the concept of Observables. Think of an Observable as a stream of data that can emit multiple values over time, asynchronously or synchronously. Its strength lies in handling complex asynchronous operations, event streams, and time-based data. RxJS boasts a vast arsenal of operators (map, filter, switchMap, etc.) that allow developers to declaratively manipulate and compose these data streams. Observables are lazy as they only start emitting values when you explicitly subscribe to them. This subscription also necessitates careful unsubscription to prevent memory leaks. The Resource API is a significant addition in Angular 19 that directly relates to handling asynchronous operations in a Signal-centric way.

Example (RxJS - Fetching and Displaying Data):

import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-rxjs',
  imports: [CommonModule],
  template: `
  <div *ngIf="user">User Name: {{ user.name }}</div>
    <div *ngIf="error">Error loading user.</div>
  `,
  styleUrl: './rxjs.component.css'
})

export class RxjsComponent {
  user: User | null = null;
  error = false;
  private destroy$ = new Subject<void>();

  constructor(private http: HttpClient) { }

  ngOnInit(): void {
    this.http.get<User>('https://jsonplaceholder.typicode.com/users/1').pipe(
      takeUntil(this.destroy$)
    ).subscribe({
      next: (data) => this.user = data,
      error: () => this.error = true,
    });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Angular Signals: Fine-Grained Reactivity

Angular Signals, a built-in feature, offer a different approach focusing on fine-grained reactivity. A Signal holds a single value that changes over time, and crucially, it automatically notifies its dependencies (parts of your template or code that read the Signal’s value) when that value updates. Signals are inherently synchronous, ensuring immediate updates to dependent computations and effects. They aim to simplify state management, particularly for local component state, reducing the boilerplate often associated with RxJS’s BehaviorSubject. Angular automatically tracks Signal dependencies, leading to more precise change detection. You access a Signal’s current value by calling it as a function (e.g., count()). Side effects are managed using effect().

Introducing the Resource API (Asynchronous Operations with Signals)

Angular 19 introduces the Resource API (currently experimental but a significant development) as a more integrated way to handle asynchronous operations directly with Signals. It aims to simplify common async patterns like fetching data, managing loading states, and handling errors, often reducing the need for direct RxJS usage in simpler scenarios. The Resource API allows you to define a “resource” that represents an asynchronous operation. It provides Signals that automatically track the loading state, the resolved value, and any potential errors. This offers a more declarative and Signal-native way to manage asynchronous data in your templates. In this example, the resource function takes a source Signal (this.userId) and a factory function that returns an Observable for fetching the user data based on the userId. The user constant then becomes an object with .value() (a Signal holding the fetched data), .loading() (a Signal indicating loading state), and .error() (a Signal indicating an error).

Example:

import { Component, inject, signal, resource } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
@Component({
  selector: 'app-resource',
  imports: [CommonModule],
  template: `
    <p>hasValue: {{productDetails.hasValue()}}</p>
<div class = "col-3">
  @if (productDetails.error()) {
    <div>{{productDetails.error()}}</div>
  }
  @if (productDetails.hasValue()) {
    <div>
      {{productDetails.value().name}}
    </div>
  }
  <div>
    Loading: {{productDetails.isLoading()}}
  </div>
  <div>
    Error: {{productDetails.error()}}
  </div>
</div>
<div>
  <button (click) = "reloadData()">
    reload Data
  </button>
</div>
  `,
  styleUrl: './resource.component.css'
})
export class ResourceComponent {

  private http = inject(HttpClient);
  userId = signal(1);

  productDetails = resource<any, unknown>({
    request: this.userId,
    loader: async (params) => {
      const userId = params.request;
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      return await response.json();
    }

  });
  reloadData() {
    this.userId.set(this.userId() + 1);
    this.productDetails.reload();
  }
}

Let’s start with the declaration of the resource. The optional request parameter accepts the input signal to which the asynchronous resource is linked. In the above example it is our userId but it could be a computed signal as well consisting of multiple values. Then there is a loader function which should return a promise, with which we asynchronously download data. The created resource named productDetails allows us to access the current value signal (which also returns undefined when the resource is not available at the moment), access the status signal (one of: idle, error, loading, reloading, resolved, local), access extra signals like ‘isLoading’ or ‘error’,

trigger ‘loader’ function again (using reload method),

Feature

Angular Signals

Rxjs Observables

Resource API (Signals)

Core Concept

Single reactive value

Asynchronous data stream

Signal-based async resource management

Asynchronicity

Primarily synchronous

Designed for asynchronous operations

Built for asynchronous operations

Operators

Limited built-in (e.g., computed)

Extensive set of powerful operators

Implicitly manages loading/error

Subscription

Implicit dependency tracking

Explicit subscription and unsubscription

Implicitly managed

Complexity

Generally simpler for basic cases

Can be complex, steeper learning curve

Aims for simplicity in common cases



When to use Signal, Rxjs or Resource Api:

Signals when:
  • Managing local component state requires simple reactivity.

  • Creating derived, reactive values using computed.

  • Achieving optimal UI performance through fine-grained updates.

  • Handling component inputs and outputs with the new Signal-based API.

Lean on RxJS for: (No change)
  • Handling asynchronous operations like HTTP requests, especially when complex transformations or error handling are needed.

  • Managing complex streams of events (user input, WebSockets).

  • Implementing intricate data transformations and combinations.

  • Interacting with existing Angular APIs that return Observables and require extensive operator usage.

Consider the Resource API when:
  • Fetching data and managing loading/error states in a more Signal-native way.

  • Dealing with simpler asynchronous data dependencies.

  • Aiming for a more declarative approach to common async tasks within components

The Synergy of Signals and RxJS:

Angular 19 provides excellent interoperability between Signals and RxJS through the @angular/core/rxjs-interop package (toSignal, toObservable). This allows you to leverage the strengths of both: using RxJS for complex async logic in services and then converting the results to Signals for efficient rendering in components.

toSignal: Converts an Observable to a Signal, allowing you to leverage Signals’ reactivity with asynchronous data sources.

  • Example: Fetching user data via HTTP and displaying it in a component using a Signal.

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-app-to-signal',
  template: `
    <div *ngIf="userName()">User Name: {{ userName() }}</div>
    <div *ngIf="!userName()">Loading...</div>
  `,
  imports: [CommonModule],
  styleUrl: './app-to-signal.component.css'
})

export class AppToSignalComponent {
  private http = inject(HttpClient);
  private user$ = this.http
    .get<{ name: string }>('https://jsonplaceholder.typicode.com/users/1')
    .pipe(map((user) => user.name));

  userName = toSignal(this.user$);
}

toObservable: Converts a Signal to an Observable, enabling integration with RxJS operators for complex transformations.

  • Example: Converts a counter Signal to an Observable, applies the map operator to double the value, and displays the result.

import { Component, signal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-to-observable-example',
  template: `
    <div>Signal Value: {{ counter() }}</div>
    <div>Observable Result: {{ observableResult | async }}</div>
    <button (click)="increment()">Increment</button>
  `,
  imports: [CommonModule]
})
export class ToObservableExampleComponent {
  counter = signal(0);
  counterObservable: Observable<number> = toObservable(this.counter); // Signal to Observable
  observableResult = this.counterObservable.pipe(map((value) => value * 2));

  increment() {
    this.counter.update((value) => value + 1);
  }
}

Conclusion:

Angular Signals have significantly improved the developer experience by offering a more intuitive and performant way to manage reactive state. The introduction of linked signals, and the resource API, further enhances the signals api. While RxJS remains essential for complex asynchronous operations, Angular Signals have become the preferred choice for many component-level reactive scenarios. The interlop features allow for a very smooth transition, and usage of both systems. Angular is evolving to make reactive programming more efficient and developer-friendly.

Ready to build intelligent systems? Hire expert Angular developers from The Right Software.