4

I have been wrestling with this problem and feel like I have a fundamental misunderstanding. I am using the redux-observable library in React which glues redux together with RxJS for handling asynchrony. My problem is that I have to handle a large upload and I want to show progress as the file is loaded.

The function uploadFileEpic needs to return an Observable<Action> to work with redux-observable. The uploadObservable represents the workflow that I want to accomplish. If I just return the uploadObservable the upload works but I don't get any handleUploadFileProgress actions from the progressSubscriber in the ajax call. Ideally the progressSubscriber would be adding elements to another observable that I could merge with uploadObservable. You see me trying to use merge here but the TypeScript compiler complains saying the return is not assignable to an ObservableInput.

I keep going in circles so I feel my understanding must be fundamentally off. I feel like I'm missing some simple RxJS magic here. Thanks for the help!

import { Observable, Observer, Subscriber, Subject, of } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { ofType } from 'redux-observable';
import { catchError, delay, map, mergeMap, tap, merge } from 'rxjs/operators';
import { apis } from '../../config';

export const enum ActionType {
  InitialFileUpload
  FileProgress
  UploadFileSuccess
  UploadFileFail
}

const handleInitialFileUpload = (file: File, timeLimit: number) => ({
  type: ActionType.InitialFileUpload,
  file,
  timeLimit
})

const handleFileProgress = (file: File, percentComplete: number) => ({
  type: ActionType.FileProgress,
  file,
  percentComplete
})

const handleUploadFileSuccess = (file: File, timeLimit: number) => ({
  type: ActionType.UploadFileSuccess,
  file,
  timeLimit
})

const handleUploadFileFail = (file: File, timeLimit: number) => ({
  type: ActionType.UploadFileFail,
  file,
  timeLimit
})


export const uploadFileEpic= action$ =>
  action$.pipe(
    ofType(ActionType.InitialFileUpload),
    mergeMap((action: any) => {
      const { file, timeLimit } = action;
      const data = new FormData()
      data.append('importFile', file, file.name)
      data.append('timeLimit', timeLimit)
      const progressSubject = new Subject();

      const ajaxRequest = {
        url: apis.gateway.run,
        method: 'POST',
        body: data,
        headers: {},
        progressSubscriber: Subscriber.create(
          (e: ProgressEvent) => {
            const percentComplete = Math.round((e.loaded / e.total) * 100)
            console.log("Progress event")
            progressSubject.next(handleUploadFileProgress(file, percentComplete))
          }
        )
      }

      const uploadObservable = ajax(ajaxRequest)
        .pipe(
          map(res => handleUploadFileSuccess(file)),
          delay(SUCCESSFUL_UPLOAD_NOTIFICATION_LENGTH),
          map(() => handleUploadFileRemove(file)),
          catchError(error => of(handleUploadFileFail(file, error.response)))
        )

      return merge(uploadObservable, progressSubject)
      }
    )
  )
Matthew Crews
  • 4,105
  • 7
  • 33
  • 57

1 Answers1

1

You seem to be importing merge from rxjs/operators. There, merge is treated as an operator and thus returning an OperatorFunction. By importing from simply rxjs you get the static merge that correctly returns an Observable which will be flattened by your mergeMap.

rsmidt
  • 69
  • 1
  • 7