简体   繁体   中英

Transforming an Array of Observables to an Array

I'm using forkJoin to turn several observables into one which I then map over to transform into some view model that I use for my view in my component.

My model is as follows:

export interface MyModel {
    itemOne: Observable<ItemOne[]>;
    itemTwo: Observable<ItemTwo[]>;
    itemThree: Observable<ItemThree[]>;
}

My forkJoin looks like:

this._subscriptions.push(this.myService.getData()
    .flatMap(data => Observable.forkJoin(data
        .map(data => {
            let myModel: MyModel = {
                itemOne: this.myService.getSomeData(),
                itemTwo: this.myService.getSomeData(),
                itemThree: this.myService.getSomeData()
            };
            return Observable.forkJoin(Observable.from([myModel]));
}))).subscribe(details => {
    details.map(this.toMyModelViewModel.bind(this));
}));

Now, in my toMyModelViewModel method, I'd like to pick each value from these Observables and was wondering how I could accomplish this?

toMyModelViewModel(value: MyModel): any {
    return {
        itemOne: value.itemOne,
        itemTwo: value.itemTwo,
        itemThree: value.itemThree
    };
}

Do I have to subscribe to each value to get the current value or is there another, possible better/cleaner way of doing this? Ultimately, I'd like to have an array of MyModel objects that way I don't have to worry about using data | async data | async in my view.

Thanks

From a quick scan of your code, it seems that you have an initial api call ( this.myService.getData() ) from which you will construct your result, but that result is based on combining the results of several api calls ( this.myService.getSomeData() ).

I feel like you need the MyModel interface to have no Observables:

export interface MyModel {
    itemOne: ItemOne[];
    itemTwo: ItemTwo[];
    itemThree: ItemThree[];
}

... toMyViewModel is a factory method:

function toMyModelViewModel(itemOne: any, itemTwo: any, itemThree: any): MyModel {
    return {
        itemOne: itemOne as ItemOne[],
        itemTwo: itemTwo as ItemTwo[],
        itemThree: itemThree as ItemThree[]
    } as MyModel;
}

... then you'd use switchMap / forkJoin as follows:

this.myService.getData().switchMap(data => {
  return Observable.forkJoin(
    this.myService.getSomeData(),
    this.myService.getSomeData(),
    this.myService.getSomeData(),
    toMyModelViewModel
}).subscribe(...);

You can convert the array of observables to an observable of an array, using whichever method you like -- the one that Miller shows in his answer will work. I personally like the combineLatest trick (quoting from redent84's answer to this question: How do I create an observable of an array from an array of observables? ).

let arrayOfObservables: [Observable<E>] = ...
let wholeSequence: Observable<[E]> = Observable.combineLatest(arrayOfObservables) 

Now, for your real question:

Do I have to subscribe to each value to get the current value or is there another, possible better/cleaner way of doing this? Ultimately, I'd like to have an array of MyModel objects that way I don't have to worry about using data | async in my view.

Unfortunately, the only way to get an Array<any> out of an Observable<Array<any>> is to subscribe to that observable and save the inner value to an array defined elsewhere, or (if you're using Angular as you are), to use the async pipe.

You can think of it this way: once an Observable , always an Observable . If the value is wrapped up in an Observable , you cannot get it out. The closest thing to this behavior might be using BehaviorSubject with the getValue method, but it's definitely not an option for Observables. This is a good thing because it prevents you from trying to use an asynchronous value "synchronously" in your program.

I also feel like I should mention that the async pipe is a really useful utility and should be preferred to manually subscribing in general. When you manually create a subscription in your component class, you will need to remember to save that subscription and clean it up in the ngOnDestroy lifecycle hook. The async pipe is great because it does all of this cleanup for you. Plus, you can use it with the safe navigation operator to handle values that are possible undefined.

<ul>
  <li *ngFor="let obj of (possiblyUndefined$ | async)?.objects">
    {{ obj.title }}
  </li>
</ul>

That code would be a lot more complicated if you wrote it all in your component class.


To summarize:

1) You can use Observable.combineLatest(arrayOfObservables) to squish your array of observables into an observable of an array.
2) You cannot get an array "out of" an observable. You must either subscribe to that observable or use the async pipe.

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