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 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)
}
)
)