Upload & Download

In diesem letzten Teil werfen wir einen Blick auf die Dateiupload und Download. Weitere Teile zu der Blogpost Reihe “Externe Daten in Angular” findest du hier: Externe Daten in Angular – Ein Überblick zur Blogpost-Reihe

Upload

Zu den Dateien gehören auch mehr als nur die binären Daten, sondern auch beschreibende Daten wie Dateiname. Das Übertragen des Dateninhalts reicht meistens nicht aus.

Um weitere Informationen zu übertragen, nutzen wir eine FormData. Neben der Datei übergeben wir auch den Dateinamen. Für die Übertragung der Daten nutzen wir das HttpClientModule von Angular. Der Benutzer soll informiert werden, wie der Fortschritt des Dateiuploads ist. Dazu werden wir neben jeden Dateiupload einen Fortschrittsbalken darstellen. Um die Status der Übertragung zu nutzen, müssen wir den Parameter {reportProgress: true} angeben. Die upload-Methode liefert einen Observable<number> zurück. Die ganzzahlige zurückgelieferte Zahl, stellt den Fortschritt in Prozent da. 1 entspricht 1 %, 42 entspricht 42 % und fertig ist der Upload, wenn 100 zurückgeliefert wird.

import {Injectable} from '@angular/core';
import {HttpClient, HttpEventType, HttpRequest, HttpResponse} from '@angular/common/http';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';

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

    readonly url = 'http://localhost:8000/upload';

    constructor(private http: HttpClient) {
    }

    public upload(file: File): Observable {

        const formData: FormData = new FormData();
        formData.append('file', file, file.name);

        const req = new HttpRequest('POST', this.url, formData, {
            reportProgress: true
        });

        return this.http.request(req).pipe(map(event => {
            if (event.type === HttpEventType.UploadProgress) {
                return Math.round(100 * event.loaded / event.total);
            } else if (event instanceof HttpResponse) {
                return 100;
            }
        }));
    }
}

Für die Interaktion mit dem Benutzer steht das HTML Element <input type="file" multiple />.
Da das Standardelement optisch nicht zu unserer Anwendung passt, haben wir das Element versteckt. Der Benutzer klickt auf einen anderen Button. Programmatisch wird anschließend das versteckte Standardelement geklickt und der Nutzer kann die Dateien für den Upload aussuchen.

Auf dem Change-Event (change)="onFilesAdded()" wird der Upload ausgelöst. Da ggf. mehrere Dateien hochgeladen werden, wird in einer for Schleife für jedes einzelne Dokument der Upload gestartet.

Durch [ngStyle]="{width: (uploadFile.status | async) + '%'}" wird im Template die Fortschrittsanzeige realisiert.

Der gesamte Programmcode ist unter https://github.com/dornsebastian/angular-file-updownload (Angular Frontend)
und https://github.com/dornsebastian/express-file-updownload (NodeJS Backend) zu finden.

@Component({
    selector: 'app-upload',
    templateUrl: './upload.component.html',
    styleUrls: ['./upload.component.css']
})
export class UploadComponent implements OnInit {

    @ViewChild('file') file;
    public files: UploadFileEntity[] = [];

    constructor(private uploadService: UploadService) {
    }

    ngOnInit() {
    }

    onFilesAdded() {
        const files: { [key: string]: File } = this.file.nativeElement.files;
        for (const key in files) {
            if (!isNaN(Number(key))) {
                const file = files[key];
                const status = this.uploadService.upload(file);
                this.files.unshift(new UploadFileEntity(file, status));
            }
        }
    }

    addFiles() {
        this.file.nativeElement.click();
    }

}

Download

Die Herausforderung bei Downloads ist eine andere: Die Datei soll nicht im Browser geöffnet werden. Die Datei soll in den Downloads-Ordner oder in einem anderen Ordner gespeichert werden.

Für die technische Umsetzung nutzen wir in diese Codebeispiel die Bibliothek file-saver. Mit der Library können binäre Daten auf Benutzerwunsch in den Downloads gespeichert werden.
Neben den binären Daten (Blob) muss noch der gewünschte Dateiname angegeben werden.

export const saveFile = (blobContent: Blob, fileName: string) => {
    const blob = new Blob([blobContent], {type: 'application/octet-stream'});
    saveAs(blob, fileName);
};

Die Methode downloadFile lädt die Datei von einer URL herunter und
speichert diese dann lokal mit der Bibliothek file-saver.

    downloadFile() {
        const filename = 'photo.jpg';

        // Process the file downloaded
        this.http.get(this.url, {responseType: 'blob'}).subscribe(blob => {
            saveFile(blob, filename);
        });
    }

}

Dateigröße

Abschließend noch ein paar Worte zu den Dateigrößen. Der Beispielcode behandelt die Dokumente zu hoch- und herunterladen am Stück. Dies ist für kleine Dateien geeignet – jedoch nicht für große Dateien. Für große Dateien sollten die Dateien als Stream ausgetauscht werden.

Der angesprochene Programmcode ist unter GitHub zu finden: