ngx-http-fetch-tracking
December 13, 2025 · View on GitHub
Angular library providing upload and download progress tracking for the Fetch API backend
Features
- ✅ 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
Why Use This Library?
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.
Installation
npm install @pegasusheavy/ngx-http-fetch-tracking
Or with pnpm:
pnpm add @pegasusheavy/ngx-http-fetch-tracking
Or with yarn:
yarn add @pegasusheavy/ngx-http-fetch-tracking
Quick Start
1. Configure in your Application
Standalone 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 {}
2. Use in Your Components
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;
}
});
}
}
Advanced Configuration
Custom Configuration Options
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
})
),
],
};
Configuration Options
| 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 |
API Reference
Providers
withFetchProgress(config?)
HttpClient feature for standalone applications (Angular 17+).
provideHttpClient(
withFetchProgress({
uploadChunkSize: 32 * 1024,
})
);
provideFetchProgressBackend(config?)
Provider function for module-based applications.
@NgModule({
providers: [
provideFetchProgressBackend({
credentials: 'include'
})
]
})
Classes
FetchProgressBackend
Custom HttpBackend implementation using the Fetch API.
export class FetchProgressBackend implements HttpBackend {
constructor(config?: FetchProgressBackendConfig);
handle(request: HttpRequest<unknown>): Observable<HttpEvent<unknown>>;
}
createFetchProgressBackend(config?)
Factory function to create a FetchProgressBackend instance.
const backend = createFetchProgressBackend({
uploadChunkSize: 128 * 1024,
});
Stream Utilities (Advanced)
For advanced use cases, you can use the low-level stream utilities directly:
createProgressStream(body, total, onProgress, options?)
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 }
);
wrapStreamWithProgress(stream, total, onProgress)
Wraps an existing ReadableStream with progress tracking.
const trackedStream = wrapStreamWithProgress(existingStream, totalSize, (loaded, total) => {
console.log(`Loaded: ${loaded} bytes`);
});
getBodySize(body)
Calculates the total size of a request body.
const size = await getBodySize(formData);
console.log(`Body size: ${size} bytes`);
Examples
Upload with Progress Bar
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;
},
});
}
}
Download with Progress
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;
},
});
}
}
How It Works
This library replaces Angular's default HttpBackend (which uses XMLHttpRequest) with a custom implementation based on the Fetch API.
Upload Progress
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
Download Progress
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
Important Note
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.
Browser Compatibility
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+
Requirements
- Angular 17.0.0 or higher
- RxJS 7.0.0 or higher
Contributing
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
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Pegasus Heavy Industries
Links
Acknowledgments
- Inspired by the need for better upload progress tracking in Angular applications
- Built with modern web standards and best practices