Angular 8 – Upload/Display/Delete files to/from Firebase Storage using @angular/fire

angular-8-firebase-storage-tutorial-angular-fire-feature-image

In this tutorial, ozenero.com shows you way to upload, get, delete Files to/from Firebase Storage in a simple Angular 8 App using @angular/fire. Files’ info will be stored in Firebase Realtime Database.

Related Posts:
Angular 8 Firebase tutorial: Integrate Firebase into Angular 8 App with @angular/fire
Angular 8 Firebase CRUD operations with @angular/fire
Angular 8 Firestore tutorial with CRUD application example – @angular/fire

Want to know to to deploy Angular App on Hostings?
>> Please visit this Series (with Instructional Videos)

Angular 8 App with Firebase Storage

Technologies

– Angular 8
– RxJs 6
– @angular/fire 5.1.3
– firebase 5.11.1

Angular 8 App Overview

angular-8-upload-files-firebase-storage-demo

We will build an Angular 8 Firebase App that can:
– helps user choose file from local and upload it to Firebase Storage
– show progress with percentage
– save file metadata to Firebase Realtime Database
(Functions above from the posts: Upload File to Storage)
– get list Files and display

How to do

angular-8-upload-files-firebase-storage-overview

– Upload file:
+ save file to Firebase Cloud Storage
+ retrieve {name, url} of the file from Firebase Cloud Storage
+ save {name, url} to Firebase Realtime Database

– Get/delete files: use file {name, url} stored in Database as reference to Firebase Cloud Storage.

So, after upload process, the results will be like:

-> Firebase Storage:

angular-8-firebase-storage-results

-> Firebase Realtime Database:

angular-8-firebase-storage-database-results

Integrate Firebase into Angular 8 App

Please visit this post to know step by step.

Define Model Class

We define FileUpload class with fields: key, name, url, file:

export class FileUpload {
  key: string;
  name: string;
  url: string;
  file: File;

  constructor(file: File) {
    this.file = file;
  }
}

Upload File to Firebase Storage

Uploading a File to Firebase includes 2 action:
– upload file to Firebase Storage.
– save file’s info to Firebase Database.

private basePath = '/uploads';

constructor(private db: AngularFireDatabase, private storage: AngularFireStorage) { }

pushFileToStorage(fileUpload: FileUpload): Observable {
  const filePath = `${this.basePath}/${fileUpload.file.name}`;
  const storageRef = this.storage.ref(filePath);
  const uploadTask = this.storage.upload(filePath, fileUpload.file);

  uploadTask.snapshotChanges().pipe(
    finalize(() => {
      storageRef.getDownloadURL().subscribe(downloadURL => {
        console.log('File available at', downloadURL);
        fileUpload.url = downloadURL;
        fileUpload.name = fileUpload.file.name;
        this.saveFileData(fileUpload);
      });
    })
  ).subscribe();

  return uploadTask.percentageChanges();
}

private saveFileData(fileUpload: FileUpload) {
  this.db.list(this.basePath).push(fileUpload);
}

We use upload() method that returns an AngularFireUploadTask for monitoring.
AngularFireUploadTask has snapshotChanges() method for emitings the raw UploadTaskSnapshot when the file upload progresses.

// get notified when the download URL is available
uploadTask.snapshotChanges().pipe(
    finalize(() => this.downloadURL = fileRef.getDownloadURL() )
 )
.subscribe();

To get the url of the uploaded file, we use the finalize() method from RxJS with getDownloadURL() doesn’t rely on the task.

Finally, we return the upload completion percentage as Observable<number> using AngularFireUploadTask‘s percentageChanges() method.

Retrieve List of Files from Firebase Storage

We get the list from Firebase Storage using files’info (name/url) stored in Firebase Realtime Database:

getFileUploads(numberItems): AngularFireList {
  return this.db.list(this.basePath, ref =>
    ref.limitToLast(numberItems));
}

Display List of Files

With getFileUploads() method, we can get list of FileUploads including the key/id in Firebase Realtime Database.

We will use snapshotChanges().pipe(map()) to store the key, it’s so important to use this key for removing individual item (FileUpload):

