Dropzone

Reactive wrapper around the Drag and Drop API. Create file drop zones with Angular signals.

Loading demo...

Usage

angular-ts
import { Component, viewChild, ElementRef } from '@angular/core';
import { dropzone } from '@signality/core';

@Component({
  template: `
    <div 
      #zone 
      class="dropzone" 
      [class.over]="drop.isOver()"
    >
      @if (drop.files().length > 0) {
        <p>{{ drop.files().length }} file(s) dropped</p>
      } @else {
        <p>Drop files here</p>
      }
    </div>
  `,
})
export class DropzoneDemo {
  readonly zone = viewChild<ElementRef>('zone');
  readonly drop = dropzone(this.zone); 
}

Parameters

ParameterTypeDescription
targetMaybeElementSignal<HTMLElement>Drop zone element
optionsDropzoneOptionsOptional configuration (see Options below)

Options

OptionTypeDefaultDescription
acceptMaybeSignal<string>'*'Comma-separated accepted file types (MIME types, wildcards, or extensions). Ignored when validator is provided
multipleMaybeSignal<boolean>trueAllow multiple files
preventDocumentDropbooleantruePrevent drops outside zone
validator(file: File) => boolean-Custom per-file validation predicate. When provided, accept is ignored
onReject(files: File[]) => void-Callback invoked with rejected files after each drop
injectorInjector-Optional injector for DI context

Return Value

The dropzone() function returns a DropzoneRef:

PropertyTypeDescription
isOverSignal<boolean>Whether dragging over the zone
filesWritableSignal<File[]>Dropped files (writable)
isDraggingSignal<boolean>Whether any drag is in progress

Examples

Image upload

angular-ts
import { Component, viewChild, ElementRef, computed } from '@angular/core';
import { dropzone } from '@signality/core';

@Component({
  template: `
    <div 
      #zone 
      class="image-dropzone"
      [class.dragover]="drop.isOver()"
    >
      @if (previewUrl()) {
        <img [src]="previewUrl()" alt="Preview" />
      } @else {
        <p>📷 Drop an image here</p>
      }
    </div>
  `,
})
export class ImageUpload {
  readonly zone = viewChild<ElementRef>('zone');
  readonly drop = dropzone(this.zone, {
    accept: 'image/*', 
    multiple: false,
  });
  
  readonly previewUrl = computed(() => {
    const files = this.drop.files();
    if (files.length === 0) return null;
    return URL.createObjectURL(files[0]);
  });
}

File size validation with rejection feedback

angular-ts
import { Component, viewChild, ElementRef, signal } from '@angular/core';
import { dropzone } from '@signality/core';

@Component({
  template: `
    <div #zone class="dropzone" [class.over]="drop.isOver()">
      <p>Drop files (max 5 MB each)</p>
    </div>

    @for (msg of errors(); track msg) {
      <p class="error">{{ msg }}</p>
    }
  `,
})
export class ValidatedUpload {
  readonly zone = viewChild<ElementRef>('zone');
  readonly errors = signal<string[]>([]);

  readonly drop = dropzone(this.zone, {
    validator: (file) => file.size <= 5 * 1024 * 1024, 
    onReject: (rejected) => { 
      this.errors.set(rejected.map(f => `${f.name} exceeds 5 MB`)); 
    }, 
  });
}

SSR Compatibility

On the server, signals initialize with safe defaults:

  • isOverfalse
  • files[]
  • isDraggingfalse

Type Definitions

typescript
interface DropzoneOptions extends WithInjector {
  readonly accept?: MaybeSignal<string>;
  readonly multiple?: MaybeSignal<boolean>;
  readonly preventDocumentDrop?: boolean;
  readonly validator?: (file: File) => boolean;
  readonly onReject?: (files: File[]) => void;
}

interface DropzoneRef {
  readonly isOver: Signal<boolean>;
  readonly files: WritableSignal<File[]>;
  readonly isDragging: Signal<boolean>;
}

function dropzone(
  target: MaybeElementSignal<HTMLElement>,
  options?: DropzoneOptions
): DropzoneRef;
Edit this page on GitHub Last updated: Mar 19, 2026, 23:28:23