DEV Community

Cover image for Observables and RxJs operators in Angular.
Sameer Trimade
Sameer Trimade

Posted on

Observables and RxJs operators in Angular.

What are Observables in Angular?

An Observable can be thought of as a data source. In Angular project, an Observable is basically an Object we import from a third party package called RxJs. The Observable is implemented in such a way that it follows the observable pattern that means we have an Observable, then we have an Observer and in between we have a stream(or timeline) and on this timeline we can have multiple events emitted by the Observable. So, an Observable could emit data because you trigger it to do so, for example-

  • Suppose, an Observable is connected to a button so whenever the button is clicked an event in a data package is emitted automatically.
  • Suppose, an Observable is connected to HTTP request so when the response returns, the response is emitted as a data package.

Observer- is actually our own code meaning it is the .subscribe() function where we have 3 ways of handling data packages-

  1. handle data of the Observable
  2. handle error of the Observable
  3. handle completion of the Observable

We use Observables to handle Asynchronous tasks (because all the data sources like User events, Http request etc. are Asynchronous tasks, meaning you don’t know when these tasks will happen and how long they will take to finish. So when you execute your normal application code, you don’t want to wait for these events or you don’t want to wait for completion of such HTTP request because that would block execution of your normal application code).

In the below piece of code 👇, we are using params as an Observable and here we do subscribe to the Observable to be informed about changes in the data.
params is a stream of route parameters and this stream gives us a new route parameter whenever we go to a new page, whenever that parameter in the URL changes and then in the .subscribe function we get new route parameter.

ngOnInit() {
    this.route.params.subscribe((paramsData: Params) => {
      this.id = +paramsData.id;
    });
  }
Enter fullscreen mode Exit fullscreen mode

You don’t need to unsubscribe to the Observables that are provided by Angular (It is internally done by Angular). For example- the params Observable is provided by Angular so we don’t need to unsubscribe it.

NOTE: Observable is a feature which is not provided by JavaScript or Typescript, instead it is provided by RxJs which is a 3rd party package.

In the below piece of code 👇, we are using a function called interval() provided by the RxJs package. The interval() function returns an Observable that emits sequential numbers after every specified interval of time(in milliseconds).

import { Component, OnDestroy, OnInit } from '@angular/core';
import { interval, Subscription } from 'rxjs';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
})
export class HomeComponent implements OnInit, OnDestroy {
  private observableSubscription$: Subscription;
  constructor() {}

  ngOnInit() {
    this.observableSubscription$ = interval(1000).subscribe((count) => {
      console.log(count); // ---> Output: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 …………so on
    });
  }

  ngOnDestroy(): void {
    this.observableSubscription$.unsubscribe();
  }
} 
Enter fullscreen mode Exit fullscreen mode

interval() function returns an Observable therefore we need to subscribe to this Observable to receive the emitted value. Looking at the above implementation, the interval() function after every 1 second will print the emitted value on the browser console.

(With respect to above piece of code) ——> If the HomeComponent is destroyed, maybe via navigating away from the component or using the destroy(...)method, we will still be seeing the console log on the browser. This is because the AppComponent has been destroyed but the subscription still lives on, it hasn't been canceled.

If a subscription is not closed the function callback attached to it will be continuously called, this poses a huge memory leak and will slow down the overall app performance.

To prevent this memory leaks we have to unsubscribe from the subscriptions, we do so by calling the unsubscribe method in the Observable.

In Angular, we have to unsubscribe from the Observable when the component is being destroyed. Luckily, Angular has a ngOnDestroy hook that is called before a component is destroyed, this enables developer to call the unsubscribe method which will avoid hanging subscriptions, open portals, and all that may come in the future to bite us back.

NOTE-
Whenever we use Observables in a component in Angular, we should set up the ngOnDestroy method, and call the unsubscribe method on all of the Observables.

RxJs Operators in Observable-

Sometimes we don’t want the raw data from the .subscription() method. We might want to transform this raw data or filter some data points. We can do this inside the .subscription() method but there is another better approach. We can make use of Operators between an Observable and a .subscription() method, this means that the data points will first reach the Operators that manipulates the data and then we use .subscription() method to get the result of these operators.
We can use Operators on any Observable by calling a method called .pipe() which is provided by the RxJs package.

