npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ng-dd-file

v1.0.0

Published

Drag and drop file upload component for Angular with multiple files support, file size validation, and image preview

Readme

ng-dd-file

npm version License: MIT

Drag and drop file upload component for Angular with multiple files support, file size validation, and automatic image preview.

✨ Features

  • 📁 Drag & Drop - Intuitive drag and drop interface
  • 🖱️ Click to browse - Traditional file picker support
  • 📸 Image preview - Automatic base64 preview for images
  • File validation - Size limits per file and total
  • 📊 Multiple files - Support for single or multiple uploads
  • 🎨 Customizable - Custom labels and CSS classes
  • 🔍 File type filter - Accept specific file types
  • Standalone component support (Angular 14+)
  • 🔧 NgModule compatible (backward compatible)

🔧 Compatibility

| ng-dd-file | Angular | Standalone | |------------|---------------|------------| | 1.0.x | 14.x - 18.x | ✅ Yes | | 0.1.x | 15.x | ❌ No |

📦 Installation

NPM

npm install --save ng-dd-file

YARN

yarn add ng-dd-file

🚀 Usage

Method 1: Standalone Component (Angular 14+) ⚡ Recommended

app.component.ts:

import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, NgDdFileComponent],
  template: `
    <ng-dd-file 
      [filesList]="filesList" 
      [multiple]="true"
      [maxFiles]="5"
      [maxFileSize]="2048"
      [labels]="{ text: 'Drag and drop files here', btn: 'BROWSE FILES' }"
      (filesAdd)="onFilesAdd($event)"
      (fileRemove)="onFileRemove($event)"
      (fileError)="onFileError($event)">
    </ng-dd-file>

    <div *ngFor="let file of filesList; let i = index">
      {{ file.name }} - {{ formatFileSize(file.size) }}
    </div>
  `
})
export class AppComponent {
  filesList: File[] = [];

  onFilesAdd(files: File[]) {
    this.filesList.push(...files);
    console.log('Files added:', files);
  }

  onFileRemove(event: { file: File, index: number }) {
    this.filesList.splice(event.index, 1);
    console.log('File removed:', event);
  }

  onFileError(error: FileError) {
    console.error('File error:', error);
    alert(`${error.message}${error.file ? ` (${error.file.name})` : ''}`);
  }

  formatFileSize(bytes: number): string {
    return bytes < 1024 ? bytes + ' B' : (bytes / 1024).toFixed(2) + ' KB';
  }
}

Method 2: NgModule (Traditional) 🔧

Step 1: Import the module

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NgDdFileModule } from 'ng-dd-file';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    NgDdFileModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 2: Use in your component

app.component.html:

<ng-dd-file 
  [btnClass]="{ file: 'custom-file-btn', remove: 'custom-remove-btn' }"
  [filesList]="filesList" 
  [labels]="{ text: 'Arraste e solte os arquivos', btn: 'SELECIONE O ARQUIVO' }"
  [maxFiles]="3"
  [maxFileSize]="200"
  [maxTotalSize]="300"
  [multiple]="true"
  typeFileAccept="image/*"
  (fileError)="fileError($event)"
  (fileRemove)="fileRemove($event)" 
  (filesAdd)="filesAdd($event)">
</ng-dd-file>

<div *ngFor="let file of filesList; let i = index">
  {{ file.name }}
</div>

app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  filesList: any[] = [];

  filesAdd(files: any[]) {
    this.filesList.push(...files);
  }

  fileRemove(event: { file: any, index: number }) {
    this.filesList.splice(event.index, 1);
  }

  fileError(error: FileError) {
    console.error('File error:', error.type, error.message, error.file?.name);
    // Handle different error types
    switch(error.type) {
      case 'size':
        alert(`File "${error.file?.name}" is too large!`);
        break;
      case 'total':
        alert('Total file size limit exceeded!');
        break;
      case 'type':
        alert(`File type not allowed: ${error.file?.name}`);
        break;
    }
  }
}

📖 API Reference

Inputs

| Input | Type | Default | Description | |------------------|-----------------------------------|---------|-------------| | btnClass | { file?: string, remove?: string } | - | Custom CSS classes for file input and remove button | | filesList | any[] | - | Array of files to display | | labels | { text: string, btn: string } | { text: 'Arraste e solte os arquivos', btn: 'SELECIONE O ARQUIVO' } | Drag area text and button label | | maxFiles | number | - | Maximum number of files allowed | | maxFileSize | number (KB) | - | Maximum size per file in kilobytes | | maxTotalSize | number (KB) | - | Maximum total size of all files in kilobytes | | multiple | boolean | false | Allow multiple file selection | | typeFileAccept | string | '*' | File type filter (e.g., 'image/*', '.pdf', etc.) |

Outputs

