Angular Library for uploading files with Real-Time Progress bar, File Preview, Drag && Drop and Custom Template with Multi Language support
bash
npm install ngx-ntk-file-picker
`
📦 Features
$3
- Drag & Drop Upload - Intuitive drag and drop interface
- Real-time Progress Tracking - Live upload progress indicators
- File Preview - Preview images, documents, and other file types
- Image Cropping - Built-in image cropping with Cropper.js
- Multi-file Support - Upload single or multiple files
- File Validation - Comprehensive file type and size validation
- Custom Templates - Flexible template customization
- Multi-language Support - Internationalization ready
$3
- Custom Adapters - Flexible API integration
- File Type Filtering - Restrict file types and extensions
- Size Limits - Individual and total file size restrictions
- Auto Upload - Configurable automatic upload behavior
- Error Handling - Comprehensive error management
- Responsive Design - Mobile-friendly interface
- Accessibility - ARIA support and keyboard navigation
🔧 Usage
$3
`typescript
import { NgModule } from "@angular/core";
import { FilePickerModule } from "ngx-ntk-file-picker";
@NgModule({
imports: [FilePickerModule],
})
export class AppModule {}
`
$3
`html
`
$3
`typescript
import { Component } from "@angular/core";
import { FilePickerAdapter, UploadResponse, UploadStatus } from "ngx-ntk-file-picker";
import { Observable, of } from "rxjs";
@Component({
selector: "app-file-upload",
template: ,
})
export class FileUploadComponent {
customAdapter: FilePickerAdapter = {
uploadFile(fileItem): Observable {
// Custom upload logic
return of({
status: UploadStatus.UPLOADED,
body: { url: "https://example.com/file.jpg" },
});
},
removeFile(fileItem): Observable {
// Custom remove logic
return of({ success: true });
},
};
customCaptions = {
dropzone: {
title: "فایل خود را اینجا رها کنید",
or: "یا",
browse: "انتخاب فایل",
},
cropper: {
crop: "برش",
cancel: "لغو",
},
previewCard: {
remove: "حذف",
uploadError: "خطا در آپلود",
},
};
onUploadSuccess(file: any): void {
console.log("Upload successful:", file);
}
onUploadFail(error: any): void {
console.error("Upload failed:", error);
}
onValidationError(error: any): void {
console.error("Validation error:", error);
}
}
`
📚 API Reference
$3
| Property | Type | Default | Description |
| ---------------------- | ----------------- | --------------- | ------------------------------------------------------- |
| adapter | FilePickerAdapter | - | Custom API adapter for upload/remove operations |
| accept | string | - | File types to accept (e.g., 'image/\*,application/pdf') |
| fileExtensions | string[] | - | Allowed file extensions |
| fileMaxSize | number | - | Maximum file size in MB |
| fileMaxCount | number | - | Maximum number of files |
| totalMaxSize | number | - | Total maximum size for all files in MB |
| uploadType | string | 'multi' | 'single' or 'multi' upload mode |
| enableAutoUpload | boolean | true | Auto-upload on file selection |
| enableCropper | boolean | false | Enable image cropping |
| showeDragDropZone | boolean | true | Show drag & drop zone |
| showPreviewContainer | boolean | true | Show file preview container |
| cropperOptions | object | - | Cropper.js configuration options |
| customValidator | function | - | Custom validation function |
| captions | UploaderCaptions | DefaultCaptions | Custom captions for i18n |
| itemTemplate | TemplateRef | - | Custom preview item template |
| dropzoneTemplate | TemplateRef | - | Custom dropzone template |
$3
| Event | Type | Description |
| ----------------- | ------------ | ----------------------------- |
| uploadSuccess | EventEmitter | File upload success event |
| uploadFail | EventEmitter | File upload failure event |
| removeSuccess | EventEmitter | File removal success event |
| validationError | EventEmitter | File validation error event |
| fileAdded | EventEmitter | File added to queue event |
| fileRemoved | EventEmitter | File removed from queue event |
$3
`typescript
export abstract class FilePickerAdapter {
abstract uploadFile(fileItem: FilePreviewModel): Observable;
abstract removeFile(fileItem: FilePreviewModel): Observable;
}
export interface UploadResponse {
body?: any;
status: UploadStatus;
progress?: number;
}
export enum UploadStatus {
UPLOADED = "UPLOADED",
IN_PROGRESS = "IN PROGRESS",
ERROR = "ERROR",
}
`
$3
`typescript
export interface FilePreviewModel {
fileId?: string;
fileName: string;
fileSize: number;
fileType: string;
fileCategory: string;
fileUrl?: string;
file?: File;
uploaded?: boolean;
progress?: number;
error?: string;
}
`
🎨 Customization
$3
`html
Drop files here or click to browse
{{ file.fileName }}
{{ file.fileSize | fileSize }}
`
$3
`scss
// Custom file picker styles
.ngx-ntk-file-picker {
.dropzone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 40px;
text-align: center;
transition: all 0.3s ease;
&:hover {
border-color: #2196f3;
background-color: #f5f5f5;
}
&.drag-over {
border-color: #4caf50;
background-color: #e8f5e8;
}
}
.preview-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
margin-top: 20px;
}
.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 12px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.file-image {
width: 100%;
height: 150px;
object-fit: cover;
border-radius: 4px;
}
.file-info {
margin-top: 8px;
.file-name {
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.file-size {
font-size: 12px;
color: #666;
}
}
.progress-bar {
margin-top: 8px;
height: 4px;
background-color: #e0e0e0;
border-radius: 2px;
overflow: hidden;
.progress-fill {
height: 100%;
background-color: #4caf50;
transition: width 0.3s ease;
}
}
}
}
`
🌐 Internationalization
$3
`typescript
const persianCaptions: UploaderCaptions = {
dropzone: {
title: "فایل خود را اینجا رها کنید",
or: "یا",
browse: "انتخاب فایل",
},
cropper: {
crop: "برش",
cancel: "لغو",
},
previewCard: {
remove: "حذف",
uploadError: "خطا در آپلود",
},
};
`
$3
`typescript
import { Component } from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
@Component({
selector: "app-file-upload",
template: ,
})
export class FileUploadComponent {
constructor(private translate: TranslateService) {}
getCaptions() {
const currentLang = this.translate.currentLang;
return {
dropzone: {
title: this.translate.instant("filepicker.dropzone.title"),
or: this.translate.instant("filepicker.dropzone.or"),
browse: this.translate.instant("filepicker.dropzone.browse"),
},
cropper: {
crop: this.translate.instant("filepicker.cropper.crop"),
cancel: this.translate.instant("filepicker.cropper.cancel"),
},
previewCard: {
remove: this.translate.instant("filepicker.previewCard.remove"),
uploadError: this.translate.instant("filepicker.previewCard.uploadError"),
},
};
}
}
`
🔒 Security
$3
`typescript
// Custom validator function
customValidator = (file: File): Observable => {
return new Observable(observer => {
// Check file type
if (!this.isValidFileType(file.type)) {
observer.next(false);
observer.complete();
return;
}
// Check file size
if (file.size > this.maxFileSize 1024 1024) {
observer.next(false);
observer.complete();
return;
}
// Check file content (optional)
this.validateFileContent(file).subscribe(
isValid => {
observer.next(isValid);
observer.complete();
},
error => {
observer.next(false);
observer.complete();
}
);
});
};
private isValidFileType(fileType: string): boolean {
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
return allowedTypes.includes(fileType);
}
private validateFileContent(file: File): Observable {
// Implement content validation logic
return of(true);
}
`
🧪 Testing
$3
`typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FilePickerModule } from "ngx-ntk-file-picker";
import { HttpClientTestingModule } from "@angular/common/http/testing";
describe("FileUploadComponent", () => {
let component: FileUploadComponent;
let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FilePickerModule, HttpClientTestingModule],
declarations: [FileUploadComponent],
}).compileComponents();
fixture = TestBed.createComponent(FileUploadComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should handle file upload success", () => {
const mockFile = new File(["test"], "test.jpg", { type: "image/jpeg" });
spyOn(component.uploadSuccess, "emit");
component.onUploadSuccess({ file: mockFile, response: { success: true } });
expect(component.uploadSuccess.emit).toHaveBeenCalled();
});
it("should validate file size correctly", () => {
const mockFile = new File(["test"], "test.jpg", { type: "image/jpeg" });
Object.defineProperty(mockFile, "size", { value: 1024 * 1024 }); // 1MB
component.fileMaxSize = 5; // 5MB
expect(component.validateFile(mockFile)).toBe(true);
component.fileMaxSize = 0.5; // 500KB
expect(component.validateFile(mockFile)).toBe(false);
});
});
`
$3
`typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FilePickerModule } from "ngx-ntk-file-picker";
import { HttpClientTestingModule } from "@angular/common/http/testing";
describe("FilePicker Integration", () => {
let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FilePickerModule, HttpClientTestingModule],
declarations: [FileUploadComponent],
}).compileComponents();
fixture = TestBed.createComponent(FileUploadComponent);
});
it("should display dropzone when showDragDropZone is true", () => {
fixture.componentInstance.showDragDropZone = true;
fixture.detectChanges();
const dropzone = fixture.nativeElement.querySelector(".dropzone");
expect(dropzone).toBeTruthy();
});
it("should hide dropzone when showDragDropZone is false", () => {
fixture.componentInstance.showDragDropZone = false;
fixture.detectChanges();
const dropzone = fixture.nativeElement.querySelector(".dropzone");
expect(dropzone).toBeFalsy();
});
it("should accept only specified file types", () => {
fixture.componentInstance.accept = "image/*";
fixture.detectChanges();
const fileInput = fixture.nativeElement.querySelector('input[type="file"]');
expect(fileInput.accept).toBe("image/*");
});
});
`
⚡ Performance
$3
`typescript
// Use OnPush change detection for better performance
@Component({
selector: "app-file-upload",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileUploadComponent {
// Implement trackBy function for ngFor
trackByFile(index: number, file: FilePreviewModel): string {
return file.fileId || file.fileName;
}
// Use async pipe for observables
uploadProgress$ = new BehaviorSubject(0);
// Implement proper cleanup
ngOnDestroy(): void {
this.uploadProgress$.complete();
}
}
`
$3
`typescript
// Proper file cleanup
private cleanupFiles(): void {
this.files.forEach(file => {
if (file.fileUrl && file.fileUrl.startsWith('blob:')) {
URL.revokeObjectURL(file.fileUrl);
}
});
}
// Cancel ongoing uploads
private cancelUploads(): void {
this.uploadSubscriptions.forEach(sub => sub.unsubscribe());
this.uploadSubscriptions = [];
}
`
📝 Examples
$3
`typescript
@Component({
selector: "app-image-upload",
template: ,
})
export class ImageUploadComponent {
cropperConfig = {
aspectRatio: 16 / 9,
viewMode: 1,
dragMode: "move",
autoCropArea: 1,
restore: false,
guides: true,
center: true,
highlight: false,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: false,
};
onImageUploaded(file: FilePreviewModel): void {
console.log("Image uploaded:", file.fileUrl);
}
}
`
$3
`typescript
@Component({
selector: "app-document-upload",
template: ,
})
export class DocumentUploadComponent {
onDocumentUploaded(file: FilePreviewModel): void {
console.log("Document uploaded:", file.fileName);
}
}
``