Framework-agnostic image optimization library with parallel processing, format conversion, and compression. Works with Angular, React, Vue, and vanilla JavaScript.
npm install ngx-media-optimizerProfessional framework-agnostic library for image optimization, conversion, and compression







Transform, optimize, and compress images effortlessly in Angular, React, Vue, or any JavaScript framework with parallel processing, reactive state management, and zero configuration.
Features • Installation • Quick Start • API Reference • Examples • Exported Types
---
---
``bash`
npm install ngx-media-optimizer
- Angular: 18.x, 19.x, 20.x, or 21.x (optional - library is framework-agnostic)
- React: 16.8+ (with hooks support)
- Vue: 3.x (with composition API)
- TypeScript: 5.0+
- RxJS: 7.x
This library is framework-agnostic and works with:
- ✅ Angular - Fully supported with TypeScript types
- ✅ React - Works with hooks (useState, useEffect)
- ✅ Vue - Compatible with Composition API
- ✅ Vanilla JS - No framework required
- ✅ Any other framework - Uses standard JavaScript callbacks
Note: All image processing dependencies are bundled. No additional installations required! ✨
---
Angular:
`typescript
import { Component, inject, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { ImageConverterService, type ImageFile } from 'ngx-media-optimizer';
@Component({
selector: 'app-image-processor',
standalone: true,
template: `
})
export class ImageProcessorComponent implements OnDestroy {
private imageService = inject(ImageConverterService);
private cdr = inject(ChangeDetectorRef);
// Component state
images: ReadonlyArray
completedCount: number = 0;
savingsPercentage: number = 0;
// Unsubscribe functions
private unsubscribe?: () => void;
constructor() {
// Subscribe to state changes
this.unsubscribe = this.imageService.onImagesChange((images) => {
this.images = images;
this.completedCount = this.imageService.completedCount;
this.savingsPercentage = this.imageService.savingsPercentage;
this.cdr.markForCheck();
});
}
ngOnDestroy() {
this.unsubscribe?.();
}
}
React:
`typescript
import { useEffect, useState } from 'react';
import { ImageConverterService } from 'ngx-media-optimizer';
const imageService = new ImageConverterService();
function ImageProcessor() {
const [images, setImages] = useState([]);
const [completedCount, setCompletedCount] = useState(0);
const [savingsPercentage, setSavingsPercentage] = useState(0);
useEffect(() => {
// Subscribe to state changes
const unsubscribe = imageService.onImagesChange((images) => {
setImages(images);
setCompletedCount(imageService.completedCount);
setSavingsPercentage(imageService.savingsPercentage);
});
// Cleanup on unmount
return () => unsubscribe();
}, []);
// ... rest of component
}
`
Vue:
`typescript
import { ref, onMounted, onUnmounted } from 'vue';
import { ImageConverterService } from 'ngx-media-optimizer';
const imageService = new ImageConverterService();
export default {
setup() {
const images = ref([]);
const completedCount = ref(0);
const savingsPercentage = ref(0);
let unsubscribe;
onMounted(() => {
// Subscribe to state changes
unsubscribe = imageService.onImagesChange((imgs) => {
images.value = imgs;
completedCount.value = imageService.completedCount;
savingsPercentage.value = imageService.savingsPercentage;
});
});
onUnmounted(() => {
unsubscribe?.();
});
return { images, completedCount, savingsPercentage };
}
}
`
`typescript`
onFileSelect(event: Event) {
const input = event.target as HTMLInputElement;
const files = input.files;
if (files) {
this.imageService.convertFormat(files, {
outputFormat: 'webp',
quality: 80,
maxSizeMB: 1
}).subscribe({
next: () => console.log('✅ Conversion complete!'),
error: (err) => console.error('❌ Conversion failed:', err)
});
}
}
Angular: {{ image.name }} Saved {{ getSavings(image) }}%
`html
@for (image of images; track image.id) {
}
Completed: {{ completedCount }}
Total Savings: {{ savingsPercentage }}%
React:
`jsx
{images.map(image => (

{image.name}
Saved {getSavings(image)}%
))}
Completed: {completedCount}
Total Savings: {savingsPercentage}%
`Vue:
`vue
![]()
{{ image.name }}
Saved {{ getSavings(image) }}%
Completed: {{ completedCount }}
Total Savings: {{ savingsPercentage }}%
`---
📖 API Reference
$3
The main service for image processing operations.
#### Core Methods
#####
convertFormat(files, options): ObservableConverts images between formats with compression.
Parameters:
-
files: FileList | File[] - Images to convert
- options: ConvertOptions - Conversion configurationOptions:
`typescript
interface ConvertOptions {
outputFormat?: 'webp' | 'jpeg' | 'png'; // Default: 'webp'
quality?: number; // 0-100, Default: 80
maxSizeMB?: number; // Default: 10
maxWidthOrHeight?: number; // Default: 1920
useWebWorker?: boolean; // Default: true
}
`Example:
`typescript
this.imageService.convertFormat(files, {
outputFormat: 'webp',
quality: 85,
maxSizeMB: 2,
maxWidthOrHeight: 1920
}).subscribe(() => console.log('Done!'));
`---
#####
compressImages(files, options): ObservableCompresses images while preserving their original format.
Parameters:
-
files: FileList | File[] - Images to compress
- options: CompressOptions - Compression configurationOptions:
`typescript
interface CompressOptions {
quality?: number; // 0-100, Default: 80
maxSizeMB?: number; // Default: 10
maxWidthOrHeight?: number; // Default: 1920
useWebWorker?: boolean; // Default: true
}
`Example:
`typescript
this.imageService.compressImages(files, {
quality: 90,
maxSizeMB: 1
}).subscribe(() => console.log('Compressed!'));
`---
#### State Management
Callback-based reactive state - Framework agnostic and memory-leak safe.
##### Subscribe to State Changes
`typescript
// Subscribe to images changes
onImagesChange(callback: (images: ReadonlyArray) => void): () => void// Subscribe to upload status changes
onUploadingChange(callback: (isUploading: boolean) => void): () => void
// Subscribe to upload progress changes
onProgressChange(callback: (progress: number) => void): () => void
`Example:
`typescript
// Angular
constructor() {
this.unsubscribe = this.imageService.onImagesChange((images) => {
this.images = images;
this.cdr.markForCheck();
});
}ngOnDestroy() {
this.unsubscribe?.(); // Important: cleanup to prevent memory leaks
}
// React
useEffect(() => {
const unsubscribe = imageService.onImagesChange(setImages);
return () => unsubscribe();
}, []);
// Vue
onMounted(() => {
unsubscribe = imageService.onImagesChange(imgs => {
images.value = imgs;
});
});
onUnmounted(() => {
unsubscribe?.();
});
`##### State Properties (Getters)
All properties are read-only and computed automatically.
`typescript
// Image state
readonly images: ReadonlyArray;
readonly completedImages: ReadonlyArray;
readonly completedCount: number;// Statistics
readonly totalOriginalSize: number;
readonly totalCompressedSize: number;
readonly savingsPercentage: number; // 0-100
// Upload state
readonly isUploading: boolean;
readonly uploadProgress: number; // 0-100
`Example:
`typescript
console.log(this.imageService.images); // Current images array
console.log(this.imageService.completedCount); // Number of completed
console.log(this.imageService.savingsPercentage); // Total savings %
`---
#### Utility Methods
#####
formatBytes(bytes): stringConverts bytes to human-readable format.
`typescript
this.imageService.formatBytes(1024); // "1.00 KB"
this.imageService.formatBytes(1048576); // "1.00 MB"
`#####
getImageSize(file): stringGets formatted size of a file.
`typescript
this.imageService.getImageSize(file); // "2.5 MB"
`#####
getSavingsPercentage(original, compressed): numberCalculates compression savings percentage.
`typescript
this.imageService.getSavingsPercentage(1000000, 500000); // 50
`---
$3
Advanced utility service for image validation and analysis.
#### Methods
#####
validateImage(file): { valid: boolean; error?: string }Validates if a file is a supported image.
`typescript
import { ImageUtilsService } from 'ngx-media-optimizer';const utils = inject(ImageUtilsService);
const result = utils.validateImage(file);
if (result.valid) {
console.log('Valid image!');
} else {
console.error(result.error);
}
`#####
getImageDimensions(file): Promise<{ width: number; height: number }>Gets image dimensions asynchronously.
`typescript
const dims = await utils.getImageDimensions(file);
console.log(${dims.width}x${dims.height}); // "1920x1080"
`#####
shouldCompress(file, threshold?): booleanChecks if image should be compressed based on size.
`typescript
if (utils.shouldCompress(file, 1024 * 1024)) { // 1MB
console.log('Compression recommended');
}
`#####
getImageInfo(file): PromiseGets comprehensive image information.
`typescript
const info = await utils.getImageInfo(file);
console.log(info.width); // 1920
console.log(info.height); // 1080
console.log(info.aspectRatio); // 1.78
console.log(info.aspectRatioString); // "16:9"
console.log(info.formattedSize); // "2.5 MB"
console.log(info.format); // "image/jpeg"
`#####
createThumbnail(file, options): PromiseCreates a thumbnail from an image.
`typescript
const thumb = await utils.createThumbnail(file, {
maxSizeMB: 0.1,
maxWidthOrHeight: 200
});
console.log(thumb.size); // Much smaller than original
`---
💡 Examples
$3
`typescript
import { Component, inject, signal, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ImageConverterService, type ImageFile } from 'ngx-media-optimizer';@Component({
selector: 'app-media-optimizer',
standalone: true,
imports: [CommonModule],
template:
or drag and drop here
Images: {{ completedCount }} / {{ images.length }}
Total Savings: {{ savingsPercentage }}%
Original: {{ formatBytes(totalOriginalSize) }}
Compressed: {{ formatBytes(totalCompressedSize) }}
{{ image.name }}
{{ formatBytes(image.originalSize) }} →
{{ formatBytes(image.compressedSize) }}
💾 Saved {{ getSavingsPercentage(image.originalSize, image.compressedSize) }}%
@if (completedCount > 0) {
,
styles: []
})
export class ImageConverterComponent implements OnDestroy {
private imageService = inject(ImageConverterService);
private cdr = inject(ChangeDetectorRef); // Local UI state
protected isDragging = signal(false);
protected quality = signal(80);
// Component state (updated via callbacks)
protected images: ReadonlyArray = [];
protected completedCount: number = 0;
protected savingsPercentage: number = 0;
protected totalOriginalSize: number = 0;
protected totalCompressedSize: number = 0;
// Unsubscribe function
private unsubscribe?: () => void;
constructor() {
// Subscribe to image state changes
this.unsubscribe = this.imageService.onImagesChange((images) => {
this.images = images;
this.completedCount = this.imageService.completedCount;
this.savingsPercentage = this.imageService.savingsPercentage;
this.totalOriginalSize = this.imageService.totalOriginalSize;
this.totalCompressedSize = this.imageService.totalCompressedSize;
this.cdr.markForCheck();
});
}
ngOnDestroy(): void {
this.unsubscribe?.(); // Cleanup to prevent memory leaks
}
onDragOver(event: DragEvent): void {
event.preventDefault();
this.isDragging.set(true);
}
onDragLeave(event: DragEvent): void {
event.preventDefault();
this.isDragging.set(false);
}
onDrop(event: DragEvent): void {
event.preventDefault();
this.isDragging.set(false);
const files = event.dataTransfer?.files;
if (files) {
this.processImages(files);
}
}
onFileSelect(event: Event): void {
const input = event.target as HTMLInputElement;
if (input.files) {
this.processImages(input.files);
input.value = ''; // Reset input
}
}
onQualityChange(event: Event): void {
const value = (event.target as HTMLInputElement).value;
this.quality.set(Number(value));
}
private processImages(files: FileList): void {
this.imageService.convertFormat(files, {
outputFormat: 'webp',
quality: this.quality()
}).subscribe({
next: () => console.log('✅ All images processed'),
error: (err: Error) => console.error('❌ Processing failed:', err)
});
}
downloadImage(image: ImageFile): void {
const link = document.createElement('a');
link.href = image.compressedUrl;
link.download = image.name;
link.click();
}
downloadAll(): void {
this.images()
.filter(img => img.status === 'completed')
.forEach(img => this.downloadImage(img));
}
clearAll(): void {
// Clear implementation (would access internal service methods)
}
formatBytes(bytes: number): string {
return this.imageService.formatBytes(bytes);
}
getSavingsPercentage(original: number, compressed: number): number {
return this.imageService.getSavingsPercentage(original, compressed);
}
}
`---
$3
`typescript
import { Component, inject } from '@angular/core';
import { ImageConverterService } from 'ngx-media-optimizer';
import { HttpClient } from '@angular/common/http';@Component({
selector: 'app-uploader',
template:
})
export class UploaderComponent {
private imageService = inject(ImageConverterService);
private http = inject(HttpClient);
isUploading = this.imageService.isUploading;
uploadProgress = this.imageService.uploadProgress;
onSelect(event: Event): void {
const files = (event.target as HTMLInputElement).files;
if (files) {
this.imageService.convertFormat(files, {
outputFormat: 'webp',
quality: 85
}).subscribe();
}
}
upload(): void {
const completed = this.imageService.completedImages();
// Custom upload logic
completed.forEach(async image => {
const response = await fetch(image.compressedUrl);
const blob = await response.blob();
const file = new File([blob], image.name, { type: blob.type });
const formData = new FormData();
formData.append('image', file);
this.http.post('/api/upload', formData).subscribe({
next: () => console.log(✅ Uploaded ${image.name}),
error: (err) => console.error(❌ Upload failed:, err)
});
});
}
}
`---
$3
`typescript
import { Component, inject } from '@angular/core';
import { ImageUtilsService } from 'ngx-media-optimizer';@Component({
selector: 'app-validator',
template:
{{ validationMessage() }}
})
export class ValidatorComponent {
private utils = inject(ImageUtilsService);
validationMessage = signal('');
validationClass = signal('');
async validateFile(event: Event): Promise {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
// Basic validation
const validation = this.utils.validateImage(file);
if (!validation.valid) {
this.validationMessage.set(validation.error!);
this.validationClass.set('error');
return;
}
// Check size
if (this.utils.shouldCompress(file, 5 1024 1024)) {
this.validationMessage.set('⚠️ File is large. Compression recommended.');
this.validationClass.set('warning');
}
// Get detailed info
const info = await this.utils.getImageInfo(file);
this.validationMessage.set(
✅ Valid image: ${info.width}x${info.height}, ${info.formattedSize}
);
this.validationClass.set('success');
}
}
`---
� Exported Types
The library exports the following services and TypeScript interfaces:
$3
`typescript
import {
ImageConverterService, // Main service for image conversion and compression
ImageUtilsService // Utility service for validation and analysis
} from 'ngx-media-optimizer';
`####
ImageConverterService
Main service providing image conversion, compression, and state management.Key Features:
- Image format conversion (PNG, JPG, JPEG, WebP)
- Parallel batch processing with configurable concurrency
- Callback-based reactive state management
- Real-time progress tracking
- Memory-efficient processing
- Automatic cleanup
Usage:
`typescript
const service = new ImageConverterService();
// or in Angular
const service = inject(ImageConverterService);
`####
ImageUtilsService
Utility service for image validation, analysis, and thumbnail generation.Key Features:
- Image validation
- Dimension detection
- Size recommendations
- Comprehensive image info
- Thumbnail creation
Usage:
`typescript
const utils = new ImageUtilsService();
// or in Angular
const utils = inject(ImageUtilsService);
`---
$3
`typescript
import type {
ImageFile, // Processed image with metadata
ImageFormat, // Supported image formats
ConvertOptions, // Options for format conversion
CompressOptions, // Options for compression
ImageInfo // Detailed image information
} from 'ngx-media-optimizer';
`####
ImageFile
Represents a processed image with all metadata and state.`typescript
interface ImageFile {
id: string; // Unique identifier
name: string; // Original filename
originalSize: number; // Original file size in bytes
compressedSize: number; // Compressed file size in bytes
originalUrl: string; // Object URL for original image
compressedUrl: string; // Object URL for compressed image
status: 'pending' | 'processing' | 'completed' | 'error';
format: ImageFormat; // Image MIME type
}
`Example:
`typescript
const image: ImageFile = {
id: 'abc123',
name: 'photo.jpg',
originalSize: 2500000, // 2.5 MB
compressedSize: 850000, // 850 KB
originalUrl: 'blob:...',
compressedUrl: 'blob:...',
status: 'completed',
format: 'image/jpeg'
};
`---
####
ImageFormat
Supported image MIME types.`typescript
type ImageFormat = 'image/png' | 'image/jpeg' | 'image/jpg' | 'image/webp';
`Example:
`typescript
const format: ImageFormat = 'image/webp';
`---
####
ConvertOptions
Configuration options for image format conversion.`typescript
interface ConvertOptions {
outputFormat?: 'webp' | 'jpeg' | 'png'; // Target format (default: 'webp')
quality?: number; // 0-100 (default: 80)
maxSizeMB?: number; // Max file size in MB (default: 10)
maxWidthOrHeight?: number; // Max dimension in pixels (default: 1920)
useWebWorker?: boolean; // Use web worker (default: false)
}
`Example:
`typescript
const options: ConvertOptions = {
outputFormat: 'webp',
quality: 85,
maxSizeMB: 2,
maxWidthOrHeight: 1920,
useWebWorker: false
};service.convertFormat(files, options).subscribe();
`---
####
CompressOptions
Configuration options for image compression (maintains original format).`typescript
interface CompressOptions {
quality?: number; // 0-100 (default: 80)
maxSizeMB?: number; // Max file size in MB (default: 10)
maxWidthOrHeight?: number; // Max dimension in pixels (default: 1920)
useWebWorker?: boolean; // Use web worker (default: false)
}
`Example:
`typescript
const options: CompressOptions = {
quality: 90,
maxSizeMB: 1,
maxWidthOrHeight: 2048,
useWebWorker: false
};service.compressImages(files, options).subscribe();
`---
####
ImageInfo
Comprehensive image information returned by ImageUtilsService.getImageInfo().`typescript
interface ImageInfo {
width: number; // Image width in pixels
height: number; // Image height in pixels
size: number; // File size in bytes
formattedSize: string; // Human-readable size (e.g., "2.5 MB")
format: string; // MIME type (e.g., "image/jpeg")
aspectRatio: number; // Decimal aspect ratio (e.g., 1.78)
aspectRatioString: string; // Readable ratio (e.g., "16:9")
}
`Example:
`typescript
const utils = new ImageUtilsService();
const info: ImageInfo = await utils.getImageInfo(file);console.log(info);
// {
// width: 1920,
// height: 1080,
// size: 2500000,
// formattedSize: "2.38 MB",
// format: "image/jpeg",
// aspectRatio: 1.7777777777777777,
// aspectRatioString: "16:9"
// }
`---
$3
Full type-safe usage:
`typescript
import {
ImageConverterService,
ImageUtilsService,
type ImageFile,
type ConvertOptions,
type ImageInfo
} from 'ngx-media-optimizer';// Services
const converter = new ImageConverterService();
const utils = new ImageUtilsService();
// Type-safe options
const options: ConvertOptions = {
outputFormat: 'webp',
quality: 85
};
// Type-safe callback
converter.onImagesChange((images: ReadonlyArray) => {
images.forEach((img: ImageFile) => {
if (img.status === 'completed') {
console.log(
${img.name}: ${img.compressedSize} bytes);
}
});
});// Type-safe utility
const validation: { valid: boolean; error?: string } = utils.validateImage(file);
const info: ImageInfo = await utils.getImageInfo(file);
`---
�🔄 Migration Guide
$3
The library has been renamed from
@ngx-utils/media-optimizer to ngx-media-optimizer.Steps:
1. Uninstall old package:
`bash
npm uninstall @ngx-utils/media-optimizer
`2. Install new package:
`bash
npm install ngx-media-optimizer
`3. Update imports:
`typescript
// Old
import { ImageConverterService } from '@ngx-utils/media-optimizer';
// New
import { ImageConverterService } from 'ngx-media-optimizer';
`4. API remains the same - No code changes needed! ✨
---
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
$3
`bash
Clone repository
git clone https://github.com/barbozaa/media-optimizer-workspace.gitInstall dependencies
cd media-optimizer
npm installRun tests
npx vitest runBuild library
npm run build:lib
`$3
`bash
Run all 95 tests
npx vitest runWatch mode
npx vitestCoverage report (100% coverage)
npx vitest run --coverage
``---
MIT © Barboza
---
- Built with browser-image-compression
- Powered by Angular (framework-agnostic)
- Tested with Vitest
- 100% TypeScript with complete type safety
---
- � Issues: GitHub Issues
- 💡 Discussions: GitHub Discussions
- 📦 NPM: ngx-media-optimizer
---
Made with ❤️ for the JavaScript community
⭐ Star us on GitHub if this project helped you!