https://github.com/h-k-dev/angular-file-drop
This is a 2 weekend thing
https://github.com/h-k-dev/angular-file-drop
Last synced: 7 days ago
JSON representation
This is a 2 weekend thing
- Host: GitHub
- URL: https://github.com/h-k-dev/angular-file-drop
- Owner: h-k-dev
- License: mit
- Created: 2026-06-11T20:36:25.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-30T21:44:13.000Z (about 1 month ago)
- Last Synced: 2026-06-30T22:14:04.472Z (about 1 month ago)
- Language: TypeScript
- Homepage: https://h-k-dev.github.io/angular-file-drop/angular-file-drop/
- Size: 606 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-angular - @h-k-dev/angular-file-drop - A lightweight Angular drag‑and‑drop directive for files and folders with traversal, filtering, and click‑to‑pick—no stylesheets, no XHR, no DOM mutation. (Third Party Components / File Upload)
- fucking-awesome-angular - @h-k-dev/angular-file-drop - A lightweight Angular drag‑and‑drop directive for files and folders with traversal, filtering, and click‑to‑pick—no stylesheets, no XHR, no DOM mutation. (Third Party Components / File Upload)
README
# Angular File Drop
[](https://www.npmjs.com/package/@h-k-dev/angular-file-drop)
[](./LICENSE)
A lightweight, **signal-based** Angular directive for drag-and-drop file and folder uploads — with directory traversal, `accept`-type filtering, and a click-to-open file picker. No stylesheets, no XHR wrappers, no DOM mutation. Just the `File` objects.
> This is the workspace repository. The published package lives in [`projects/angular-file-drop`](./projects/angular-file-drop).
## The Philosophy
**[Try the live demo →](https://h-k-dev.github.io/angular-file-drop)**
Dropzone.js is great, but it often fights against modern Angular architecture by injecting its own CSS, mutating the DOM, and hijacking HTTP requests with its own XHR wrappers.
`angular-file-drop` is the "Angular-only" alternative. It does a portion of what Dropzone does, but strictly the Angular way:
- **Native directive** — binds to your existing elements using standard Angular syntax.
- **Headless** — handles the messy HTML5 drag-and-drop events and hands you raw `File` objects.
- **Zero network opinions** — you upload with Angular's own `HttpClient`, keeping interceptors and auth intact.
- **Bring your own UI** — no forced stylesheets. Style your dropzone exactly how your app needs it.
## Features
- Drag-and-drop **and** click-to-open file picker on a single element
- Recursive **folder/directory traversal** (modern File System Access API, with legacy `webkitGetAsEntry` and plain `FileList` fallbacks)
- `accept`-style filtering by extension or MIME (`.png`, `image/*`, `application/pdf`, `*/*`)
- Single- or multiple-file enforcement
- Automatic hidden-file filtering (`.git`, `.DS_Store`, …)
- Signal-based drag-over state for easy template styling
- SSR-safe (guards all browser-only APIs)
- A ready-made `FILE_TYPES` map of common `accept` strings
## Installation
```bash
npm install @h-k-dev/angular-file-drop
```
Requires Angular `17.3+` (standalone directives + signals).
## Quick Start
The directive is standalone — import `AngularFileDrop` directly into your component.
```ts
import { Component } from '@angular/core';
import { AngularFileDrop, FileDropEvent } from '@h-k-dev/angular-file-drop';
@Component({
selector: 'app-uploader',
imports: [AngularFileDrop],
template: `
Drag files here, or click to browse
`,
})
export class UploaderComponent {
onDrop(event: FileDropEvent) {
for (const { file, relativePath } of event.files) {
console.log(relativePath, file.size);
}
}
}
```
```css
.is-dragging {
outline: 2px dashed #4f46e5;
background: #eef2ff;
}
```
## Examples
### Restrict accepted types
Pass any `accept`-style string. The `FILE_TYPES` map provides ready-made values.
```ts
import { FILE_TYPES } from '@h-k-dev/angular-file-drop';
@Component({
imports: [AngularFileDrop],
template: `
Images and PDFs only
`,
})
export class Component {
accept = `${FILE_TYPES.ANY_IMAGE},${FILE_TYPES.PDF}`; // "image/*,.pdf,application/pdf"
}
```
You can also write the string by hand: `[acceptedFiles]="'.png,.jpg,image/*'"`.
### Single file only
```html
Drop a single file
```
### Drop a whole folder
`directory` (on by default) recursively walks dropped folders. Each `DroppedFile` carries a `relativePath` that preserves the folder structure.
```ts
template: `
Drop a folder`;
onDrop(event: FileDropEvent) {
// e.g. "photos/2026/holiday/IMG_001.jpg"
event.files.forEach((f) => console.log(f.relativePath));
}
```
### Open a folder picker on click
Set `directoryPicker` to make the click-to-open dialog a folder chooser instead of a file chooser.
```html
Click to choose a folder
```
### React to drag events and style the zone
The `isDragOver` signal flips while a valid file drag is over the element. Grab the directive instance via the `dropZone` export.
```html
{{ zone.isDragOver() ? 'Release to upload' : 'Drag files here' }}
```
### Manual activation (custom button)
Disable the built-in click handling with `isManualActivation` and open the picker yourself from a child control.
```ts
@Component({
imports: [AngularFileDrop],
template: `
Drag files here
Browse files
Browse folder
`,
})
export class Component {
onDrop(event: FileDropEvent) {
/* ... */
}
}
```
### Disable the zone
```html
…
```
### Upload with HttpClient
The directive stays out of your network layer — wire it up however you like.
```ts
onDrop(event: FileDropEvent) {
const body = new FormData();
event.files.forEach((f) => body.append('files', f.file, f.relativePath));
this.http.post('/api/upload', body).subscribe();
}
```
## API Reference
### Inputs
| Input | Type | Default | Description |
| ------------------- | --------- | ------- | ------------------------------------------------------------------------------------------- |
| `multiple` | `boolean` | `true` | Allow more than one file. When `false`, only the first file is emitted. |
| `directory` | `boolean` | `true` | Recursively traverse dropped folders. |
| `directoryPicker` | `boolean` | `false` | Make the click-to-open dialog a **folder** picker (`webkitdirectory`) rather than files. |
| `acceptedFiles` | `string` | `''` | `accept`-style filter, e.g. `.png,image/*,application/pdf`. Empty accepts everything. |
| `ignoreHiddenFiles` | `boolean` | `true` | Drop dotfiles and files inside dot-folders (`.git`, `.DS_Store`, …). |
| `clickable` | `boolean` | `true` | Open the file picker when the host element is clicked or activated via keyboard. |
| `disabled` | `boolean` | `false` | Ignore all drops, clicks, and keyboard activation. |
| `isManualActivation`| `boolean` | `false` | Disable built-in click/keyboard activation so you can call the `open*` methods yourself. |
### Outputs
| Output | Payload | Description |
| ----------- | ---------------- | ------------------------------------------------------- |
| `fileDrop` | `FileDropEvent` | Emitted after files are dropped or chosen and filtered. |
| `dragEnter` | `DragEvent` | A valid file drag entered the element. |
| `dragOver` | `DragEvent` | A valid file drag is moving over the element. |
| `dragLeave` | `DragEvent` | A valid file drag left the element. |
### Public members
| Member | Description |
| ------------------------------- | ------------------------------------------------------------------ |
| `isDragOver: Signal` | `true` while a valid file drag is over the element. |
| `openPicker(event?, options?)` | Open the hidden file input. `options.directory` toggles folder mode. |
| `openFilePicker(event?)` | Open a file picker. |
| `openDirectoryPicker(event?)` | Open a folder picker. |
Access these in templates via the `dropZone` export: `#zone="dropZone"`.
### Types
```ts
interface DroppedFile {
file: File;
relativePath: string; // preserves folder structure, e.g. "docs/report.pdf"
}
interface FileDropEvent {
files: DroppedFile[];
}
```
### Exported utilities
The directive's pure helpers are exported for advanced use and testing: `containsFiles`, `setDropEffect`, `isHiddenPath`, `filterHiddenFiles`, `isFileAccepted`, `filterAcceptedFiles`, `enforceMultiple`, `toDroppedFiles`, `readDroppedFiles`, `walkHandles`, `walkEntries`, `createHiddenFileInput`, and the `FILE_TYPES` map.
## Development
```bash
npm install
npm test # run the vitest unit suite
npm run test:ci # single run (CI)
npm run build # build the library with ng-packagr
```
## Browser Support
Works in all modern browsers. Folder traversal uses the File System Access API where available and falls back to `webkitGetAsEntry` and then a plain `FileList`. Server-side rendering is safe — all browser-only APIs are guarded.
## License
MIT © h-k-dev