Angular data undefined outside subscribe

671 Views Asked by At

I know it was asked million times, but i really don't understand how it works. I tried to new Promise, but there is nothing inside then() i tried to do it with mergeMap but messed up. I want to get current user's favorite items. And to do it i'm calling this method:

user: IUser;
tracks: ITrack[] = [];

public getFavorites() {
    this.userService.getFavourite(this.user.id).subscribe(
      (tracks: ILike[]) => {
        if (tracks) {
          tracks.forEach(track => {
            this.tracks.push(this.loadTracks(track.id));
          });
        }
      }
    );

For this method i need user id which i get like this and :

private async loadUser() {
    this.accountService.currentUser$.subscribe((user: IUser) => {
      // getting into another service, because currentUser don't have id
      this.userService.getUserByUsername(user.username).subscribe((u: IUser) => {
        this.user = u;
      })
    })
  }

and and also track_id:

loadTracks(id: number) {
    let track: ITrack;
    this.trackService.getTrack(id).subscribe(t => track = t);
    return track;
}

ngOnInit():

this.loadUser();
this.getFavorites();

How to do it properly with mergeMap?

3

There are 3 best solutions below

1
On BEST ANSWER

I think you will want to use switchMap in this case.

this.accountService.currentUser$.pipe(
    switchMap((user: IUser)=> {
       return this.userService.getUserByUsername(user.username);
    }),
    tap(user => {
       console.log(user); // Check if its work
    }),
    switchMap(user => {
       return this.userService.getFavourite(this.user.id)
    }),
    tap(tracks => {
       console.log(tracks); // Now you have track
    })
); // You can .subscribe() here or use async pipe

You also can split this logic and reuse

const getUser$ = this.accountService.currentUser$.pipe(
    switchMap((user: IUser)=> {
       return this.userService.getUserByUsername(user.username);
    })
);

Read more about Higher order observable here

1
On

You may find it easier to understand if you keep everything as observables (until you really need to subscribe). We can declare each piece of data as an observable source, and then define one observable from another one.

user$: Observable<IUser> = this.accountService.currentUser$.pipe(
    switchMap(user => this.userService.getUserByUsername(user.username))
);

tracks$: Observable<ITrack[]> = this.user$.pipe(
    switchMap(user => this.userService.getFavourite(user.id)),
    switchMap(tracks => forkJoin(
        tracks.map(t => this.trackService.getTrack(t.id))
    ))
);

Now, simply subscribe to tracks$ to get the list of tracks. No need for extra variables to store data in, no calling "load" methods or even using ngOnit.

Notice how the definition of tracks$ begins with user$. This makes it clear that when user$ emits a new value (accountService.currentUser$ changes), tracks$ will emit the updated tracks list automatically.

Note: You may find this answer helpful as the question involves a similar situation and the answer has a very in-depth explaination.

0
On

You are probably looking for switchMap instead of mergeMap. mergeMap should be used when you want to combine the results of observables.

Here is a working example: https://stackblitz.com/edit/typescript-sdf2gy?file=index.ts

let userLocation$ = of({ userId: 123 }).pipe(delay(2000));
let getFavourite = function (userId) {
  return of({ someData: 'someData: ' + userId }).pipe(delay(1000));
};

userLocation$
  .pipe(switchMap((data) => getFavourite(data.userId)))
  .subscribe((v) => console.log(v));