0

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

Original Solution:

file-upload.component.ts

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
  }

}

file-upload.component.html

<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>

2 Answers 2

2

I just built something like this :)

I stored the percentageChanges() observables in an array and looped through them in the template

<div class="tasks">
  <mat-progress-bar
    class="mt-2"
    *ngFor="let progress$ of progressObservables"
    mode="determinate"
    [value]="progress$ | async"
  ></mat-progress-bar>
</div>

Then I used the Promise.all function to wait for all of them and close the dialog. Since all Observables are also Promises this works great.

this.tasks: AngularFireUploadTask[] = ...;
this.progressObservables = this.tasks.map(t => t.percentageChanges());
Promise.all(this.tasks).then(() => this.dialogRef.close());
Sign up to request clarification or add additional context in comments.

2 Comments

Yes, that works nice! A shame only that there is no example how to do it in a reactive way. I liked the possibility of pausing and resuming upload... Like it was shown here: angularfirebase.com/lessons/…
If you loop over the tasks instead of the percentChanges() you will have access to the pause() function and should be able to design a UI that will accomplish that.
1

So far I managed to replicate the following functionality:

  • upload
  • snapshots (bytesTransferred / totalBytes)
  • progresses

file-upload.component.ts

@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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.