Signals is one of the biggest innovations in Angular, bringing a fundamental change to the way state management and reactivity are handled in applications. Introduced in Angular 16, and becoming the preferred method of communication between components and the view starting from version 19, Signals offer a simpler and more readable approach to data management compared to the widely popular RxJS. While not as extensive as RxJS, Signals are undoubtedly a reasonable alternative for simpler components, particularly when compared to traditional variables relying on the change detection mechanism.
Let’s begin by revisiting the concept of reactive programming. Angular Signals are deeply rooted in this programming paradigm.
What is Reactive Programming and Why Is It Better?
Reactive programming is a software development approach that focuses on responding to data changes in real time. It is based on an asynchronous data flow model, where an application automatically reacts to changes in data or events. In simple terms: we subscribe to changes in a data stream and respond to each change by executing a specific operation (method). This subscription is written only once, and any change in the variable’s value at any point will trigger the execution of the operation (e.g., displaying an alert with the variable’s value).
In contrast, imperative programming involves writing code that describes, step by step, how to achieve a specific goal. Here, the developer manually controls the flow of data by invoking appropriate functions or procedures at specific moments. In short: after each change to a variable’s value, the programmer must also remember to call the functions that should execute (e.g., displaying an alert with the variable’s value).
Advantages of Reactive Programming
- Code Readability: Reactive code better describes the application logic, especially in complex scenarios.
- Flexibility: Data streams can be easily modified or extended.
- Asynchronous Handling: Tools like RxJS and Angular Signals enable efficient handling of asynchronous data without nested code (commonly known as callback hell).
- Improved Performance: Updates are limited to the elements that truly need to be changed.
What Are Signals?
Signals are a new Angular feature that provide a reactive way to manage application state. Signals are:
- Reactive Variables: These automatically refresh data whenever their value changes.
- Closely Linked to Unidirectional Data Flow: They align with the concept of top-down data flow.
- Easier to Debug and More Efficient: They eliminate the need for manual subscription/unsubscription, which was required in RxJS.
Key Advantages of Signals Over RxJS
| Feature | Signals | RxJS |
|---|---|---|
| Simplicity | Declarative, intuitive API | Requires multiple operators and subscriptions |
| Subscriptions | No need for manual subscription management | Subscriptions require manual management (e.g., unsubscribe) |
| Debugging | Better support in developer tools | Harder to track (e.g., in DevTools) |
| Performance | Lower CPU overhead | RxJS subscriptions can be more resource-intensive |
| API Complexity | Intuitive methods like get() and set() | Requires knowledge of RxJS operators |
| Asynchronous Support | YES! With the introduction of resources() | YES |
One of the biggest advantages RxJS held over Signals was its ability to handle asynchronous tasks. However, with the introduction of resources(), this gap has narrowed significantly. It is likely that the Angular team aims to eventually replace RxJS with Signals entirely. Signals represent the future; while their use is currently optional, at some point, you’ll need to adopt them.
For now, RxJS remains indispensable in certain scenarios due to the flexibility provided by its extensive list of operators. Will Signals evolve to the point of eliminating the need for RxJS in projects? One of Angular’s foundational ideas is the Out of the Box approach, where everything works seamlessly right after installation.

