A Comprehensive Guide to Angular Observables

Nam Le
10 min readApr 28, 2023

--

Angular Observables

Introduction

In Angular, Observables are an essential part of reactive programming, a programming paradigm that emphasizes the propagation of change through streams of data. Observables allow developers to handle asynchronous data and events more streamlined and efficient than traditional techniques like callbacks or promises.

Observables are a powerful tool for building complex applications that require real-time data updates, event handling, and more. In this article, we’ll explore the basics of Observables in Angular, including what they are, how they work, and some of their most common use cases. By the end of this article, you’ll have a solid understanding of how Observables can help you build better Angular applications.

What are Observables?

Observables are a core feature of reactive programming in Angular. An Observable is a stream of data that can be observed over time. Observables are similar to arrays or other data structures but with a few key differences:

  • Observables can emit multiple values over time, whereas arrays are static and contain a fixed set of values.
  • Observables can handle asynchronous data sources like user input, network requests, and timers, whereas synchronous data structures like arrays cannot.
  • Observables can be combined, transformed, and composed in various ways to create more complex data streams.

Observables are a cornerstone of the Angular framework and are widely used in event handling, data binding, and asynchronous programming. In the next section, we’ll explore how to create Observables in Angular.

Observable Creation

There are several ways to create Observables in Angular, depending on your use case and the data source you are working with. Here are a few common techniques:

  • Creating Observable from scratch: Using the Observable constructor, you can create an Observable from scratch. It allows you to define a custom data stream and emit values manually using the next() method. Here's an example:
const myObservable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
});
  • Using RxJS operators: RxJS is a popular library for working with Observables in Angular and provides a wide range of operators for creating, transforming, and combining Observables. For example, you can use the of() operator to create an Observable that emits a fixed set of values like this:
import { of } from 'rxjs';

const myObservable = of(1, 2, 3);
  • Creating Observables from events: You can create an Observable from a DOM event or other event source using the fromEvent() operator. It allows you to handle user input or other events reactively and efficiently. Here's an example:
import { fromEvent } from 'rxjs';

const button = document.getElementById('myButton');
const clickObservable = fromEvent(button, 'click');

Many other techniques for creating Observables in Angular include working with promises, timers, and HTTP requests. The following section will explore how to work with Observables using operators.

Observables and Operators

Operators are a crucial feature of working with Observables in Angular. Operators are functions that allow you to transform or manipulate Observables in various ways. Here are some of the most commonly used operators in Angular:

  • map(): The map() operator transforms each value emitted by an Observable by applying a function to it. For example, you can use map() to transform a stream of numbers into a stream of their squares, like this:
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

const numbers = of(1, 2, 3);
const squared = numbers.pipe(map(x => x * x));

squared.subscribe(x => console.log(x)); // Output: 1, 4, 9
  • filter(): The filter() operator filters out values emitted by an Observable that do not meet a specified condition. For example, you can use filter() to only emit even numbers from a stream, like this:
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';

const numbers = of(1, 2, 3, 4, 5);
const evenNumbers = numbers.pipe(filter(x => x % 2 === 0));

evenNumbers.subscribe(x => console.log(x)); // Output: 2, 4
  • merge(): The merge() operator combines multiple Observables into a single stream of values. For example, you can use merge() to combine two streams of numbers into a single stream, like this:
import { of, merge } from 'rxjs';

const numbers1 = of(1, 2, 3);
const numbers2 = of(4, 5, 6);
const merged = merge(numbers1, numbers2);

merged.subscribe(x => console.log(x)); // Output: 1, 2, 3, 4, 5, 6

There are many other operators available in Angular, including tap(), switchMap(), and catchError(). These operators can help you create more powerful and efficient data streams in your Angular applications.

Subscribing to Observables

To consume data emitted by an Observable, you need to subscribe to it. Subscribing to an Observable is similar to registering an event listener and allows you to receive and handle values emitted by the Observable. Here’s an example of how to subscribe to an Observable:

import { of } from 'rxjs';

const numbers = of(1, 2, 3);
numbers.subscribe(
value => console.log(value),
error => console.error(error),
() => console.log('Completed')
);

