Upload Large File As Chunks In SharePoint Online Using Angular

Introduction 

 
This article will help you upload the large files in the SharePoint online library into smaller chunks. This article is based on the article, Programmatically Uploading Large Files In SharePoint Online, with some enhancement.
 
It will walk through step-by-step how to upload the file in SharePoint Online.
 
Step 1
 
Create a library in Sharepoint online, eg. Name it Test_ABC.
 
Step 2
 
Install Node.js from here 
 
Step 3
 
Create an Angular project(LargeFileUpload) using AngularCLI 
 
Step 4
 
Create a proxy setting for Angular with Sharepoint online using my blog Proxy With Angular 6+ And SharePoint Environment.
 
Step 5
 
Create a FileUpload Component using the below Command
  1. ng g c FileUpload  
Step 6
 
Create a FileUpload and GlobalServices services using the following commandL
  1. ng g s services/FileUpload  
  2. ng g s services/GlobalService  
GlobalServices.ts
  1. export class GlobalServiceService {  
  2.   constructor() { }  
  3.   public sharePointPageObject = {  
  4.     webAbsoluteUrl: '',  
  5.     webRelativeUrl : '',  
  6.     userId: 0  
  7.   };  
  8. }  
app.component.ts 
  1. import { Component } from '@angular/core';  
  2. import { GlobalServiceService } from './services/global-service.service';  
  3. declare const _spPageContextInfo;  
  4. @Component({  
  5.   selector: 'app-root',  
  6.   templateUrl: './app.component.html',  
  7.   styleUrls: ['./app.component.css']  
  8. })  
  9. export class AppComponent {  
  10.   constructor(  
  11.     private globalService: GlobalServiceService  
  12.   ) { }  
  13.   ngOnInit() {  
  14.     this.globalService.sharePointPageObject.webAbsoluteUrl = window.location.href.indexOf('localhost') > -1 ? '/Your sitecollection'  
  15.       : _spPageContextInfo.webAbsoluteUrl;  
  16.     this.globalService.sharePointPageObject.webRelativeUrl = window.location.href.indexOf('localhost') > -1 ? '/Your sitecollection'  
  17.       : _spPageContextInfo.webRelativeUrl;  
  18.     this.globalService.sharePointPageObject.userId = window.location.href.indexOf('localhost') > -1 ? 22 : _spPageContextInfo.userId;  
  19.   }  
  20. }  
Once the template for file upload is created, then update the file-upload.component.html
  1. File Upload: <input placeholder="Upload File" type="file" (change)="largeFileUpload($event)">  
