Reader Stacks

Uploading Multiple Files in Angular

The multiple attribute on a file input is the easy part — looping through the resulting FileList and appending each file to FormData under a repeated key is what a backend actually needs to receive them all.

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).

Topics: File Uploads & Media