In Typescript Angular, how can I invoke a function on each element of an array as part of a sequence of Observables, and then return the total number of successful operations?

I am faced with the task of creating a parent record followed by multiple child records (order does not matter), and ending with a logging action.

I am knowledgeable on how to chain single actions on an observable by mapping them together. For example:

-- create one parent record and return its ID:

createParentRecord(parentInfo: parentType): Observable<number>

-- create one child record and return its ID

createChildRecord(childInfo: childType): Observable<number>

-- log the entry

logStuff(parentId: number, childId: number)

-- the composite function executes all the actions and returns the parent ID

doTheThing(): Observable<number> {
   /* ... */
   let parentId: number;
   let childId: number;

   return createParent(parentInfo).pipe(
     tap(id => (parentId = id)),
     mergeMap(id => { childInfo.parentId = parentId; return createChildRecord(childInfo);} ),
     mergeMap(childId => { childInfo.childId = childId; return logStuff(parentId, childId); ),
     map( () => parentId)
   ); 
}

Question

This method works perfectly.

However, if I have an array of childInfo[] instead of just childInfo, how do I approach this? How can I handle an array of childInfo[] to subscribe to and retrieve inner observable results? I simply want a count of successful results or any results returned by each observable. Should I use forkJoin()?

I am struggling to find a solution to this. Can someone offer some assistance?

Answer №1

If I have understood your query correctly, my approach would be as follows. I have provided comments inline for better understanding.

function doTheThing(): Observable<any> {
   /* ... */
   let parentId: number;
   // Assume there is an array containing information about children to be saved
   const childInfos = new Array(3).fill({})

   return createParent('Parent Info').pipe(
     tap(id => (parentId = id)),
     concatMap(id => { 
         // Each childInfo object is updated with the result of the createParent operation
         childInfos.forEach(c => c.parentId = parentId); 
         // An array of Observables is created - each representing a createChildRecord operation
         const childInfosObs = childInfos.map(ci => createChildRecord(ci))
         // All child record operations are executed concurrently using forkJoin
         return forkJoin(childInfosObs);
      }),
     concatMap(childIds => { 
       // forkJoin returns an array of results in the same order as the array of Observables passed to it
       // We loop through the array to link each childInfo with its corresponding result
       childInfos.forEach((ci, i) => ci.childId = childIds[i])
       // Finally, we log the results of forkJoin and return them
       return logStuff(parentId, childIds).pipe(
         map(() => childIds)
       );
     } )
   ); 
}

It is worth noting that I prefer using concatMap over mergeMap for database or REST operations to maintain the sequence of actions, unless a specific reason warrants otherwise.

If necessary, you can also add error handling logic to each observable within childInfosObs, as forkJoin will throw an error if any of the Observables it handles encounters an error.

For a demonstration of this approach, you can refer to this StackBlitz example.

Answer №2

Blockquote

To simplify the process, you can convert your ChildInfo[] to an observable array and then merge it using the following approach:

return createParent(parentInfo).pipe(
  tap(newParentId => parentId = newParentId),
  mergeMap(newParentId => forkJoin(
   childInfos.map(childInfo => { // utilizing an Array map.
     childInfo.parentId = parentId;
     return createChildRecord(childInfo); // this should return childInfo, as it resembles a CRUD operation.
   })
  )),

It's important to note that this method only functions if createChildRecord completes, as forkJoin will not emit otherwise. My solution assumes it's a single call. By having createChildRecord return the updated childInfo, your future operations will be much smoother.

createChildRecord(childInfo: ChildInfo): Observable<ChildInfo> {

  const childId = /** TBD **/
  return of({...childInfo, childId});
}

To enhance readability, consider enclosing the inner observable within a separate method.

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

What are the best practices for integrating RxJS into Angular 2 projects?

How can I write code like this in Angular 2? var closeButton1 = document.querySelector('.close1'); var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click'); I have attempted various methods to incorporate this into an An ...

Exploring Typescript's conditional types and narrowing branches

When I use the following code snippet: type Identity <T extends string> = T; type MaybeString = string | undefined; type StringOrNever = MaybeString extends undefined ? never : Identity<MaybeString>; The compiler raises an error stating that ...

The Vue router fails to load when using create-vue@3

I've been experimenting with the Vue Router, but it's not giving me the expected outcome. While following the tutorial on https://router.vuejs.org/guide/, I found that if I use the CDN and place it in a standalone HTML file, it works fine. Howev ...

Animate specifically new items in a list rendered via ngFor in Angular animation

I am working with a list of array items in an ngFor loop. I have a basic button that adds an item to the array. My goal is to apply an animation only to the newly added items, but currently all the existing list items also receive the animation upon page l ...

Using Rollup alongside @rollup/plugin-babel and Typescript: Anticipated a comma, received a colon instead

I've encountered a problem while working with Rollup 4: [!] RollupError: Expected ',', got ':' (Note that you need plugins to import files that are not JavaScript) src/index.ts (48:19) Although my Babel configuration appears to ...

What is the best way to define a category in order to utilize a saved string as a variable for referencing it?

An object named CONFIG holds the following information: export const CONFIG = { buttonDestinations: { detailedStats: `detailedStats`, mealPlans: `mealPlans`, products: `products` }, buttonTexts: { detailedStats: ...

Encountering the error "TS(2604): JSX element type 'App' does not have any construct or call signatures" while trying to export an array of JSX Elements

I have a function that returns an array of JSX Elements. When I pass this to ReactDOM.render, I encounter the error mentioned above. wrappers.tsx const FooterWithStore:React.FC = () => ( <Provider store={store}> <FooterLangWrapper ...

How can nested json be sorted effectively based on two specific fields?

Example Data: [{ 'ID': objID(abc123), 'Department': 'IT', 'Employees': [ { 'ID': 3, 'StartDate': '24-12-2022T08:30', 'active': true }, { ...

Creating a composite object in Angular 2 reactive forms: a step-by-step guide

These are the two classes I have: export class Machine { name = ''; computer = new Computer() } export class Computer { os = ''; } Then in my component, using reactive forms, I have: ngOnInit() { this.form = this.fb ...

What is the best way to increase the height of an image beyond the limits of its container, causing it to overlap with other elements

My challenge involves creating a horizontal scrolling list of movie posters. I want the posters to grow in size when hovered over, expanding outside of their container and overlapping other elements. I attempted to use 'position: absolute' on the ...

Dynamic getter/setter in Typescript allows for the creation of functions

I'm facing a challenge in making Typescript automatically infer types for dynamically created getter and setter functions. In my code, I have a class called MyClass which contains a map of containers: type Container = { get: () => Content s ...

How to dynamically add a component in Angular 7/8 when a button is clicked

I am facing an issue with importing a common component when a button is clicked. The component contains standard HTML elements which can be viewed on Stackblitz However, upon clicking the button, an error is thrown: Error: Cannot read property 'c ...

I am looking for guidance on the proper way to import MatDrawer and MatDrawerContainer in the app.module.ts file for an Angular

When attempting to implement a side nav using angular material and clicking on the toolbar icon, everything was functioning correctly until I encountered an error while trying to open the navbar: The error message displayed: Unexpected directive 'Ma ...

What methods can be implemented to ensure ComponentOverride's universality?

These type definitions for markdown-to-jsx don't seem to be generic enough, causing issues like the one mentioned below. For more details, refer to Why is type SFC<AnchorProps> not assignable to type SFC<{}>? /Users/sunknudsen/Sites/sunk ...

Angular: Dynamically add or delete an interceptor based on conditions

Is it possible to dynamically include or exclude an interceptor based on user selection? For my application, I want to enable Azure AD based SSO using the @azure/msal-angular package https://www.npmjs.com/package/@azure/msal-angular that provides an inter ...

Implement a class in Typescript that allows for the addition of properties at runtime

I'm currently in the process of incorporating Typescript definitions into an existing codebase that utilizes the Knockout library. Within the code, there is a prevalent pattern that appears as follows: interface SomeProperties { // Assorted prope ...

What methods can I use to create an RXJS stream that only updates the state once all sequential calls have been successful?

Currently, I am delving into rxjs within the realm of Angular. However, I am facing difficulties in merging these concepts to accurately portray the following scenario: I initiate an HTTP request to an endpoint, which returns some data This data is then u ...

Incorrect tsx date interpretation when dealing with years such as 0022

I am facing an issue with dates in tsx. The problem lies in the fact that when I set a date like 30/11/0022, it interprets the date as 30/11/1922, which is incorrect. Here is the input element I have in tsx: <FormikField name="Birthdate" disa ...

Issue with custom Angular-Slickgrid formatter: Bootstrap NGbTooltip not functioning as expected

In my Angular application with Bootstrap 4, I have implemented angular-slickgrid. I have created a custom formatter for a specific column named 'detail' as shown below: Custom formatter code in (custom.formatter.ts file): export const detailFor ...

Steps to define a JavaScript mixin in VueJS

Currently, I am working on a Vue project with TypeScript and in need of using a mixin from a third-party library written in JavaScript. How can I create a .d.ts file to help TypeScript recognize the functions defined in the mixin? I have attempted the fol ...