Posts

Easy file uploads with Firebase Cloud Storage and AngularJS

In the previous tutorial, we learned how to use Firebase and AngularJS to store and view data in Realtime database. In this tutorial, we’ll learn how to store, list, download and delete files in Firebase Cloud Storage using AngularJS

Cloud Storage

Cloud Storage is a storage service which stores files as ‘objects’. These objects are kept in containers called ‘buckets’. You can read, write and delete objects, save metadata and set security rules. It uses Google Cloud Storage at its backend.

Getting started

  1. Firstly, we need to get Firebase initialization code. To do so, refer to the instructions in the previous tutorial here.
  2. Select Storage from Develop section on the left-side and click Get Started. There will be a prompt about security rules. Click Got it.Security Prompt
     
  3. Click Rules and change allow read/write permissions without requiring authentication. You can change these rules later.
    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write: if true;
        }
      }
    }
    
  4. Create index.html as follows and add the Firebase initialization code.
    <!DOCTYPE html>
    <html ng-app="app">
    
     <head>
    
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
     <title page-title></title>
    
     <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css'>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
     <link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap.min.css" />
    
     <style>
     body{
         font-family:”Open Sans” , sans-serif
     }
     </style>
     </head>
     <body>
     <nav class="navbar navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">Files</a>
            </div>
        </div>
     </nav>
    
     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
     <script src="https://www.gstatic.com/firebasejs/3.6.6/firebase.js"></script>
     <script src="https://cdn.firebase.com/libs/angularfire/2.3.0/angularfire.min.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.16/js/jquery.dataTables.min.js"></script>
     <script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap.min.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-datatables/0.6.2/angular-datatables.min.js"></script>
    
    <script>
      // Initialize Firebase
       var config = {
        apiKey: ********************,
        authDomain: ********************,
        databaseURL:********************,
        projectId: **********************,
        storageBucket: **********************,
        messagingSenderId: **********************,
       };
       firebase.initializeApp(config);
    </script>
     <script src="scripts/app.js"></script>
     <script src="scripts/controllers.js"></script>
     </body>
     </html>
    
  5. Create app.js and import AngularFire and Datatables.
    angular.module(app, [
            'firebase',
            'datatables'
        ])
    

Uploading a file

File Upload in Cloud Storage
Step1: Add the file upload form to index.html
Here file input executes the onChange function when any file is selected. Upload function is called when the form submits.

 <div class="container">
   <div ng-controller="UploadCtrl">
    <form name="uploadForm" ng-submit="upload()">
      <h3>File Uploader</h3>
      <div class="input-group">
        <input onchange="angular.element(this).scope().onChange(this.files)" class="form-control" type="file" />
        <div class="input-group-btn">
          <button class="btn btn-default" type="submit">Upload</button>
        </div>
      </div>
    </form>
  </div>
</div>

 

Step2: Create controllers.js and add the following code:

function UploadCtrl($scope, $firebaseStorage, $firebaseObject) {

    let fileToUpload = null;
    $scope.onChange = function onChange(fileList) {
        fileToUpload = fileList[0];
    };
    $scope.upload = function() {
        if (fileToUpload) {
            let storageRef = firebase.storage().ref(fileToUpload.name);
            let storage = $firebaseStorage(storageRef);
            let uploadTask = storage.$put(fileToUpload);
            uploadTask.$complete((snapshot) => {
                let ref = firebase.database().ref("Files");
                let pushKey = ref.push().key;
                let formData = $firebaseObject(ref.child(pushKey));
                formData.name = fileToUpload.name;
                formData.timestamp = firebase.database.ServerValue.TIMESTAMP;
                formData.url = snapshot.downloadURL;
                formData.$save().then(() => {
                    angular.element("input[type='file']").val(null);
                    fileToUpload = null;
                })
            });
        }
    }
}

angular
    .module('app')
    .controller('UploadCtrl', UploadCtrl)

Explanation:

  1. onChange function sets the fileToUpload variable with the selected file.
    let fileToUpload = null;
    $scope.onChange = function onChange(fileList) {
            fileToUpload = fileList[0];
        };
    
  2. In the upload function, we create a Firebase Storage reference for the selected file. $firebaseStorage is a service provided by AngularFire. It takes in this reference to generate a $firebaseStorage object.
    $scope.upload = function() {
            if (fileToUpload) {
                let storageRef = firebase.storage().ref(fileToUpload.name);
                let storage = $firebaseStorage(storageRef);
    
  3. To upload the selected file, the fileToUpload variable to passed to $put method of $firebaseStorage object. $put uploads the file and returns an upload task object. This object is used to track the upload progress and pause/resume or cancel the upload. We are using the $complete method of the upload task to execute some code after the upload is successful.
    let uploadTask = storage.$put(fileToUpload);
    uploadTask.$complete((snapshot) => {
    
  4. Firebase does not provide any API to get the list of all files at a specific location. To get this list, we save the details of the uploaded file to the database when the file upload is successful.
    We can later access the Files location in the database get the download URLs for the uploaded files. The file details are saved in a random push key generated by native Firebase SDK API. This is explained in the previous Firebase tutorial here.

    let ref = firebase.database().ref("Files");
    let pushKey = ref.push().key;
    let formData = $firebaseObject(ref.child(pushKey));
    
  5. The details of the uploaded file are saved in the formData object created earlier. First, we set the name property using fileToUpload object. Then set the timestamp using firebase.database.ServerValue.TIMESTAMP. It is a Firebase feature where the value of the timestamp will be set automatically when the data is saved in database. Finally, we use the snapshot object returned by the $complete method to get the downloadURL of the uploaded file.
    formData.name = fileToUpload.name;
    formData.timestamp = firebase.database.ServerValue.TIMESTAMP;
    formData.url = snapshot.downloadURL;
    
  6. Here we use the $save method of formData object to save file details in the database. After the file is saved, we reset the file input and set fileToUpload back to null.
    formData.$save().then(() => {
                        angular.element("input[type='file']").val(null);
                        fileToUpload = null;
                    })
    

Listing uploaded files

Listing Files

Step1: Add this to index.html, after div for UploadCtrl.

  <div ng-controller="tableCtrl">
    <h3>Uploaded Files</h3>
    <table datatable="ng" class="table row-border table-striped table-hover compact display">
    <thead>
      <tr>
        <th>Upload Date</th>
        <th>Name</th>
        <th>Actions</th>
    </tr>
    </thead>
    <tbody>
      <tr ng-repeat="(key,value) in files">
        <td>{{value.timestamp | date}}</td>
        <td>{{value.name}}</td>
        <td>
          <a href="{{value.url}}" class="btn btn-default" target="_blank">Download</a>
          <a href="#" class="btn btn-default" ng-click="delete(key, value.name)">Delete</a>
        </td>
      </tr>
    </tbody>
    </table>
  </div>

Here we have setup a datatable showing the upload date, file name and buttons to download or delete a file. File details are shown from files object created in tableCtrl.

Step2: Create tableCtrl in controllers.js

function tableCtrl($scope, $firebaseStorage, $firebaseObject){
    let fileRef = firebase.database().ref('Files');
    $scope.files = $firebaseObject(fileRef);
    $scope.delete = (key, name) => {
        let storageRef = firebase.storage().ref(name);
        let storage = $firebaseStorage(storageRef);
        storage.$delete().then(() => {
            delete $scope.files[key];
            $scope.files.$save();
        })
    }
}

angular
    .module('app')
    .controller('UploadCtrl', UploadCtrl)
    .controller('tableCtrl', tableCtrl)

Explanation:

  1. Reference is created to the Files location in database. Then data for all files is downloaded using $firebaseObject.
    function tableCtrl($scope, $firebaseStorage, $firebaseObject){
        let fileRef = firebase.database().ref('Files');
        let storageRef = firebase.storage().ref();
        $scope.files = $firebaseObject(fileRef);
    
  2. This function deletes the file from Cloud Storage and removes it from database.
    We get a reference to the file in Cloud Storage. Then we create a $firebaseStorage object using this reference. Finally we use the $delete method of $firebaseStorage to delete this file.
    When the file is deleted, we remove the file from files object and save the changes back to the database.

    $scope.delete = (key, name) => {
            let storageRef = firebase.storage().ref(name);
            let storage = $firebaseStorage(storageRef);
            storage.$delete().then(() => {
                delete $scope.files[key];
                $scope.files.$save();
            })
        }
    

This tutorial is now over. There are many features which you can explore further in its documentation. You can improve this app by adding a progress bar, better error handling, file previews and more. Check out the documentation from references given below.

References

Further reading

Five steps to build a basic contacts app using Firebase and AngularJS

In this tutorial, we will create a basic contacts app using AngularJS and Firebase. We are going to assume that you have basic understanding about using AngularJS.

Introduction to Firebase

It is a mobile development platform by Google. It provides products such as database, storage, authentication, messaging, analytics and more. Developers can start working on their apps without writing server-side code. It provides APIs to handle things like database, authentication, etc. on front-end. It can easily scale from small to large apps as it is based on Google Cloud infrastructure. Firebase Realtime database stores data in a JSON tree structure. You can store key-value pairs and create nested levels of data, like how JSON data is structured. But it can’t store real arrays.

What is AngularFire?

Firebase provides a Javascript SDK which provides various APIs to work with. AngularFire is a helper library. It aims to remove boilerplate involved in creating AngularJS bindings for Firebase APIs. It provides extra features like 3-way data-binding and array handling which makes integration between AngularJS and Firebase easier. AngularFire does not replace the SDK, rather it supplements it. You can use AngularFire APIs or native APIs depending upon what makes your work easier.

Setting Up Realtime Database:

Go to firebase.com to create a free account. Then go to Firebase Console and create new project.

Creating Project

Now, go to Database under Develop section on the left-side. Click Get Started under Realtime Database.

Selecting Database

Enable Start in test mode. To keep things simple for this tutorial, we are going to use test mode. It makes the database open to read and write publicly. Don’t worry, you can change these settings later on.

Test Mode

Setting up project files:

Step 1: Create index.html as following:

<!DOCTYPE html>
<html ng-app="app">

 <head>

 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">

 <title page-title></title>

 <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css'>
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
 <link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap.min.css" />

 <style>
 body{
     font-family:”Open Sans” , sans-serif
 }
 </style>
 </head>
 <body>
 <nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">Address Book</a>
        </div>
       <ul class="nav navbar-nav">
          <li><a ui-sref="home">Home</a></li>
          <li><a ui-sref="add">Add</a></li>
       </ul>
    </div>
 </nav>
 <div ui-view></div>

 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
 <script src="//unpkg.com/@uirouter/angularjs/release/angular-ui-router.min.js"></script>
 <script src="https://www.gstatic.com/firebasejs/3.6.6/firebase.js"></script>
 <script src="https://cdn.firebase.com/libs/angularfire/2.3.0/angularfire.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.16/js/jquery.dataTables.min.js"></script>
 <script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-datatables/0.6.2/angular-datatables.min.js"></script>

 <script>
 // Initialize Firebase
 var config = {
    apiKey: ********************,
    authDomain: ********************,
    databaseURL:********************,
    projectId: **********************,
    storageBucket: **********************,
    messagingSenderId: **********************,
 };
 firebase.initializeApp(config);
 </script>
 <script src="scripts/app.js"></script>
 <script src="scripts/config.js"></script>
 <script src="scripts/controllers.js"></script>
 </body>
 </html>

Now, open your newly created project on Firebase console. Click “Add Firebase to your web app” and copy the initialization code. Don’t copy the first script tag as we have already added it earlier in the index. Replace the initialization code in index.html with it.

Initializing Firebase

 

Step 2: Create a scripts folder and create following files:
app.js

angular.module('app', [
        'ui.router',
        'firebase',
        'datatables'
    ])

Here we need to import modules of Angular UI Router, AngularFire and datatables (to be explained later).

config.js

function config($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/home');
    $stateProvider
       .state('home', {
            url: "/home",
            templateUrl: "views/home.html"
        })
        .state('add', {
            url: "/add",
            templateUrl: "views/add.html"
        })
}

angular
    .module('app')
    .config(config)

Then we setup the URL and pages for the Home and Add pages using Angular UI Router. /home is set to be the default page using $urlRouterProvider.

 

Step 3: Create a views folder and create home.html file with just “Hello World!” text.
Create add.html page as:

<div class="container" ng-controller="addContactCtrl">
<form ng-submit="add()">
    <div class="form-group">
        <label>Name</label>
        <input type="text" class="form-control" ng-model="formData.name">
    </div>
    <div class="form-group">
        <label>Address</label>
        <input type="text" class="form-control" ng-model="formData.address">
    </div>
    <div class="form-group">
        <label>Phone No.</label>
        <input type="text" class="form-control" ng-model="formData.phone">
    </div>
    <button class='btn btn-default' type="submit">Save</button>
</form>
</div>

Here the input text is stored in the formData object using ng-model. When the user submits the form, add function is executed.
Add function will store the new person’s details in Firebase.

 

Step 4: Create controllers.js and add the following code:

function addContactCtrl($scope, $state, $firebaseObject) {

    let ref = firebase.database().ref('Contacts');
    let pushKey = ref.push().key;
    $scope.formData = $firebaseObject(ref.child(pushKey));

    $scope.add = function(){
        $scope.formData.$save().then(() => {
            $state.go('home');
        });
    }
};
angular
    .module('app')
    .controller('addContactCtrl', addContactCtrl)

Add page now looks like this:

Add Contact Page

Let’s explain it step by step:

  1. Here we have used AngularJS Dependency Injection to use $scope, $state and $firebaseObject services. $scope is used to connect the views with the controller and access data on both sides. $state is used to redirect the user to different ‘states’ or links declared earlier in config.js. Finally $firebaseObject is an AngularFire service to get and save data in Firebase.
    function addContactCtrl($scope, $state, $firebaseObject) {
  2. To add, change or remove data in Firebase, we need to get a reference to a specific location in the database. For this tutorial, the contact details are stored in ‘Contacts’ key. So, the reference points to /Contacts.
    wlet ref = firebase.database().ref('Contacts');
  3. Firebase SDK provides a feature called push keys. These are randomly generated keys which can be used as unique keys to store data, without worrying about data getting overwritten by someone else.
    AngularFire does not directly generate these keys. So we have to use a native Firebase method for this purpose. To generate a push key, add .push() to a reference. It returns an object containing the push key. This key is stored into the pushKey variable.

    let pushKey = ref.push().key;
  4. Here, we have added a child reference to the reference we already created. So if the push key generated was ‘abcd’, then the the new reference now points to /Contacts/abcd. $firebaseObject is an AngularFire function. It takes in a Firebase reference and returns a customized object which contains the data at the reference location and some helper methods.
    If some data is already stored at /Contacts/abcd, it is automatically downloaded in the returned object. $scope.formData is the variable used earlier in add.html to store the contact details of a new user. This data is now stored in the object returned by $firebaseObject.

    $scope.formData = $firebaseObject(ref.child(pushKey));
  5. This function runs on form submit. To save contact details in Firebase, $save method is called on a $firebaseObject. $save is an AngularFire method. Any data added to $scope.formData through ng-model will get saved directly in Firebase.
    $save returns a promise. In its .then function, we use $state.go to redirect the user to the home page after a contact gets saved in firebase.

    $scope.add = function(){
        $scope.formData.$save().then(() => {
            $state.go('home');
        });
    }

What is Datatables?

It is a jQuery plugin which is used to create tables which can be easily sorted, filtered and edited. It’s packed with features but we going to setup a very basic table using it’s Angular JS plugin ie, Angular-DataTables.

Setting up DataTables:

Step 1: Create home controller to get contacts from Firebase

function homeCtrl($scope, $firebaseObject){
    const ref = firebase.database().ref('Contacts');
    $scope.contacts = $firebaseObject(ref);
}
angular
    .module('app')
    .controller('homeCtrl', homeCtrl)
    .controller('addContactCtrl', addContactCtrl)

Here we created a reference to the Contacts key in Firebase and downloaded its data in $scope.contacts using $firebaseObject.

Step 2: Create home.html in views folder

<div class='container' ng-controller="homeCtrl">
<table datatable="ng" class="table row-border table-striped table-hover compact display">
    <thead>
    <tr>
        <th>Name</th>
        <th>Address</th>
        <th>Phone No.</th>
    </tr>
    </thead>
    <tbody>
      <tr ng-repeat="contact in contacts">
        <td>{{contact.name}}</td>
        <td>{{contact.address}}</td>
        <td>{{contact.phone}}</td>
      </tr>
    </tbody>
</table>
</div>

Here we have initialised datatables using the datatables=”ng” directive. It specifies that the data in the table is rendered using AngularJS.
In tbody, ng-repeat directive creates rows for every contact in $scope.contacts object.

Home Page
And thus, the basic contacts app is now complete. You are free to improve it by adding more features like editing or deletion of contacts.

Conclusion:

And thus, the basic contacts app is now complete. We learned how to setup a database in Firebase. Then we added Firebase JavaScript SDK to the AngularJS project. We setup two pages, one to view contacts and other to add new contacts. Then we used AngularFire, a helper library, to easily store data in Firebase. Finally, we used Datatables, a plugin which helps in creating html tables.

We have just scratched the surface of what you can do with Firebase and AngularJS. You can add more features like edit/delete, authentication, backup and restore etc.

References

Further reading

Portfolio Items