Home > database >  Angular 11 RxJS: concatMap for each array item to call ReplaySubject Observable and finalize not w
Angular 11 RxJS: concatMap for each array item to call ReplaySubject Observable and finalize not w

Time:01-21

I am not an expert with Rxjs. I have the following code. I am trying to upload multiple files and for each file, i want to base64 convert and have all the result in final array. Below code is not working and the finalize is not invoked at all. Can anyone help me what the problem with my code?

private validateSelectedFiles(files: FileList): void {
  this.spinnerService.show();
  from(files)
    .pipe(
      concatMap(f => this.convertFile(f)),
      finalize(() => {
        console.log('finalize'); //not coming here
        this.spinnerService.hide();
      }),
      scan((acc, curr) => {
        acc.push(curr)
        return acc;
      }, [])
    ).subscribe(result => {
      console.log('result', result); //Only once it is coming here
    })
}

private convertFile(file: File): Observable<string> {
  const result  = new ReplaySubject<string>(1);
  const reader = new FileReader();
  reader.readAsBinaryString(file);
  reader.onload = (event) => {
    result.next(btoa(event.target.result.toString()))
  };
  return result;
}

Expected functionality:

  1. for each upload files, convert it to base64
  2. get the array of base64 converted result for further processing

Following code worked for me:

private convertAttachmentsToBase64(): void {
        forkJoin(Array.from(this.addedFiles).map(file => this.convertFile(file)))
        .pipe(
            takeUntil(this.destroy$),
            finalize(() => this.spinnerService.hide()),
        )
        .subscribe((result) => this.attachmentsToUpload = result);      
    }   



// Convert file to Base64   
    private convertFile(file: File): Observable<AttachmentToUpload> {
        const result  = new Subject<AttachmentToUpload>();
        const reader = new FileReader();
        reader.readAsBinaryString(file);
        reader.onload = (event) => {
            result.next({
                fileName: file.name,
                data: btoa(event.target.result.toString())
            })
            result.complete();
        };
        return result;
    }   

CodePudding user response:

I could suggest the following

  1. Use RxJS forkJoin Array#from Array#map instead of from scan combination.
  2. Use new Observable() to create an observable instead of a ReplaySubject.
  3. I don't see a HTTP request in the question at the moment. I've added it using a switchMap operator.
  4. I am not sure if the convertFile() actually converts the file to Base64. As an alternative you could try this solution.
  5. Important: forkJoin will only emit if all the source observable emit. So if the onload function isn't triggered by the FileReader(), the forkJoin won't emit and the finalize and subscribe won't be triggered.
private validateSelectedFiles(files: FileList): void {
  forkJoin(Array.from(files).map(file => this.convertFile(file))).pipe(
    switchMap((filesB64) => this.http.post('some url')),
    finalize(() => this.spinnerService.hide())
  ).subscribe({
    next: (result: any) => {
      // handle response
    },
    error: (error: any) => {
      // handle errors
    }
  });
}

private convertFile(file: File): Observable<string> {
  return new Observable((observer: any) => {
    const reader = new FileReader();
    reader.onload = (event: any) => {
      observer.next(btoa(event.target.result.toString()));
      observer.complete();
    };
    reader.readAsBinaryString(file);
  });
}

Update: Add Array#from() for FileList (credit: https://stackoverflow.com/a/40902462/6513921)

CodePudding user response:

finalize() is a tear-down handler that is called when the chain is terminated (after source Observable completes, errors or the chain is unsubscribed).

In your case you're using concatMap() to merge another Observable into the chain. However, you're merging ReplaySubject that never completes on its own so the chain is never terminated and thus finalize() is never invoked.

It doesn't look like you even need to be using ReplaySubject so you can complete the Subject immediately when the file is loaded.

private convertFile(file: File): Observable<string> {
  const result  = new Subject<string>();
  const reader = new FileReader();
  reader.readAsBinaryString(file);
  reader.onload = (event) => {
    result.next(btoa(event.target.result.toString()));
    result.complete();
  };
  return result;
}
  •  Tags:  
  • Related