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
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
– 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:
-> Firebase Realtime Database:
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 FileUpload
s 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
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.
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>