Leverage RxJs Pipe for transforming Observables into various data types

Currently, I am dealing with an Observable that I need to transform into an array of a different class called ChartData[]. This transformed array will be used as a data source for creating highcharts graphs, such as column charts and pie charts.

I attempted to utilize the RxJS pipe operator on the Observable to invoke the reduce operator on my data. However, I encountered difficulties as the reduce operator seemed to be ineffective in iterating over the items in the Observable. Below is the code snippet showcasing my attempt:

this.foodService.getAllRecipes().pipe(
    reduce((array: ChartData[], value: Recipe[], i: number) => {
        const author = this.createOrFindAuthor(array, value[i]);
        author.y += 1;

        return array;
      }, new Array<ChartData>())
  ).subscribe(data => this.chartData$ = of(data.sort((a, b) => b.y - a.y)));
}

The method getAllRecipes() returns the Observable.

The variable this.chartData$ holds an Observable.

Although I successfully achieved the desired result using the reduce function within the subscribe operator, where the graphs displayed the expected data, I was intrigued to explore performing the reduction as a pipeable operator. Here is the code excerpt showcasing the reduction being done in the subscribe block:

this.foodService.getAllRecipes().subscribe((data) => {
    const list = data.reduce((arr: ChartData[], v: Recipe) => {
      const author = this.createOrFindAuthor(arr, v);
      author.y += 1;

      return arr;
    }, new Array<ChartData>());

    this.chartData$ = of(list.sort((a, b) => b.y - a.y));
  });

While attempting to integrate the subscribe block into the pipeable reduce operator, I encountered compile errors indicating that the method expected Recipe[] for the value. It left me pondering whether I was only receiving the Observable and needed to further process it. Is my understanding of how the pipeable operator functions on the Observable flawed?

For your reference, provided below are the models and the createOrFindAuthor function:

export class Recipe {
    public Title: string;
    public Author: string;
    public Source: string;
    public Page: number;
    public Link?: string;
}

export class ChartData {
    name: string;
    y: number;
}

private createOrFindAuthor(array: ChartData[], recipe: Recipe): ChartData {
  const name = (recipe.Author.length > 0 ? recipe.Author : 'UNKNOWN');

  let found = array.find(i => i.name === name);

  if (!found) {
    const newData = new ChartData();
    newData.name = name;
    newData.y = 0;
    array.push(newData);
    found = newData;
  }

  return found;
}

Answer №1

Chau Tran guided me in the right direction. It turns out that I needed to use the switchMap operator to convert the Observable to a Recipe[], allowing the reduce operator to accept Recipe as the value. Here is the updated solution:

this.foodService.getAllReceipes()
  .pipe(
    switchMap(data => data as Recipe[]),            <<== ADDED THIS

    reduce((array: ChartData[], value: Recipe) => {
        const author = this.createOrFindAuthor(array, value);
        author.y += 1;

        return array;
      }, new Array<ChartData>()),

      switchMap(data => this.chartData$ = of(data.sort((a, b) => b.y - a.y)))
  )
  .subscribe();

Answer №2

In the aftermath of the reduce operation, experiment with the following:

switchMap(data => {
    this.chartData$ = of(data.sort((a, b) => b.y - a.y));
    return this.chartData$;
})
.subscribe()

Answer №3

For a comprehensive demonstration of the stackblitz example showcasing the usage of reduce(), I have put together a project. The main focus lies within demoReduce.ts:

import { Observable, of } from 'rxjs'
import { reduce, tap } from 'rxjs/operators'

type Book = {
  title: string
  noPages: number
}

type Library = {
  totalPages: number
  books: Book[]
}

export const demoReduce = () => {
  const books$: Observable<Book> = of(
    { title: 'book 1', noPages: 10 },
    { title: 'book 2', noPages: 20 },
    { title: 'book 3', noPages: 30 },
  )

  return books$.pipe(
    // --- reduce a stream of "Book" into a "Library"
    reduce<Book, Library>((previous, book) => {
      // --- add book to "Library" and increment totalPages in "Library"
      return {
        totalPages: previous.totalPages + book.noPages,
        books: [
          ...previous.books,
          book
        ]
      }
    }, { totalPages: 0, books: [] }),
    tap(val => console.log(val))
  )
}

To see the observable in action, simply click the "Demo Reduce" button and observe the console output.

This process transforms a sequence of Books into a singular Library object.

Keep in mind:

  • It is peculiar that stackblitz displays an error (indicated by a red underline) for reduce() in the online editor. No such error was encountered in IntelliJ/WebStorm, leading me to suspect a stackblitz bug.

Update:

Here's an alternative function that receives Observable<Book[]> as input (not yet vetted):

