Introduction
With the introduction of signals in Angular, many of us are gradually adapting our patterns and habits. For years, RxJS was the default way to handle reactivity in Angular applications. While RxJS is powerful, it often comes with a steep learning curve and verbose code, especially for simple cases.
In this article, I’ll walk you through a common scenario: fetching a list. We’ll start with an imperative approach, then refactor it using RxJS for a more declarative and reactive style, and finally simplify it even further using Angular signals.
The Scenario: Fetching A Simple List
Imagine you have a list of items fetched from an API. You need to:
- Fetch the list
- Show it in the UI
No pagination, no complex logic — just the basics.
1️⃣ The Imperative Approach
This is often how we start: calling HTTP methods and updating local component state manually.
Service
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ListService {
#http = inject(HttpClient);
fetchList() {
return this.#http.get<Item[]>('/api/list');
}
}
Component
import { Component, OnInit, inject } from '@angular/core';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
})
export class ListComponent implements OnInit {
#listService = inject(ListService);
public list: Item[] = [];
ngOnInit() {
this.#listService.fetchList().subscribe((data) => {
this.list = data;
});
}
}
Template
@for (item of list; track $index) {
<div>{{ item.prop }}</div>
}
✅ Observations
- Control of
ChangeDetectorRefis necessary ifOnPushis used.
2️⃣ Reactive and Declarative with RxJS BehaviorSubject
Now, let’s move logic into the service. We’ll expose an observable so the component doesn’t handle subscriptions directly:
Service
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class ListService {
#http = inject(HttpClient);
#list = new BehaviorSubject<Item[]>([]);
// Expose observable stream
public list$ = this.#list.asObservable();
fetchList() {
this.#http.get<Item[]>('/api/list').subscribe((data) => {
this.#list.next(data);
});
}
}
Component
import { Component, OnInit, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
imports: [AsyncPipe],
})
export class ListComponent implements OnInit {
#listService = inject(ListService);
public list$ = this.#listService.list$;
ngOnInit() {
this.#listService.fetchList();
}
}
Template
@let data = list$ | async;
@for (item of data; track $index) {
<div>{{ item.prop }}</div>
}
✅ Observations
- Reactive and declarative: the component doesn’t manage state manually.
- We rely on
asyncpipe to skip manual change detection.
3️⃣ Simplified and Cleaner with Angular Signals
Now, let’s refactor using Angular signals:
Service
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ListService {
#http = inject(HttpClient);
#list = signal<Item[]>([]);
public list = this.#list.asReadonly();
fetchList() {
this.#http.get<Item[]>('/api/list').subscribe((data) => {
this.#list.set(data);
});
}
}
Component
import { Component, OnInit, inject } from '@angular/core';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
})
export class ListComponent implements OnInit {
#listService = inject(ListService);
public list = this.#listService.list;
ngOnInit() {
this.#listService.fetchList();
}
}
Template
@for (item of list(); track $index) {
<div>{{ item.prop }}</div>
}
✅ Observations
- No
BehaviorSubjectorasyncpipe. - Signals automatically notify Angular when values change.
- You don’t even need Zone.js.
❓ Why Bother?
For newcomers, RxJS can feel overwhelming. Signals give you a simpler entry point to write reactive code without fully committing to RxJS complexity.
That doesn’t mean signals replace RxJS in all scenarios — streams, complex operators, or multi-value flows are still RxJS territory. But for state management and simple list updates, signals just feel more natural and Angular-native.
Any Final Thoughts?
We still use RxJS for more complex cases, but here I wanted to show you the simplest setup just to get started. If you’d like to see a full CRUD example using a reactive approach, feel free to reach out or leave a comment.
Top comments (0)