In this example, we create an Observable using the of() operator, which emits a fixed set of values. We then subscribe to the Observable and provide three callback functions as arguments:

  • The first function handles each value emitted by the Observable, and logs it to the console.
  • The second function handles errors during the Observable stream and logs them to the console.
  • The third function is called when the Observable completes and logs a message to the console.

When you run this code, you should see the values 1, 2, and 3 logged to the console, followed by the message Completed.

It’s important to note that Observables are lazy, meaning that they won’t emit any values until you subscribe to them. It allows you to control when and how data is retrieved and processed in your Angular applications. However, it’s also important to remember to unsubscribe from Observables when you’re finished with them, to prevent memory leaks and other issues.

Using Observables in Angular Services

One of the most common use cases for Observables in Angular is in services, where they can be used to retrieve and manipulate data from APIs, databases, or other sources. Here’s an example of a simple service that uses Observables to retrieve data from a fake API:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = '<https://jsonplaceholder.typicode.com/posts>';
constructor(private http: HttpClient) {}
getPosts(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
}

In this example, we create a service called DataService that uses Angular's built-in HttpClient to make a GET request to a fake API, located at the URL https://jsonplaceholder.typicode.com/posts. We define a method called getPosts() that returns an Observable of type any[], which represents an array of JSON objects.

To use this service in a component, we simply need to inject it into the constructor and call the getPosts() method:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
selector: 'app-root',
template: `
<h1>Posts</h1>
<ul>
<li *ngFor="let post of posts">{{ post.title }}</li>
</ul>
`
})
export class AppComponent {
posts: any[];
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.getPosts().subscribe(
data => this.posts = data,
error => console.error(error),
() => console.log('Posts loaded')
);
}
}

In this example, we create a component called AppComponent which uses the DataService to retrieve a list of posts from the fake API. We define a ngOnInit() method that subscribes to the getPosts() Observable and handles the emitted values. When the Observable completes, we set the posts property of the component to the retrieved data.

By using Observables and services in this way, we can create powerful and flexible data pipelines in our Angular applications.

Combining Observables

One of the most powerful features of Observables is the ability to combine and manipulate them in various ways. It allows you to create complex data pipelines that handle various use cases. Here are some examples of how to combine Observables in Angular:

  1. Merge: The merge() operator allows you to combine multiple Observables into a single stream. Here's an example:
import { interval, merge } from 'rxjs';

const source1 = interval(1000);
const source2 = interval(2000);
const merged = merge(source1, source2);
merged.subscribe(
value => console.log(value)
);

In this example, we create two Observables using the interval() operator, which emits a value every second and every two seconds, respectively. We then merge these two Observables into a single stream using the merge() operator. Finally, we subscribe to the merged Observable and log each emitted value to the console.

  1. CombineLatest: The combineLatest() operator allows you to combine the latest values emitted by multiple Observables into a single stream. Here's an example:
import { interval, combineLatest } from 'rxjs';

const source1 = interval(1000);
const source2 = interval(2000);
const combined = combineLatest(source1, source2);
combined.subscribe(
value => console.log(value)
);

In this example, we create two Observables using the interval() operator, which emits a value every second and every two seconds, respectively. We then use the combineLatest() operator to combine the latest values emitted by both Observables into a single stream. Finally, we subscribe to the combined Observable and log each emitted value to the console.

  1. Zip: The zip() operator allows you to combine the values emitted by multiple Observables into arrays. Here's an example:
import { of, zip } from 'rxjs';

const source1 = of(1, 2, 3);
const source2 = of('a', 'b', 'c');
const zipped = zip(source1, source2);
zipped.subscribe(
value => console.log(value)
);

In this example, we create two Observables using the of() operator, which emits a fixed set of values. We then use the zip() operator to combine the values emitted by both Observables into arrays. Finally, we subscribe to the zipped Observable and log each emitted value to the console.

By combining and manipulating Observables in this way, you can create complex data pipelines that can handle a wide variety of use cases in your Angular applications.

Using Operators to Transform and Filter Data

