
Angular File Picker Service
Description
This is a simple file picker service that can be used to pick files from the user. fast and easy. without the need to create a file input element in the dom.
// file-picker.service.ts
import { wait } from '@lib/common/fun';
export class FilePicker {
static #ref: HTMLInputElement;
static #getFileInput(): HTMLInputElement {
if (this.#ref) return this.#ref;
const input = document.createElement('input');
input.type = 'file';
this.#ref = input;
return this.#ref;
}
public static async pick(opt: { multiple?: boolean; accept?: string } = {}): Promise<File[]> {
const input = this.#getFileInput();
input.multiple = opt.multiple;
input.accept = opt.accept ?? '*/*';
const onWindowFocusP = new Promise((res) => window.addEventListener('focus', res, { once: true }));
input.click();
await onWindowFocusP;
await wait(100);
const files = Array.from(input.files ?? []);
input.value = '';
return files;
}
}
// to use it in angular
@Injectable({ providedIn: 'root' })
export class FilePickerService {
readonly #ngZone = inject(NgZone);
pick(opt: { multiple?: boolean; accept?: string } = {}): Promise<File[]> {
return this.#ngZone.runOutsideAngular(() => FilePicker.pick(opt));
}
}
definition of wait function
export const wait = (x: number, signal?: AbortSignal): Promise<number> => {
return new Promise((s, f) => {
const id = setTimeout(s, x, x);
signal?.addEventListener('abort', () => {
clearTimeout(id);
f('AbortError');
});
});
};
Setup in Angular
Import the FilePickerService
in your app module/component providers
Note:
runOutsideAngular
is used to prevent angular from running change detection when the user selects a file. so that it doesn’t cause any performance issues.
Usage
For plain typescript/javascript
import { FilePicker } from './file-picker';
const [file] = await FilePicker.pick();
console.log(file);
// select multiple files
const files = await FilePicker.pick({ multiple: true });
console.log(files);
// select only images
const files = await FilePicker.pick({ accept: 'image/*' });
console.log(files);
For Angular
import { Component, inject } from '@angular/core';
import { FilePickerService } from './file-picker.service';
@Component({
selector: 'test-page',
standalone: true,
imports: [CommonModule],
template: `<button (click)="pick()">Pick</button>`,
})
export class TestComponent implements OnInit {
readonly #filePickerService = inject(FilePickerService);
async pick(id: number) {
// single file
const [file] = await this.#filePickerService.pick();
console.log(file);
// multiple files
const files = await this.#filePickerService.pick({ multiple: true });
console.log(files);
// only images
const files = await this.#filePickerService.pick({ accept: 'image/*' });
console.log(files);
}
}