devqon
devqon

Reputation: 13997

rxjs: Combine result of observables while already displaying the first with async pipe

What is the best way in angular with rxjs to already display the result of a first observable, and combining the data when other observables are finished?

Example:

@Component({
    selector: 'app-component',
    template: `
<div *ngFor="let group of groups$ | async">
    <div *ngFor="let thisItem of group.theseItems">
        ...
    </div>
    <div *ngFor="let thatItem of group.thoseItems">
        ...
    </div>
</div>
`
})
export class AppComponent implements OnInit {
    ...
    ngOnInit() {
        this.groups$ = this.http.get<IThisItem[]>('api/theseItems').pipe(
            map(theseItems => {
                return theseItems.groupBy('groupCode');
            })
        );

        // combine these results? This operation can take 5 seconds
        this.groups$$ = this.http.get<IThatItem[]>('api/thoseItems').pipe(
            map(thoseItems => {
                return thoseItems.groupBy('groupCode');
            })
        );
    }
}

I understand it can be done by subscribing to both, and merge the results. But is it possible to use pipe operators for this, and using the async pipe?

Upvotes: 3

Views: 2311

Answers (3)

Athanasios Kataras
Athanasios Kataras

Reputation: 26450

I think you could go with the combineLatest rxjs operator. This might mean that you change the handling in your template a bit too.

I could not use your example as I don't know your get functions but basically the same principle applies.

Check stackblitz here for an example:

export class AppComponent  {

  private firstObservable = of([{name: 'name1'}]).pipe(startWith([]));
  private secondObservable = of([{name: 'name2'}]).pipe(startWith([]));

  combined = combineLatest(this.firstObservable, this.secondObservable).pipe(
        map(([firstResult, secondResult]) => {
          return [].concat(firstResult).concat(secondResult)
        }) 
   );
}

Html output:

<span *ngFor="let item of combined | async">{{item.name}}<span>

Upvotes: 4

spierala
spierala

Reputation: 2699

You could use merge and scan.

  first$: Observable<Post[]> = this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts?userId=1');
  second$: Observable<Post[]>  = this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts?userId=2');
  combinedPosts$: Observable<Post[]> = merge(this.first$, this.second$).pipe(
    scan((acc: Post[], curr: Post[]) => [...acc, ...curr], [])
  )

https://www.learnrxjs.io/operators/combination/merge.html Make one observable from many.

https://www.learnrxjs.io/operators/transformation/scan.html Scan is similar to array.reduce... you can accumulate the results of each observable emission.

Working example: https://stackblitz.com/edit/angular-lrwwxw

The combineLatest operator is less ideal, since it requires that each observable emits before the combined observable can emit: https://www.learnrxjs.io/operators/combination/combinelatest.html

Be aware that combineLatest will not emit an initial value until each observable emits at least one value.

Upvotes: 2

Timothy
Timothy

Reputation: 3593

Async pipe is just a subscriber to an observable... To answer to your question you can use any possible ways... For example:

<div *ngFor="let group of groups$ | async as groups">
    <div *ngFor="let thisItem of group.theseItems">
        ...
    </div>
</div>

public groups$: Observable<type> = this.http.get<IThatItem[]>.pipe(
  startWith(INITIAL_VALUE)
);

or

public groups$: Observable<type> = combineLatest(
  of(INITIAL_VALUE),
  this.http.get<IThatItem[]>
)

Upvotes: 1

Related Questions