3

So I have two tables. One of which is my user which stores blocks The other stores the data for these blocks.

I am trying to combine these correctly so in my component I get a list of blocks with their data according to dataID

- user
    - blocks
        - block1
            - blockSize
            - blockOptions
                - dataID
        - ...

- data
  - dataId1
      - stuff ...
  - ...

With the following code I get my blocks and data but the data node is still a FirebaseObervable. So I have to use the async in my dumb components. What I am asking is how do I combine these correctly so I only have to use the async pipe and push the data to subcomponents. While not losing dynamic changes.

return this.db.list(`/user/${this.userId}/blocks`).switchMap((blocks:Block[]) => {
  let blockData$ = blocks.map(block => {        
      return this.db.object(`/data/${block.blockOptions.dataID}`)
  });
  return Observable.combineLatest(blockData$,blocks, (bData,block) => {
    block.data = bData;
    return blocks;
  });
})

Template example

//what I want to happen
*ngFor="let block of blocks | async">
<dumb-component [blockData] = "block.data"></dumb-component>

//what I have to do now
*ngFor="let block of blocks | async">
<dumb-component [blockData] = "block.data | async"></dumb-component>

Thanks in advance I feel really close any nudge in the right direction would be appreciated

1 Answers1

3

You are close. It's only the Observable.combineLatest call that needs some changes:

return this.db.list(`/user/${this.userId}/blocks`)
  .switchMap((blocks: Block[]) => {
    let arrayOf$ = blocks.map(block => {
      return this.db.object(`/data/${block.blockOptions.dataID}`)
    });
    return Observable.combineLatest(arrayOf$, (...arrayOfData) => {
      blocks.forEach((block, index) => {
        block.data = arrayOfData[index];
      });
      return blocks;
    });
  });

Your call to blocks.map returns an array of observables, so the combineLatest signature you want to use is this one:

export function combineLatest<T, R>(
  array: ObservableInput<T>[],
  project: (...values: Array<T>) => R,
  scheduler?: IScheduler
): Observable<R>;

You pass it an array of observables and it calls your project function with the results. Just collect them into an array, iterate your blocks, and assign the result at the appropriate index.


If you are using ngrx or OnPush change detection, you should make sure that mutations are not made to the blocks array or the blocks themselves. To avoid mutations, the combineLatest call would be something like this:

return Observable.combineLatest(arrayOf$, (...arrayOfData) => {
  return blocks.map((block, index) => {
    const data = arrayOfData[index];
    if (block.data !== data) {
        // If the block has no data or if the data has changed,
        // copy the block to avoid mutating it.
        return Object.assign({}, block, { data });
    }
    return block;
  }
});
cartant
  • 57,105
  • 17
  • 163
  • 197
  • Cartant Thank you so much. I gave up hammering at it over the weekend. Because my brain was mush. – Shane Moore Aug 07 '17 at 12:21
  • @ShaneMoore, you can upvote or accept the answer if it helped – Max Koretskyi Aug 07 '17 at 13:50
  • you're right, this one is almost exactly the same as [this one](https://stackoverflow.com/a/45529139/2545680) – Max Koretskyi Aug 07 '17 at 13:51
  • @Cartant Okay I jumped the gun. I am only seeing updates from when the Block updates. But if the block data updates I do not see the change until the Block changes. { { "size": { <=== Block Updates size when I change values "cols": 1, "rows": 1 }, "data": { "alerts": "uck", <=== alerts do not update until I update the block } } – Shane Moore Aug 07 '17 at 14:41
  • 1
    `combineLatest` will see any changes. I suspect it's down to the change detection strategy. Be aware that they code in the answer mutates the block when the data changes. That is, it just assigns to the block's `data` property. If you are using `OnPush`, that will be a problem. You will need to tweak the code to ensure the block is cloned if `data` is assigned, etc. – cartant Aug 07 '17 at 16:40
  • @Cartant Okay this is what happened. I was banging my head on the wrong thing. It is not the template I am using ngrx/store and i never updated my state correctly I will post my fix in the comment below – Shane Moore Aug 07 '17 at 19:00
  • Yeah, if you are using `ngrx` you don't want to be mutating data. However, you should be able to do better than cloning every block in the array. See the updated answer. – cartant Aug 07 '17 at 21:17
  • Do you guys have a working example of how to get combinelatest to working with angualrfire2 getting multiple objects? – Ross Rawlins Oct 29 '18 at 09:34