How Angular 20's Signals Will Change Your Code Forever

With the release of Angular 20, Signals has officially arrived, and it will revolutionize reactive programming in Angular. If you've struggled with RxJS, complex subscriptions, or extensive state management, Signals provide a simpler, cleaner, and more predictable way to manage reactive state in your components. What Are Signals? Signals are a new reactive primitive in Angular that allow you to declare reactive values that automatically track dependencies and trigger updates when changed — similar to reactive variables in SolidJS or fine-grained reactivity in Vue. They let you write declarative UI logic without the overhead of subscriptions, change detection hacks, or ngOnChanges() gymnastics. Before: The Familiar World of RxJS and Lifecycle Hooks Think about how you've handled simple reactive state until now. A common pattern for a component that fetches data might look something like this: You inject HttpClient. You use the ngOnInit lifecycle hook to trigger the HTTP request. You use .subscribe() to handle the result, setting a component property. If you needed that state to be reactive (e.g., re-fetching when an input changes), you might reach for a BehaviorSubject from RxJS and manage subscriptions manually. This works, but it's often a lot of imperative boilerplate for a declarative goal. You're telling Angular how to do things step-by-step, not just what the end state should be. After: The Signal Toolkit in Angular 20 Angular 20 solidifies a new, cleaner toolkit. Now that effect, and toSignal are stable, they become first-class citizens in your developer toolbox. effect(): Your Go-To for Side Effects An effect is a special function that runs whenever one of its dependent signals changes. It's the new, official way to bridge the reactive world of signals with the non-reactive world of browser APIs. What it replaces: Manual subscriptions or ngOnChanges for tasks like: Logging analytics events when a value changes. Updating localStorage. Interacting directly with a third-party DOM library. // app.component.ts import { Component, signal, effect } from '@angular/core'; @Component(...) export class AppComponent { query = signal('Angular 20'); constructor() { // This effect will automatically run whenever 'this.query' changes. effect(() => { console.log(`Current search query is: ${this.query()}`); // Perfect for logging, analytics, or other side effects. }); } updateQuery(newQuery: string) { this.query.set(newQuery); } } Use effect for side effects, but never use it to update another signal. That's what computed signals are for. toSignal(): The Perfect Bridge from RxJS Let's be clear: RxJS isn't going away. It's still incredibly powerful for handling complex asynchronous events. But not every piece of state needs a full-blown stream. toSignal() creates the perfect bridge, allowing you to convert an Observable into a signal. What it changes: Instead of managing subscriptions with .subscribe() and the async pipe, you can now seamlessly integrate RxJS-powered services into your signal-based components. // user.service.ts import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class UserService { private http = inject(HttpClient); getUsers() { return this.http.get('/api/users'); } } // user-list.component.ts import { Component, inject } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { UserService } from './user.service'; @Component(...) export class UserListComponent { private userService = inject(UserService); // Convert the Observable directly to a signal. // No .subscribe(), no async pipe needed in the template. users = toSignal(this.userService.getUsers(), { initialValue: [] }); // In your template, you can just use: // @for (user of users(); track user.id) { ... } } Basic Example Before (Using @Input + ngOnChanges) `@Input() count!: number; message = ''; ngOnChanges() { this.message = Count is ${this.count}; } ` The old approach requires lifecycle hooks, manual updates and imperative code. Angular 20 `import { signal, computed } from '@angular/core'; count = signal(0); message = computed(() => Count is ${this.count()});` Now: message automatically updates when count changes No need for lifecycle hooks No manual wiring — just pure declarative logic Conclusion Angular 20 is the official invitation to leave the old patterns behind. Stable signals are no longer a "nice-to-have" but the central, recommended way to build modern Angular applications. By embracing effect for side effects, toSignal for bridging with RxJS, and computed for derived state, you won't just be using new features—you'll be writing code that is cleaner, more resilient, and more performant by default. This is more than an update. It’s a permanent evolut

Jun 24, 2025 - 08:20
 0
How Angular 20's Signals Will Change Your Code Forever

