Chunk Upload

You can use the Upload to split files into chunks and send them asynchronously through multiple requests to the server.

Setup

To enable file chunking, configure the chunkable option. It accepts both boolean and ChunkSettings parameters.

If you use a ChunkSettings parameter, the Upload enables you to specify the following options:

  • size—Determines the size of each chunk in bytes. The default value is 1048576 i.e. 1MB.
  • autoRetryAfter—Will attempt a failed chunk upload after the specified number of milliseconds. The default value is 100.
  • maxAutoRetries—Determines the number of attempts to retry uploading a failed chunk. The default value is 1.
  • resumable—Determines if the file upload process could be paused and later resumed. The default value is true.

If you use a boolean parameter and set the value to true, the component will apply the default ChunkSettings values.

Along with the uploaded file chunk, the FormData contains a JSON stringified ChunkMetadata object with the following information:

  • contentType—The MIME type of the file.
  • fileName—The name of the file.
  • fileSize—The size of the file.
  • fileUid—The unique identifier of the file.
  • chunkIndex—The index of the currently uploaded chunk.
  • totalChunks—The total number of chunks that the file is split in.

You can use this information to reconstruct the original file on the backend.

import { Component } from '@angular/core';
import { ChunkSettings } from '@progress/kendo-angular-upload';

@Component({
  selector: 'my-upload',
  template: `
    <p>Files will be split into chunks of 100kb</p>
    <kendo-upload
        [saveUrl]="uploadSaveUrl"
        [removeUrl]="uploadRemoveUrl"
        [chunkable]="chunkSettings">
    </kendo-upload>
  `
})
export class UploadComponent {
   uploadSaveUrl = 'saveUrl'; // should represent an actual API endpoint
    uploadRemoveUrl = 'removeUrl'; // should represent an actual API endpoint

    public chunkSettings: ChunkSettings = {
        size: 102400
    };
}

import { Component, Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpProgressEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Observable, of, concat } from 'rxjs';
import { delay } from 'rxjs/operators';
import { ChunkMetadata } from '@progress/kendo-angular-upload';

@Component({
    selector: 'my-app',
    template: `<my-upload></my-upload>`
})
export class AppComponent {
}

/*
  Mocked backend service.
  For further details, check
  https://angular.io/guide/http#writing-an-interceptor
*/

@Injectable()
export class UploadInterceptor implements HttpInterceptor {
    public fileMap = [];

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url === 'saveUrl') {
            if (!this.isIE()) {
                // Parsing the file metadata.
                const metadata: ChunkMetadata = JSON.parse(req.body.get('metadata'));

                // Retrieving the uid of the uploaded file.
                const fileUid = metadata.fileUid;

                if (metadata.chunkIndex === 0) {
                    this.fileMap[fileUid] = [];
                }

                // Storing the chunks of the file.
                this.fileMap[fileUid].push(req.body.get('files'));

                // Checking if this is the last chunk of the file
                if (metadata.chunkIndex === metadata.totalChunks - 1) {
                    // Reconstructing the initial file
                    // const completeFile = new Blob(
                    //     this.fileMap[metadata.fileUid],
                    //     { type: metadata.contentType }
                    // );
                }
            }

            const events: Observable<HttpEvent<any>>[] = [30, 60, 100].map((x) => of(<HttpProgressEvent>{
                type: HttpEventType.UploadProgress,
                loaded: x,
                total: 100
              }));

            const success = of(new HttpResponse({ status: 200 })).pipe(delay(1000));
            events.push(success);

            return concat(...events);
        }

        if (req.url === 'removeUrl') {
            return of(new HttpResponse({ status: 200 }));
        }

        return next.handle(req);
    }

    public isIE(): RegExpMatchArray {
        return window.navigator.userAgent.match(/(MSIE|Trident)/);
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UploadModule } from '@progress/kendo-angular-upload';
import { UploadComponent } from './upload.component';
import { UploadInterceptor } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';


import { AppComponent } from './app.component';

@NgModule({
    imports:      [ BrowserModule, HttpClientModule, UploadModule, BrowserAnimationsModule, FormsModule, ReactiveFormsModule ],
    declarations: [ AppComponent, UploadComponent ],
    bootstrap:    [ AppComponent ],
    providers: [
      {
        provide: HTTP_INTERCEPTORS,
        useClass: UploadInterceptor,
        multi: true
      }
    ]
  })

  export class AppModule { }
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Sequential Chunk Upload

By default all files are uploaded concurrently. In order to upload the files one by one, set the concurrent option to false.

import { Component } from '@angular/core';
import { ChunkSettings } from '@progress/kendo-angular-upload';

@Component({
  selector: 'my-upload',
  template: `
    <p>Files will be split into chunks of 100kb</p>
    <kendo-upload
        [saveUrl]="uploadSaveUrl"
        [removeUrl]="uploadRemoveUrl"
        [chunkable]="chunkSettings"
        [concurrent]="false">
    </kendo-upload>
  `
})
export class UploadComponent {
   uploadSaveUrl = 'saveUrl'; // should represent an actual API endpoint
    uploadRemoveUrl = 'removeUrl'; // should represent an actual API endpoint

    public chunkSettings: ChunkSettings = {
        size: 102400
    };
}

import { Component, Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpProgressEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Observable, of, concat } from 'rxjs';
import { delay } from 'rxjs/operators';
import { ChunkMetadata } from '@progress/kendo-angular-upload';

@Component({
    selector: 'my-app',
    template: `<my-upload></my-upload>`
})
export class AppComponent {
}

