Angular library providing upload and download progress tracking for the Fetch API backend
- ✅ Upload Progress Tracking - Track file upload progress in real-time
- ✅ Download Progress Tracking - Monitor download progress for responses
- ✅ Fetch API Backend - Modern alternative to XMLHttpRequest-based backends
- ✅ Streaming Support - Efficient handling using ReadableStreams
- ✅ Angular 17+ Compatible - Built with latest Angular features
- ✅ TypeScript - Full type safety and IntelliSense support
- ✅ Zero Dependencies - Only peer dependencies on Angular core packages
- ✅ Tree-Shakeable - Optimized for bundle size
Angular's built-in HttpClient uses XMLHttpRequest under the hood, which has limited support for upload progress tracking. This library provides a custom HttpBackend implementation using the modern Fetch API with ReadableStreams to accurately track both upload and download progress.
npm install @pegasusheavy/ngx-http-fetch-trackingOr with pnpm:
pnpm add @pegasusheavy/ngx-http-fetch-trackingOr with yarn:
yarn add @pegasusheavy/ngx-http-fetch-trackingStandalone API (Angular 17+)
import { ApplicationConfig } from '@angular/core';
import { withFetchProgress } from '@pegasusheavy/ngx-http-fetch-tracking';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(withFetchProgress())],
};Module-based (Angular 14-16)
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { provideFetchProgressBackend } from '@pegasusheavy/ngx-http-fetch-tracking';
@NgModule({
imports: [HttpClientModule],
providers: [provideFetchProgressBackend()],
})
export class AppModule {}import { Component } from '@angular/core';
import { HttpClient, HttpEventType } from '@angular/common/http';
@Component({
selector: 'app-upload',
template: `
<input type="file" (change)="onFileSelected($event)" />
<div *ngIf="uploadProgress !== null">Upload Progress: {{ uploadProgress }}%</div>
`,
})
export class UploadComponent {
uploadProgress: number | null = null;
constructor(private http: HttpClient) {}
onFileSelected(event: Event): void {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
this.http
.post('/api/upload', formData, {
reportProgress: true,
observe: 'events',
})
.subscribe((event) => {
if (event.type === HttpEventType.UploadProgress) {
const percentDone = event.total ? Math.round((100 * event.loaded) / event.total) : 0;
this.uploadProgress = percentDone;
} else if (event.type === HttpEventType.Response) {
console.log('Upload complete!', event.body);
this.uploadProgress = null;
}
});
}
}import { ApplicationConfig } from '@angular/core';
import { withFetchProgress } from '@pegasusheavy/ngx-http-fetch-tracking';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withFetchProgress({
credentials: 'include', // Include credentials in requests
uploadChunkSize: 32 * 1024, // 32KB chunks for upload tracking
trackDownloadProgress: true, // Enable download progress
trackUploadProgress: true, // Enable upload progress
})
),
],
};| Option | Type | Default | Description |
|---|---|---|---|
credentials |
RequestCredentials |
'same-origin' |
Controls whether credentials are included in requests |
uploadChunkSize |
number |
65536 (64KB) |
Size of chunks for upload progress tracking |
trackDownloadProgress |
boolean |
true |
Enable/disable download progress events |
trackUploadProgress |
boolean |
true |
Enable/disable upload progress events |
HttpClient feature for standalone applications (Angular 17+).
provideHttpClient(
withFetchProgress({
uploadChunkSize: 32 * 1024,
})
);Provider function for module-based applications.
@NgModule({
providers: [
provideFetchProgressBackend({
credentials: 'include'
})
]
})Custom HttpBackend implementation using the Fetch API.
export class FetchProgressBackend implements HttpBackend {
constructor(config?: FetchProgressBackendConfig);
handle(request: HttpRequest<unknown>): Observable<HttpEvent<unknown>>;
}Factory function to create a FetchProgressBackend instance.
const backend = createFetchProgressBackend({
uploadChunkSize: 128 * 1024,
});For advanced use cases, you can use the low-level stream utilities directly:
Wraps a body in a ReadableStream that tracks upload progress.
const stream = await createProgressStream(
formData,
totalSize,
(loaded, total) => {
console.log(`Progress: ${loaded}/${total} bytes`);
},
{ chunkSize: 64 * 1024 }
);Wraps an existing ReadableStream with progress tracking.
const trackedStream = wrapStreamWithProgress(existingStream, totalSize, (loaded, total) => {
console.log(`Loaded: ${loaded} bytes`);
});Calculates the total size of a request body.
const size = await getBodySize(formData);
console.log(`Body size: ${size} bytes`);import { Component } from '@angular/core';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-file-upload',
standalone: true,
imports: [CommonModule],
template: `
<div class="upload-container">
<input type="file" (change)="onFileSelected($event)" [disabled]="uploading" />
<div *ngIf="uploading" class="progress-bar">
<div class="progress-fill" [style.width.%]="uploadProgress"></div>
<span class="progress-text">{{ uploadProgress }}%</span>
</div>
<div *ngIf="uploadComplete" class="success">✅ Upload complete!</div>
</div>
`,
styles: [
`
.progress-bar {
width: 100%;
height: 30px;
background-color: #f0f0f0;
border-radius: 5px;
position: relative;
margin-top: 10px;
}
.progress-fill {
height: 100%;
background-color: #4caf50;
border-radius: 5px;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
width: 100%;
text-align: center;
line-height: 30px;
font-weight: bold;
}
`,
],
})
export class FileUploadComponent {
uploading = false;
uploadProgress = 0;
uploadComplete = false;
constructor(private http: HttpClient) {}
onFileSelected(event: Event): void {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
this.uploading = true;
this.uploadProgress = 0;
this.uploadComplete = false;
const formData = new FormData();
formData.append('file', file);
this.http
.post('/api/upload', formData, {
reportProgress: true,
observe: 'events',
})
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.uploadProgress = event.total ? Math.round((100 * event.loaded) / event.total) : 0;
} else if (event.type === HttpEventType.Response) {
this.uploading = false;
this.uploadComplete = true;
}
},
error: (error) => {
console.error('Upload failed:', error);
this.uploading = false;
},
});
}
}import { Component } from '@angular/core';
import { HttpClient, HttpEventType } from '@angular/common/http';
@Component({
selector: 'app-download',
template: `
<button (click)="downloadFile()" [disabled]="downloading">Download File</button>
<div *ngIf="downloading">Download Progress: {{ downloadProgress }}%</div>
`,
})
export class DownloadComponent {
downloading = false;
downloadProgress = 0;
constructor(private http: HttpClient) {}
downloadFile(): void {
this.downloading = true;
this.downloadProgress = 0;
this.http
.get('/api/download/large-file.zip', {
reportProgress: true,
observe: 'events',
responseType: 'blob',
})
.subscribe({
next: (event) => {
if (event.type === HttpEventType.DownloadProgress) {
this.downloadProgress = event.total
? Math.round((100 * event.loaded) / event.total)
: 0;
} else if (event.type === HttpEventType.Response) {
// Save the blob
const blob = event.body as Blob;
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'large-file.zip';
link.click();
window.URL.revokeObjectURL(url);
this.downloading = false;
}
},
error: (error) => {
console.error('Download failed:', error);
this.downloading = false;
},
});
}
}This library replaces Angular's default HttpBackend (which uses XMLHttpRequest) with a custom implementation based on the Fetch API.
When you make a request with a body:
- The library wraps the request body in a
ReadableStream - As chunks are read from the stream, progress events are emitted
- Angular's
HttpClientreceives these events just like with XMLHttpRequest
For response handling:
- The library reads the response body as a stream
- Progress events are emitted as chunks arrive
- The final response is assembled and delivered to your code
Progress events measure when bytes are read from/written to streams, not actual network transmission. The browser may buffer data, so there can be slight timing differences compared to actual network activity.
This library requires:
- ReadableStream API support
- Fetch API support
- TextEncoder/TextDecoder support
All modern browsers support these features:
- ✅ Chrome 52+
- ✅ Firefox 65+
- ✅ Safari 10.1+
- ✅ Edge 79+
- Angular 17.0.0 or higher
- RxJS 7.0.0 or higher
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Pegasus Heavy Industries
- Inspired by the need for better upload progress tracking in Angular applications
- Built with modern web standards and best practices