| Output | Type | Description | |--------------|---------------------------------------------------------|--------------------------------| | filesAdd | EventEmitter<FileWithBase64[]> | Emitted when files are added | | fileRemove | EventEmitter<{ file: FileWithBase64, index: number }> | Emitted when a file is removed | | fileError | EventEmitter<FileError> | Emitted when validation fails |

Interfaces

interface FileWithBase64 extends File {
  base64?: string;  // Base64 string for image preview (automatically added for images)
  url?: string;     // Optional URL property
}

interface FileError {
  type: 'size' | 'total' | 'type';   // Error type
  message: string;                   // Error description
  file?: File;                       // The file that caused the error (when applicable)
}

---

## 💡 Examples

### Image Upload with Preview

```typescript
import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-image-upload',
  standalone: true,
  imports: [CommonModule, NgDdFileComponent],
  template: `
    <ng-dd-file 
      [filesList]="images" 
      [multiple]="true"
      [maxFiles]="4"
      [maxFileSize]="5120"
      typeFileAccept="image/*"
      [labels]="{ text: 'Drop images here', btn: 'SELECT IMAGES' }"
      (filesAdd)="onImagesAdd($event)"
      (fileRemove)="onImageRemove($event)">
    </ng-dd-file>

    <div class="image-preview" *ngFor="let image of images">
      <img [src]="image.base64" alt="Preview" style="max-width: 200px;">
      <p>{{ image.name }}</p>
    </div>
  `
})
export class ImageUploadComponent {
  images: any[] = [];

  onImagesAdd(files: any[]) {
    this.images.push(...files);
  }

  onImageRemove(event: { file: any, index: number }) {
    this.images.splice(event.index, 1);
  }
}

PDF Documents Only

import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';

@Component({
  selector: 'app-pdf-upload',
  standalone: true,
  imports: [NgDdFileComponent],
  template: `
    <ng-dd-file 
      [filesList]="documents" 
      [multiple]="true"
      [maxFileSize]="10240"
      typeFileAccept=".pdf"
      [labels]="{ text: 'Drop PDF files', btn: 'SELECT PDFs' }"
      (filesAdd)="onPdfAdd($event)"
      (fileError)="onError($event)">
    </ng-dd-file>
  `
})
export class PdfUploadComponent {
  documents: File[] = [];

  onPdfAdd(files: File[]) {
    this.documents.push(...files);
  }

  onError(error: FileError) {
    alert(`${error.message}${error.file ? ` (${error.file.name})` : ''}`);
  }
}

Single File Upload

import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';

@Component({
  selector: 'app-single-upload',
  standalone: true,
  imports: [NgDdFileComponent],
  template: `
    <ng-dd-file 
      [filesList]="file ? [file] : []" 
      [multiple]="false"
      [maxFiles]="1"
      [labels]="{ text: 'Drop one file', btn: 'SELECT FILE' }"
      (filesAdd)="onFileAdd($event)">
    </ng-dd-file>

    <div *ngIf="file">
      Selected: {{ file.name }}
    </div>
  `
})
export class SingleUploadComponent {
  file: File | null = null;

  onFileAdd(files: File[]) {
    this.file = files[0];
  }
}

Form Integration with Upload

import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-form-upload',
  standalone: true,
  imports: [CommonModule, NgDdFileComponent],
  template: `
    <form (submit)="uploadFiles($event)">
      <ng-dd-file 
        [filesList]="files" 
        [multiple]="true"
        (filesAdd)="onFilesAdd($event)"
        (fileRemove)="onFileRemove($event)"
        (fileError)="onFileError($event)">
      </ng-dd-file>

      <button type="submit" [disabled]="files.length === 0">
        Upload {{ files.length }} file(s)
      </button>
    </form>

    <div *ngIf="uploading">Uploading...</div>
    <div *ngIf="uploadSuccess">Upload successful!</div>
    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
  `
})
export class FormUploadComponent {
  files: File[] = [];
  uploading = false;
  uploadSuccess = false;
  error = '';

  constructor(private http: HttpClient) {}

  onFilesAdd(files: File[]) {
    this.files.push(...files);
    this.error = '';
  }

  onFileRemove(event: { file: File, index: number }) {
    this.files.splice(event.index, 1);
  }

  onFileError(error: FileError) {
    this.error = `${error.message}${error.file ? ` (${error.file.name})` : ''}`;
  }

  uploadFiles(event: Event) {
    event.preventDefault();
    this.uploading = true;

    const formData = new FormData();
    this.files.forEach((file, index) => {
      formData.append(`file${index}`, file);
    });

    this.http.post('/api/upload', formData).subscribe({
      next: () => {
        this.uploading = false;
        this.uploadSuccess = true;
        this.files = [];
      },
      error: (err) => {
        this.uploading = false;
        console.error('Upload error:', err);
      }
    });
  }
}

File Size Validation

import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';

@Component({
  selector: 'app-validated-upload',
  standalone: true,
  imports: [NgDdFileComponent],
  template: `
    <ng-dd-file 
      [filesList]="files" 
      [multiple]="true"
      [maxFiles]="10"
      [maxFileSize]="2048"
      [maxTotalSize]="10240"
      [labels]="{ 
        text: 'Max 2MB per file, 10MB total', 
        btn: 'SELECT FILES' 
      }"
      (filesAdd)="onFilesAdd($event)"
      (fileError)="onError($event)">
    </ng-dd-file>

    <div class="error" *ngIf="errorMessage">
      {{ errorMessage }}
    </div>
  `
})
export class ValidatedUploadComponent {
  files: File[] = [];
  errorMessage = '';

  onFilesAdd(files: File[]) {
    this.files.push(...files);
    this.errorMessage = '';
  }

  onError(error: FileError) {
    this.errorMessage = `${error.message}${error.file ? ` (${error.file.name})` : ''}`;
    setTimeout(() => this.errorMessage = '', 5000);
  }
}

🎨 Customization

Custom Styling

<ng-dd-file 
  [btnClass]="{ 
    file: 'my-file-button', 
    remove: 'my-remove-button' 
  }">
</ng-dd-file>
/* Use ::ng-deep for deep styling */
::ng-deep .my-file-button {
  background: #007bff;
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
}

::ng-deep .my-remove-button {
  background: #dc3545;
  color: white;
}

Custom Labels (Internationalization)

// English
labels = { text: 'Drag and drop files here', btn: 'BROWSE' }

// Portuguese
labels = { text: 'Arraste e solte os arquivos', btn: 'SELECIONAR' }

// Spanish
labels = { text: 'Arrastra y suelta archivos aquí', btn: 'EXAMINAR' }

🌍 Complete Standalone Example

// app.component.ts
import { Component } from '@angular/core';
import { NgDdFileComponent } from 'ng-dd-file';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, NgDdFileComponent],
  template: `
    <div class="container">
      <h1>File Upload Demo</h1>
      
      <ng-dd-file 
        [filesList]="files" 
        [multiple]="true"
        [maxFiles]="5"
        [maxFileSize]="5120"
        [maxTotalSize]="20480"
        typeFileAccept="image/*,.pdf"
        [labels]="{ 
          text: 'Drag and drop or click to upload', 
          btn: 'SELECT FILES' 
        }"
        [btnClass]="{ file: 'btn-primary', remove: 'btn-danger' }"
        (filesAdd)="onFilesAdd($event)"
        (fileRemove)="onFileRemove($event)"
        (fileError)="onError($event)">
      </ng-dd-file>

      <div class="error" *ngIf="error">{{ error }}</div>

      <div class="files-list" *ngIf="files.length > 0">
        <h3>Uploaded Files ({{ files.length }})</h3>
        <div class="file-item" *ngFor="let file of files; let i = index">
          <img *ngIf="file.base64" [src]="file.base64" alt="preview">
          <div class="file-info">
            <strong>{{ file.name }}</strong>
            <span>{{ formatSize(file.size) }}</span>
          </div>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .container { max-width: 800px; margin: 0 auto; padding: 20px; }
    .error { color: red; margin: 10px 0; }
    .files-list { margin-top: 20px; }
    .file-item { display: flex; gap: 10px; padding: 10px; border: 1px solid #ddd; margin: 5px 0; }
    .file-item img { width: 50px; height: 50px; object-fit: cover; }
  `]
})
export class AppComponent {
  files: any[] = [];
  error = '';

  onFilesAdd(files: any[]) {
    this.files.push(...files);
    this.error = '';
  }

  onFileRemove(event: { file: any, index: number }) {
    this.files.splice(event.index, 1);
  }

  onError(error: FileError) {
    // Build detailed error message
    let errorMsg = error.message;
    
    if (error.file) {
      errorMsg += ` (File: ${error.file.name})`;
    }
    
    this.error = errorMsg;
    setTimeout(() => this.error = '', 5000);
    
    // Log for debugging
    console.error('File Error:', {
      type: error.type,
      message: error.message,
      file: error.file?.name
    });
  }

  formatSize(bytes: number): string {
    if (bytes < 1024) return bytes + ' B';
    if (bytes < 1048576) return (bytes / 1024).toFixed(2) + ' KB';
    return (bytes / 1048576).toFixed(2) + ' MB';
  }
}

⚠️ Important Notes

  1. File sizes are in KB: maxFileSize and maxTotalSize are specified in kilobytes (KB).

  2. Image preview: Images (png, jpg, jpeg) automatically get a base64 property for preview.

  3. File validation: Errors are thrown when limits are exceeded - make sure to handle the fileError event.

  4. Accept types: Use standard HTML accept patterns: 'image/*', '.pdf,.doc', etc.

  5. Error handling: The fileError event returns a FileError object with type, message, and optionally file properties, allowing for detailed error handling and user feedback.


📄 License

MIT © Alvaro Marinho

🐛 Issues

Report issues at: https://github.com/alvaromarinho/libs/issues

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.