Master RxJS observables and reactive programming in Angular. Learn operators, subscriptions, and async patterns for building reactive applications.
Master RxJS observables and reactive programming in Angular. Learn operators, subscriptions, and async patterns for building reactive applications.
Observables are lazy collections of multiple values over time. Think of them as streams of data that you
can observe and react to:
They don't execute until someone subscribes
They can emit values over time
Cancelable:
Subscriptions can be unsubscribed
Composable:
They can be combined and transformed using operators
Multi-value:
They can emit zero, one, or many values
Single value, not cancelable, always eager
Observables:
Multiple values, cancelable, lazy execution
RxJS provides several ways to create observables:
Learn about Using of() and from().
1import { of, from } from 'rxjs';
2
3// of() - emit values in sequence
4const numbers$ = of(1, 2, 3, 4, 5);
5numbers$.subscribe(value => console.log(value));
6// Output: 1, 2, 3, 4, 5
7
8// from() - convert array, promise, or iterable to observable
9const array$ = from([10, 20, 30]);
10array$.subscribe(value => console.log(value));
11// Output: 10, 20, 30
12
13const promise$ = from(fetch('/api/data'));
14promise$.subscribe(response => console.log(response));Learn about Using interval() and timer().
1import { interval, timer } from 'rxjs';
2
3// interval() - emit values at specified interval
4const counter$ = interval(1000); // Emit every second
5counter$.subscribe(value => console.log(value));
6// Output: 0, 1, 2, 3, 4...
7
8// timer() - emit after delay, then optionally at intervals
9const delayed$ = timer(2000); // Emit after 2 seconds
10delayed$.subscribe(() => console.log('Delayed!'));
11
12const periodic$ = timer(1000, 500); // Delay 1s, then every 500ms
13periodic$.subscribe(value => console.log(value));Learn about Creating Custom Observables.
1import { Observable } from 'rxjs';
2
3const custom$ = new Observable(subscriber => {
4 // Emit values
5 subscriber.next('First');
6 subscriber.next('Second');
7
8 // Async emission
9 setTimeout(() => {
10 subscriber.next('Delayed');
11 subscriber.complete(); // Signal completion
12 }, 1000);
13
14 // Return cleanup function
15 return () => {
16 console.log('Observable unsubscribed');
17 };
18});
19
20const subscription = custom$.subscribe({
21 next: value => console.log(value),
22 error: err => console.error(err),
23 complete: () => console.log('Complete!')
24});Operators are functions that transform, filter, or combine observables. Here are the most commonly used:
Learn about map() - Transform Values.
1import { of } from 'rxjs';
2import { map } from 'rxjs/operators';
3
4const numbers$ = of(1, 2, 3, 4, 5);
5const squared$ = numbers$.pipe(
6 map(x => x * x)
7);
8
9squared$.subscribe(value => console.log(value));
10// Output: 1, 4, 9, 16, 25Learn about filter() - Filter Values.
1import { of } from 'rxjs';
2import { filter } from 'rxjs/operators';
3
4const numbers$ = of(1, 2, 3, 4, 5, 6);
5const even$ = numbers$.pipe(
6 filter(x => x % 2 === 0)
7);
8
9even$.subscribe(value => console.log(value));
10// Output: 2, 4, 6Learn about tap() - Side Effects.
1import { of } from 'rxjs';
2import { tap, map } from 'rxjs/operators';
3
4const numbers$ = of(1, 2, 3);
5numbers$.pipe(
6 tap(x => console.log('Before:', x)),
7 map(x => x * 2),
8 tap(x => console.log('After:', x))
9).subscribe();
10// Output:
11// Before: 1
12// After: 2
13// Before: 2
14// After: 4...Learn about switchMap() - Flatten and Switch.
1import { fromEvent } from 'rxjs';
2import { switchMap, debounceTime } from 'rxjs/operators';
3
4// Search as user types
5const searchBox = document.getElementById('search');
6const search$ = fromEvent(searchBox, 'input').pipe(
7 debounceTime(300), // Wait 300ms after typing stops
8 switchMap(event => {
9 const query = event.target.value;
10 return fetch(\`/api/search?q=\${query}\`).then(r => r.json());
11 })
12);
13
14search$.subscribe(results => console.log(results));Learn about catchError() - Error Handling.
1import { of, throwError } from 'rxjs';
2import { catchError, map } from 'rxjs/operators';
3
4const api$ = fetchData().pipe(
5 map(data => data.value),
6 catchError(error => {
7 console.error('Error occurred:', error);
8 return of({ value: 'default' }); // Return fallback
9 })
10);
11
12api$.subscribe(value => console.log(value));Learn about combineLatest() - Combine Multiple Streams.
1import { combineLatest, of } from 'rxjs';
2
3const name$ = of('John');
4const age$ = of(30);
5const city$ = of('New York');
6
7combineLatest([name$, age$, city$]).subscribe(
8 ([name, age, city]) => {
9 console.log(\`\${name}, \${age}, from \${city}\`);
10 }
11);
12// Output: John, 30, from New YorkAngular integrates RxJS throughout the framework. Here are common use cases:
Learn about HTTP Client with Observables.
1import { Injectable, inject } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { Observable } from 'rxjs';
4import { map, catchError } from 'rxjs/operators';
5
6@Injectable({ providedIn: 'root' })
7export class UserService {
8 private http = inject(HttpClient);
9
10 getUsers(): Observable<User[]> {
11 return this.http.get<User[]>('/api/users').pipe(
12 map(users => users.sort((a, b) => a.name.localeCompare(b.name))),
13 catchError(error => {
14 console.error('Failed to load users:', error);
15 return of([]); // Return empty array on error
16 })
17 );
18 }
19
20 getUser(id: number): Observable<User> {
21 return this.http.get<User>(\`/api/users/\${id}\`);
22 }
23}Learn about Using Async Pipe in Templates.
1import { Component, inject } from '@angular/core';
2import { CommonModule } from '@angular/common';
3import { UserService } from './user.service';
4
5@Component({
6 selector: 'app-user-list',
7 standalone: true,
8 imports: [CommonModule],
9 template: \`
10 <div>
11 @if (users$ | async; as users) {
12 @for (user of users; track user.id) {
13 <div class="user-card">
14 {{ user.name }} - {{ user.email }}
15 </div>
16 } @empty {
17 <p>No users found</p>
18 }
19 }
20
21 <!-- Loading state -->
22 @if (loading$ | async) {
23 <div class="spinner">Loading...</div>
24 }
25 </div>
26 \`
27})
28export class UserListComponent {
29 private userService = inject(UserService);
30
31 users$ = this.userService.getUsers();
32 loading$ = of(true).pipe(delay(2000), map(() => false));
33}Subjects are special observables that can multicast to multiple observers:
Learn about Subject Types.
1import { Subject, BehaviorSubject, ReplaySubject } from 'rxjs';
2
3// Subject - no initial value, no replay
4const subject = new Subject<number>();
5subject.next(1);
6subject.subscribe(v => console.log('A:', v));
7subject.next(2); // A: 2
8subject.next(3); // A: 3
9
10// BehaviorSubject - requires initial value, replays last value
11const behavior = new BehaviorSubject<number>(0);
12behavior.next(1);
13behavior.next(2);
14behavior.subscribe(v => console.log('B:', v)); // B: 2 (gets last value)
15behavior.next(3); // B: 3
16
17// ReplaySubject - replays specified number of values
18const replay = new ReplaySubject<number>(2); // Replay last 2
19replay.next(1);
20replay.next(2);
21replay.next(3);
22replay.subscribe(v => console.log('C:', v));
23// C: 2, C: 3 (gets last 2 values)Learn about Using BehaviorSubject in Services.
1import { Injectable } from '@angular/core';
2import { BehaviorSubject, Observable } from 'rxjs';
3
4@Injectable({ providedIn: 'root' })
5export class StateService {
6 private stateSubject = new BehaviorSubject<AppState>({
7 user: null,
8 loading: false
9 });
10
11 // Expose as observable (hide subject methods)
12 state$: Observable<AppState> = this.stateSubject.asObservable();
13
14 updateUser(user: User): void {
15 const currentState = this.stateSubject.value;
16 this.stateSubject.next({
17 ...currentState,
18 user
19 });
20 }
21
22 setLoading(loading: boolean): void {
23 const currentState = this.stateSubject.value;
24 this.stateSubject.next({
25 ...currentState,
26 loading
27 });
28 }
29}Proper subscription management is crucial to prevent memory leaks:
Learn about Unsubscribing in ngOnDestroy.
1import { Component, OnInit, OnDestroy } from '@angular/core';
2import { Subscription } from 'rxjs';
3
4@Component({...})
5export class MyComponent implements OnInit, OnDestroy {
6 private subscription = new Subscription();
7
8 ngOnInit() {
9 // Add all subscriptions
10 this.subscription.add(
11 this.service.getData().subscribe(data => {
12 // Handle data
13 })
14 );
15
16 this.subscription.add(
17 this.service.getOtherData().subscribe(data => {
18 // Handle other data
19 })
20 );
21 }
22
23 ngOnDestroy() {
24 // Unsubscribe all at once
25 this.subscription.unsubscribe();
26 }
27}Learn about Using takeUntilDestroyed (Angular 16+).
1import { Component, OnInit, inject } from '@angular/core';
2import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
4@Component({...})
5export class MyComponent implements OnInit {
6 private service = inject(DataService);
7 private destroyRef = inject(DestroyRef);
8
9 ngOnInit() {
10 // Automatically unsubscribes when component is destroyed
11 this.service.getData()
12 .pipe(takeUntilDestroyed(this.destroyRef))
13 .subscribe(data => {
14 // Handle data
15 });
16 }
17}Async pipe in templates (handles unsubscribe automatically)
HTTP requests (complete automatically after response)
Finite observables that complete (of(), from(), timer() with no interval)
Use the async pipe in templates to automatically manage subscriptions
Name observables with a $ suffix (e.g., users$) for clarity
Always unsubscribe from long-lived observables in ngOnDestroy
Use operators to transform data streams rather than subscribing multiple times
Use switchMap for dependent HTTP requests to cancel previous requests
Use shareReplay() to share observable results across multiple subscribers
Handle errors with catchError to prevent stream termination
Use BehaviorSubject for state management in services
Prefer takeUntilDestroyed() over manual unsubscribe in Angular 16+
Observables are lazy, cancelable streams that can emit multiple values over time
RxJS operators like map, filter, and switchMap enable powerful data transformation and composition
The async pipe automatically subscribes and unsubscribes, preventing memory leaks
Subjects enable multicasting and are useful for event buses and state management
BehaviorSubject stores and replays the last emitted value to new subscribers
Always unsubscribe from long-lived observables to prevent memory leaks
Angular's HttpClient returns observables that automatically complete after the response
Use catchError to handle errors gracefully and prevent stream termination
Combining operators with pipe() creates powerful, declarative data processing pipelines
Your progress is saved in this browser, on this device only.