Return forkJoin selectively



I have a case where an application should return menu based on given context.

Below are the sources of menuA and menuB.

// example of menu derived from given source
  menuA() {
    return [
      {
        a: 'demo1',
        b: 'demo2',
        c: 'demo3'
      },
      {
        e: 'demo4',
        f: 'demo5',
        g: 'demo6'
      }
    ];
  }

  // example of menu fetched from different source eg: db
  async menuB() {
    const ret = [];
    const h = {
      a: 'demo1',
      b: 'demo2',
      c: 'demo3'
    };
    ret.push(h);
    return await ret;
  }

Given my limited knowledge and experience on rxjs, I was hoping something like the snippet below can accept a string of menuA or menuB to return an observable of the required menu:

getMenu$(context: string) {
    return forkJoin(
      {
        menuA: of(this.menuA()),
        menuB: from(this.menuB())
      }
    ).pipe(
      mergeMap((m: any) => {
        return m[context];
      })
    )
  }

The above invokes warning as below:

Argument of type 'Observable<unknown>' is not assignable to parameter of type 'OperatorFunction<{ menuA: ({ a: string; b: string; c: string; e?: undefined; f?: undefined; g?: undefined; } | { e: string; f: string; g: string; a?: undefined; b?: undefined; c?: undefined; })[]; menuB: { a: string; b: string; c: string; }[]; }, unknown>'.
  Type 'Observable<unknown>' provides no match for the signature '(source: Observable<{ menuA: ({ a: string; b: string; c: string; e?: undefined; f?: undefined; g?: undefined; } | { e: string; f: string; g: string; a?: undefined; b?: undefined; c?: undefined; })[]; menuB: { a: string; b: string; c: string; }[]; }>): Observable<...>'.ts(2345)
The 'this' context of type 'void' is not assignable to method's 'this' of type 'Observable<any>'.ts(2684)

Based on a comment below, I have also added the warning message when map is used to replace the mergeMap:

// modified getMenu$:
getMenu$(context: string) {
    return forkJoin(
      {
        menuA: of(this.menuA()),
        menuB: from(this.menuB())
      }
    ).pipe(
      map(m => m[context])
    )
  }

Warning message:

Argument of type 'Observable<unknown>' is not assignable to parameter of type 'OperatorFunction<{ menuA: ({ a: string; b: string; c: string; e?: undefined; f?: undefined; g?: undefined; } | { e: string; f: string; g: string; a?: undefined; b?: undefined; c?: undefined; })[]; menuB: { a: string; b: string; c: string; }[]; }, unknown>'.
  Type 'Observable<unknown>' provides no match for the signature '(source: Observable<{ menuA: ({ a: string; b: string; c: string; e?: undefined; f?: undefined; g?: undefined; } | { e: string; f: string; g: string; a?: undefined; b?: undefined; c?: undefined; })[]; menuB: { a: string; b: string; c: string; }[]; }>): Observable<...>'.ts(2345)
The 'this' context of type 'void' is not assignable to method's 'this' of type 'Observable<unknown>'.ts(2684)

I am using angular 12 and rxjs "~6.6.0".

Answer

DEMO: https://stackblitz.com/edit/typescript-ktm2hz?file=index.ts

function menuA() {
      return [
        {
          a: 'demo1',
          b: 'demo2',
          c: 'demo3'
        },
        {
          e: 'demo4',
          f: 'demo5',
          g: 'demo6'
        }
      ];
    }
    
    // example of menu fetched from different source eg: db
    function menuB() {
      return of([
        {
          a: 'demo1',
          b: 'demo2',
          c: 'demo3'
        }
      ]).pipe(delay(500));
    }
    
    function getMenu$(context: string) {
      return forkJoin(of(menuA()), menuB().pipe(first())).pipe(
        map(([menuA, menuB]) => ({ menuA, menuB })),
        map(m => m[context])
      );
    }
    getMenu$('menuA').subscribe(data => {
      console.log(data);
    });
    getMenu$('menuB').subscribe(data => {
      console.log(data);
    });

UPDATE:

you need to declare your menus. I have created Enum for it

DEMO with Angular 12: https://stackblitz.com/edit/angular-12-template-wcgohi?file=src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs';
import { first, map, delay } from 'rxjs/operators';

enum Menus {
  menuA = 'menuA',
  menuB = 'menuB'
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  menuA(): { [key: string]: any }[] {
    return [
      {
        a: 'demo1',
        b: 'demo2',
        c: 'demo3'
      },
      {
        e: 'demo4',
        f: 'demo5',
        g: 'demo6'
      }
    ];
  }

  // example of menu fetched from different source eg: db
  menuB(): Observable<{ [key: string]: any }[]> {
    return of([
      {
        a: 'demo1',
        b: 'demo2',
        c: 'demo3'
      }
    ]).pipe(delay(500));
  }

  getMenu$(context: Menus): Observable<{ [key: string]: any }[]> {
    return forkJoin([of(this.menuA()), this.menuB().pipe(first())]).pipe(
      map(([menuA, menuB]) => ({ menuA, menuB })),
      map(m => m[context])
    );
  }
  ngOnInit() {
    this.getMenu$(Menus.menuA).subscribe(data => {
      console.log(data);
    });
    this.getMenu$(Menus.menuB).subscribe(data => {
      console.log(data);
    });
  }
}




Source: stackoverflow