How to upload files in Angular
File upload in Angular requires combining an HTML file input with Angular’s HttpClient and the browser’s FormData API to send files as multipart form data.
As the creator of CoreUI with Angular development experience since 2014, I’ve implemented file upload components for profile photos, document management systems, and bulk import tools in enterprise applications.
The correct approach uses reportProgress: true with HttpEventType to track upload progress, giving users real-time feedback on large file uploads.
Combining this with client-side validation for file type and size prevents wasted network requests.
Create a service that sends files with progress tracking.
// upload.service.ts
import { Injectable } from '@angular/core'
import { HttpClient, HttpRequest, HttpEventType, HttpResponse } from '@angular/common/http'
import { Observable, filter, map } from 'rxjs'
@Injectable({ providedIn: 'root' })
export class UploadService {
private readonly uploadUrl = '/api/upload'
constructor(private http: HttpClient) {}
uploadFile(file: File): Observable<number | string> {
const formData = new FormData()
formData.append('file', file, file.name)
const req = new HttpRequest('POST', this.uploadUrl, formData, {
reportProgress: true
})
return this.http.request(req).pipe(
map(event => {
if (event.type === HttpEventType.UploadProgress) {
return Math.round(100 * (event.loaded / (event.total ?? event.loaded)))
}
if (event instanceof HttpResponse) {
return (event.body as any).url
}
return 0
}),
filter(value => value !== 0)
)
}
}
FormData.append adds the file to the multipart payload. reportProgress: true triggers UploadProgress events as the file transfers. Dividing loaded by total gives the upload percentage. When HttpResponse arrives, the observable emits the uploaded file’s URL.
File Upload Component with Progress Bar
Build a component that handles file selection, validation, and progress display.
// file-upload.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { UploadService } from './upload.service'
const MAX_SIZE_MB = 5
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']
@Component({
selector: 'app-file-upload',
standalone: true,
imports: [CommonModule],
template: `
<input type="file" (change)="onFileSelected($event)" accept=".jpg,.jpeg,.png,.webp,.pdf" />
<p *ngIf="error" class="text-danger">{{ error }}</p>
<div *ngIf="progress > 0 && progress < 100">
<progress [value]="progress" max="100"></progress>
<span>{{ progress }}%</span>
</div>
<p *ngIf="uploadedUrl">
Uploaded: <a [href]="uploadedUrl" target="_blank">View file</a>
</p>
`
})
export class FileUploadComponent {
progress = 0
error = ''
uploadedUrl = ''
constructor(private uploadService: UploadService) {}
onFileSelected(event: Event): void {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file) return
this.error = ''
this.progress = 0
this.uploadedUrl = ''
if (!ALLOWED_TYPES.includes(file.type)) {
this.error = 'Only JPG, PNG, WebP and PDF files are allowed'
return
}
if (file.size > MAX_SIZE_MB * 1024 * 1024) {
this.error = `File must be smaller than ${MAX_SIZE_MB}MB`
return
}
this.uploadService.uploadFile(file).subscribe({
next: (value) => {
if (typeof value === 'number') {
this.progress = value
} else {
this.uploadedUrl = value
}
},
error: (err) => {
this.error = err.message
}
})
}
}
Client-side validation for MIME type and file size runs before the upload starts, avoiding wasted bandwidth on invalid files. The accept attribute on the input filters the file picker dialog but is not a security control — always validate on the server too.
Drag and Drop Upload Zone
Accept files dropped onto a target element.
@Component({
selector: 'app-drop-zone',
standalone: true,
template: `
<div
class="drop-zone"
[class.active]="isDragging"
(dragover)="onDragOver($event)"
(dragleave)="isDragging = false"
(drop)="onDrop($event)"
>
Drop files here or click to select
</div>
`
})
export class DropZoneComponent {
isDragging = false
constructor(private uploadService: UploadService) {}
onDragOver(event: DragEvent): void {
event.preventDefault()
this.isDragging = true
}
onDrop(event: DragEvent): void {
event.preventDefault()
this.isDragging = false
const file = event.dataTransfer?.files[0]
if (file) {
this.uploadService.uploadFile(file).subscribe()
}
}
}
event.preventDefault() in dragover is required to allow dropping. The isDragging flag toggles a CSS class that highlights the drop zone when a file is hovering over it.
Best Practice Note
This is the same upload component structure used in CoreUI Angular admin templates for avatar and document management pages. For multiple file uploads, loop over input.files and call uploadFile for each file. Always validate file types and size on the server — client-side checks are only UX improvements. After uploading, see how to preview images before upload in Angular to show a thumbnail before the file is sent.



