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.
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(...);
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());
}