How to "pass" data when using a declarative/reactive data access approach with RxJS in Angular?

Viewed 864

The classic pattern for data access in Angular suggests code like this:

Classic Data Access Pattern

getProducts(): Observable<Product[]> {
  return this.http.get<Product[]>(this.productsUrl)
    .pipe(
      tap(data => console.log(JSON.stringify(data))),
      catchError(this.handleError)
    );

Above we have a method the returns an Observable. When the data is returned from the http endpoint, that Observable emits the data in a shape defined by the Product[] generic parameter where Product is an interface.

For a more reactive application, the declarative/reactive approach is often suggested. It changes the above method to a property declaration like this:

Reactive Data Access Pattern

products$ = this.http.get<Product[]>(this.url)
  .pipe(
    tap(data => console.log(JSON.stringify(data))),
    catchError(this.handleError)
  );

Notice that this declares a property in the service for the Observable instead of a method. The $ suffix on products$ is a convention used to distinguish properties that are Observables vs other types of data structures such as general objects or arrays.

This pattern is often used when there is UI interactivity (such as filtering based on a selection), when working with complex data (such as combining data from multiple end points) and when sharing data across components (such as when each component needs to react when the data changes).

The reactive data access pattern presents a common problem. How do we "pass" data required by the URL when using this technique?

For example, say that the http request requires a category to only pull down products for that category:

this.http.get<Product[]>(`${this.url}?cat=${catId}`)

How can this category be "passed in" since there is no method?

1 Answers

Instead of thinking how to "pass" data, think instead about how to "emit" that value over time. To emit data, we define our own Observable using Subject or BehaviorSubject.

private categorySubject = new Subject<number>();
categorySelectedAction$ = this.categorySubject.asObservable();

products$ = this.categorySelectedAction$.pipe(
 switchMap(catId=>this.http.get<Product[]>(`${this.url}?cat=${catId}`))
    .pipe(
      tap(data => console.log(data)),
      catchError(this.handleError)
  ));

selectedCategoryChanged(categoryId: number): void {
  this.categorySubject.next(categoryId);
}

In the above code, we define our Observable using a Subject. We declare the Subject private so it cannot be accessed from outside our service. We then expose the read-only part of that Subject using asObservable().

Whenever the user selects a different category, the component calls selectedCategoryChanged which emits the selected categoryId into the stream defined by our Subject.

For products$, when a value is emitted, it is piped through a set of operators. The switchMap is a higher-order mapping operator that automatically subscribes to the inner Observable (the this.http.get...) and flattens the result. The returned products for the specified category are then emitted into the products$ stream.

To see a more complete solution, see this github: https://github.com/DeborahK/Angular-RxJS/tree/master/APM-Final

Related