A Detailed Guide to RxJS in Angular

Tuesday, July 22, 2025

Reactive Extensions for JavaScript (RxJS) and observables enable developers to build more responsive, scalable, and easily maintainable Angular applications. This capability is valuable for Angular development companies when building modern web apps that require efficient handling of asynchronous operations and dynamic data flows. 

In this article, we will explore different concepts of reactive programming in Angular and how to use them under various scenarios.

1. RxJS Overview

RxJS is a library providing functional and reactive programming patterns that allow you to work with streams of data and events. Instead of Promises and callbacks, RxJS uses composable observables, which simplifies writing and composing asynchronous code. 

RxJS is an acronym for Reactive Extensions for JavaScript. You can easily integrate this library with other web development libraries and frameworks, such as Angular, and also implement it in other programming languages like PHP, Python, etc. Moreover, it supports different platforms, including Android. 

Until the Observable types become a part of the language and the supported browsers, developers use RxJS for handling asynchronous programming. This library provides utility functions that enable developers to create and use observables. The utility functions also allow them to: 

  • Composing Multiple Streams 
  • Mapping Values 
  • Converting Existing Code 
  • Filter Streams 
  • Iterate Through the Values

2. What are Observables in Angular?

Observables are an inherent component of Angular that enable the management of a series of events or values over time. They provide an interface to manage asynchronous data such as HTTP request responses, user input, and other operations. 

Some important instances where observables are used: 

  1. User Input Handling: Observables are leveraged to listen and respond to the user-input events by the Router and Forms modules. 
  2. AJAX Requests: The HTTP module in Angular employs observables to manage AJAX requests and their responses, allowing for efficient data retrieval and manipulation.

The observable lifecycle can be divided into three stages: 

  • Next: A value is released.
  • Error: An error is stopped and emitted.
  • Complete: Shows that the observable is complete.

3. What are Operators?

With Observables at the foundation, Operators are fundamental components for RxJS that allow you to compose complex asynchronous code in a clear and declarative manner. So, if you are creating a reactive Angular application, operators are just the features you need. 

You can use them to transform, combine, map, filter, and manipulate observables to create complex data flows. RxJS offers a wide range of operators. Let’s discuss a few in brief:  

  1. Map: This operator applies a function to every value emitted by the observable to transform it. 
  2. Filter: The values emitted by the observable can be easily filtered depending on their condition with the help of this operator. 
  3. Merge: Leveraging this operator, it is possible to combine multiple observables into a single observable that emits a value from every observable. 
  4. Retry: Using this operator, you can resubscribe to the observable every time it encounters an error. It can also help manage retry logic while making HTTP requests. 

There are numerous operators available in RxJS. These allow developers to build flexible, scalable, and efficient data flows that can easily manage complex scenarios in an Angular application.

4. Working with RxJS in Angular

Let’s consider a simple example of how to use RxJS in an Angular application:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-greetings',
  template: `

{{ greetingMessage }}

`, styleUrls: ['./greetings.component.css'], standalone: true, }) export class GreetingsComponent implements OnInit { greetingMessage = 'Waiting for greetings...'; ngOnInit(): void { const greetings = ['Hello!', 'Namaste!', 'Bonjour!']; const greetingObservable$ = new Observable((observer) => { greetings.forEach((greeting, index) => { setTimeout(() => { console.log('called'); observer.next(greeting); if (index === greetings.length - 1) { observer.complete(); } }, (index + 1) * 1000); }); }); greetingObservable$.subscribe({ next: (greeting) => { this.greetingMessage = greeting; console.log(greeting); }, complete: () => (this.greetingMessage = 'All greetings done!'), }); } }

The above code is a basic implementation to understand the use of RxJS observables in Angular to handle a sequence of asynchronous events. It creates the greetingObservable$ that emits a series of greeting messages at specific timed intervals. It’s a convention in Angular and RxJS to use the $ symbol at the end of the variable name. The $ symbol basically denotes that the Angular variable holds an observable constructor. 

The observable constructor uses the observer.next() method to emit the greeting message. As a result, the component’s greetingMessage property is updated. The setTimeout method is used to emit greetings after a one-second delay. Since Angular automatically detects changes to component properties, the view updates to display the latest greeting.