export const demoReduceWithArray = () => {
  const books$: Observable<Book[]> = of([
    { title: 'book 1', noPages: 10 },
    { title: 'book 2', noPages: 20 },
    { title: 'book 3', noPages: 30 }
  ])

  return books$.pipe(
    // --- reduce a stream of "Book[]" into a "Library"
    reduce<Book[], Library>((previous, books) => {
      // --- add each book to "Library" and increment totalPages in "Library"
      books.map(book => {
        previous = {
          totalPages: previous.totalPages + book.noPages,
          books: [
            ...previous.books,
            book
          ]
        }
      })
      return previous
    }, { totalPages: 0, books: [] }),
    tap(val => console.log(val))
  )
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Generate a div element dynamically upon the click of a button that is also generated dynamically

Putting in the effort to improve my Angular skills. I've found Stack Overflow to be extremely helpful in putting together my first app. The service used by my app is located in collectable.service.ts: export class CollectableService { private col ...

Struggling to translate JavaScript code into Typescript

Currently in the process of converting my JavaScript code to Typescript, and encountering an error while working on the routes page stating Binding element 'allowedRoles' implicitly has an 'any' type. ProtectedRoutes.tsx const Protecte ...

Learn how to display data from the console onto an HTML page using Angular 2

I am working on a page with 2 tabs. One tab is for displaying active messages and the other one is for closed messages. If the data active value is true, the active messages section in HTML should be populated accordingly. If the data active is false, th ...

Next.js (TypeScript) - Error: Property 'req' is not recognized on the type 'GetServerSideProps'

Currently, I am tackling a challenge involving the utilization of next-auth and attempting to access the session from within getServerSideProps. However, in order to achieve this, it is essential for me to provide the context request and context response ...

A guide on simulating x-date-pickers from mui using jest

I have successfully integrated a DateTimePicker into my application, but I am facing an issue with mocking it in my Jest tests. Whenever I try to mock the picker, I encounter the following error: Test suite failed to run TypeError: (0 , _material.gen ...

This error message in AngularJS indicates that the argument 'fn' is not being recognized as a function

I am currently working with angularjs and typescript and I am attempting to create a directive in the following manner: Below is my controller : export const test1 = { template: require('./app.html'), controller($scope, $http) { ...

Experimenting with Nuxtjs application using AVA and TypeScript

I'm in the process of developing a Nuxt application using TypeScript and intend to conduct unit testing with AVA. Nonetheless, upon attempting to run a test, I encounter the following error message: ✖ No test files were found The @nuxt/typescrip ...

Steps for retrieving multiple documents from Firestore within a cloud function

In my cloud function, I have set up a trigger that activates on document write. This function is designed to check multiple documents based on the trigger and execute if/else statements accordingly. I have developed a method that retrieves all documents u ...

How can I dynamically change and load a configuration file based on the URL parameter using Angular?

My query involves modifying the config file on pageload based on a URL parameter. I currently have implemented the following: config-loader.service.ts @Injectable() export class ConfigLoaderService { constructor(private injector: Injector, private ht ...

Create an instance of a class from a group of subclasses, all the while retaining the ability to access static members in Types

I seem to have encountered a dilemma where I am looking to have both the static and abstract keywords used for a member of an abstract class in TypeScript, but it appears that this combination is not supported. The nearest workaround I could come up with ...

Error message: Unable to locate Bootstrap call in standalone Angular project after executing 'ng add @angular/pwa' command

Having an issue while trying to integrate @angular/pwa, it keeps showing me an error saying "Bootstrap call not found". It's worth mentioning that I have removed app.module.ts and am using standalone components in various places without any module. Cu ...

Tips for Implementing Error Handling in Angular using Sweetalert2

On this code snippet, I have implemented a delete confirmation popup and now I am looking to incorporate error handling in case the data is not deleted successfully. confirmPopUp(){ Swal.fire({ title: 'Are You Sure?', text: 'Deleti ...

Unable to fetch data from URL in Angular using the HttpClientModule

I have a goal in my application to retrieve data from the following URL and showcase it within the app: https://jsonplaceholder.typicode.com/posts/1 The issue I'm encountering is that the data is not being displayed in my app. The console is showing ...

Crafting interactive buttons with angular material

I've been working on an angular application where I created 5 mat flat buttons using angular material. <button mat-flat-button [ngClass]="this.selected == 1 ? 'tab_selected' : 'tab_unselected'" (click)="change(1)">B-L1</b ...

Ways to conceal a table and button in the absence of data for display

I've been working on a way to hide the table and the 'changeState' button when there's no data present. Currently, I have set it up so that a message saying 'No entries in the list!' pops up briefly before disappearing, bringi ...

Protractor failing to synchronize with Angular2 load sequence

Software versions: Protractor version: 5.1.2 Node version: 6.9.0 Angular version: 2.4.10 OnPrepare function includes the step browser.get('/')<code> followed by a login within an <code>it block. The first issue encounte ...

What makes a pristine form essential in angular at all times?

Here is the code snippet: <form #userForm="ngForm" (ngSubmit)="save(userForm)"> <input type="email" #contactEmail="ngModel" email minlength="2" [(ngModel)]="contactInformation.email" class="form-control" id="contactEmail" name="contactEmail" ...

What are some effective strategies for incorporating React states with input variables?

As someone who is new to working with React, I am currently facing a challenge with my input form in React Typescript. My goal is to utilize the useState hook to store the values of various input fields such as name, email, and others. Currently, I have de ...

Acquire the Angular Type<> directly from the component instance in Angular 5

First and foremost, I want to clarify that my requirement is for the Angular Type instance of a component, not just the TypeScript definition. The current scenario is as follows: I am working within a service where I receive an input of the actual instanc ...