How to Upload Files from Ionic to Firebase Storage Last update: 2020-01-14
Firebase can be used in a lot of ways, and besides the database the second biggest feature might be its cloud storage.
In this tutorial we will continue with an app from a previous tutorial on using different media files with Ionic. You can grab the code for the previous app and apply the changes of this post, or just look how the file upload could work in general!
Finally we will be able to capture media files, store them on our device and then upload them to Firebase storage!
For this app you will also need a Firebase account and an app inside the console - both are free so go ahead and create them!
Preparing our Media App
The first change we make to our previous project is adding Firebase, and another page where we can display all files that we uploaded. Therefore run inside the folder of the previous project:
npm install firebase @angular/fire
ionic g page cloudList
npm install @ionic-native/in-app-browser
ionic cordova plugin add cordova-plugin-inappbrowser
The InAppBrowser will be used to showcase that we actually got a real URL to the files after uploading them - of course this is not really mandatory otherwise.
For our simple case we will also disable the security rules on our Storage inside Firebase, so navigate to Storage -> Rules and exchange the current rules for these, which allow read and write access to everyone:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
You should of course change these after testing and have appropriate security rules in place in a real app!
Adding AngularFire and Firebase
Now it’s time to connect our Ionic app to Firebase, and for this you need the credentials of your Firebase app. You can add a new app to your Firebase project by clicking at the top left on the wheel and then Project settings. At the bottom of that page you can edit your apps or add a new web project.
Once you created this you need to copy the configuration which looks like this:
We can copy it out and paste it into our environments/environment.ts and make it look like this (but of course with your values inside):
export const environment = {
production: false,
firebaseConfig: {
apiKey: '',
authDomain: '',
databaseURL: '',
projectId: '',
storageBucket: '',
messagingSenderId: '',
appId: ''
}
};
With that information in place we can initialise the AngularFire module inside our app/app.module.ts and also import the AngularFireStorageModule
which is needed to access the storage:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ImagePicker } from '@ionic-native/image-picker/ngx';
import { File } from '@ionic-native/File/ngx';
import { MediaCapture } from '@ionic-native/media-capture/ngx';
import { Media } from '@ionic-native/media/ngx';
import { StreamingMedia } from '@ionic-native/streaming-media/ngx';
import { PhotoViewer } from '@ionic-native/photo-viewer/ngx';
import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFireStorageModule],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
ImagePicker,
MediaCapture,
File,
Media,
StreamingMedia,
PhotoViewer,
InAppBrowser
],
bootstrap: [AppComponent]
})
export class AppModule {}
Now our app is connected with Firebase and ready to upload some files!
Uploading Files to Firebase Storage
In the previous tutorial we captured files and stored them on our device, so we have all information locally available. Now we want to upload a certain file from a path, and we can do this by converting it to a Blob!
The Cordova file plugin already comes with a helpful function called readAsArrayBuffer
that will read a local file, but we also need to make sure that we set the right mimetype for the file.
To do so, we add a simple helper function that checks the extension of the file - of course you might have different files so feel free to extend that function to your needs.
Next we create a random id and combine it with a date to create a uniqueish name for our file inside Firebase storage.
The rest becomes super easy: We just call the upload
function on the AngularFireStorage and pass the new name (or path to the file) plus the blob we created. This will return an UploadTask
, which offers some cool functionality like subscribing to the changes in order to display a progress bar!
Now go ahead and change your previous app/home/home.page.ts to include the new functionality as well:
// All other imports from before!
import { AngularFireStorage } from '@angular/fire/storage';
export class HomePage implements OnInit {
files = [];
uploadProgress = 0;
constructor(
private imagePicker: ImagePicker,
private mediaCapture: MediaCapture,
private file: File,
private media: Media,
private streamingMedia: StreamingMedia,
private photoViewer: PhotoViewer,
private actionSheetController: ActionSheetController,
private plt: Platform,
private toastCtrl: ToastController,
private storage: AngularFireStorage
) {}
// All other functionality from before ...
async uploadFile(f: FileEntry) {
const path = f.nativeURL.substr(0, f.nativeURL.lastIndexOf('/') + 1);
const type = this.getMimeType(f.name.split('.').pop());
const buffer = await this.file.readAsArrayBuffer(path, f.name);
const fileBlob = new Blob([buffer], type);
const randomId = Math.random()
.toString(36)
.substring(2, 8);
const uploadTask = this.storage.upload(
`files/${new Date().getTime()}_${randomId}`,
fileBlob
);
uploadTask.percentageChanges().subscribe(change => {
this.uploadProgress = change;
});
uploadTask.then(async res => {
const toast = await this.toastCtrl.create({
duration: 3000,
message: 'File upload finished!'
});
toast.present();
});
}
getMimeType(fileExt) {
if (fileExt == 'wav') return { type: 'audio/wav' };
else if (fileExt == 'jpg') return { type: 'image/jpg' };
else if (fileExt == 'mp4') return { type: 'video/mp4' };
else if (fileExt == 'MOV') return { type: 'video/quicktime' };
}
}
Inside the view we already had a list previously, and we just add the progress bar and another sliding button to perform the upload task, so go ahead and change the app/home/home.page.html to:
<ion-header>
<ion-toolbar color="primary">
<ion-title>
Ionic Media Capture
</ion-title>
<ion-buttons slot="end">
<ion-button routerLink="/cloud-list">
<ion-icon slot="icon-only" name="cloud-done"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-progress-bar [value]="uploadProgress" color="success"></ion-progress-bar>
<ion-list>
<ion-item-sliding *ngFor="let f of files">
<ion-item (click)="openFile(f)">
<ion-icon name="image" slot="start" *ngIf="f.name.endsWith('jpg')"></ion-icon>
<ion-icon name="videocam" slot="start" *ngIf="f.name.endsWith('MOV') || f.name.endsWith('mp4')"></ion-icon>
<ion-icon name="mic" slot="start" *ngIf="f.name.endsWith('wav')"></ion-icon>
<ion-label class="ion-text-wrap">
{{ f.name }}
<p>{{ f.fullPath }}</p>
</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option (click)="deleteFile(f)" color="danger">
<ion-icon name="trash" slot="icon-only"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option (click)="uploadFile(f)" color="primary">
<ion-icon name="cloud-upload" slot="icon-only"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar color="primary">
<ion-button fill="clear" expand="full" color="light" (click)="selectMedia()">
<ion-icon slot="start" name="document"></ion-icon>
Select Media
</ion-button>
</ion-toolbar>
</ion-footer>
Now you can run your app on a device, capture a media file of any type (that will be stored on the device first) and then call the cloud upload to send it to Firebase Storage!
You can see the result by going to the Storage area of your Firebase app after you uploaded a file.
Working with Files in Firebase Storage
If you also want to list all files in your app that you’ve uploaded to Firebase, you can do it as well. At the time writing this it was not possible through AngularFire, but you can also directly use the web SDK of Firebase for cases like this.
All you have to do is call listAll
on the reference to your storage folder and it will return a list of references. These references contain a lot of information, and we just extract the interesting fields and push it to our local array which we will use within our view in the next step.
The most interesting part here is perhaps the getDownloadURL
, which returns a Promise. That’s why we have to wait for it (or use the async pipe in the view, whatever you prefer), but this will return the full URL to the hosted file on Firebase!
Go ahead and change your app/cloud-list/cloud-list.page.ts to:
import { Component, OnInit } from '@angular/core';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import * as firebase from 'firebase/app';
@Component({
selector: 'app-cloud-list',
templateUrl: './cloud-list.page.html',
styleUrls: ['./cloud-list.page.scss']
})
export class CloudListPage implements OnInit {
cloudFiles = [];
constructor(private iab: InAppBrowser) {}
ngOnInit() {
this.loadFiles();
}
loadFiles() {
this.cloudFiles = [];
const storageRef = firebase.storage().ref('files');
storageRef.listAll().then(result => {
result.items.forEach(async ref => {
this.cloudFiles.push({
name: ref.name,
full: ref.fullPath,
url: await ref.getDownloadURL(),
ref: ref
});
});
});
}
openExternal(url) {
this.iab.create(url);
}
deleteFile(ref: firebase.storage.Reference) {
ref.delete().then(() => {
this.loadFiles();
});
}
}
This means we can use this URL in our app to display files directly from Storage, or like we do, even open an external browser with the URL!
Finally, you can also call different functions on the reference itself like we do in the delete
function.
To wrap things up, we build a super simple list around the array inside the app/cloud-list/cloud-list.page.html like this:
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/"></ion-back-button>
</ion-buttons>
<ion-title>Cloud Files</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let f of cloudFiles">
<ion-button slot="start" (click)="openExternal(f.url)">
<ion-icon name="open" slot="icon-only"></ion-icon>
</ion-button>
<ion-label class="ion-text-wrap">
{{ f.name }}
<p>{{ f.full }}</p>
</ion-label>
<ion-button slot="end" (click)="deleteFile(f.ref)" color="danger">
<ion-icon name="trash" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
</ion-list>
</ion-content>
Now you can upload your files, see which files were uploaded, delete them or even use their full path to the hosted file.
Conclusion
Working with Firebase storage is a great way to easily host different files that your app needs, like user avatars or other uploaded files.
The biggest challenge might actually be the conversion from file to blob, but with the functions of this tutorial you should be prepared to handle everything!
You can also find a video version of this article below.