Uploading multiple files at once builds directly on the single-file upload pattern covered elsewhere on this site — the multiple attribute on the file input, and looping through the resulting FileList to build a multi-file FormData payload, are the real differences.
The template with a multi-file input
Capturing multiple 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 genuine JavaScript array — Array.from() converts it into a real array, which is what then allows using standard array methods (.map(), .forEach(), .filter()) on the selected files.
Displaying the selected files before upload
-
{{ file.name }} ({{ (file.size / 1024).toFixed(1) }} KB)
removeFile(fileToRemove: File): void {
this.selectedFiles = this.selectedFiles.filter(file => file !== fileToRemove);
}
Building FormData with multiple files
onSubmit(): void {
const formData = new FormData();
this.selectedFiles.forEach((file, index) => {
formData.append(`files[${index}]`, file, file.name);
});
this.http.post('/api/uploads', formData).subscribe({
next: () => console.log('Upload successful'),
error: (err) => console.error('Upload failed', err),
});
}
Appending each file with an indexed key (files[0], files[1], and so on) matches the array-style field naming a Laravel backend expects for multi-file validation and processing — following the array validation pattern covered elsewhere on this site for handling this on the server side.
The Laravel side, handling multiple uploaded files
public function store(Request $request)
{
$request->validate([
'files' => 'required|array',
'files.*' => 'file|mimes:jpeg,png,pdf|max:5120',
]);
$paths = [];
foreach ($request->file('files') as $file) {
$paths[] = $file->store('uploads', 'public');
}
return response()->json(['paths' => $paths]);
}
Validating total combined file size across all selected files
onFilesSelected(event: Event): void {
const input = event.target as HTMLInputElement;
if (input.files) {
const files = Array.from(input.files);
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
if (totalSize > 20 * 1024 * 1024) {
this.uploadError = 'Total file size must be under 20MB.';
return;
}
this.selectedFiles = files;
this.uploadError = null;
}
}
Beyond validating each individual file's size, checking the combined total across every selected file catches a scenario where several individually-acceptable files together exceed a reasonable overall upload limit — worth adding for any multi-file upload where cumulative size, not just per-file size, genuinely matters.
Tracking upload progress for a multi-file upload
Following the same reportProgress/observe: 'events' pattern covered for single-file uploads elsewhere on this site, progress tracking works identically for a multi-file FormData payload — the reported progress simply reflects the combined upload of the entire request body, all files included together.