Then file-upload.component.ts as:
  1. import { Component, OnInit } from '@angular/core';  
  2. import { FileUploadService } from '../services/file-upload.service';  
  3.   
  4. @Component({  
  5.   selector: 'app-file-upload',  
  6.   templateUrl: './file-upload.component.html',  
  7.   styleUrls: ['./file-upload.component.css']  
  8. })  
  9. export class FileUploadComponent implements OnInit {  
  10.   
  11.   constructor(  
  12.     private fileUploadService: FileUploadService  
  13.   ) { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   largeFileUpload(event: any) {  
  18.     let fileList: FileList = event.target.files;  
  19.     if (fileList.length != 0) {  
  20.       this.fileUploadService.fileUpload(fileList[0], "Test_ABC", fileList[0].name).then(addFileToFolder => {  
  21.         console.log("Large File Uploaded Successfully");  
  22.       }).catch(error => {  
  23.         console.log("Error while uploading" + error);  
  24.       });  
  25.     }  
  26.   }  
  27. }  
file-upload.services.ts
  1. import { Injectable } from '@angular/core';  
  2. import { GlobalServiceService } from './global-service.service';  
  3. import { HttpClient, HttpErrorResponse } from '@angular/common/http';  
  4. declare const $: any;  
  5. @Injectable({  
  6.   providedIn: 'root'  
  7. })  
  8. export class FileUploadService {  
  9.   
  10.   constructor(  
  11.     private globalService: GlobalServiceService,  
  12.     private httpClient: HttpClient  
  13.   ) { }  
  14.   public siteUrl: string = this.globalService.sharePointPageObject.webAbsoluteUrl;  
  15.   public siteRelativeUrl: string = this.globalService.sharePointPageObject.webAbsoluteUrl != "/" ? this.globalService.sharePointPageObject.webAbsoluteUrl : "";  
  16.   public fileUpload(file: any, documentLibrary: string, fileName: string) {  
  17.     return new Promise((resolve, reject) => {  
  18.       this.createDummyFile(fileName, documentLibrary).then(result => {  
  19.         let fr = new FileReader();  
  20.         let offset = 0;  
  21.         // the total file size in bytes...    
  22.         let total = file.size;  
  23.         // 1MB Chunks as represented in bytes (if the file is less than a MB, seperate it into two chunks of 80% and 20% the size)...    
  24.         let length = 1000000 > total ? Math.round(total * 0.8) : 1000000  
  25.         let chunks = [];  
  26.         //reads in the file using the fileReader HTML5 API (as an ArrayBuffer) - readAsBinaryString is not available in IE!    
  27.         fr.readAsArrayBuffer(file);  
  28.         fr.onload = (evt: any) => {  
  29.           while (offset < total) {  
  30.             //if we are dealing with the final chunk, we need to know...    
  31.             if (offset + length > total) {  
  32.               length = total - offset;  
  33.             }  
  34.             //work out the chunks that need to be processed and the associated REST method (start, continue or finish)    
  35.             chunks.push({  
  36.               offset,  
  37.               length,  
  38.               method: this.getUploadMethod(offset, length, total)  
  39.             });  
  40.             offset += length;  
  41.           }  
  42.           //each chunk is worth a percentage of the total size of the file...    
  43.            const chunkPercentage = (total / chunks.length) / total * 100;  
  44.           console.log("Chunk Percentage: "+chunkPercentage);  
  45.           if (chunks.length > 0) {  
  46.             //the unique guid identifier to be used throughout the upload session    
  47.             const id = this.generateGUID();  
  48.             //Start the upload - send the data to S    
  49.             this.uploadFile(evt.target.result, id, documentLibrary, fileName, chunks, 0, 0, chunkPercentage, resolve, reject);  
  50.           }  
  51.         };  
  52.       })  
  53.     });  
  54.   }  
  55.   
  56.   createDummyFile(fileName, libraryName) {  
  57.     return new Promise((resolve, reject) => {  
  58.       // Construct the endpoint - The GetList method is available for SharePoint Online only.    
  59.       var serverRelativeUrlToFolder = "decodedurl='" + this.siteRelativeUrl + "/" + libraryName + "'";  
  60.       var endpoint = this.siteUrl + "/_api/Web/GetFolderByServerRelativePath(" + serverRelativeUrlToFolder + ")/files" + "/add(overwrite=true, url='" + fileName + "')"  
  61.       const headers = {  
  62.         "accept""application/json;odata=verbose"  
  63.       };  
  64.       this.executePost(endpoint, this.convertDataBinaryString(2), headers).then(file => resolve(true)).catch(err => reject(err));  
  65.     });  
  66.   }  
  67.   // Base64 - this method converts the blob arrayBuffer into a binary string to send in the REST request    
  68.   convertDataBinaryString(data) {  
  69.     let fileData = '';  
  70.     let byteArray = new Uint8Array(data);  
  71.     for (var i = 0; i < byteArray.byteLength; i++) {  
  72.       fileData += String.fromCharCode(byteArray[i]);  
  73.     }  
  74.     return fileData;  
  75.   }  
  76.   //this method sets up the REST request and then sends the chunk of file along with the unique indentifier (uploadId)    
  77.   uploadFileChunk(id, libraryPath, fileName, chunk, data, byteOffset) {  
  78.     return new Promise((resolve, reject) => {  
  79.       let offset = chunk.offset === 0 ? '' : ',fileOffset=' + chunk.offset;  
  80.       //parameterising the components of this endpoint avoids the max url length problem in SP (Querystring parameters are not included in this length)    
  81.       let endpoint = this.siteUrl + "/_api/web/getfilebyserverrelativeurl('" + this.siteRelativeUrl + "/" + libraryPath + "/" + fileName + "')/" + chunk.method + "(uploadId=guid'" + id + "'" + offset + ")";  
  82.       const headers = {  
  83.         "Accept""application/json; odata=verbose",  
  84.         "Content-Type""application/octet-stream"  
  85.       };  
  86.       this.executePost(endpoint, data, headers).then(offset => resolve(offset)).catch(err => reject(err));  
  87.     });  
  88.   }  
  89.   //the primary method that resursively calls to get the chunks and upload them to the library (to make the complete file)    
  90.   uploadFile(result, id, libraryPath, fileName, chunks, index, byteOffset, chunkPercentage, resolve, reject) {  
  91.     //we slice the file blob into the chunk we need to send in this request (byteOffset tells us the start position)    
  92.     const data = this.convertFileToBlobChunks(result, chunks[index]);  
  93.     //upload the chunk to the server using REST, using the unique upload guid as the identifier    
  94.     this.uploadFileChunk(id, libraryPath, fileName, chunks[index], data, byteOffset).then(value => {  
  95.       const isFinished = index === chunks.length - 1;  
  96.       index += 1;  
  97.       const percentageComplete = isFinished ? 100 : Math.round((index * chunkPercentage));  
  98.       console.log("Percentage Completed:" +percentageComplete)  
  99.       //More chunks to process before the file is finished, continue    
  100.       if (index < chunks.length) {  
  101.         this.uploadFile(result, id, libraryPath, fileName, chunks, index, byteOffset, chunkPercentage, resolve, reject);  
  102.       } else {  
  103.         resolve(value);  
  104.       }  
  105.     }).catch(err => {  
  106.       console.log('Error in uploadFileChunk! ' + err);  
  107.       reject(err);  
  108.     });  
  109.   }  
  110.   //Helper method - depending on what chunk of data we are dealing with, we need to use the correct REST method...    
  111.   getUploadMethod(offset, length, total) {  
  112.     if (offset + length + 1 > total) {  
  113.       return 'finishupload';  
  114.     } else if (offset === 0) {  
  115.       return 'startupload';  
  116.     } else if (offset < total) {  
  117.       return 'continueupload';  
  118.     }  
  119.     return null;  
  120.   }  
  121.   //this method slices the blob array buffer to the appropriate chunk and then calls off to get the BinaryString of that chunk    
  122.   convertFileToBlobChunks(result, chunkInfo) {  
  123.     return result.slice(chunkInfo.offset, chunkInfo.offset + chunkInfo.length);  
  124.   }  
  125.   generateGUID() {  
  126.     function s4() {  
  127.       return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);  
  128.     }  
  129.     return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();  
  130.   }  
  131.   async executePost(url, data, requestHeaders) {  
  132.     const res = await this.httpClient.post(url, data, requestHeaders).toPromise().catch((err: HttpErrorResponse) => {  
  133.       const error = err.error;  
  134.       return error;  
  135.     });  
  136.     return this.parseRetSingle(res);  
  137.   }  
  138.   parseRetSingle(res) {  
  139.     if (res) {  
  140.       if (res.hasOwnProperty('d')) {  
  141.         return res.d;  
  142.       } else if (res.hasOwnProperty('error')) {  
  143.         const obj: any = res.error;  
  144.         obj.hasError = true;  
  145.         return obj;  
  146.       } else {  
  147.         return {  
  148.           hasError: true,  
  149.           comments: res  
  150.         };  
  151.       }  
  152.     } else {  
  153.       return {  
  154.         hasError: true,  
  155.         comments: 'Check the response in network trace'  
  156.       };  
  157.     }  
  158.   }  
  159. }  
Finally, run the code using the command:
  1. npm run start  
Now, your requests will be served at http://localhost:4200/.
 
Click on browse and select the file for upload.
 
Once the project runs successfully, the library looks like:
 
Download the full source code from the attachment.