FileDialog

Signal-based utility for programmatically opening the native file picker dialog. Creates a hidden <input type="file"> element under the hood and manages its lifecycle automatically.

Loading demo...

Usage

angular-ts
import { Component } from '@angular/core';
import { fileDialog } from '@signality/core';

@Component({
  template: `
    <button (click)="fd.open()">Select Files</button>

    @for (file of fd.files(); track file.name) {
      <p>{{ file.name }} ({{ file.size }} bytes)</p>
    }

    @if (fd.files().length) {
      <button (click)="fd.files.set([])">Clear</button>
    }
  `,
})
export class FileUploadComponent {
  readonly fd = fileDialog({ accept: 'image/*' }); 
}

Parameters

ParamTypeDefaultDescription
multipleMaybeSignal<boolean>trueWhether to allow selecting multiple files
acceptMaybeSignal<string>'*'Comma-separated accepted file types (MIME types, wildcards, or extensions)
captureMaybeSignal<string>Mobile capture source: 'user' or 'environment'
directoryMaybeSignal<boolean>falseSelect directories instead of files (non-standard)
validator(file: File) => booleanCustom per-file validation predicate. When provided, accept is ignored
onReject(files: File[]) => voidCallback invoked with rejected files after selection
injectorInjectorOptional injector for DI context

Return Value

The fileDialog() function returns a FileDialogRef object:

PropertyTypeDescription
filesWritableSignal<File[]>Selected files. Reset via files.set([]) (also clears the input element)
open() => voidOpen the file picker dialog

Examples

Image picker

angular-ts
import { Component } from '@angular/core';
import { fileDialog } from '@signality/core';

@Component({
  template: `
    <button (click)="fd.open()">Pick Images</button>
    <p>{{ fd.files().length }} image(s) selected</p>
  `,
})
export class ImagePicker {
  readonly fd = fileDialog({ accept: 'image/*', multiple: true });
}

Single file selection

angular-ts
import { Component } from '@angular/core';
import { fileDialog } from '@signality/core';

@Component({
  template: `
    <button (click)="fd.open()">Upload PDF</button>
    @if (fd.files().length) {
      <p>Selected: {{ fd.files()[0].name }}</p>
    }
  `,
})
export class PdfUpload {
  readonly fd = fileDialog({ accept: '.pdf', multiple: false });
}

Directory selection

angular-ts
import { Component } from '@angular/core';
import { fileDialog } from '@signality/core';

@Component({
  template: `
    <button (click)="fd.open()">Select Folder</button>
    <p>{{ fd.files().length }} file(s) in folder</p>
  `,
})
export class FolderPicker {
  readonly fd = fileDialog({ directory: true });
}

File size validation with rejection feedback

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

@Component({
  template: `
    <button (click)="fd.open()">Upload Files</button>
    <p>Max 5 MB each</p>

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

  readonly fd = fileDialog({
    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:

  • files → empty File[] array
  • open → no-op function

Type Definitions

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

interface FileDialogRef {
  readonly files: WritableSignal<File[]>;
  readonly open: () => void;
}

function fileDialog(options?: FileDialogOptions): FileDialogRef;
Edit this page on GitHub Last updated: Mar 19, 2026, 23:28:23