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

Clicking on the mat-icon-button with the matSuffix in the password field will always trigger a

Seeking assistance with implementing mat-icon-button matSuffix in Angular for a login page. This is my first time working with Angular and I am facing an issue with the password field. I have used mat-icon-button matSuffix, but whenever I click on the ico ...

Is it possible to utilize the OnBlur prop based on a certain condition?

To display a component when the input is focused, follow the steps below: Click here for not focused state When you click on the text input, the component should appear like this: Click here for focused state The code snippet provided works correctly. ...

What is the method for creating a new array of objects in Typescript with no initial elements?

After retrieving a collection of data documents, I am iterating through them to form an object named 'Item'; each Item comprises keys for 'amount' and 'id'. My goal is to add each created Item object to an array called ' ...

Troubleshooting Issue: Data not appearing on Angular frontend when fetching from Laravel API

Utilizing Laravel for the back end and Angular for the front end development. The code segments related to Angular can be found in employee.component.ts file: import { Component, OnInit } from '@angular/core'; import { DataService } from 'sr ...

Error: Can't access the 'http' property because it's undefined in Angular 2

Recently, I successfully integrated the gapi client into my Angular 2 application. However, I am now facing an issue where my http object is showing as undefined and I can't seem to figure out why. Here's the snippet of code that's causing ...

Syntax for TypeScript generic promises definition

I'm struggling to fully grasp the definition of Promise in TypeScript, as shown below: /** * Represents the completion of an asynchronous operation */ interface Promise<T> { /** * Attaches callbacks for the resolution and/or rejectio ...

Find all objects in an array of objects that contain at least one value that matches a given string

I am currently integrating search functionality in my application. The UI search results are generated from an array of objects. My goal is to loop through the name, custNumber, and sneak values in each object and display only the ones that contain a subst ...

When object signatures match exactly, TypeScript issues a warning

I am facing an issue with typescript while trying to use my own custom type from express' types. When I attempt to pass 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>' as a parameter of type 'Context&a ...

Angular - obtain a secure reference to a lazily loaded DOM element

In my project, I have a specific template section that should only be present in the DOM when its corresponding object exists. In addition to this requirement, I need to access the form reference and attach an Observable using fromEvent('change') ...

Encountering a Node V18 Peer Dependency Conflicté”™

Can someone please help me understand what's causing this error? Every time I try to install a dependency, this keeps popping up. I'm completely lost and unsure of what's happening. npm ERR! 1 more (the root project) npm ERR! peer ...

Launching a Material UI Modal nested within a parent component

I have a table displaying various teams. Each row in the table has a menu option that, when clicked, should open either a modal or a dialog box. I want to keep the table, menu functionality, and modals as separate components for better organization. Here&a ...

The unit test is running successfully on the local environment, but it is failing on Jenkins with the error code TS2339, stating that the property 'toBeTruthy' is not recognized on the type 'Assertion'

I've been tackling a project in Angular and recently encountered an issue. Running 'npm run test' locally shows that my tests are passing without any problems. it('should create', () => { expect(component).toBeTruthy();}); How ...

Generating an array from multiple worksheets and implementing a For Each loop

Currently, I am working on creating an array from multiple worksheets and utilizing a For Each loop to access them. This method has been quite successful for most tasks, but I have encountered an issue with sorting the tables on the worksheets. Could some ...

Is there a way to programmatically add a timestamp to a form in Angular6?

Is there a way to automatically populate new forms with the current datetime value? this.editForm.patchValue({ id: chatRoom.id, creationDate: chatRoom.creationDate != null ? chatRoom.creationDate.format(DATE_TIME_FORMAT) : null, roo ...

Issue: The Observable type does not contain a timer property

Displayed below is the code snippet: import {Component} from 'angular2/core'; import {Observable} from 'rxjs/Rx'; @Component({ selector: 'my-app', template: 'Ticks (every second) : {{ticks}}' }) export class AppCom ...

What is the correct way to add type annotations to an Axios request?

I have meticulously added type annotations to all endpoints in my API using the openapi-typescript package. Now, I am looking to apply these annotations to my Axios requests as well. Here is a snippet of code from a Vue.js project I have been developing: ...

Modify the System.config() in Angular 2 following the segregation of JavaScript and TypeScript files

My project follows the folder structure of quick-start ToH, which can be found at https://angular.io/docs/ts/latest/tutorial/toh-pt1.html In order to separate the .ts and .js files, I included the following line in the tsconfig.json file: "outDir": "dist" ...

Accessing enum values in a view with Typescript and AngularJS version 1.5

Recently started working with Angular 1.5 and Typescript I have a service that returns data in an array format called devices.headerEntries: [{name:id,value:45} ,{name:Mode,value:1},{name:State,value:2},{name:serialnum,value:123434} I created a componen ...

Is it possible for a redis client to function without having a redis datastore installed?

Currently in my node web server, I am utilizing the npm module known as redis. Upon executing my code... const client = redis.createClient(); client.on("error", function (err) { console.log("Error " + err); }); client.hmset(["key", "test keys 1", "t ...

Retrieving information from an array and displaying it dynamically in Next.js

I've been diving into the Next.js framework lately and I've hit a roadblock when it comes to working with dynamic routes and fetching data from an array. Despite following the basics of Next.js, I'm still stuck. What am I looking for? I ne ...