How to Use Signals?
Angular Signals introduce four key elements:
signal– A reactive variable.computed– A calculated variable based on other signals.effect– A reaction triggered by changes in signals.resources– Handling asynchronous operations.
Example of using signal()
Traditional Approach Using Change Detection:
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Counter: {{ counter }}</p>
<button (click)="increment()">Increment</button>
</div>
`,
})
export class CounterComponent {
counter = 0;
increment() {
this.counter++;
}
}New Approach Using Angular Signals:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Counter: {{ counter() }}</p>
<button (click)="increment()">Increment</button>
</div>
`,
})
export class CounterComponent {
counter = signal(0);
increment() {
this.counter.update(value => value + 1);
}
}Example of using computed()
The computed() function allows you to create a reactive variable that automatically recalculates its value when any of its dependencies change.
@Component({
selector: 'app-product',
template: `
<div>
<p>Base Price: {{ price() }}$</p>
<p>Price with Tax: {{ priceWithTax() }}$</p>
<button (click)="increasePrice()">Increase Price</button>
</div>
`,
})
export class ProductComponent {
price = signal(100);
taxRate = signal(0.2);
// Cena z podatkiem
priceWithTax = computed(() => this.price() * (1 + this.taxRate()));
increasePrice() {
this.price.update(value => value + 10);
}
}Example of using effect()
Every modification to the message (or any other signal in this component) will initiate the effect. While creating multiple effects is feasible, it’s generally discouraged due to performance implications, as each effect will be triggered whenever any signal undergoes a change within the component. There’s no mechanism to create an effect that exclusively reacts to alterations in a singular signal.
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-logger',
template: `
<div>
<p>{{ message() }}</p>
<button (click)="changeMessage()">Change Message</button>
</div>
`,
})
export class LoggerComponent {
message = signal('Hello, world!');
constructor() {
// Nasłuchujemy zmiany message
effect(() => {
console.log(`Message zmieniło się na: ${this.message()}`);
});
}
changeMessage() {
this.message.set('New message!');
}
}Example of State Management in services using Angular Signals
This code is way easier to read than the RxJS version.
import { Injectable, signal, computed } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class CartService {
private items = signal<string[]>([]);
// Publiczny dostęp do stanu
itemsCount = computed(() => this.items().length);
addItem(item: string) {
this.items.update(currentItems => [...currentItems, item]);
}
removeItem(item: string) {
this.items.update(currentItems => currentItems.filter(i => i !== item));
}
getItems() {
return this.items();
}
}Example of using resources() for pagination
import { Component, signal, resources } from '@angular/core';
@Component({
selector: 'app-post-list',
template: `
<div>
<div *ngFor="let post of postsResource.data || []">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</div>
<button (click)="prevPage()" [disabled]="page() === 1">Previous</button>
<button (click)="nextPage()">Next</button>
</div>
`,
})
export class PostListComponent {
page = signal(1);
pageSize = signal(20);
postsResource = resources(() => {
return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${this.page()}&_limit=${this.pageSize}`)
.then(res => res.json());
});
nextPage() {
this.page.update(p => p + 1);
}
prevPage() {
this.page.update(p => (p > 1 ? p - 1 : 1));
}
changePageSize(size: number) {
this.pageSize.set(size);
}
}Isn’t this a lot cleaner than using RxJS? Check out how we do it with RxJS operators:
@Component({
selector: 'app-post-list',
template: `
<div>
<div *ngFor="let post of posts$ | async">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</div>
<button (click)="prevPage()" [disabled]="page() === 1">Previous</button>
<button (click)="nextPage()">Next</button>
</div>
`,
})
export class PostListComponent {
private _page$ = new BehaviorSubject<number>(1);
protected page$ = this._page$.asObservable();
private _pageSize$ = new BehaviorSubject<number>(20);
protected pageSize$ = this._pageSize$.asObservable();
posts$ = this.pageSize$.pipe(
combineLatestWith(this.page$),
switchMap(([ pageSize, page]) => fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${pageSize}`),
takeUntilDestroyed()
);
nextPage() {
const currentPage = this._page.value;
this._page.next(++currentPage);
}
prevPage() {
const currentPage = this._page.value;
this._page.next(--currentPage);
}
changePageSize(size: number) {
this._pageSize.next(size);
}
}The performance benefits of Signals
Compared to the old-school Change Detection way
Angular uses zone.js to check the whole component tree for changes. This means that even a tiny change can trigger a full check, slowing things down. Signals are different. They’re more focused. When a Signal changes, only the parts that depend on it get updated. This is much faster and more efficient.
Compared to using RxJS
Signals solve a lot of the headaches that come with RxJS, like:
- Less subscription management: Signals handle reactivity for you, so you don’t need to subscribe.
- No more unsubscribing: Signals don’t create RxJS subscriptions, meaning you don’t have to worry about cleaning them up.
- Faster templates: Signals are optimized for Angular templates, making them more performant.

Will Signals replace RxJS?
No, RxJS has always been an optional tool in the Angular world. The introduction of Signals gives you another option, but it’s completely up to you whether you want to use them. Angular even provides tools to combine Signals and RxJS, so you can choose the best approach for your project. Check out the official docs for more details.
Summary
Angular’s biggest competitor, React, has always boasted a quick start and high performance. After a few short tutorials, even a junior developer could build a fast application. To achieve similar performance in Angular, developers had to delve into reactive programming with RxJS, which was a higher barrier to entry. Angular’s introduction of Signals aims to lower this barrier, making it easier for junior developers to build efficient applications. For experienced Angular developers, Signals offer a more intuitive way to manage state and can lead to better performance in certain scenarios. Additionally, the Angular team is actively working to replace RxJS operators with built-in Signals functionality, encouraging adoption. We can expect to see even more performance improvements and new features in the future.