Operators are functions that can be used to transform, filter, or otherwise manipulate the data emitted by Observables. There are many operators available in the RxJS library that can be used with Angular Observables. Here are a few examples:

  1. Map: The map() operator allows you to transform each emitted value in an Observable into a new value. Here's an example:
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

const source = of(1, 2, 3);
const mapped = source.pipe(map(value => value * 2));
mapped.subscribe(
value => console.log(value)
);

In this example, we create an Observable using the of() operator, which emits a fixed set of values. We then use the map() operator to transform each emitted value by multiplying it by 2. Finally, we subscribe to the mapped Observable and log each emitted value to the console.

  1. Filter: The filter() operator allows you to filter the values emitted by an Observable based on a given condition. Here's an example:
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';

const source = of(1, 2, 3);
const filtered = source.pipe(filter(value => value % 2 === 0));
filtered.subscribe(
value => console.log(value)
);

In this example, we create an Observable using the of() operator, which emits a fixed set of values. We then use the filter() operator to only emit values that are even. Finally, we subscribe to the filtered Observable and log each emitted value to the console.

  1. Reduce: The reduce() operator allows you to accumulate the values emitted by an Observable and return a single value. Here's an example:
import { of } from 'rxjs';
import { reduce } from 'rxjs/operators';

const source = of(1, 2, 3);
const reduced = source.pipe(reduce((acc, value) => acc + value));
reduced.subscribe(
value => console.log(value)
);

In this example, we create an Observable using the of() operator, which emits a fixed set of values. We then use the reduce() operator to accumulate the values emitted by the Observable and return their sum. Finally, we subscribe to the reduced Observable and log the final accumulated value to the console.

By using operators like map(), filter(), and reduce(), you can transform, filter, and manipulate the data emitted by Observables to fit your specific use case in your Angular applications.

Using Subjects to Emit Values

In addition to Observables, the RxJS library also provides the Subject class, which can be used to emit values to multiple subscribers. A Subject is both an Observable and an Observer, which means that it can emit values and subscribe to other Observables.

Here’s an example of how you can create a Subject in Angular:

import { Subject } from 'rxjs';

export class MyComponent {
mySubject = new Subject<string>();
emitValue(value: string) {
this.mySubject.next(value);
}
}

In this example, we create a Subject of type string and store it as a property of the MyComponent class. We then define a method called emitValue() that accepts a string value and calls the next() method of the mySubject object to emit that value to any subscribers.

You can subscribe to a Subject just like any other Observable:

myComponent.mySubject.subscribe(
value => console.log(value)
)

In this example, we subscribe to the mySubject Subject of the myComponent object and log any emitted values to the console.

One thing to note is that when you subscribe to a Subject, you will receive all values that were emitted since the Subject was created. If you only want to receive values emitted after you subscribe, you can use a BehaviorSubject instead:

import { BehaviorSubject } from 'rxjs';

export class MyComponent {
mySubject = new BehaviorSubject<string>('initial value');
emitValue(value: string) {
this.mySubject.next(value);
}
}

In this example, we create a BehaviorSubject of type string and set its initial value to 'initial value'. When you subscribe to a BehaviorSubject, you will receive the initial value first, followed by any values emitted after you subscribe.

Using Subject and BehaviorSubject can be a powerful way to emit and subscribe to values in your Angular applications. They allow you to easily share data between components and services, and can simplify complex data flow scenarios.

Conclusion

Angular Observables are a powerful tool for managing asynchronous data flow in your applications. With the help of the RxJS library, you can create and manipulate Observables to handle everything from user input to HTTP requests.

In this article, we covered the basics of Observables, including how to create and subscribe to them, and how to use operators to transform and filter emitted values. We also looked at some advanced topics, such as using Subjects to emit values to multiple subscribers.

By understanding how to use Angular Observables effectively, you can build more responsive and reactive applications that provide a better user experience. Whether you’re working with simple form data or complex data streams, Observables can help you manage it with ease.

So next time you’re working on an Angular application, consider using Observables to handle your data flow needs. With their powerful capabilities and rich feature set, you’ll be able to create applications that are more robust, flexible, and scalable.

--

--