如何使用 AngularFire2 和 Firebase 存储上传文件数组?

Posted

技术标签:

【中文标题】如何使用 AngularFire2 和 Firebase 存储上传文件数组?【英文标题】:How to upload array of files using AngularFire2 and Firebase Storage? 【发布时间】:2019-02-09 11:56:24 【问题描述】:

我一直在努力寻找这个问题的答案。大多数示例,包括 AngularFire2 文档和来自 angularfirebase.com 的精彩教程,仅侧重于当时上传一个文件。我想建立一个照片库 cms,所以这个选项效率低下,至少可以说......

对于我最初的问题,我从 Leon Radley 那里得到了一个答案,感谢他的回答,它帮助我重新思考了这种方法。

尽管如此,该解决方案对我来说并不是最优的。我决定回答我自己的问题,这样它可能会对某人有所帮助,甚至更好,有人会提出更好的解决方案。

随着我提供更多功能,我将更新我的答案。我只是重写angularfirebase.com 提供的解决方案以利用文件数组。

注意这里用到了Custom Drop Event和FileList Directive,更多细节请参考源码

原解决方案:

文件上传.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
  


文件上传.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>

【问题讨论】:

【参考方案1】:

我刚刚构建了这样的东西:)

我将 percentChanges() observables 存储在一个数组中,并在模板中循环遍历它们

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

然后我使用 Promise.all 函数等待所有这些并关闭对话框。 因为所有的 Observables 也是 Promise,所以效果很好。

this.tasks: AngularFireUploadTask[] = ...;
this.progressObservables = this.tasks.map(t => t.percentageChanges());
Promise.all(this.tasks).then(() => this.dialogRef.close());

【讨论】:

是的,很好用!遗憾的是没有示例如何以反应方式进行操作。我喜欢暂停和恢复上传的可能性......就像这里显示的那样:angularfirebase.com/lessons/… 如果您循环遍历任务而不是 percentChanges(),您将可以访问 pause() 函数,并且应该能够设计一个可以完成此任务的 UI。【参考方案2】:

到目前为止,我设法复制了以下功能:

上传 快照(bytesTransferred / totalBytes) 进展

文件上传.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()
        ),
      )
    )
  

文件上传.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>

【讨论】:

以上是关于如何使用 AngularFire2 和 Firebase 存储上传文件数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用angularfire2检查孩子是不是存在于firebase列表中

如何使用 angularfire2 valuechanges 方法获取随机数据

AngularFire2:实时数据库:如何获取键和值

如何解决 rxjs Typescript 错误(Ionic 3,angularfire2)

如何在 AngularFire2 中获取当前用户的访问令牌?

Angularfire2:如何从 Firebase 存储中检索文件内容