0
votes

I have a simple app on Angular/rxjs/Ngrx which requests list of default films from the api.

component.ts

export class MoviesComponent implements OnInit {
  private movies$: Observable<{}> = 
  this.store.select(fromRoot.getMoviesState);
  private films = [];

  constructor(public store: Store<fromRoot.State>) {}

  ngOnInit() {
    this.store.dispatch(new MoviesApi.RequestMovies());
    this.movies$.subscribe(film => this.films.push(film));
    console.log(this.films)
  }

effects.ts

  @Effect()
  requestMovies$: Observable<MoviesApi.MoviesApiAction> = this.actions$
    .pipe(
      ofType(MoviesApi.REQUEST_MOVIES),
      switchMap(actions => this.MoviesApiServ.getDefaultMoviesList()
        .pipe(
          mergeMap(movies => of(new MoviesApi.RecieveMovies(movies))),
          catchError(err => {
            console.log('err', err);
            return of(new MoviesApi.RequestFailed(err));
          })
        )
      )
    );

service.ts

export class MoviesApiService {
  private moviesList = [];

  constructor(private http: HttpClient) { }


  public getDefaultMoviesList(): Observable<{}> {
    DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
    return from(this.moviesList);
  }

  public getMovieByTitle(movieTitle: string): Observable<{}> {
    return this.http.get<{}>(this.buildRequestUrl(movieTitle))
      .pipe(retry(3), 
        catchError(this.handleError)
      );
  }

}

DEFAULT_MOVIES is just array with titles.

So my getDefaultMoviesList method is not sending data. But if I replace this.moviesList to hardcoced array of values it works as expected. What I'm doing wrong?

UPD I wanted to loop over the default list of films, then call for each film getMovieByTitle and collect them in array and send as Observable. Is there any better solution?

2
Did you try returning of(this.moviesList); Interesting fact is Observable.of([]) will be an empty array when you subscribe to it. Where as when you subscribe to Observable.from([]) you wont get any value. - nircraft
@nircraft, I wanted to loop over the default list of films, then call for each film getMovieByTitle and collect them in array and send as Observable. Is there any better solution? and yes I tried of() and nothing happend - Ievghen Chernenko
Yes you can do something like this: const apiCalls = []; DEFAULT_MOVIES.forEach(movie=> { apiCalls.push(this.getMovieByTitle(movie)); }); Observable.forkJoin(apiCalls).subscribe(responses => {...}); - nircraft
@nircraft, it's weird but I tried of() once again and now it works as expected. Thank you so much! Now I can accept your answer - Ievghen Chernenko
glad it helped. - nircraft

2 Answers

-1
votes

You can try creating the observable using of operator.

Ex: of(this.moviesList);

One intersting fact to note is that Observable.of([]) will be an empty array when you subscribe to it. Where as when you subscribe to Observable.from([]) you wont get any value.

Observable.of, is a static method on Observable. It creates an Observable for you, that emits value(s) that you specify as argument(s) immediately one after the other, and then emits a complete notification.

0
votes

1) You should probably move this line to the service contructor, otherwise you will push a second array of default movies every time you getDefaultMoviesList:

DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));

2) Actually you should probably merge the output of each http.get:

  public getDefaultMoviesList(): Observable<{}> {
    return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
      .pipe(retry(3), 
        catchError(this.handleError)
      )))
  }

3) You should actually only do that once and store it in BehaviorSubject not to make new HTTP request on each getDefaultMoviesList

private movies$: BehaviorSubject<any> = new BehaviorSubject<any>();

public getMovies$() {
  return this.movies$.mergeMap(movies => {
    if (movies) return of(movies);
    return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
      .pipe(retry(3), 
        catchError(this.handleError)
      ))) 
  })
}

4) Your implementation shouldn't work at all since:

public getDefaultMoviesList(): Observable<{}> {
  DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => 
        this.moviesList.push(item))); // This line will happen after http get completes
  return from(this.moviesList); // This line will happen BEFORE the line above
}

So you will always return an Observable of empty array.

5) You shouldn't use map if you don't want to map your array to another one. You should use forEach instead.

map is used like this:

const mappedArray = toMapArray.map(element => someFunction(element));