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

61 thoughts on “Angular 8 – Upload/Display/Delete files to/from Firebase Storage using @angular/fire”

  1. Hi,

    This tutorial was extremely useful and simple to understand.
    I am facing an error at this line “fileUpload.name = fileUpload.file.name;” with the below message:
    Cannot assign to read only property ‘name’ of object ‘[object File]’
    I am not sure how to resolve it.
    With just the URL, the upload works fine. Can you help? Thanks a lot in advance.

  2. Good – I should definitely pronounce, impressed with your site. I had no trouble navigating through all tabs as well as related info ended up being truly simple to do to access. I recently found what I hoped for before you know it in the least. Reasonably unusual. Is likely to appreciate it for those who add forums or something, web site theme . a tones way for your client to communicate. Nice task..

  3. I’ve been looking for a quick keto meal plan for a month, so I don’t have to count calories by myself and don’t have to invent a nice recipe out of a huge number of products.
    This one is easy and simple – a ready-made plan for a month! Excellent menu, everyone will love it. And most importantly – you can download it for free right now: http://ketomybrain.com/

  4. Please let me know if you’re looking for a article writer for your site. You have some really good articles and I feel I would be a good asset. If you ever want to take some of the load off, I’d love to write some material for your blog in exchange for a link back to mine. Please shoot me an e-mail if interested. Thanks! din sko outlet tumbt.teswomango.com/map7.php

  5. I loved as much as you’ll receive carried out right here. The sketch is tasteful, your authored material stylish. nonetheless, you command get got an edginess over that you wish be delivering the following. unwell unquestionably come more formerly again as exactly the same nearly very often inside case you shield this hike. spa anläggningar i stockholmsområdet hogurg.prizsewoman.com/map20.php

  6. Awesome blog! Is your theme custom made or did you download it from somewhere? A design like yours with a few simple adjustements would really make my blog shine. Please let me know where you got your theme. Bless you pulled pork på högrev recept laten.sewomabest.com/map7.php

  7. hi!,I like your writing so much! share we keep in touch more about your article on AOL? I require an expert in this space to solve my problem. May be that’s you! Having a look forward to look you. blåsor i mungipan asun.teswomango.com/map6.php

  8. Hey excellent website! Does running a blog like this take a lot of work? I have virtually no expertise in programming but I had been hoping to start my own blog soon. Anyway, should you have any suggestions or tips for new blog owners please share. I understand this is off topic but I just had to ask. Many thanks! best fungal nail treatment leate.sewomabest.com/map15.php

  9. I’m not sure why but this website is loading very slow for me. Is anyone else having this issue or is it a problem on my end? I’ll check back later and see if the problem still exists. smärta vid äggstockarna canri.prizsewoman.com/map17.php

  10. Hey there would you mind sharing which blog platform you’re working with? I’m planning to start my own blog soon but I’m having a hard time choosing between BlogEngine/Wordpress/B2evolution and Drupal. The reason I ask is because your layout seems different then most blogs and I’m looking for something completely unique. P.S Sorry for getting off-topic but I had to ask! lediga lokaler mjölby prept.teswomango.com/map4.php

  11. When someone writes an paragraph he/she maintains the thought of a user in his/her mind that how a user can know it. Therefore that’s why this post is amazing. Thanks! palladium skor malmö suagi.prizsewoman.com/map20.php

  12. Sweet blog! I found it while browsing on Yahoo News. Do you have any tips on how to get listed in Yahoo News? I’ve been trying for a while but I never seem to get there! Cheers swiss skin renewal bluff nasta.teswomango.com/map10.php

  13. Hmm is anyone else experiencing problems with the images on this blog loading? I’m trying to determine if its a problem on my end or if it’s the blog. Any feed-back would be greatly appreciated. wifi förstärkare 5ghz thersp.prizsewoman.com/map12.php

  14. You’re so interesting! I don’t believe I’ve truly read something like this before. So good to discover somebody with some genuine thoughts on this topic. Seriously.. thanks for starting this up. This site is one thing that is required on the internet, someone with a little originality! att komma tillbaka efter utmattningssyndrom rosa.teswomango.com/map11.php

  15. Its like you learn my mind! You appear to grasp a lot approximately this, like you wrote the ebook in it or something. I feel that you simply could do with some p.c. to power the message home a little bit, however other than that, that is wonderful blog. An excellent read. I’ll definitely be back. expert på att rodna recension earer.prizsewoman.com/map3.php

  16. You are so cool! I don’t think I’ve read a single thing like that before. So wonderful to find another person with some unique thoughts on this subject matter. Seriously.. thank you for starting this up. This site is one thing that is needed on the web, someone with a bit of originality! gravid under mens amoc.sewomabest.com/map7.php

  17. Wonderful beat ! I would like to apprentice while you amend your site, how can i subscribe for a blog web site? The account helped me a acceptable deal. I had been tiny bit acquainted of this your broadcast offered bright clear idea janome symaskin test riaver.prizsewoman.com/map19.php

  18. Hi superb blog! Does running a blog similar to this require a lot of work? I’ve virtually no knowledge of programming however I had been hoping to start my own blog in the near future. Anyway, if you have any suggestions or tips for new blog owners please share. I know this is off subject nevertheless I just had to ask. Kudos! vit chokladtryffel recept breezi.teswomango.com/map3.php

  19. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get three e-mails with the same comment. Is there any way you can remove people from that service? Appreciate it! life europe ab markti.teswomango.com/map2.php

  20. My spouse and I stumbled over here by a different website and thought I may as well check things out. I like what I see so now i’m following you. Look forward to looking at your web page for a second time. blommande buskar i sol hipshe.sewomabest.com/map12.php

  21. Very nice post. I just stumbled upon your blog and wanted to say that I’ve really enjoyed surfing around your blog posts. In any case I will be subscribing to your rss feed and I hope you write again very soon! hur får man bort diarre nofunc.sewomabest.com/map6.php

  22. It’s a shame you don’t have a donate button! I’d certainly donate to this brilliant blog! I suppose for now i’ll settle for bookmarking and adding your RSS feed to my Google account. I look forward to brand new updates and will talk about this blog with my Facebook group. Chat soon! hår som går av camre.sewomabest.com/map27.php

  23. Howdy just wanted to give you a quick heads up. The text in your post seem to be running off the screen in Ie. I’m not sure if this is a format issue or something to do with web browser compatibility but I figured I’d post to let you know. The style and design look great though! Hope you get the problem fixed soon. Kudos bh inlägg tyg avoc.teswomango.com/map8.php

  24. Your style is unique compared to other people I’ve read stuff from. Thanks for posting when you’ve got the opportunity, Guess I will just book mark this site. fiske shop online dibor.prizsewoman.com/map23.php

  25. Everything composed was actually very logical. However, think on this, suppose you added a little information? I ain’t suggesting your content isn’t good, however what if you added something that grabbed folk’s attention? I mean is a little vanilla. You should glance at Yahoo’s front page and note how they write post headlines to get viewers to click. You might try adding a video or a related pic or two to grab people excited about what you’ve got to say. In my opinion, it would make your website a little bit more interesting. bygga garage själv ringsa.teswomango.com/map6.php

  26. Thanks for a marvelous posting! I really enjoyed reading it, you are a great author.I will always bookmark your blog and definitely will come back later in life. I want to encourage you continue your great writing, have a nice holiday weekend! fejk mk väska brever.sewomabest.com/map22.php

  27. I’ve learn some just right stuff here. Definitely value bookmarking for revisiting. I wonder how much attempt you put to make this type of wonderful informative web site. stora torget uppsala markla.teswomango.com/map1.php

  28. Its like you read my mind! You appear to know so much about this, like you wrote the book in it or something. I think that you can do with some pics to drive the message home a bit, but other than that, this is fantastic blog. An excellent read. I’ll definitely be back. recept biffar med fetaost icfra.prizsewoman.com/map22.php

  29. Hi, Neat post. There’s an issue together with your website in internet explorer, might check this? IE still is the market chief and a large component of other folks will pass over your excellent writing because of this problem. skins thermal compression restn.prizsewoman.com/map8.php

  30. Wow that was strange. I just wrote an really long comment but after I clicked submit my comment didn’t show up. Grrrr… well I’m not writing all that over again. Anyways, just wanted to say fantastic blog! svensk ungersk lexikon sewomabest.com/map19.php

Leave a Reply

Your email address will not be published. Required fields are marked *