5
votes

I am learning NGRX and RXJS in my Podcast project.

My current question is: how to call progress Actions within my @effect based on my Download Action.

The effect looks like this and works:

@Effect()
downloadEpisodes$ = this.actions$.ofType( episodeActions.DOWNLOAD_EPISODE )
.pipe(
  map( ( action: episodeActions.DownloadEpisodes ) => action.payload ),
  switchMap( ( episode ) => {
    return this.episodesService
      .download( episode ) // -> download the media file as blobl
      .pipe(
          map( event => this.episodesService.getHttpProgress( event ) ), // => report download progress
          tap( message => {
            console.log( message );

          } ),
          // switchMap( response => new episodeActions.DownloadProgressEpisodes( response ) // => doesn't work cause its not iterable accoring to typescript.
          last(), // => emit last (completed) message to caller
          switchMap( response => {
            return this.episodesService.downloadSuccess( response ) // => process response blob
            .pipe(
              tap( response  => console.log('--->', response )),
              map( response => new episodeActions.DownloadEpisodesSuccess( response ) ),
              catchError( error => of (new episodeActions.DownloadEpisodesFail( error ) ) )
            )
          })
      )
  } )
);

This will report the following in my console:

Downloading file
episodes.effect.ts:57 File is 7% downloaded.
episodes.effect.ts:57 File is 11% downloaded.
episodes.effect.ts:57 File is 15% downloaded.
episodes.effect.ts:57 File is 18% downloaded.
episodes.effect.ts:57 File is 26% downloaded.
episodes.effect.ts:57 File is 30% downloaded.
episodes.effect.ts:57 File is 44% downloaded.
episodes.effect.ts:57 File is 48% downloaded.
episodes.effect.ts:57 File is 70% downloaded.
episodes.effect.ts:57 File is 74% downloaded.
episodes.effect.ts:57 File is 100% downloaded.
episodes.effect.ts:57 HttpResponse {headers: HttpHeaders, status: 200,             statusText: "OK", url: "http://localhost:3000/episodes/https%3A%2F%2Fwww.s…ple-videos.com%2Faudio%2Fmp3%2Fcrowd-cheering.mp3", ok: true, …}

What do I want to change?

Well, the tap message under the getHttpProgress function is called as many times until the upload is done. Then the last() call forwards the httpResponse to the next switchMap.

What I would like to achieve is to dispatch an action new episodeActions.DownloadProgressEpisodes( message ) during the Download progress (multiple times) to store the download progress within the episode (call the reducer to update the episode entity).

Unfortunately, I don't understand how to dispatch multiple DownloadProgressEpisodes actions during the download process without breaking or the last switchMap that calls the this.episodesService.downloadSuccess( response ). I tried to add a action call after the tap, before the last call as it seemed to be an Iterator. However, it didn't dispatch those actions, nothing showed up in my redux devTool. When I dispatch the DownloadProgressEpisode from my service within the getHttpProgress call, It works but I am not sure if that is the proper way.

In the end I want to see the following in my redux devTool:

[Podcast] Download Episode;
[Podcast] Download Episode Progress; // payload 0%
[Podcast] Download Episode Progress; // payload 10%
...etc
[Podcast] Download Episode Progress; // payload 100%
[Podcast] Download Episode Success;

What is the best way to solve this?

Or alternatively, should I use different effects for the Download, which calls an effect for the DownloadProgress which repeats itself until finished and then call and effect for DownloadSuccess?

1

1 Answers

2
votes

I would split this effect in several smaller ones. Right now you're doing several things in the same effect: download, update progress and process the result. You can try to create an effect for each of them.

Download the episode:

@Effect()
downloadEpisode$ = this.actions$.pipe(
  ofType(episodeActions.DOWNLOAD_EPISODE),
  switchMap(({ payload }) => this.episodesService
      .download(payload).pipe(
         map(response => new episodeActions.DownloadEpisodesSuccess(response)),
         catchError(err => of(new episodeActions.DownloadEpisodesFail(error))),
      )
  )
)

Catch the download success action and process it:

@Effect()
processEpisode$ = this.actions$.pipe(
  ofType(episodeActions.DOWNLOAD_EPISODE_SUCESS),
  switchMap(({ payload }) => this.episodesService
      .downloadSuccess(payload).pipe(
         map(response => new episodeActions.ProcessEpisodesSuccess(response)),
         catchError(err => of(new episodeActions.ProcessEpisodesFail(error))),
      )
  )
)

And about showing/updating the progress:

@Effect()
updateProgress$ = this.actions$.pipe(
  ofType(episodeActions.DOWNLOAD_EPISODE),
  switchMap(({ payload }) => this.episodesService
      .getHttpProgress(payload).pipe(
         map(res => new episodeActions.DownloadEpisodesProgress(res)),
      )
  )
)

This way of showing the progress assumes that getHttpProgress method will return a new observable each time a new episode is downloaded. We listed for the action that notifies the download was started and then we switch map to the output of getHttpProgress.