With the release of Angular 20, Signals has officially arrived, and it will revolutionize reactive programming in Angular. If you've struggled with RxJS, complex subscriptions, or extensive state management, Signals provide a simpler, cleaner, and more predictable way to manage reactive state in your components.

What Are Signals?

Signals are a new reactive primitive in Angular that allow you to declare reactive values that automatically track dependencies and trigger updates when changed — similar to reactive variables in SolidJS or fine-grained reactivity in Vue.

They let you write declarative UI logic without the overhead of subscriptions, change detection hacks, or ngOnChanges() gymnastics.

Before: The Familiar World of RxJS and Lifecycle Hooks

Think about how you've handled simple reactive state until now. A common pattern for a component that fetches data might look something like this:
You inject HttpClient.
You use the ngOnInit lifecycle hook to trigger the HTTP request.
You use .subscribe() to handle the result, setting a component property.
If you needed that state to be reactive (e.g., re-fetching when an input changes), you might reach for a BehaviorSubject from RxJS and manage subscriptions manually.
This works, but it's often a lot of imperative boilerplate for a declarative goal. You're telling Angular how to do things step-by-step, not just what the end state should be.

After: The Signal Toolkit in Angular 20

Angular 20 solidifies a new, cleaner toolkit. Now that effect, and toSignal are stable, they become first-class citizens in your developer toolbox.

  1. effect(): Your Go-To for Side Effects An effect is a special function that runs whenever one of its dependent signals changes. It's the new, official way to bridge the reactive world of signals with the non-reactive world of browser APIs.

What it replaces: Manual subscriptions or ngOnChanges for tasks like:
Logging analytics events when a value changes.
Updating localStorage.
Interacting directly with a third-party DOM library.

// app.component.ts
import { Component, signal, effect } from '@angular/core';

@Component(...)
export class AppComponent {
  query = signal('Angular 20');

  constructor() {
    // This effect will automatically run whenever 'this.query' changes.
    effect(() => {
      console.log(`Current search query is: ${this.query()}`);
      // Perfect for logging, analytics, or other side effects.
    });
  }

  updateQuery(newQuery: string) {
    this.query.set(newQuery);
  }
}

Use effect for side effects, but never use it to update another signal. That's what computed signals are for.

  1. toSignal(): The Perfect Bridge from RxJS Let's be clear: RxJS isn't going away. It's still incredibly powerful for handling complex asynchronous events. But not every piece of state needs a full-blown stream. toSignal() creates the perfect bridge, allowing you to convert an Observable into a signal.

What it changes: Instead of managing subscriptions with .subscribe() and the async pipe, you can now seamlessly integrate RxJS-powered services into your signal-based components.

// user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);

  getUsers() {
    return this.http.get('/api/users');
  }
}

// user-list.component.ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { UserService } from './user.service';

@Component(...)
export class UserListComponent {
  private userService = inject(UserService);

  // Convert the Observable directly to a signal.
  // No .subscribe(), no async pipe needed in the template.
  users = toSignal(this.userService.getUsers(), { initialValue: [] });

  // In your template, you can just use:
  // @for (user of users(); track user.id) { ... }
}

Basic Example

Before (Using @Input + ngOnChanges)
`@Input() count!: number;
message = '';

ngOnChanges() {
this.message = Count is ${this.count};
}
`

The old approach requires lifecycle hooks, manual updates and imperative code.

Angular 20
`import { signal, computed } from '@angular/core';

count = signal(0);
message = computed(() => Count is ${this.count()});`

Now:

message automatically updates when count changes

No need for lifecycle hooks

No manual wiring — just pure declarative logic

Conclusion

Angular 20 is the official invitation to leave the old patterns behind. Stable signals are no longer a "nice-to-have" but the central, recommended way to build modern Angular applications.
By embracing effect for side effects, toSignal for bridging with RxJS, and computed for derived state, you won't just be using new features—you'll be writing code that is cleaner, more resilient, and more performant by default.
This is more than an update. It’s a permanent evolution of best practices. So, go ahead—start refactoring a component.