Exploring Reactive Programming with RxJS and organizing data into individual streams

As I delve deeper into working reactively with Angular 15 and RxJS observables for a UI component, my focus lies on subscribing to data solely within the component template (html). The data is fetched from an external system through a service. However, a crucial challenge I face is that the received data spans multiple days and requires segmentation for display purposes.

The displayed data consists of individual components representing rows retrieved from the service call, which involves making an HTTP request to an external host.

this.Entries$ = this.Http_.get<Array<IEntry>>('http://host.com/api/entry');

The data obtained is structured as an array of records containing information such as EntryDate, UserId, Description, TimeWorked, etc. The external API sends these records back as a flat array in no particular order, necessitating potential sorting for processing requirements.

[
   { "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 2.5, ... },
   { "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 4.5, ... },
   { "EnterDate": 20221025, "UserId": "BSmith", "TimeWorked": 5, ... },
   { "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 4, ... },
   { "EnterDate": 20221026, "UserId": "BSmith", "TimeWorked": 5, ... },
   { "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 2, ... },
]

My current HTML template iterates over the Entries$ observable assuming it's meant for a single day only.

<ng-container *ngFor="let OneEntry of (Entries$ | async)">
    <one-entry-component [data]=OneEntry />
</ng-container>

I aim to segregate the array of records into distinct datasets based on their EntryDate (and optionally user), akin to using groupBy(), but accessing internal record references within the groupBy() function remains unclear to me.

Once segmented, I envision having multiple one-day-components on the page, each containing the one-entry-component within them.

|---------------------------------------------------------------|
|                                                               |
| |-One Day 1-------------###-|  |-One Day 2-------------###-|  |
| |                           |  |                           |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |                           |  |                           |  |
| |---------------------------|  |---------------------------|  |
|                                                               |
| |-One Day 3-------------###-|  |-One Day 4-------------###-|  |
| |                           |  |                           |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [ One Line           ]  |  |  [ One Line            ]  |  |
| |   [...

These separate boxes represent unique days accounted for in the response. Should there be two different dates, the display would adjust accordingly, accommodating varying numbers of dates.

To achieve this, I require an Observable featuring the necessary dates for segmentation (and possibly users) to pass as data to the

<one-day-component [data]=OneDateOneUser$ />
. This data aids in tallying time entries for the title, a task simplified through a .pipe(map()) operation.

Within the one-day-component, I would then iterate through the OneDateOneUser$ observable to extract individual records to convey to the one-entry-component, mirroring existing functionality.

While researching how to achieve this, the RxJS groupBy operator seems promising. Despite its potential benefits, navigating and manipulating the inner array data poses a learning curve given my novice status with RxJS.

The example provided uses individual records and not an array, showcasing the efficacy of the RxJS reference when applied appropriately.

import { of, groupBy, mergeMap, reduce, map } from 'rxjs';
 
of(
  { id: 1, name: 'JavaScript' },
  { id: 2, name: 'Parcel' },
  { id: 2, name: 'webpack' },
  { id: 1, name: 'TypeScript' },
  { id: 3, name: 'TSLint' }
).pipe(
  groupBy(p => p.id, { element: p => p.name }),
  mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [`${ group$.key }`]))),
  map(arr => ({ id: parseInt(arr[0], 10), values: arr.slice(1) }))
)
.subscribe(p => console.log(p));
 
// displays:
// { id: 1, values: [ 'JavaScript', 'TypeScript' ] }
// { id: 2, values: [ 'Parcel', 'webpack' ] }
// { id: 3, values: [ 'TSLint' ] }

However, altering the data structure in the example by converting it to an array similar to how my data is returned results in issues, emphasizing the need for further exploration and understanding to address the problem effectively:

import { of, groupBy, mergeMap, reduce, map } from 'rxjs';
 
of(
[
  { id: 1, name: 'JavaScript' },
  { id: 2, name: 'Parcel' },
  { id: 2, name: 'webpack' },
  { id: 1, name: 'TypeScript' },
  { id: 3, name: 'TSLint' }
]
).pipe(
  groupBy(p => p.id, { element: p => p.name }),
  mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [`${ group$.key }`]))),
  map(arr => ({ id: parseInt(arr[0], 10), values: arr.slice(1) }))
)
.subscribe(p => console.log(p));
 

Answer №1

Have you considered transforming that Array<IEntry> into a Record<number, IEntry> using something like the groupBy method from lodash and an RxJS map operator?

This approach allows you to achieve the desired result by applying some CSS properties like flex-wrap and flex-row in the template and looping through the entries of the record:

Take a look at this functional example on CodePen

