I was struggling with finding an answer to this question. Most examples, including AngularFire2 docs and a great tutorial from angularfirebase.com only focuses on uploading one file at the time. I want to build a photo gallery cms, so that option would be inefficient, to say the least...
To my original question, I got one answer from Leon Radley and thanks for that, it helped rethink the approach.
Nevertheless, the solution was not optimal for me. I decided to answer my own question so it might help somebody, or even better, somebody will suggest a better solution.
I will be updating my answer, as I come up with more functionality. I am simply rewriting solution provided by angularfirebase.com to make use of an array of files.
Note that there is Custom Drop Event and FileList Directive used, refer to the source for more details
import { Component, OnInit } from '@angular/core';
import { AngularFireStorage, AngularFireUploadTask } from 'angularfire2/storage';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'file-upload',
templateUrl: './file-upload.component.html',
styleUrls: ['./file-upload.component.scss']
})
export class FileUploadComponent {
// Main task
task: AngularFireUploadTask;
// Progress monitoring
percentage: Observable<number>;
snapshot: Observable<any>;
// Download URL
downloadURL: Observable<string>;
// State for dropzone CSS toggling
isHovering: boolean;
constructor(private storage: AngularFireStorage, private db: AngularFirestore) { }
toggleHover(event: boolean) {
this.isHovering = event;
}
startUpload(event: FileList) {
// The File object
const file = event.item(0)
// Client-side validation example
if (file.type.split('/')[0] !== 'image') {
console.error('unsupported file type :( ')
return;
}
// The storage path
const path = `test/${new Date().getTime()}_${file.name}`;
// Totally optional metadata
const customMetadata = { app: 'My AngularFire-powered PWA!' };
// The main task
this.task = this.storage.upload(path, file, { customMetadata })
// Progress monitoring
this.percentage = this.task.percentageChanges();
this.snapshot = this.task.snapshotChanges()
// The file's download URL
this.downloadURL = this.task.downloadURL();
}
// Determines if the upload task is active
isActive(snapshot) {
return snapshot.state === 'running' && snapshot.bytesTransferred < snapshot.totalBytes
}
}
<div class="dropzone"
dropZone
(hovered)="toggleHover($event)"
(dropped)="startUpload($event)"
[class.hovering]="isHovering">
<h3>AngularFire Drop Zone</h3>
<div class="file">
<label class="file-label">
<input class="file-input" type="file" (change)="startUpload($event.target.files)">
<span class="file-cta">
<span class="file-icon">
<i class="fa fa-upload"></i>
</span>
<span class="file-label">
or choose a file…
</span>
</span>
</label>
</div>
</div>
<div *ngIf="percentage | async as pct">
<progress class="progress is-info"
[value]="pct"
max="100">
</progress>
{{ pct | number }}%
</div>
<div *ngIf="snapshot | async as snap">
{{ snap.bytesTransferred | fileSize }} of {{ snap.totalBytes | fileSize }}
<div *ngIf="downloadURL | async as url">
<h3>Results!</h3>
<img [src]="url"><br>
<a [href]="url" target="_blank" rel="noopener">Download Me!</a>
</div>
<button (click)="task.pause()" class="button is-warning" [disabled]="!isActive(snap)">Pause</button>
<button (click)="task.cancel()" class="button is-danger" [disabled]="!isActive(snap)">Cancel</button>
<button (click)="task.resume()" class="button is-info" [disabled]="!(snap?.state === 'paused')">Resume</button>
</div>
So far I managed to replicate the following functionality:
@Component({
selector: 'app-file-upload',
templateUrl: './file-upload.component.html',
styleUrls: ['./file-upload.component.scss'],
})
export class FileUploadComponent {
tasks$: Observable<any>
progresses$: any
snapshots$: any
isHovering: boolean
constructor(
private storage: AngularFireStorage,
private db: AngularFirestore,
) {}
toggleHover(event: boolean) {
this.isHovering = event
}
startUpload(event: HTMLInputEvent) {
this.tasks$ = from([Array.from(event.target.files)]).pipe(
map(files =>
files.map(file => {
const path = `test/${new Date().getTime()}_${file.name}`
const customMetadata = { app: 'My AngularFire-powered PWA!' }
return this.storage.upload(path, file, { customMetadata })
}),
),
)
this.snapshots$ = this.tasks$.pipe(
map(files =>
files.map(file =>
file.snapshotChanges(),
),
)
)
this.progresses$ = this.tasks$.pipe(
map(files =>
files.map(file =>
file.percentageChanges()
),
)
)
}
}
file-upload.component.html
<div class="dropzone"
appDropZone
(hovered)="toggleHover($event)"
(dropped)="startUpload($event)"
[class.hovering]="isHovering">
<h3>AngularFire Drop Zone</h3>
<div class="file">
<label class="file-label">
<input class="file-input" type="file" (change)="startUpload($event)"
multiple>
<span class="file-cta">
<span class="file-icon">
<i class="fa fa-upload"></i>
</span>
<span class="file-label">
or choose a file…
</span>
</span>
</label>
</div>
</div>
<div>
<ul>
<li *ngFor="let progress of progresses$ | async">
<progress
class="progress is-info"
[value]="progress | async"
max="100">
</progress>
{{progress | async | number }}%
</li>
</ul>
</div>
<div>
<ul>
<li *ngFor="let snap of snapshots$ | async">
<p *ngIf="snap | async">
{{ (snap | async)?.bytesTransferred | fileSize }} of
{{ (snap | async)?.totalBytes | fileSize }}
</p>
</li>
</ul>
</div>