Extending single-file upload (covered in an earlier guide on Angular file upload with FormData) to handle several files at once needs two changes: the multiple attribute on the input itself, and looping through the resulting file list when building the request.
1. The HTML: adding the multiple attribute
<input type="file" multiple (change)="onFilesSelected($event)">
With multiple present, the browser's native file picker allows selecting several files at once (shift-click or ctrl/cmd-click in most OS file dialogs) — without it, the input only ever accepts a single file, regardless of anything on the Angular side.
2. Reading the selected files
selectedFiles: File[] = [];
onFilesSelected(event: Event): void {
const input = event.target as HTMLInputElement;
if (input.files) {
this.selectedFiles = Array.from(input.files);
}
}
input.files is a FileList, not a real JavaScript array — it doesn't have array methods like .map() or .filter() directly available. Array.from() converts it into a genuine array, which is considerably easier to work with in the rest of the component (displaying a list of selected files, removing one before upload, etc.).
3. Building the FormData with multiple files
upload(): void {
const formData = new FormData();
this.selectedFiles.forEach((file, index) => {
formData.append('files[]', file, file.name);
});
this.http.post('/api/uploads', formData).subscribe({
next: () => console.log('Upload complete'),
error: (err) => console.error('Upload failed', err),
});
}
Appending each file under the same key name — commonly with a trailing [], though the exact convention depends on what the backend expects — is what lets a single FormData payload carry several files in one request; the backend then reads that key as an array of files rather than a single one.
4. Showing a preview list before uploading
<ul>
<li *ngFor="let file of selectedFiles; let i = index">
{{ file.name }} ({{ (file.size / 1024).toFixed(1) }} KB)
<button (click)="removeFile(i)">Remove</button>
</li>
</ul>
removeFile(index: number): void {
this.selectedFiles.splice(index, 1);
}
Letting the user review and remove individual files before submitting — rather than uploading immediately on selection — is standard UX for a multi-file upload, and is straightforward once the files are held in a genuine array rather than the raw FileList.
5. Validating each file before allowing it to be added
onFilesSelected(event: Event): void {
const input = event.target as HTMLInputElement;
if (!input.files) return;
const maxSizeBytes = 5 * 1024 * 1024;
const validFiles = Array.from(input.files).filter((file) => {
if (file.size > maxSizeBytes) {
this.errors.push(`${file.name} exceeds the 5MB limit`);
return false;
}
return true;
});
this.selectedFiles = [...this.selectedFiles, ...validFiles];
}
Validating each file individually (rather than the whole batch as one unit) means one oversized or wrong-type file among several selected doesn't have to block the valid ones — a more forgiving UX than rejecting the entire selection over a single problem file.
6. Upload progress for multiple files
As covered for single-file upload, reportProgress: true and observe: 'events' on the HttpClient call track progress — for multiple files sent in one combined request, that progress reflects the whole request's total bytes, not a per-file breakdown. Showing individual per-file progress bars typically means sending a separate request per file instead of one combined request, which is a real trade-off between simplicity (one request) and granular UI feedback (per-file requests).