import {groupBy} from 'lodash'
const fakeData = [
   { "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 2.5, ... },
   { "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 4.5, ... },
   { "EnterDate": 20221025, "UserId": "BSmith", "TimeWorked": 5, ... },
   { "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 4, ... },
   { "EnterDate": 20221026, "UserId": "BSmith", "TimeWorked": 5, ... },
   { "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 2, ... },
]


// Replace "of" with your API call
entriesByDate$: Observable<Record<number, IEntry>> = of(fakeData).pipe(
    map(allEntries => groupBy(allEntries, 'EnterDate'))
)

<div *ngIf="entriesByDate$ | async as entries" class="flex flex-row flex-wrap">
    <ng-container *ngFor="let [enterDate, entries] of Object.entries(entries)">
        <entry-group-component [title]="enterDate" [data]="entries" />
    </ng-container>
</div>

If you prefer not to import lodash, you can create your own grouping function using Array#reduce:

function groupByEnterDate(entries: Array<IEntry>) {
  return entries.reduce(
    (acc, current) => {
      const key = current.EnterDate
      const groupedByKey = acc[key] ?? []
      return { ...acc, [key]: [...groupedByKey, current] }
    },
    {}
  )
}

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

Typescript loading icon directive

Seeking to create an AngularJS directive in TypeScript that wraps each $http get request with a boolean parameter "isShow" to monitor the request status and dynamically show/hide the HTML element depending on it (without utilizing $scope or $watch). Any ...

Asynchronous requests from clients paired with server-side rendering

Exploring the realm of SEO with Angular4/Node.js has presented a unique challenge for me. Utilizing Angular Universal allows for server-side rendering, enabling me to inject meta keywords, title, and image URLs into the HTML before it reaches the browser. ...

Ravaging a JSON array in Delphi

This piece of code is causing a memory leak. Can you suggest the correct approach to tackle this issue? JSONArrayObject := TJSONArray.Create; try JSONArrayObject := TJSONObject.ParseJSONVal ...

Issues arise when attempting to use the Android KeyUp, KeyDown, and KeyPress events in conjunction with Angular2

I am encountering an issue where I consistently receive a keyCode of 229 in Android Chrome browsers when running either: <input type="text" (keydown)="testKeyCodes($event)"/> <!-- or --> <input type="text" (keyup)="testKeyCodes($event)"/& ...

Receiving an error in Typescript when passing an object dynamically to a React component

Encountering a typescript error while attempting to pass dynamic values to a React component: Error message: Property 'title' does not exist on type 'string'.ts(2339) import { useTranslation } from "react-i18next"; import ...

Observe how Observable breaks down strings into individual letters (like in Karma and Angular)

Attempting to simulate an HTTP service in Angular tests (Karma), I included the following in the providers array: { provide: service, useValue: { getData: () => new Observable((subscriber) => { subscriber.next('i ...

Using a Typescript typeguard to validate function parameters of type any[]

Is it logical to use this type of typeguard check in a function like the following: Foo(value: any[]) { if (value instanceof Array) { Console.log('having an array') } } Given that the parameter is defined as an array o ...

Is there a way to verify the presence of multiple array indices in React with TypeScript?

const checkInstruction = (index) => { if(inputData.info[index].instruction){ return ( <Text ref={instructionContainerRef} dangerouslySetInnerHTML={{ __html: replaceTextLinks(inputData.info[index].instruction) ...

Tips for converting a string array constant into a union type

I have a string array that I want to use to create a new type where the properties correspond to the elements in the array. There are different types of arrays and I have a function that generates different output types based on the input array. const RG ...

Whenever comparing the types 'string[]' and 'DeliveryTypeEnum', this condition will consistently result in 'true' as there is no intersection between the two. This is highlighted by the error code ts(2367)

Hello everyone, I'm a junior developer and could use some assistance if (query.deliveryType && query.deliveryType != DeliveryTypeEnum.EITHER) { search.push({ terms: { "deliveryType.keyword&q ...

The exportAs attribute is not specified as "ngForm" for any directive

Encountered an error while attempting to test the LoginComponent PhantomJS 2.1.1 (Linux 0.0.0): Executed 3 of 55 (1 FAILED) (0 secs / 0.307 secs) PhantomJS 2.1.1 (Linux 0.0.0) LoginComponent should create FAILED Failed: Uncaught (in promise): Error: Templ ...

Having difficulty installing TypeScript on my machine

https://i.stack.imgur.com/l6COf.pngHaving trouble installing TypeScript with the following error: npm WARN registry Using outdated package data from https://registry.npmjs.org/ due to an error during revalidation. npm ERR! code E500 npm ERR! 500 Interna ...

What is the best way to create a dynamic URL linking to an external site in Angular 5?

When attempting to create a link like this: <a [href]="getUrl()">click me</a> getUrl() { return this.domSanitizer.bypassSecurityTrustUrl('http://sampleUrl.com'); } The link is not clickable. When hovering over the ...

The functionality of *ngIf fails to display the template when the array is void of any elements

view image descriptionHello, I am new to using Angular and I'm encountering an issue with the ngIf directive not working as expected in the tutorial. I have included my code snippets below. Any assistance would be greatly appreciated. My component: i ...

Tips for implementing <mat-progress-bar> in .ts file when making API service requests with Angular

I'm currently utilizing an API call to retrieve an image from a service, and I would like to display a progress bar while the image is being fetched. It seems that I need to incorporate the progress bar within the service as the image data is returned ...

Troubleshooting: Angular 2 router events subscription not triggering

Currently, I am utilizing a breadcrumbs component that subscribes to the router events. However, there is an issue where the subscription does not trigger the first time the component loads. ngOnInit(): void { console.log("oninit beradcumbs"); this.ro ...

Transferring elements from one array to multiple arrays

I have been working with the basics of JavaScript arrays and here is my code snippet: let arr = ['students', 'exams', [{'sub1':80, 'sub2':60},{'grade_sub1':'A', 'grade_sub2':'B&apos ...

Having trouble with [at-loader] while incorporating Typescript and react-bootstrap in webpack?

While working with webpack, I encountered an error message when using a component in react-bootstrap along with typescript. The error displayed is as follows: ERROR in [at-loader] ./node_modules/react-bootstrap/esm/NavbarCollapse.d.ts:4:18 TS2320: ...

In the world of Typescript, object-based type inference reigns

I'm grappling with TypeScript to correctly deduce typing in the given code snippet: type Customer = { name: string } type Item = { price: number } const customerConfig = { action: () => [{name: 'Alice'}] as Customer[], } const item ...

How can one click the button within the expanded dropdown while hovering over the navigation item in Angular and Bootstrap?

Issue Description: Upon hovering over a navigation item, the dropdown container is displayed but it's not clickable. Desired Behavior: Hovering over a navigation item should display the dropdown container and allow clicking on its contents. Furthermo ...