Skip to content

pegasusheavy/ngx-http-fetch-tracking

Repository files navigation

ngx-http-fetch-tracking

Angular library providing upload and download progress tracking for the Fetch API backend

npm version License: MIT

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:

  1. The library wraps the request body in a ReadableStream
  2. As chunks are read from the stream, progress events are emitted
  3. Angular's HttpClient receives these events just like with XMLHttpRequest

Download Progress

For response handling:

  1. The library reads the response body as a stream
  2. Progress events are emitted as chunks arrive
  3. 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. 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

About

Angular library providing upload progress tracking for the Fetch API backend

Resources

License

Stars

Watchers

Forks

Packages

No packages published