RxJS basics for Angular
RxJS is a library for working with streams of events, and it is installed together with Angular. HttpClient, the router and forms return RxJS Observable objects, so you need to know a little of it. Honestly: RxJS is big and not easy, but a typical application needs only the five ideas from this lesson, and component state is easier to keep in signals.
Observable and subscribe
An Observable is a source that can emit one or more values over time — like a Promise that can answer more than once. An HttpClient request emits one value, the server response, and completes.
// src/app/post-list/post-list.ts
this.api.getPosts().subscribe({
next: (posts) => this.posts.set(posts),
error: (err) => console.error('Error', err),
complete: () => console.log('Done'),
});next receives a value, error receives an error, and complete is called at the end. Most often you only need next, and you pass it as a plain function, as in the previous lesson.
pipe and operators
The pipe() method passes values through operators — functions that transform them. Operators are imported from rxjs:
// src/app/post-api.ts
import { map } from 'rxjs';
getTitles() {
return this.http.get<Post[]>(this.url).pipe(
map((posts) => posts.slice(0, 5)),
);
}The method returns an Observable that emits only the first five posts. A component subscribes to it the same way as before.
| Operator | What it does |
|---|---|
map | Transforms a value |
filter | Lets only matching values through |
tap | Runs an action without changing the value (a log, for example) |
catchError | Catches an error and substitutes a fallback value |
switchMap | Switches to a new Observable, cancelling the previous one |
The async pipe
Instead of subscribing by hand, you can give the Observable to the template. The async pipe subscribes and unsubscribes by itself:
// src/app/post-list/post-list.ts
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { PostApi } from '../post-api';
@Component({
selector: 'app-post-list',
imports: [AsyncPipe],
templateUrl: './post-list.html',
})
export class PostList {
posts$ = inject(PostApi).getPosts();
}<!-- src/app/post-list/post-list.html -->
@if (posts$ | async; as posts) {
@for (post of posts; track post.id) {
<h3>{{ post.title }}</h3>
}
} @else {
<p>Loading...</p>
}A dollar sign at the end of the name is the usual marker for an Observable. Until the response arrives, async returns null, and "Loading..." is shown.
toSignal
The toSignal() function turns an Observable into a signal — so HTTP data is read in the template like any signal:
// src/app/post-list/post-list.ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { PostApi } from '../post-api';
@Component({
selector: 'app-post-list',
templateUrl: './post-list.html',
})
export class PostList {
posts = toSignal(inject(PostApi).getPosts(), { initialValue: [] });
}The template uses posts(), as in the data loading lesson. Subscribing and unsubscribing happen automatically.
Common mistake
Subscribing by hand to a long-lived stream, such as route parameter changes, and never unsubscribing. The subscription outlives the component and keeps running. Use async, toSignal or the takeUntilDestroyed() operator from @angular/core/rxjs-interop — they unsubscribe by themselves.