Renátó Bogár
Renato's blog

Renato's blog

RxJS Pt. 4 - Reactive approach

RxJS Pt. 4 - Reactive approach

Renátó Bogár's photo
Renátó Bogár
·May 22, 2022·

6 min read

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

  • Why is it good to use reactive approach ?
  • Prodecural pattern
  • Reactive solution
  • Handling errors
  • Change detection

Many of us learned procedural patterns for retrieving data in Angular using RxJS. In this article I would like to talk about reactive.

Reactive means working with observables directly. So instead of reading data into an array and then binding it to that array, we work observables and bind to those observables. By working with observables, we can set up observable pipelines that react to emitted items and leverage the many operators available in RxJS.

We transform, compose, and combine observables, to better manage multiple data sources, and we define action streams to easily react to user or application actions.

Why is it good to use reactive approach ?

  • It improves performance
  • Handles state
  • Reacts to user actions
  • Simplifies our code

Prodecural pattern

Let's quickly talk about the procedural pattern Angular uses to retrieve data and display it. I will write about how to improve this pattern in this article. When we retrieve data, our first step is to define the shape of the data.

Usually we define this in a separated .ts file

export interface Car {
    id: number
    color: string
    price?: number
    engine?: number
}

We define in an interface what we are expecting in the response from the API. The question mark identifies any optional fields that may not be present in the response data. These are sometimes called nullable fields.

Now in an Angular application from the client side, the services are responsible to get the response from the APIs, and these services are injected (Dependency Injection) into the components those can display the retrieved data after rendering.

So the next step is to have our service file

...
export class CarService {
...
    getCars(): Observable <Car[]> {
        return this.http.get<Car[]>(this.APIURL)
        .pipe(
            tap(response => console.log('Cars: ', JSON.stringify(respose))),
            catchError(this.handleError)
        )
    }
}

After this, in the component we inject our service, and then in the ngOninit lifecycle-hook we call the service and subscribe to it

export class CarlistComponent implements OnInit{
...
construct (private carService: CarService) {
    ngOnInit(): void {
        this.sub = this.CarService.getCars()
        .subscribe({
           next: cars => this.cars = cars,
           error: err => this.errorMessage = err
        })
    }}
...
}

and now this method runs each time the website is initialized. After this we can render this information out with an *ngFor directive

Now this a long way to display the data and it is the procedural pattern, and this type of handling requests requires unsubscribing when the component is no longer displayed (usually in the ngOnDestroy lifecycle-hook) on the UI to avoid memory leaks. Memory leaking is not something that could destroy your computer or browser or something. It is just making the UI slower which is not good for the user experience.

Now let's make it more reactive using the async pipe.

Reactive solution

In the reactive approach we access an Observable directly in the template using the async pipe. The async pipe automatically subscribes to the Observable when the component is initialised. It returns each emitted value from that Observable. When a new item is emitted, the component is marked to be checked for changes and runs change detection, modifying the UI as needed. This makes the async pipe a key part of our reactive development strategy, and the async pipe automatically unsubscribes when the component is destroyed to avoid memory leaks.

How to change our code to use the async pipe? The async pipe does not require change in our service code, only in our component and template.

However, our component .ts file looks like this a little more reactive

...
cars$: Observable <Car[]>

constructor(private carService: CarService) {}

ngOnInit(): void {
    this.product$ = this.carService.getCars()
}
...

And our template would still look something like this:

<div *ngIf = "cars$ | async as cars>

Handling errors

When working with observables, there are place where error could occur. To prevent observable errors from crashing our application it is important to catch these errors. It is important to mention that any errors stop the Observable, meaning it will not emit any more items. That means once an observable has an error, we can not use it anymore.

Handling an error from an observable involves three steps.

  1. Catch the error - prevents crashing unhandled error in our application
  2. Optionally rethrow the error - if we want to pass along the error, we rethrow the error along the subscription chain
  3. Replace the errored Observable with a new Observable - an observable with an error stops and will not emit any more items, we often replace that errored observable with a new observable

RxJS provides serveral features specifically for error handling.

  1. catchError - catches an error in our observable pipeline
  2. throwError - throws the error along the subscription chain
  3. EMPTY - constant we can use to replace an errored observable with a valid, but empty observable

catchError

  • Catches any errors that occur on an observable
  • Must be after any operator that could generate an error
  • Used for catching errors
  • Rethrows the error
  • Replaces the error Observable to continue after an error occurs
of(2,4,6)
    .pipe(
        map(i=>{
            if(i === 6){
                throw 'Error'
            }
            return i
        }),
        catchError(err => of('six'))
    )
    .subscribe({
        next: x => console.log(x),
        error: err => console.log(err)
    })

we use the map operator to synthetically throw an error if the emitted value is 6. Here we use catchError to catch the error. In this error handler we create a new Observable that emits the word 'six' and completes. When the error occurs the observer does not get notified of the error, because the catchError caught it and handled it. Instead the Observer's next method is called with the emitted value from the new Observable.

throwError

To rethrow an error, we can use the RxJS throwError. throwError is basically a creation function that creates a new replacement Observable that emits new items, and when subscribed, immediately emits an error notification.

throwError( ()=>err )

You can use throwError to propagate an error to other parts of the application.

EMPTY

When defining a new replacement observable, the EMPTY constant can be useful.

return EMPTY

You can use EMPTY to return an EMPTY observable immediately.

Now since we use the more reactive way our error handling could be the following -an example

this.car$ = this.carService.getCars()
    .pipe(
        catchError(err => {
            this.errorMessage = err
            return EMPTY
        })
    )

Here it is important to return EMPTY, since this.car$ is directly attached on the template (html file). But why? Why would we bind our observables directly using async pipe ?

There are several benefits using async pipe

  • No need to subscribe - handles it for us
  • No need to unsubscribe
  • Improve change detection

Change detection

Now what is this change detection? Angular uses change detection to tract changes to the application data so that it knows when to update the UI with changed data. Change detection ensures our template bindings display the current data from our component.

Change detections in Angular By default there are two change detections in angular:

  • Default
  • OnPush

Default Every component is checked when any change is detected

OnPush Improves performance by minimizing change detection cycles Component is only checked if @Input properties change, or event emits, our a bound Observable emits, if we use async pipe. We have to set the change detection strategy in a components decorator, like this

@Component({
....
    changeDetection: ChangeDetectionStrategy.OnPush
...
})

How to make still more reactive? Using declarative approach. This is, how our code looks after the declarative approach implementation

CarService.ts

private car$ = this.http.get<Car[]>(this.APIURL)
        .pipe(
            tap(response => console.log('Cars: ', JSON.stringify(respose))),
            catchError(this.handleError)
        )
    }

CarlistComponent.ts

this.car$ = this.carService.car$
    .pipe(
        catchError(err => {
            this.errorMessage = err
            return EMPTY
        })
    )

Thank you for reading this article. Keep up reading ☺️

Did you find this article valuable?

Support Renátó Bogár by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this