The RxJS observable calls the observer.complete() method after all greetings are sent nd the final message (“All greetings done!”) is displayed.

4.1 Using RxJS Operators

RxJS operators enable you to manipulate, combine, and transform observables. For example, you can use operators to throttle the user input or retry a failed HTTP request. In the code below, we transform the data from an observable using the map operator.

import { map } from ‘rxjs/operators';
const names$ = of('alice', 'bob', 'charlie').pipe(
      map(name => name.toUpperCase())
    );

In the above code, the map operator converts the data emitted by the observable before it reaches the subscriber. Here, the data emitted by the observable are ‘alice’, ‘bob’, and ‘charlie’. You can observe that the names are in lowercase, which will be converted into uppercase, i.e., to ‘ALICE’, ‘BOB’, and ‘CHARLIE’ respectively by the map operator.

4.2 Error Handling with RxJS

In asynchronous programming, error handling is the most difficult task, which can be simplified using operators like retry and catchError from RxJS.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { retry, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

interface UserProfile {
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-profile',
  template: `
    

Welcome, {{ profile.name }}

@if(errorMessage){

{{ errorMessage }}

} `, styles: [`.error { color: red; }`], standalone: true, }) export class UserProfileComponent implements OnInit { profile: UserProfile = { name: 'Loading…', email: '' }; errorMessage = ''; constructor(private http: HttpClient) {} ngOnInit(): void { this.http .get('/api/user/profile') .pipe( retry(1), catchError((err) => { console.error('Request failed:', err); this.errorMessage = 'Could not load your profile. Showing guest view.'; // Return a harmless fallback value so stream can complete or else you can also throw an error return of({ name: 'Guest', email: '' } as UserProfile); }) ) .subscribe({ next: (data) => (this.profile = data), complete: () => console.log('Profile stream completed'), }); } }

The retry(1) operator used in the code above indicates one retry if the HTTP request fails. If it fails on the first retry, instead of propagating the error, the catchError operator will catch it, log it to the console, and display a user-friendly errorMessage. After this, it returns a fallback UserProfile of { name: ‘Guest’, email: ” }, ensuring the completion of the stream so that the UI always has something meaningful to display.

5. Using Observables in Angular Services

In Angular development services, observables are commonly used to manipulate and retrieve data from databases, APIs, and other sources. The code below shows a simple Angular service that uses an observable to retrieve data from a mock API from jsonplaceholder:

// user-todo.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

@Injectable({ providedIn: 'root' })
export class UserTodoService {
  private API_URL = 'https://jsonplaceholder.typicode.com/todos';

  constructor(private http: HttpClient) {}

  getPendingTodoTitles(): Observable {
    return this.http.get(this.API_URL).pipe(
      // 1) Only those not yet completed
      map(todos => todos.filter(t => !t.completed)),
      // 2) Extract their titles
      map(pending => pending.map(item => item.title))
    );
  }
}

The fake API is located at this URL. We have to create a service called UserTodoService that uses the HttpClient module from Angular to make a GET request. The service will include a method named getPendingTodoTitles(), which returns an Observable<string[]>, representing an array of strings. 

In the getPendingTodoTitles() method, we’re fetching all todos from the mock api, then filtering only those todos that are still not completed, and then mapping only the titles of those todos.

Injecting the service into the constructor and calling the getPendingTodoTitles() method allows you to use the service within the component and helps you to get all the pending todo titles to show in the UI.

// todo-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserTodoService } from './user-todo.service';

@Component({
  selector: 'app-todo-list',
  standalone: true,
  template: `
    

Pending Tasks

    @for (let title of todoTitles; track $index) {
  • {{ title }}
  • }
@if (error) {

{{ error }}

} `, styles: [` .error { color: red; } `] }) export class TodoListComponent implements OnInit { todoTitles: string[] = []; error = ''; constructor(private todoService: UserTodoService) {} ngOnInit(): void { this.todoService.getPendingTodoTitles().subscribe({ next: titles => this.todoTitles = titles, error: err => { console.error('Could not load todos', err); this.error = 'Failed to load tasks.'; } }); } }

Here, we are creating a component called TodoListComponent that retrieves a list of todo titles from a mock API using the UserTodoService. NgOnInit lifecycle method subscribes to the getPendingTodoTitles() Observable and manages emitted values. The component’s todoTitles property is updated with the retrieved data when the Observable emits the values. 

Using services and Observables in this manner allows you to build robust and flexible data flows for Angular projects. 

5.1 Combining Observables

Observables allow you to combine and manipulate data streams and build complex data pipelines for various use cases. Let’s consider a few examples to understand how you can combine Observables in Angular: 

  1. Merge: Angular allows you to merge multiple Observables into a single stream using the merge() operator with the following code:

    import { Component, OnInit } from "@angular/core";
    import { of, merge } from "rxjs";
    import { delay } from "rxjs/operators";
    
    @Component({
      selector: "app-merge",
      standalone: true,
      template: `
        

    Merged Greetings

      @for (msg of messages; track msg){
    • {{ msg }}
    • }
    `, }) export class MergeComponent implements OnInit { messages: string[] = []; ngOnInit(): void { // Stream A: two morning greetings, delayed 500ms const morning$ = of("Good morning!", "Time for coffee!").pipe(delay(500)); // Stream B: two evening greetings, delayed 1000ms const evening$ = of("Good evening!", "Sweet dreams!").pipe(delay(1000)); // Merge both streams so they emit as soon as each is ready const allGreetings$ = merge(morning$, evening$); allGreetings$.subscribe((greeting) => { this.messages.push(greeting); console.log(greeting); }); } }

    Here you can see that the program creates two observables, one of which emits morning greetings after 500 ms, while the other one emits evening greetings after 1 second. They are then merged into a single stream using the merge() operator. Whenever any of the greetings are available, the respective observable emits it, and it gets added to the `messages` array. As a result, the output is the merged version of the morning and evening messages, merged in the order they are received.

  2. CombineLatest: You can combine the latest values emitted by multiple Observables into a single stream using the combineLatest() operator. Run the code below to understand how it works:

    import { Component, OnInit } from "@angular/core";
    import { of, combineLatest } from "rxjs";
    import { delay } from "rxjs/operators";
    
    @Component({
      selector: "app-combine-latest",
      standalone: true,
      template: `
        

    Combined Greetings

      @for (greeting of greetings; track $index) {
    • {{ greeting }}
    • }
    `, }) export class CombineLatestComponent implements OnInit { greetings: string[] = []; ngOnInit(): void { // Morning greetings emit after 500 ms const morning$ = of("Good morning!", "Time for coffee!").pipe(delay(500)); // Evening greetings emit after 1 s const evening$ = of("Good evening!", "Sweet dreams!").pipe(delay(1000)); // combineLatest waits until both streams have emitted once, // then emits the latest values as an array whenever either updates. combineLatest([morning$, evening$]).subscribe(([morning, evening]) => { this.greetings = [morning, evening]; console.log("Combined:", morning, evening); }); } }

    The above example creates two streams, a morning greeting stream (emitted 500 ms after being subscribed) and an evening greeting stream (emitted 1 s after being subscribed). The combineLatest() method, as the name suggests, will combine the recently emitted values from the morning$ and evening$ observables. This method will wait for the first emissions from both streams before combining them. After the first emissions, the next emission from either stream will trigger a new combined output. The component then renders and displays these paired greetings together, as they both represent the latest values from each source.

  3. Zip: Combining the emitted values in multiple Observables into arrays is possible using the zip() operator. For example:

    import { Component, OnInit } from "@angular/core";
    import { of, zip } from "rxjs";
    import { map } from "rxjs/operators";
    
    @Component({
      selector: "app-zip-demo",
      standalone: true,
      template: `
        

    Fruit Colors

      @for (item of pairs; track $index) {
    • {{ item }}
    • }
    `, }) export class ZipDemoComponent implements OnInit { pairs: string[] = []; ngOnInit(): void { const fruits$ = of("Apple", "Banana", "Cherry"); const colors$ = of("Red", "Yellow", "Dark Red"); zip(fruits$, colors$) .pipe(map(([fruit, color]) => `${fruit} is ${color}`)) .subscribe((pair) => this.pairs.push(pair)); } }

    In the above code, the of() operator creates two Observables, fruits$ and colors$. The fruit$ observable emits fruit names, and their respective colors are emitted by the colors$ observable. Then the zip() operator combines their outputs into pairs. Afterwards, the map() operator converts the paired tuple into a description string, e.g., “Apple is Red”. Finally, we subscribe to the zipped stream, push each formatted string into the pairs array, and render them in the template. This is another way of combining and manipulating Observables to create complex data pipelines capable of handling a large array of scenarios for Angular apps.

6. Benefits of Using RxJS in Angular Development

There are many benefits of using RxJS during Angular app development. Here we discuss a few: 

6.1 Asynchronous Handling

RxJS effectively manages asynchronous events and data for an Angular application. The Observables easily streamlines the asynchronous processes, including the user inputs and HTTP requests, by streaming data over time. It eliminates unnecessary polling, which helps improve the overall responsiveness and performance of the application. 

6.2 Simpler Code

Developers leverage RxJS operators to transform and manipulate data streams in a declarative manner. This makes it easy to write more expressive and readable code. 

6.2 Testing and Error Handling

Observables provide a streamlined error management method using an error callback from the subscribe method. This makes it easy to manage and propagate errors in asynchronous operations. Developers can write tests that are faster to execute, easier to understand, and more predictable. 

6.3 More Flexible Data Flows

RxJS offers a robust yet flexible approach to managing data flows in Angular apps. It allows developers to create complex data flows capable of handling various instances ranging from simple data transformations to real-time data streaming with the help of subjects, operators, and observables. 

6.4 Better Scalability

There is no need for additional code in Angular apps to make them more scalable if you are using RxJS to handle complex data flows. It also enables developers to easily manage huge chunks of data and scale the application to meet the growing user requirements. 

6.5 Composability

RxJS consists of a comprehensive set of operators that developers can leverage to modify and combine data streams. These operators help build complex logic, connecting simple and reusable functions.

6.6 Consistent API

Developers using Observables can benefit from its consistent API that helps adopt a unified approach for handling different types of asynchronous data. It involves user events, HTTP requests. Or other asynchronous tasks.

7. Challenges of Using RxJS in Angular

Though there are many advantages of using JavaScript-based reactive programming in Angular applications, there are certain challenges in its practical implementation. 

7.1 Steeper Learning Curve

Developers who are not familiar with reactive programming may face difficulties in learning RxJS. The biggest challenge is to understand the large number of concepts and operators. For the same reason, developers find it difficult to maintain the Angular app code.

7.2 Operations Overhead

Using RxJS and Observables even for simple scenarios may cause extra overhead. Reactive programming provides intricacy and strength, but it isn’t always required and can complicate straightforward scenarios. 

7.3 Complex Debugging

RxJS pipelines are intricate due to it’s asynchronous data flows through different operators, making it difficult to debug them. 

7.4 Potential Memory Leaks

When developers fail to unsubscribe from the Observables properly, it may lead to memory leaks. That is why developers must manage subscriptions with caution, especially in components with extended lifespans, to avoid retaining unused resources. 

7.5 Performance Issues

The overheads caused by RxJS and Observables can negatively impact the performance of Angular applications, especially those with strict performance requirements.

8. Conclusion

RxJS is a must-have tool for every Angular developer. It helps manage robust and flexible asynchronous operations. Developers can write reactive and declarative code with RxJS, making it easy to understand. 

For handling all the asynchronous data flows in the app, including user inputs and HTTP requests, Observables can come in handy. RxJS enables you to create and manipulate this robust tool. 

Here, we discussed how to work with RxJS and explored the process of creating and combining Observables in different use cases. Understanding the effective use of Angular Observables allows you to develop reactive and responsive Angular applications that can deliver an enhanced user experience. 

FAQs

What is the use of RxJS in Angular?

Developers use RxJS in Angular to manage asynchronous operations like state changes, event handling, and HTTP requests. In Angular, RxJS Observables are often used to represent and handle data streams, particularly when interacting with APIs or user interactions. 

What is the full form of RxJS?

RxJS stands for Reactive Extensions for JavaScript. It is a reactive programming library that uses Observables to simplify the process of writing and composing callback-based or asynchronous code. 

What is next () in observable?

Whenever an observable emits a new value, the Pass in a method called a “next” handler subscribes to the emitted value.

Comments


Your comment is awaiting moderation.