/*
  Mocked backend service.
  For further details, check
  https://angular.io/guide/http#writing-an-interceptor
*/

@Injectable()
export class UploadInterceptor implements HttpInterceptor {
    public fileMap = [];

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url === 'saveUrl') {
            if (!this.isIE()) {
                // Parsing the file metadata.
                const metadata: ChunkMetadata = JSON.parse(req.body.get('metadata'));

                // Retrieving the uid of the uploaded file.
                const fileUid = metadata.fileUid;

                if (metadata.chunkIndex === 0) {
                    this.fileMap[fileUid] = [];
                }

                // Storing the chunks of the file.
                this.fileMap[fileUid].push(req.body.get('files'));

                // Checking if this is the last chunk of the file
                if (metadata.chunkIndex === metadata.totalChunks - 1) {
                    // Reconstructing the initial file
                    // const completeFile = new Blob(
                    //     this.fileMap[metadata.fileUid],
                    //     { type: metadata.contentType }
                    // );
                }
            }

            const events: Observable<HttpEvent<any>>[] = [30, 60, 100].map((x) => of(<HttpProgressEvent>{
                type: HttpEventType.UploadProgress,
                loaded: x,
                total: 100
              }));

            const success = of(new HttpResponse({ status: 200 })).pipe(delay(1000));
            events.push(success);

            return concat(...events);
        }

        if (req.url === 'removeUrl') {
            return of(new HttpResponse({ status: 200 }));
        }

        return next.handle(req);
    }

    public isIE(): RegExpMatchArray {
        return window.navigator.userAgent.match(/(MSIE|Trident)/);
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UploadModule } from '@progress/kendo-angular-upload';
import { UploadComponent } from './upload.component';
import { UploadInterceptor } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';


import { AppComponent } from './app.component';

@NgModule({
    imports:      [ BrowserModule, HttpClientModule, UploadModule, BrowserAnimationsModule, FormsModule, ReactiveFormsModule ],
    declarations: [ AppComponent, UploadComponent ],
    bootstrap:    [ AppComponent ],
    providers: [
      {
        provide: HTTP_INTERCEPTORS,
        useClass: UploadInterceptor,
        multi: true
      }
    ]
  })

  export class AppModule { }
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Disable Pause

In order to upload the files without allowing the user to pause the upload process, set the resumable option of the ChunkSettings parameter to false.

import { Component } from '@angular/core';
import { ChunkSettings } from '@progress/kendo-angular-upload';

@Component({
  selector: 'my-upload',
  template: `
    <p>Files will be split into chunks of 100kb</p>
    <kendo-upload
        [saveUrl]="uploadSaveUrl"
        [removeUrl]="uploadRemoveUrl"
        [chunkable]="chunkSettings">
    </kendo-upload>
  `
})
export class UploadComponent {
   uploadSaveUrl = 'saveUrl'; // should represent an actual API endpoint
    uploadRemoveUrl = 'removeUrl'; // should represent an actual API endpoint

    public chunkSettings: ChunkSettings = {
        size: 102400,
        resumable: false
    };
}
import { Component, Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpProgressEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Observable, of, concat } from 'rxjs';
import { delay } from 'rxjs/operators';
import { ChunkMetadata } from '@progress/kendo-angular-upload';

@Component({
    selector: 'my-app',
    template: `<my-upload></my-upload>`
})
export class AppComponent {
}

/*
  Mocked backend service.
  For further details, check
  https://angular.io/guide/http#writing-an-interceptor
*/

@Injectable()
export class UploadInterceptor implements HttpInterceptor {
    public fileMap = [];

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url === 'saveUrl') {
            if (!this.isIE()) {
                // Parsing the file metadata.
                const metadata: ChunkMetadata = JSON.parse(req.body.get('metadata'));

                // Retrieving the uid of the uploaded file.
                const fileUid = metadata.fileUid;

                if (metadata.chunkIndex === 0) {
                    this.fileMap[fileUid] = [];
                }

                // Storing the chunks of the file.
                this.fileMap[fileUid].push(req.body.get('files'));

                // Checking if this is the last chunk of the file
                if (metadata.chunkIndex === metadata.totalChunks - 1) {
                    // Reconstructing the initial file
                    // const completeFile = new Blob(
                    //     this.fileMap[metadata.fileUid],
                    //     { type: metadata.contentType }
                    // );
                }
            }

            const events: Observable<HttpEvent<any>>[] = [30, 60, 100].map((x) => of(<HttpProgressEvent>{
                type: HttpEventType.UploadProgress,
                loaded: x,
                total: 100
              }));

            const success = of(new HttpResponse({ status: 200 })).pipe(delay(1000));
            events.push(success);

            return concat(...events);
        }

        if (req.url === 'removeUrl') {
            return of(new HttpResponse({ status: 200 }));
        }

        return next.handle(req);
    }

    public isIE(): RegExpMatchArray {
        return window.navigator.userAgent.match(/(MSIE|Trident)/);
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UploadModule } from '@progress/kendo-angular-upload';
import { UploadComponent } from './upload.component';
import { UploadInterceptor } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';


import { AppComponent } from './app.component';

@NgModule({
    imports:      [ BrowserModule, HttpClientModule, UploadModule, BrowserAnimationsModule, FormsModule, ReactiveFormsModule ],
    declarations: [ AppComponent, UploadComponent ],
    bootstrap:    [ AppComponent ],
    providers: [
      {
        provide: HTTP_INTERCEPTORS,
        useClass: UploadInterceptor,
        multi: true
      }
    ]
  })

  export class AppModule { }
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

In this article