Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Reactive Patterns with RxJS for Angular
  • Toc
  • feedback
Reactive Patterns with RxJS for Angular

Reactive Patterns with RxJS for Angular

By : Lamis Chebbi
4.1 (16)
close
Reactive Patterns with RxJS for Angular

Reactive Patterns with RxJS for Angular

4.1 (16)
By: Lamis Chebbi

Overview of this book

RxJS is a fast, reliable, and compact library for handling asynchronous and event-based programs. It is a first-class citizen in Angular and enables web developers to enhance application performance, code quality, and user experience, so using reactive patterns in your Angular web development projects can improve user interaction on your apps, which will significantly improve the ROI of your applications. This book is a step-by-step guide to learning everything about RxJS and reactivity. You'll begin by understanding the importance of the reactive paradigm and the new features of RxJS 7. Next, you'll discover various reactive patterns, based on real-world use cases, for managing your application’s data efficiently and implementing common features using the fewest lines of code. As you build a complete application progressively throughout the book, you'll learn how to handle your app data reactively and explore different patterns that enhance the user experience and code quality, while also improving the maintainability of Angular apps and the developer's productivity. Finally, you'll test your asynchronous streams and enhance the performance and quality of your applications by following best practices. By the end of this RxJS Angular book, you'll be able to develop Angular applications by implementing reactive patterns.
Table of Contents (19 chapters)
close
1
Part 1 – Introduction
5
Part 2 – A Trip into Reactive Patterns
10
Part 3 – Multicasting Takes You to New Places
16
Part 4 – Final Touch

Using RxJS in Angular and its advantages

RxJS is a first-class citizen in Angular. It is part of the Angular ecosystem and is used in many features to handle asynchronous operations. Primarily, this includes the following:

  • HTTP client module
  • Router module
  • Reactive forms
  • Event emitter
  • Async pipe

We will discuss each of the following concepts in the subsequent subsections.

Note

We recommend taking a quick look at https://angular.io/docs. Here, you can find further details about the features mentioned earlier.

The HTTP client module

You might be familiar with the HTTP client API provided by Angular in order to communicate with your server over the HTTP protocol. The HttpClient service is based on observables to manage all transactions. This means that the result of calling the API methods (such as GET, PATCH, POST, or PUT) is an observable.

In the following code snippet, we have an example of an Angular service that injects the HttpClient service and fetches data from the server using the HttpClient.get() method:

Import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable} from 'rxjs';
import { environment } from '@env/environment';
const BASE_PATH = environment.basePath;
@Injectable()
export class RecipesService {
constructor(private http: HttpClient) { }
getRecipes(): Observable<Recipe[]> {
return this.http.get<Recipe[]>(`${BASE_PATH}/recipes/search/all`);
}

The following is the content of the environment.ts file where we define the basePath property of our backend:

export const environment = {
  basePath: '/app/rest',
  production: false
};

The getRecipes() method or, to be more accurate, the call to this.http.get<Recipe>(`${BASE_PATH}/recipes/search`) returns an observable that you should subscribe to in order to issue the GET request to the server. Please note that this is an example of an HTTP transaction, and it is the same for all of the other HTTP methods available in the API (such as POST, PUT, and PATCH)

For those familiar with promise-based HTTP APIs, you might be wondering, in this case, what the advantages of using observables are.

Well, there are a lot of advantages but the most important ones are listed as follows:

  • Observables are cancellable, so you can cancel the HTTP request whenever you want by calling the unsubscribe method.
  • Also, you can retry HTTP requests when an error occurs or an exception is thrown.
  • The server's response cannot be mutated by observables, although this can be the case when chaining then() to promises.

The router module

The router module, which is available in the @angular/router package, uses observables in router events and activated routes.

Router events

The router exposes events as observables. The router events allow you to intercept the navigation life cycle. The following list shows the sequence of router events:

  • NavigationStart
  • RouteConfigLoadStart
  • RouteConfigLoadEnd
  • RoutesRecognized
  • GuardsCheckStart
  • ChildActivationStart
  • ActivationStart
  • GuardsCheckEnd
  • ResolveStart
  • ResolveEnd
  • ActivationEnd
  • ChildActivationEnd
  • NavigationEnd
  • NavigationCancel
  • NavigationError
  • Scroll

    Note

    We recommend that you take a quick look at https://angular.io/api/router/Event. Here, you can find further details about the events and their order.

To intercept all the events that the router goes through, first, you should inject the Router service, which provides navigation and URL manipulation capabilities. Then, subscribe to the events observable available in the Router object, and filter the events of the RouterEvent type using the rxjs filter operator.

This is an example of an Angular service that injects the Router object in the constructor, subscribes to the router events, and just traces the event ID and path in the console. However, note that you can also introduce pretty much any specific behavior:

import { Injectable } from '@angular/core';
import { Router, RouterEvent } from '@angular/router';
import { filter } from 'rxjs/operators';
@Injectable()
export class CustomRouteService {
  constructor(public router: Router) {
    this.router.events.pipe(
      filter(event => event instanceof RouterEvent)
    ).subscribe((event: RouterEvent) => {
      console.log(`The current event is : ${event.id} | 
                  event.url`);
    });
  }
} 

You can filter any specific event by putting the target type. The following code example only filters the NavigationStart event and traces the event ID and path inside the console. However, you can also introduce pretty much any specific behavior:

import { Injectable } from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
@Injectable()
export class CustomRouteService {
  constructor(public router: Router) {
    this.router.events.pipe(
       filter(event => event instanceof NavigationStart)
    ).subscribe((event: NavigationStart) => {
      console.log(`The current event is : ${event.id} | 
                  event.url`);
    });
  
  } 
}

The majority of Angular applications have a routing mechanism. The router events change frequently over time, and it makes sense to listen to changes to execute the side effects. That's why observables are a flexible way in which to handle those streams.

The activated route

The ActivatedRoute class is a router service that you can inject into your components to retrieve information about a route's path and parameters. Many properties are based on observables. Here, you will find the contract (refers to the exposed methods and properties) of the activated route class:

class ActivatedRoute {
  snapshot: ActivatedRouteSnapshot
  url: Observable<UrlSegment[]>
  params: Observable<Params>
  queryParams: Observable<Params>
  fragment: Observable<string | null>
  data: Observable<Data>
  outlet: string
  component: Type<any> | string | null
  routeConfig: Route | null
  root: ActivatedRoute
  parent: ActivatedRoute | null
  firstChild: ActivatedRoute | null
  children: ActivatedRoute[]
  pathFromRoot: ActivatedRoute[]
  paramMap: Observable<ParamMap>
  queryParamMap: Observable<ParamMap>
  toString(): string
}

As you might have gathered, url, params, queryParams, fragment, data, paramMap, and queryParamMap are represented as observables. Refer to the following list:

  • url: This is an observable that holds the URL of the active route.
  • params: This is an observable that holds the parameters of the active route.
  • queryParams: This is an observable that holds the query parameters shared by all of the routes.
  • fragment: This is an observable that holds the URL fragment shared by all the routes.
  • data: This is an observable that holds the static and resolved data of the active route.
  • paramMap: This is an observable that holds a map of the required parameters and the optional parameters of the active route.
  • queryParamMap: This is an observable that holds a map of the query parameters available to all the routes.

All these parameters might change over time. Routes might share parameters, the parameters might have dynamic values, and it makes perfect sense to listen to those changes to register side effects or update the list of parameters.

Here's an example of an Angular component that injects the ActivatedRoute class in the constructor and subscribes, in the ngOnInit() method, to the following:

  • The url property of activatedRoute, logging the URL in the console
  • The queryParams property of activatedRoute in order to retrieve the parameter criteria and store it in a local property, named criteria:
    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    @Component({
      selector: 'app-recipes',
      templateUrl: './recipes.component.html'
    })
    export class RecipesComponent implements OnInit {
      criteria: any;
      constructor(private activatedRoute: ActivatedRoute) 
      { }
      ngOnInit() {
        this.activatedRoute.url
          .subscribe(url => console.log('The URL changed 
                                        to: ' + url));
        this.activatedRoute.queryParams.subscribe(params 
        => {
          this.processCriteria(params.criteria);
        });
      }
      processCriteria(criteria: any) {
        this.criteria = criteria;
      }
    }

Reactive forms

Reactive forms available under the @angular/forms package are based on observables to track form control changes. Here's the contract of the FormControl class in Angular:

class FormControl extends AbstractControl {
 //other properties here 
 valueChanges: Observable<any>
 statusChanges: Observable<any>
}

The FormControl properties of valueChanges and statusChanges are represented as observables that trigger change events. Subscribing to a FormControl value change is a way of triggering application logic within the component class.

Here's an example of an Angular component that subscribes to the valueChanges of a FormControl property called rating and simply traces the value through console.log(value). In this way, each time, you will get the changed value as an output:

import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
  selector: 'app-recipes',
  templateUrl: './recipes.component.html'
})
export class MyComponent implements OnInit {
  form!: FormGroup;
  ngOnInit() {
    const ratingControl = this.form.get('rating');
    ratingControl?.valueChanges.subscribe(
      (value) => {
        console.log(value);
      }
    );
  }
}

The event emitter

The event emitter, which is part of the @angular/core package, is used to emit data from a child component to a parent component through the @Output() decorator. The EventEmitter class extends the RxJS subject and registers handlers for events emitted by this instance:

class EventEmitter<T> extends Subject {
  constructor(isAsync?: boolean): EventEmitter<T>
  emit(value?: T): void
  subscribe(next?: (value: T) => void, error?: (error: any) 
  => void, complete?: () => void): Subscription
}

This is what happens under the hood when you create an event emitter and emit a value.

The following is an example of an Angular component that emits the updated value of a recipe rating:

import { Component, Output } from '@angular/core';
import { EventEmitter } from 'events';
 
@Component({
  selector: 'app-recipes',
  templateUrl: './recipes.component.html'
  })
export class RecipesComponent {
  constructor() {}
  @Output() updateRating = new EventEmitter();
  updateRecipe(value: string) {
    this.updateRating.emit(value);
  }
}

The async pipe

Here, AsyncPipe automatically subscribes to an observable when used in a component's template and emits the latest value each time. This avoids subscribing logic in the component and helps with binding and updating your asynchronous streams data in the template. In this example, we are using an async pipe inside ngIf. This div tag will only be rendered when the data$ variable emits something:

  <div *ngIf="data$ | async"></div>

We will cover the advantages and usage of async pipes in Chapter 4, Fetching Data as Streams.

Note

In the previous code snippets, the subscription to the observables was done explicitly for demonstration purposes. In a real-world example, we should include the unsubscription logic if we use an explicit subscription. We will shed light on this in Chapter 4, Fetching Data as Streams.

Now that we have learned about the advantages of using RxJS in Angular and how it makes dealing with some concepts smoother, let's explore the marble diagram, which is very handy for understanding and visualizing the observable execution.

bookmark search playlist download font-size

Change the font size

margin-width

Change margin width

day-mode

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Delete Bookmark

Modal Close icon
Are you sure you want to delete it?
Cancel
Yes, Delete