Here are some commonly used RxJS operators in Angular:

1) map operator-
The map operator takes an Observable as its input and returns a new Observable as its output. The new Observable emits the values from the original Observable, but transformed by the function you provide to the map operator. The function takes each value from the original Observable and applies a transformation to it, returning a new value that is emitted by the new Observable.
Here's a simple example of using the map operator to double the values of an Observable:

import { of } from 'rxjs';
import { map } from 'rxjs/operators';

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

doubledNumbers.subscribe(x => console.log(x));
// Output: 2, 4, 6
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers Observable emits the values 1, 2, and 3. The map operator takes this Observable as input and returns a new Observable, doubledNumbers, which emits the values 2, 4, and 6, which are the original values doubled.

2) filter operator-
The filter operator takes an Observable as its input and returns a new Observable as its output. The new Observable emits only the values from the original Observable that pass a test specified by the function you provide to the filter operator.

Here's a simple example of using the filter operator to only emit even numbers from an Observable:

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
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers Observable emits the values 1, 2, 3, 4, and 5. The filter operator takes this Observable as input and returns a new Observable, evenNumbers, which emits only the values that are even numbers, in this case 2 and 4.

3) tap operator-
The tap operator takes an Observable as its input and returns the same Observable as its output. The difference is that it allows you to perform a side effect, such as logging or performing a state update, for each value emitted by the Observable.

Here's a simple example of using the tap operator to log each value emitted by an Observable:

import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

const numbers = of(1, 2, 3);
const loggedNumbers = numbers.pipe(tap(x => console.log(x)));

loggedNumbers.subscribe();
// Output: 1, 2, 3
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers Observable emits the values 1, 2, and 3. The tap operator takes this Observable as input and returns the same Observable, but with a side effect that logs each value. When the loggedNumbers Observable is subscribed to, it logs the values 1, 2, and 3.

Note that the tap operator doesn't modify the values emitted by the Observable. It's only used to perform side effects. If you need to modify the values, you can use other operators like map or filter in combination with tap.

4) switchMap operator-
The switchMap operator takes an Observable as its input and returns a new Observable as its output. The new Observable emits values from the most recent inner Observable created by the function you provide to the switchMap operator. The function takes each value from the original Observable and maps it to a new inner Observable.

Here's a simple example of using the switchMap operator to fetch data from an API based on user input:

import { of, fromEvent } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const input = document.querySelector('input');
const input$ = fromEvent(input, 'input');

const apiCall = query => of({ query, data: `Result for ${query}` });

const data$ = input$.pipe(
  switchMap(event => apiCall(event.target.value))
);

data$.subscribe(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

In this example, the input$ Observable emits events whenever the user types in the input field. The switchMap operator takes this Observable as input and returns a new Observable, data$, which emits values from the API call. The API call is made using the apiCall function, which takes the current input value and returns an Observable with the API response.

Each time a new value is emitted by the input$ Observable, the switchMap operator cancels any previous inner Observables and creates a new one based on the latest value. This allows for the most recent API request to be used, and any previous requests to be discarded.

5) finalize operator-
The finalize operator takes an Observable as its input and returns the same Observable as its output. The difference is that it allows you to perform a side effect, such as cleanup or resource disposal, when the Observable completes or errors.

Here's a simple example of using the finalize operator to log a message when an Observable completes:

import { of } from 'rxjs';
import { finalize } from 'rxjs/operators';

const numbers = of(1, 2, 3);
const loggedNumbers = numbers.pipe(finalize(() => console.log('complete')));

loggedNumbers.subscribe();
// Output: 1, 2, 3, complete
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers Observable emits the values 1, 2, and 3. The finalize operator takes this Observable as input and returns the same Observable, but with a side effect that logs a message when the Observable completes. When the loggedNumbers Observable is subscribed to, it logs the values 1, 2, and 3, and then logs the message "complete".

To summarize, these are just a few of the many operators available in RxJS. Operators can be combined to create complex data processing pipelines, allowing you to handle and transform data in a variety of ways.

Top comments (0)