Home > Back-end >  How do i add a property from one Observable to the second Observable?
How do i add a property from one Observable to the second Observable?

Time:04-21

I am trying to add an address Observable to a user Observable.

export interface Address {
 country: string;
 state: string;
 city: string;
 street: string;
 zipCode: number;
}

export interface User {
 id: number;
 name: string;
 address?: Address;
}

The operative code is:

// Observables - declaration
let users$: Observable<User[]>;
let address$: Observable<Address[]>;

// Observables - assignation
users$ = of(users);
address$ = of(address);

const getUser = (userId: number) => of(users[userId]);

users$ = address$.pipe(
 switchMap((address: Address[]) => {
    const userArray$: Observable<User>[] = [];
    address.forEach((add, index) => {
      const user$: Observable<User> = getUser(index).pipe(
        map((user$) => ({...user$, address: add})),
        tap(userArray$ => console.log('Alla',userArray$))
      )
      userArray$.push(user$)
      tap(userArray$ => console.log('Allb',userArray$))  
    })
    return forkJoin(userArray$);
  })
 
)

// Subscription
users$.subscribe(() => console.log(users))

Several questions:

  1. Why doesn't the user$ subscription have an address Object?
  2. Why does the first tap execute and show an address object while the second tap does not appear to execute?
  3. Is concatMap the proper mapping operator?

A StackBlitz is provided here.

CodePudding user response:

Why doesn't the user$ subscription have an address Object?

The code in your question has some significant differences from the code in the stackblitz. With the code in your question, it does have an address object. With the code in the stackblitz, the reason it doesn't have an address object is that you're just using getUser(index) with no modification. You define a function on 73 that looks like a mapping function, but you aren't passing it into map

Why does the first tap execute and show an address object while the second tap does not appear to execute?

The second tap is on its own line, and not part of a pipe. Calling tap on its own doesn't do much. It (and other operators) returns a description of how you want the modified observable to behave, but you then need to pass that into .pipe to create the new observable.

Is concatMap the proper mapping operator?

Your case is simple enough (all the source observables are synchronous and emit only one value) that switchMap, concatMap, and mergeMap would all have the same result. For more complicated cases where multiple values are spread out in time, here's the difference:

concatMap: When the first value is emitted by the source observable, we begin work on the first mapped observable. That first observable must complete before we can move on to the second value of the source observable. concatMap is useful when you need to guarantee that everything happens in an exact order, and nothing terminates early. But if you have observables that never end, this will block other observables from ever running.

switchMap: When the first value is emitted, we begin work on the first mapped observable. This will continue as long as there are no more values from the source, but once a new value is emitted from the source, we will cancel the first mapped observable and switch to the second one. switchMap is useful in cases where a new value is an indication that we need to stop our work and start over.

mergeMap: Any time a value is emitted, we will work on its mapped observable too. Values will be emitted by the multiple mapped observables in whatever order they happen to arrive, and nothing will ever be cancelled. mergeMap is useful when you don't care about order, and just want to process things as soon as they arrive, in the order they arrive.

CodePudding user response:

I believe what you are looking for is to subscribe to the whole operation where you are adding the address data:

address$.pipe(
 switchMap((address: Address[]) => {
    const userArray$: Observable<User>[] = [];
    address.forEach((add, index) => {
      const user$: Observable<User> = getUser(index).pipe(
        map((user$) => ({...user$, address: add})),
        tap(userArray$ => console.log('Alla',userArray$))
      )
      userArray$.push(user$)
      tap(userArray$ => console.log('Allb',userArray$))  
    })
    return forkJoin(userArray$);
  }) 
).subscribe(console.log) // with address

// without address
users$.subscribe(console.log)

The second users$.subscribe is triggered synchronously, it doesn't wait for the above pipe operations to finish

  • Related