Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Angular File Uploads to Firebase Storage

Episode 6 written by Jeff Delaney
full courses and content on fireship.io

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v4
  • AngularFire2 v4

Update Notes: Good news! Firebase Storage is now supported natively in AngularFire2. Checkout the latest AngularFire2 Storage Tutorial.

Find an issue? Let's fix it

Currently, file storage is not supported in the AngularFire2 package, but it’s still possible to use Firebase storage by interacting directly with the Firebase JavaScript API. It’s good to understand this because you will likely need work directly with the firebase API as your app grows in complexity. This lesson also uses Lodash to make iterating over multiple files easier.


file upload demo with Angular and Firebase Storage

Firebase Upload Demo

## How to Interact with the Firebase API in Angular

Assuming you’ve installed AngularFire2 and bootstrapped your environment API credentials, you can load the firebase web API like so.

import * as firebase from 'firebase/app';

Step 1: Generate the Files

The structure of this feature here is almost identical to the Angular Firebase CRUD Tutorial - the main difference being that we are uploading files to Firebase Storage before saving the details to the Firebase realtime DB.


ng g service uploads/shared/upload
ng g class uploads/shared/upload
ng g component uploads/upload-form

Step 2: Define the Upload Class

The upload class will be used in the service layer. Notice it has a constructor for file attribute, which has a type of File. This will allows us to initialize new uploads with a JavaScript File object. You will see why this is important in the next step.

export class Upload {

$key: string;
file:File;
name:string;
url:string;
progress:number;
createdAt: Date = new Date();

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

Step 3: Building the Upload Service

The file upload process needs the (1) upload the file and (2) save a record to the database. Let’s kick this off with the imports and constructor.

Now let’s handle the main upload process using the pushUpload function. Here’s what’s happening step-by-step.


  1. Establish a reference to the firebase storage bucket.

  2. Define the uploadTask as a promise to put the file in storage.

  3. Monitor the uploadTask event using the .on function.

  4. Handle the events of in progress, success, and error.

@Injectable()
export class UploadService {

constructor(private af: AngularFire, private db: AngularFireDatabase) { }

private basePath:string = '/uploads';
uploads: FirebaseListObservable<Upload[]>;

pushUpload(upload: Upload) {
let storageRef = firebase.storage().ref();
let uploadTask = storageRef.child(`${this.basePath}/${upload.file.name}`).put(upload.file);

uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
// upload in progress
upload.progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
},
(error) => {
// upload failed
console.log(error)
},
() => {
// upload success
upload.url = uploadTask.snapshot.downloadURL
upload.name = upload.file.name
this.saveFileData(upload)
}
);
}



// Writes the file details to the realtime db
private saveFileData(upload: Upload) {
this.db.list(`${this.basePath}/`).push(upload);
}
}

Now we can reuse this upload process for both single and multiple file uploads from the component.

Deleting Files from Storage

Deleting files is a lot easier than uploading them. Here’s the code to delete files from both firebase storage and the realtime DB. Here we have separate functions for deleting the database info and the stored file.

  deleteUpload(upload: Upload) {
this.deleteFileData(upload.$key)
.then( () => {
this.deleteFileStorage(upload.name)
})
.catch(error => console.log(error))
}

// Deletes the file details from the realtime db
private deleteFileData(key: string) {
return this.db.list(`${this.basePath}/`).remove(key);
}

// Firebase files must have unique names in their respective storage dir
// So the name serves as a unique key
private deleteFileStorage(name:string) {
let storageRef = firebase.storage().ref();
storageRef.child(`${this.basePath}/${name}`).delete()
}

}

Step 4: Uploading via the Frontend Components

Now we need to give users a way to choose files and upload or delete them. Let’s start with the UploadFormComponent because that is where most of the action is happening. When a user selects files in an HTML file input, it fires the change event. Our template will listen the change event, then pass event (which contains the FileList object) to our component. We also need a couple buttons to trigger the upload process.

uploads/upload-form.ts

import { Component, OnInit } from '@angular/core';
import { UploadService } from '../shared/upload.service';
import { Upload } from '../shared/upload';
import * as _ from "lodash";

@Component({
selector: 'upload-form',
templateUrl: './upload-form.component.html',
styleUrls: ['./upload-form.component.scss']
})
export class UploadFormComponent {

selectedFiles: FileList;
currentUpload: Upload;

constructor(private upSvc: UploadService) { }

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

uploadSingle() {
let file = this.selectedFiles.item(0)
this.currentUpload = new Upload(file);
this.upSvc.pushUpload(this.currentUpload)
}

uploadMulti() {
let files = this.selectedFiles
let filesIndex = _.range(files.length)
_.each(filesIndex, (idx) => {
this.currentUpload = new Upload(files[idx]);
this.upSvc.pushUpload(this.currentUpload)}
)
}

}

uploads/upload-form.html

As an added touch, I use the snapshot of the upload progress value to adjust the width of the bootstrap progress bar.

<div *ngIf="currentUpload">
<div class="progress">
<div class="progress-bar progress-bar-animated" [ngStyle]="{ 'width': currentUpload?.progress + '%' }"></div>
</div>
Progress: {{currentUpload?.name}} | {{currentUpload?.progress}}% Complete
</div>

<label>
<input type="file" (change)="detectFiles($event)">
</label>

<button
[disabled]="!selectedFiles"
(click)="uploadSingle()">

Upload Single
</button>

<label>
<input type="file" (change)="detectFiles($event)" multiple>
</label>

<button
[disabled]="!selectedFiles"
(click)="uploadMulti()">

Upload Multiple
</button>

Step 5: Displaying Files from the Database

Looping over files follows the same process as the Realtime DB tutorial. Check out the database CRUD lesson or the full file upload code in the github repo.