this.uploadService.getFileUploads(6).snapshotChanges().pipe(
  map(changes =>
    changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
  )
).subscribe(fileUploads => {
  this.fileUploads = fileUploads;
});

Delete File from Firebase Storage

We delete file with 2 steps:
– delete file’s info from Database
– delete file from Storage

deleteFileUpload(fileUpload: FileUpload) {
  this.deleteFileDatabase(fileUpload.key)
    .then(() => {
      this.deleteFileStorage(fileUpload.name);
    })
    .catch(error => console.log(error));
}

private deleteFileDatabase(key: string) {
  return this.db.list(this.basePath).remove(key);
}

private deleteFileStorage(name: string) {
  const storageRef = this.storage.ref(this.basePath);
  storageRef.child(name).delete();
}

Practice

Project Structure Overview

angular-8-firebase-storage-turorial-project-structure

Integrate Firebase into Angular 8 App

Please visit this post to know step by step.

*Note: Don’t forget to set RULES for public read/write Database and Storage.

angular-8-firebase-storage-rules

Add Firebase config to environments variable

Open /src/environments/environment.ts file, add your Firebase configuration:

export const environment = {
  production: false,
  firebase: {
    apiKey: 'xxx',
    authDomain: 'gkz-angular-firebase.firebaseapp.com',
    databaseURL: 'https://gkz-angular-firebase.firebaseio.com',
    projectId: 'gkz-angular-firebase',
    storageBucket: 'gkz-angular-firebase.appspot.com',
    messagingSenderId: '123...'
  }
};

Create Service & Components

Run the commands below:
ng g s upload/UploadFile
ng g c upload/FormUpload
ng g c upload/ListUpload
ng g c upload/DetailsUpload

Setup @NgModule

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AngularFireModule } from '@angular/fire';
import { AngularFireDatabaseModule } from '@angular/fire/database';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { environment } from '../environments/environment';

import { AppComponent } from './app.component';
import { FormUploadComponent } from './upload/form-upload/form-upload.component';
import { ListUploadComponent } from './upload/list-upload/list-upload.component';
import { DetailsUploadComponent } from './upload/details-upload/details-upload.component';

@NgModule({
  declarations: [
    AppComponent,
    FormUploadComponent,
    ListUploadComponent,
    DetailsUploadComponent
  ],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireDatabaseModule,
    AngularFireStorageModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Create Model Class

fileupload.ts

export class FileUpload {
  key: string;
  name: string;
  url: string;
  file: File;

  constructor(file: File) {
    this.file = file;
  }
}

Create Upload File Service

upload-file.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
import { AngularFireStorage } from '@angular/fire/storage';

import { FileUpload } from './fileupload';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class UploadFileService {

  private basePath = '/uploads';

  constructor(private db: AngularFireDatabase, private storage: AngularFireStorage) { }

  pushFileToStorage(fileUpload: FileUpload): Observable {
    const filePath = `${this.basePath}/${fileUpload.file.name}`;
    const storageRef = this.storage.ref(filePath);
    const uploadTask = this.storage.upload(filePath, fileUpload.file);

    uploadTask.snapshotChanges().pipe(
      finalize(() => {
        storageRef.getDownloadURL().subscribe(downloadURL => {
          console.log('File available at', downloadURL);
          fileUpload.url = downloadURL;
          fileUpload.name = fileUpload.file.name;
          this.saveFileData(fileUpload);
        });
      })
    ).subscribe();

    return uploadTask.percentageChanges();
  }

  private saveFileData(fileUpload: FileUpload) {
    this.db.list(this.basePath).push(fileUpload);
  }

  getFileUploads(numberItems): AngularFireList {
    return this.db.list(this.basePath, ref =>
      ref.limitToLast(numberItems));
  }

  deleteFileUpload(fileUpload: FileUpload) {
    this.deleteFileDatabase(fileUpload.key)
      .then(() => {
        this.deleteFileStorage(fileUpload.name);
      })
      .catch(error => console.log(error));
  }

  private deleteFileDatabase(key: string) {
    return this.db.list(this.basePath).remove(key);
  }

  private deleteFileStorage(name: string) {
    const storageRef = this.storage.ref(this.basePath);
    storageRef.child(name).delete();
  }
}

Create Component with Upload Form

form-upload.component.ts

import { Component, OnInit } from '@angular/core';
import { UploadFileService } from '../upload-file.service';
import { FileUpload } from '../fileupload';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-form-upload',
  templateUrl: './form-upload.component.html',
  styleUrls: ['./form-upload.component.css']
})
export class FormUploadComponent implements OnInit {

