Reader Stacks

Angular File Upload With FormData

File inputs in Angular are uncontrolled — you read the selected File object off a change event, then hand it to the browser's FormData API before sending it with HttpClient.

Angular doesn't have a special file-upload directive — a file <input> is just a native DOM element, and getting the selected file out of it is a plain DOM event, not a reactive-forms binding.

1. Reading the selected file

<input type="file" (change)="onFileSelected($event)">
selectedFile: File | null = null;

onFileSelected(event: Event): void {
  const input = event.target as HTMLInputElement;
  this.selectedFile = input.files?.[0] ?? null;
}

formControlName doesn't work on <input type="file"> — browsers don't allow JavaScript to programmatically set a file input's value for security reasons, so two-way binding through Angular's forms API isn't possible here; the (change) event is the only reliable way to read it.

2. Building the FormData payload

upload(): void {
  if (!this.selectedFile) return;

  const formData = new FormData();
  formData.append('file', this.selectedFile, this.selectedFile.name);
  formData.append('description', this.description); // any extra fields go alongside it

  this.http.post('/api/uploads', formData).subscribe({
    next: () => console.log('Uploaded'),
    error: (err) => console.error('Upload failed', err),
  });
}

FormData is a browser API, not an Angular one — HttpClient recognizes it automatically and sets the request's Content-Type to multipart/form-data with the correct boundary string on its own. Setting that header manually is a common mistake: it breaks the request, because the boundary Angular would have generated no longer matches what's actually in the body.

3. Showing upload progress

For large files, a progress bar needs the request's raw events rather than just the final response — pass reportProgress: true and observe: 'events':

import { HttpEventType } from '@angular/common/http';

this.http.post('/api/uploads', formData, {
  reportProgress: true,
  observe: 'events',
}).subscribe((event) => {
  if (event.type === HttpEventType.UploadProgress && event.total) {
    this.progress = Math.round((100 * event.loaded) / event.total);
  }
  if (event.type === HttpEventType.Response) {
    console.log('Upload complete', event.body);
  }
});

4. Validating before upload

File type and size limits should be checked client-side for a fast, obvious error message — but the server must repeat the same checks, since a client-side check is trivial to bypass and never a substitute for real validation:

onFileSelected(event: Event): void {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  if (!file) return;

  const maxSizeBytes = 5 * 1024 * 1024;
  if (file.size > maxSizeBytes) {
    this.error = 'File must be under 5MB';
    return;
  }
  if (!['image/png', 'image/jpeg'].includes(file.type)) {
    this.error = 'Only PNG and JPEG files are allowed';
    return;
  }

  this.error = null;
  this.selectedFile = file;
}
Topics: File Uploads & Media APIs & Integrations