Search code examples
javascripthtmlangularrxjsngrx

Angular - Passing Array of Observables as @Input


I'm trying a replace a current NGRX implmentation of binding data in template to reusable component due to change in dataStructure of @Input.

HTML:
//replace
"<child [summary]="summaryTitle$ | async" ></child >"

//with
"<child  [summary]="summaryTitles" ></child>"
parent.comp.ts
//replace
summaryTitle$ = this.store.select(Title)

//with
summaryTitles = [{title:summaryTitle$ , text: "someConstantValue"},...]

Here I need to resolve the summaryTitles array before passing it to the <child>, any easy method to do so like using async pipe for single result.

the Idea is to re-use the existing <child> without making much changes and I wont able to add any changes to the @store as well


Solution

  • Edit: If text is never an observable:

    const result$ = combineLatest(
      summaryTitles.map(({ title, text }) => 
        title.pipe(map(title => ({ title, text })));
      )
    );
    

    A minimal reproducible example with something like stackblitz would be good, it's a pain to reproduce ngrx state and all, but something like this should work:

    import { of, map, combineLatest } from 'rxjs';
    
    const summaryTitles = [
      { title: of('1'), text: of('2') },
      { title: of('3'), text: of('4') }
    ];
    
    const result$ = combineLatest(
      summaryTitles.map(({title, text}) =>
        combineLatest([title, text]).pipe(
          map(([title, text]) => ({ title, text }))
        )
      )
    );
    
    result$.subscribe(console.log);
    

    You may want to change combineLatest for forkJoin or zip. The doc's operator decision tree and learnrxjs.io are great for that.

    Stackblitz: https://stackblitz.com/edit/rxjs-9gah4b?file=index.ts

    Edit: If the text field may be an observable or not, use isObservable, for example:

    const result$ = combineLatest(
      summaryTitles.map(({ title, text }) => {
        const text$ = isObservable(text) ? text : of(text);
        return combineLatest([title, text$]).pipe(
          map(([title, text]) => ({ title, text }))
        );
      })
    );
    

    But it may be easier to insure it's an observable somewhere else before that.

    Side note: I noticed now in my stackblitz the final result, which shouold be an array, is an "indexed object" ({ 0: 'foo', 1: 'bar' }). When I try to manually transform it to an array in ways that work in terminal it remains an object 🤷. I'm assuming this is a quirk in stackblitz. Check what you get live.