  selectedFiles: FileList;
  currentFileUpload: FileUpload;
  percentage: number;

  constructor(private uploadService: UploadFileService) { }

  ngOnInit() {
  }

  selectFile(event) {
    this.selectedFiles = event.target.files;
  }

  upload() {
    const file = this.selectedFiles.item(0);
    this.selectedFiles = undefined;

    this.currentFileUpload = new FileUpload(file);
    this.uploadService.pushFileToStorage(this.currentFileUpload).subscribe(
      percentage => {
        this.percentage = Math.round(percentage);
      },
      error => {
        console.log(error);
      }
    );
  }

}

form-upload.component.html

<div *ngIf="currentFileUpload" class="progress">
  <div class="progress-bar progress-bar-info progress-bar-striped"
    role="progressbar" attr.aria-valuenow="{{percentage}}"
    aria-valuemin="0" aria-valuemax="100"
    [ngStyle]="{width:percentage+'%'}">
    {{percentage}}%</div>
</div>
 
<label class="btn btn-default"> <input type="file"
  (change)="selectFile($event)">
</label>
 
<button class="btn btn-success" [disabled]="!selectedFiles"
  (click)="upload()">Upload</button>

Create Component for Item Details

details-upload.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { FileUpload } from '../fileupload';
import { UploadFileService } from '../upload-file.service';

@Component({
  selector: 'app-details-upload',
  templateUrl: './details-upload.component.html',
  styleUrls: ['./details-upload.component.css']
})
export class DetailsUploadComponent implements OnInit {

  @Input() fileUpload: FileUpload;

  constructor(private uploadService: UploadFileService) { }

  ngOnInit() {
  }

  deleteFileUpload(fileUpload) {
    this.uploadService.deleteFileUpload(fileUpload);
  }

}

details-upload.component.html

<a href="{{fileUpload.url}}">{{fileUpload.name}}</a>
<button (click)='deleteFileUpload(fileUpload)' class="btn btn-danger btn-xs" style="float: right">Delete</button>

Create Component to display List of Uploaded files

list-upload.component.ts

import { Component, OnInit } from '@angular/core';
import { map } from 'rxjs/operators';

import { UploadFileService } from '../upload-file.service';

@Component({
  selector: 'app-list-upload',
  templateUrl: './list-upload.component.html',
  styleUrls: ['./list-upload.component.css']
})
export class ListUploadComponent implements OnInit {

  fileUploads: any[];

  constructor(private uploadService: UploadFileService) { }

  ngOnInit() {
    // Use snapshotChanges().pipe(map()) to store the key
    this.uploadService.getFileUploads(6).snapshotChanges().pipe(
      map(changes =>
        changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
      )
    ).subscribe(fileUploads => {
      this.fileUploads = fileUploads;
    });
  }

}

list-upload.component.html

<div class="panel panel-primary">
  <div class="panel-heading">List of Files</div>
  <div *ngFor="let file of fileUploads">
    <div class="panel-body">
      <app-details-upload [fileUpload]='file'></app-details-upload>
    </div>
  </div>
</div>

Embed Components in App Component

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'ozenero';
  description = 'Angular8-Firebase Demo';
}

app.component.html

<div class="container" style="width:400px">
  <div style="color: blue; margin-bottom: 20px">
    <h1>{{title}}</h1>
    <h3>{{description}}</h3>
  </div>

  <app-form-upload></app-form-upload>

  <br />
  <br />

  <app-list-upload></app-list-upload>
</div>

Source Code

Angular8FirebaseStorage

4 1 vote
Article Rating
Subscribe
Notify of
guest
588 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments