简体   繁体   中英

Merging a Higher-order Observable into an existing object

I have an object that needs to get populated with information that I have to fetch over the wire. The new info should be stored inside the same object

Eg:

const apps = {
com.foobar.bar: {
    title:pancake,  
    existingInfo: bar
    },
com.company.android: {
    title: foobar,
    existingInfo: barfoo
    }
}

For every id inside apps I want to call a rest API which will give my some addition fields that I can add to the object. The stream should not wait for all the rest calls to be done, it should start by returning the initial value and then emit an updated object for every restcalls that it receives.

Først time any of the rest calls returns it should only populate the correct field.

{
    com.foobar.bar: {
        title:pancake,  
        existingInfo: bar,
        newStuff: foobar  //let say this is the response we get from the first restcall
        },
    com.company.android: {
        title: foobar,
        existingInfo: barfoo
    }
}

Update: I managed to get the code working but I think it perhaps could be done in a different way.

const apps$ = of(apps).pipe(
  mergeMap((apps:any) => { //**1**
    const appPromise = Object.keys(apps).map((appId) => {
      return fetchExternalAppInfo(appId).pipe(take(1));
    })
    appP.unshift(of(apps));
    return of(appP).pipe(mergeAll())
  }),
  mergeAll(),
  scan((acc, curr) => { //**2**
    if (!acc) {
      acc = curr;
    } else {
      acc[curr.appId] = Object.assign(acc[curr.appId], curr)
    }
    return acc
  }, null),
  map((result) => { //converting to an array which is ordered by title
    return _.orderBy(result, 'title')
  }),
)     

** 1 = This will return an array of Observable which has to be passed directly to mergeAll(). Is this the correct way to subscribe and merge an array of Observable

** 2 = I know that the first Object that is passed to this function is my initial value (appPromise.unshift(of(apps))). This is how I can keep the old initial value and add new value to it.


* UPDATE 2 *

This is another solution and I think this is much better: Here I'm using combineLates to edit the initial object.

const fetchedApps$ = applist$.pipe(
  mergeMap((apps:any) => { //*1
    const appPromise = Object.keys(apps).map((appId) => {
      return fetchExternalAppInfo(appId).pipe(take(1));
    })
    return of(appP).pipe(mergeAll())
  }),
  mergeAll(),
  scan((acc, curr:any = {}) => {
    acc[curr.appId] = curr;
    return acc;
  }, {}),
);

const apps$ = combineLatest(of(apps), fetchedApps$).pipe(
  map(([a,b]) => {
    return _.merge(a,b)
  }),
  map((result) => {
    return _.orderBy(result, 'title')
  }),

//*1 Are there any other way to "start subscribing" on an array of observables other than calling mergeAll() twice?

I think you're already very close to the best solution. Reading through your code, I think those hints can help you improve your code:

  • from transforms an array of values into a stream of values, that way those Object.keys can be merged into the outer observable

  • scan could be provided with apps as the initial value, that should remove the need to merge later (as long as I'm not missing anything)

  • startWith lets us sneak in an initial value, that way you can implement the immediate first notification

This could be an implementation:

const fetchedApps$ = applist$.pipe(
    mergeMap((apps:any) => from(Object.keys(apps))),
    mergeMap(key => fetchExternalAppInfo(key).pipe(first())),
    scan((acc, curr:any = {}) => {
        acc[curr.appId] = curr;
        return acc;
    }, apps),
    startWith(apps),
    map(result => _.orderBy(result, 'title'))
);

Tell me if this works for you or if there is something in your code that prevents

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM