Search code examples
angularrxjsobservablefork-joinforkjoinpool

Angular Rxjs forkJoin error: Cannot read property 'subscribe' of undefined


I'm trying to add multiple attachments to a SharePoint List item. The service method accepts the list of attachments, and it should return the response only after all files uploaded (Sharepoint API only accepts one file at a time). So I'm using rxjs forkJoin for this purpose.

Below is the service class I've written.

import { Injectable } from '@angular/core';
import { Observable, forkJoin } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { ContextService } from './context.service'
import { mergeMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AttachmentService {

  constructor(private httpClient: HttpClient) { }

  addAttachmentsToTheList(listName: string, listItemId: number, attachmentList: any[], diagest: any) {
    //get the file upload observable list
    let fileUploadOnservables: any[] = [];
    attachmentList.forEach(file => {
      fileUploadOnservables.push(this.addAttachment(listName, listItemId, file, diagest))
    });

    // use forkJoin to combine the operations and subscribe to all results
      // use forkJoin to combine the operations and subscribe to all results
      forkJoin(fileUploadOnservables)
      .subscribe(
        (response) => {
        //after all the successful requests do something
      }
      );
  }

}

  //this will add a single attachment to list
  addAttachment(listName: string, listItemId: number, file: any, diagest: any) {
    //first read the file as buffer, and pass the buffer to the Sharepoint REST API
    return this.getFileBuffer(file)
      .pipe(
        mergeMap((fileBuffer: any) => {
          //once file buffer observable returns pass the buffer to the Sharepoint REST API
          return this.httpClient.post(
            "../_api/lists/GetByTitle('" + listName + "')/items(" + listItemId + ")/AttachmentFiles/add(FileName='" + file.name + "')",
            fileBuffer,
            {
              headers: new HttpHeaders({
                'X-RequestDigest': diagest,
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
              })
            })
        })
      );
  }

  //this returns the file buffer
  getFileBuffer(file) {
    return new Observable(subscriber => {
      var reader = new FileReader();
      reader.readAsArrayBuffer(file);
      reader.onload = function (e: any) {
        subscriber.next(e.target.result);
        subscriber.complete();
      };
    });
  }
}

In component:

someMethod(){
 this.attachmentService.addAttachmentsToTheList('SomeList', 1, this.attachmentList,diagest)
   .subscribe(
   (result: any) => {
       //do something
    },
    (error: any) => {
       //handle error
    });

}

This code throws below error, but it uploads the file as well. What am I doing wrong here, how to fix this error?

Cannot read property 'subscribe' of undefined

Appreciate your help.


Solution

  • Original Answer

    It looks like the error is being caused by the fact that you are subscribing to the result of the method in the service from the component, however the method in the service is not actually returning anything to the component. You should be able to resolve this error by returning the result of the forkJoin from the method in the service and moving the subscribe() from the method in the service to the component. I would recommend adding return types to your methods as this should help to better catch errors similar to this one in the future.

      addAttachmentsToTheList(listName: string, listItemId: number, attachmentList: any[], diagest: any): Observable<...> {
        //get the file upload observable list
        let fileUploadOnservables: any[] = [];
        attachmentList.forEach(file => {
          fileUploadOnservables.push(this.addAttachment(listName, listItemId, file, diagest))
        });
    
        // use forkJoin to combine the operations and subscribe to all results
        return forkJoin(fileUploadOnservables);
      }
    

    If you need to perform further operations in the service after you have fork joined the fileUploadOnservables, then you could use the pipe function:

    return forkJoin(fileUploadOnservables).pipe(...);
    

    Updated Answer

    You could try using the from operator to split the list of values into individual events and then the toArray operator to combine each of the events instead.

      addAttachmentsToTheList(listName: string, listItemId: number, attachmentList: any[], diagest: any): Observable<...> {
        return from(attachmentList).pipe(mergeMap(value => this.addAttachment(listName, listItemId, value, diagest)), toArray());
      }