Using Firebase Cloud Firestore with Ionic

October 22, 2017, 1:51 pm Categories:

Categories

Firebase's latest NoSQL database service: Firestore is currently (at the time of writing this article) in beta and provides a faster, lightweight, cloud hosted, scalable database solution for developers to use with their applications.

In this tutorial I'll demonstrate how Ionic can leverage the Firestore service and take you through some of the ways in which we can interact with that.

What we'll be building

Using a combination of the Firebase web library and Ionic we'll develop a very simple CRUD (Create, Read, Update and Delete) application that will allow us to retrieve data on British Cities from the Cloud Firestore collection and manage that accordingly.

Initially the Cloud Firestore collection will be populated with a default location that we can then choose, if we so wish, to update, delete and add other locations to.

Once data has been stored inside the Cloud Firestore collection this will then be automatically retrieved and displayed within the HomePage component template like so:

Adding a new document will present the following form which, once successfully completed, will display an alert message and wipe the form fields of the data that was previously entered:

Updating an existing document follows a similar approach:

Finally, deletion of documents is handled directly from the HomePage template itself and, once successfully completed, alerts the user to the fact (although the record is removed from the Cloud Firestore collection it still remains displayed on the page UNTIL the alert box is closed - a UX issue in this tutorial that I'll leave for you to resolve!):

So now you know what we'll be developing let's move on to creating and configuring our Firebase project...

Getting started with Firebase

Log into your Firebase account and create a new project:

Once your new project has been completed navigate to the Database service and select Cloud Firestore from the dropdown menu:

You can then configure your security rules to start in test mode (so we can actually access the Firestore database service via the Firebase web library):

Next you'll need to obtain the Firebase details for your project from the Add Firebase to your web app option on the project Overview page:

When adding the required configuration keys/values for the Firestore service to your Ionic project (which we'll create in the next section) you will only require the following:

export const environment = {
   production: false,
   firebase : {
      apiKey       : "YOUR-API-KEY-VALUE-ENTERED-HERE",
      authDomain   : "YOUR-FIREBASE-PROJECT-DOMAIN",
      projectId    : "YOUR-FIREBASE-PROJECT-ID"
   }
};

That's all we need on the Firebase side so now let's get started with Ionic.

Creating and configuring the Ionic project

Open your system CLI, navigate to where you would usually create your digital projects and, using the Ionic CLI, run the following command to create a blank Ionic project:

ionic start ionic-firestore blank

Once created run the following commands to:

  • Change into the newly created project
  • Install the firebase/firestore web library from npm
  • Install the promise-polyfill (to avoid any promise related errors when using Firebase on iOS/Android - even though, for the purposes of this tutorial, we're only going to be developing for eventual deployment within our desktop browser)
  • Create a manage-document component for handling creating/updating documents
  • Create a Database service to manage interfacing with the Cloud Firestore service
cd ./ionic-firestore
npm install --save firebase
npm install --save promise-polyfill
ionic g page manage-document
ionic g provider database

Next you'll want to create a new folder titled environments in your ionic-firestore/src directory that contains a single typescript file - environment.ts - with your Firebase Web API configuration keys/values (remember these from the previous section? ;) ) like so:

export const environment = {
   production: false,
   firebase : {
      apiKey       : "YOUR-API-KEY-VALUE-ENTERED-HERE",
      authDomain   : "YOUR-FIREBASE-PROJECT-DOMAIN",
      projectId    : "YOUR-FIREBASE-PROJECT-ID"
   }
};

These will then be imported into, and initialised within, the ionic-firestore/src/app/app.component.ts root component for the project as follows:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { environment } from '../environments/environment';
import firebase from 'firebase';

import { HomePage } from '../pages/home/home';
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = HomePage;

   constructor(platform       : Platform, 
               statusBar      : StatusBar, 
               splashScreen   : SplashScreen) 
   {
      platform
      .ready()
      .then(() => 
      {
         // Okay, so the platform is ready and our plugins are available.
         // Here you can do any higher level native things you might need.
         statusBar.styleDefault();
         splashScreen.hide();
      });

      firebase.initializeApp(environment.firebase);
   }
}  

Finally, you’ll need to import the HttpModule for the application’s ionic-firestore/src/app/app.module.ts root module like so:

import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { DatabaseProvider } from '../providers/database/database';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    DatabaseProvider
  ]
})
export class AppModule {}  

With these in place we're ready to begin development but before we do we need to spend a little while familiarising ourselves with how Cloud Firestore manages data and what methods we can use, courtesy of the Firebase web library, to interact with the service...

Managing data with Cloud Firestore

As mentioned earlier Cloud Firestore is a NoSQL, cloud hosted database service currently in beta that differs from the pre-existing Firebase Realtime Database in the following ways:

  • Data is stored in documents which are organised inside collections
  • Offline support for iOS, Android & Web clients
  • Queries are indexed by default
  • Queries can be shallow (allows developer, if they so wish, to query only parts of a collection or document and NOT the entire document or collection itself)
  • Automatic scaling (post-beta release) which removes the need for data sharding

For developers familiar with NoSQL database solutions like MongoDB the terms collection and document will be instantly familiar.

A collection is essentially a data container for storing and organising documents which themselves represent individual records.

A document contains key/value mappings which can correspond to any of the following data types:

  • boolean
  • number
  • string
  • geo point
  • BLOB (Binary Large OBject)
  • timestamp
  • arrays
  • nested objects (aka maps)

As you might guess this wide range of data types gives us quite a lot of flexibility in terms of what data we can store when creating documents within a Cloud Firestore database.

Best of all the Firebase web library provides Firestore API methods which we can implement within our projects to access the service and start managing data. Some of these methods - which we'll be using once we start coding - include:

  • collection - Provides access to a named collection
  • doc - Allows reference to a specific document within a collection
  • set - Allows a specific document to be written to (if it doesn't exist it will be created)
  • get - Allows document data to be retrieved
  • add - Allows data to be added to a document
  • update - Allows data to be updated within a document
  • delete - Removes a document from a collection

IMPORTANT - A collection and/or a document can be created implicitly (which means we don't need to explicitly declare those within our code). If the collection or document does not exist Firestore will automatically create it for you.

This is quite useful and, as you'll see within the DatabaseProvider service (which we'll cover in the next section), we leverage this 'generosity' directly within the first method.

Interfacing with Firestore

Now that we have a clear picture of how Cloud Firestore service stores data and what methods are available for interfacing with the service we can start adding the following logic to our DatabaseProvider service:

  • Import the firestore module
  • Create a Firestore collection and populate this with an initial document
  • Retrieve all documents from the database collection
  • Add new documents to the database collection
  • Update existing documents within the database collection
  • Remove documents from the database collection

Open the ionic-firestore/src/providers/database/database.ts service and amend the code so that it resembles the following:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

// We MUST import both the firebase AND firestore modules like so
import * as firebase from 'firebase';
import 'firebase/firestore';

@Injectable()
export class DatabaseProvider {



   /**
    * @name _DB
    * @type {object}
    * @private
    * @description     Defines an object for handling interfacing with the 
    				   Cloud Firestore database service
    */
   private _DB : any;



   constructor(public http: Http) 
   {
      // Initialise access to the firestore service
      this._DB = firebase.firestore();
   }



   /**
    * Create the database collection and defines an initial document 
    * Note the use of merge : true flag within the returned promise  - this 
    * is needed to ensure that the collection is not repeatedly recreated should 
    * this method be called again (we DON'T want to overwrite our documents!)
    *
    * @public
    * @method createAndPopulateDocument
    * @param  collectionObj    {String}           The database collection we want to create
    * @param  docID            {String}           The document ID 
    * @param  dataObj          {Any}              The document key/values to be added
    * @return {Promise}
    */
   createAndPopulateDocument(collectionObj : string,
                             docID         : string,
                             dataObj       : any) : Promise<any>
   {
      return new Promise((resolve, reject) =>
      {
         this._DB
         .collection(collectionObj)
         .doc(docID)
         .set(dataObj, { merge: true }) 
         .then((data : any) =>
         {
            resolve(data);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   /**
    * Return documents from specific database collection
    *
    * @public
    * @method getDocuments
    * @param  collectionObj    {String}           The database collection we want to retrieve records from
    * @return {Promise}
    */
   getDocuments(collectionObj : string) : Promise<any>
   {
      return new Promise((resolve, reject) =>
      {
         this._DB.collection(collectionObj)
         .get()
         .then((querySnapshot) => 
         {

            // Declare an array which we'll use to store retrieved documents
            let obj : any = [];


            // Iterate through each document, retrieve the values for each field
            // and then assign these to a key in an object that is pushed into the 
            // obj array
            querySnapshot
            .forEach((doc : any) => 
            {
                obj.push({
                   id             : doc.id,
                   city           : doc.data().city,
                   population     : doc.data().population,
                   established    : doc.data().established
                });
            });

            
            // Resolve the completed array that contains all of the formatted data 
            // from the retrieved documents
            resolve(obj);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   /**
    * Add a new document to a selected database collection
    *
    * @public
    * @method addDocument
    * @param  collectionObj    {String}           The database collection we want to add a new document to
    * @param  docObj           {Any}              The key/value object we want to add
    * @return {Promise}
    */
   addDocument(collectionObj : string,
             dataObj       : any) : Promise<any> 
   {
      return new Promise((resolve, reject) =>
      {
         this._DB.collection(collectionObj).add(dataObj)
         .then((obj : any) =>
         {
            resolve(obj);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   /**
    * Delete an existing document from a selected database collection
    *
    * @public
    * @method deleteDocument
    * @param  collectionObj    {String}           The database collection we want to delete a document from
    * @param  docObj           {Any}              The document we wish to delete
    * @return {Promise}
    */
   deleteDocument(collectionObj : string,
                docID         : string) : Promise<any> 
   {
      return new Promise((resolve, reject) =>
      {
         this._DB
         .collection(collectionObj)
         .doc(docID)
         .delete()
         .then((obj : any) =>
         {
            resolve(obj);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   /**
    * Update an existing document within a selected database collection
    *
    * @public
    * @method updateDocument
    * @param  collectionObj    {String}           The database collection to be used
    * @param  docID            {String}           The document ID 
    * @param  dataObj          {Any}              The document key/values to be updated
    * @return {Promise}
    */
   updateDocument(collectionObj : string,
                docID         : string,
                dataObj       : any) : Promise<any> 
   {
      return new Promise((resolve, reject) =>
      {
         this._DB
         .collection(collectionObj)
         .doc(docID)
         .update(dataObj)
         .then((obj : any) =>
         {
            resolve(obj);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }

}

This shouldn't require too much elaboration as the JSDoc syntax and extensive commenting throughout the service should explain both the purpose of and what is happening with each method contained in our Databaseprovider service.

With this now in place let's move onto the logic for the HomePage component...

Take me home

The HomePage component, as we saw earlier on in this tutorial retrieves all of the existing documents in the Firestore database collection, provides edit and delete buttons under each displayed document and also allows the user to add a new document to the collection (which is pretty important as we wouldn't be able to generate new documents without this!)

As we haven't actually defined a collection (and, as a result of this, don't have any existing documents to retrieve) the first priority for the HomePage component (after importing the necessary modules and declaring/initialising properties) will be to create a new collection for the Cloud FireStore service and subsequently populate this with an initial document.

This will be handled by calling the createAndPopulateDocument method from the DatabaseProvider service:

createAndPopulateDocument(collectionObj : string,
                          docID         : string,
                          dataObj       : any) : Promise<any>
{
   return new Promise((resolve, reject) =>
   {
      this._DB
      .collection(collectionObj)
      .doc(docID)
      .set(dataObj, { merge: true }) 
      .then((data : any) =>
      {
         resolve(data);
      })
      .catch((error : any) =>
      {
         reject(error);
      });
   });
}

Following from this the HomePage component will then implement the necessary logic to handle the following tasks and requirements, which includes:

  • Retrieving documents from the defined database collection
  • Adding documents to this collection
  • Updating selected documents within this collection
  • Deleting selected documents within this collection
  • Using the Ionic AlertController to provide user feedback

Open the ionic-firestore/src/pages/home/home.ts component and make the following amendments to implement this logic:

import { Component } from '@angular/core';
import { NavController, AlertController } from 'ionic-angular';
import { DatabaseProvider } from '../../providers/database/database';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {



   /**
    * @name _COLL
    * @type {string}
    * @private
    * @description      Defines the name of the database collection
    */
   private _COLL 		: string 			= "Britain";




   /**
    * @name _DOC
    * @type {string}
    * @private
    * @description      Defines the initial document ID for the database collection
    */
   private _DOC 		: string 			= "Xy76Re34SdFR1";




   /**
    * @name _CONTENT
    * @type {any}
    * @private
    * @description      Used to store/provide the initial document data for the database collection
    */
   private _CONTENT  	: any;

   

   /**
    * @name locations
    * @type {any}
    * @public
    * @description      Property to store the returned documents from the database collection
    */
   public locations     : any;


   
   constructor(public navCtrl  : NavController,
               private _DB     : DatabaseProvider,
               private _ALERT  : AlertController) 
   {
      this._CONTENT = {
         city 			: "London",
         population 	: "8,787,892",
         established    : "C. 43 AD"
      };
   }




   /**
    * Retrieve all documents from the specified collection using the 
    * retrieveCollection method when the view is entered
    *
    * @public
    * @method ionViewDidEnter
    * @return {none}
    */
   ionViewDidEnter()
   {
      this.retrieveCollection();
   }




   /**
    * Creates the collection and populates that with an initial document
    * using the createAndPopulateDocument method of the DatabaseProvider
    * service
    *
    * @public
    * @method generateCollectionAndDocument
    * @return {none}
    */
   generateCollectionAndDocument() : void
   {
      this._DB.createAndPopulateDocument(this._COLL,
                                         this._DOC,
                                         this._CONTENT)
      .then((data : any) =>
      {
         console.dir(data);
      })
      .catch((error : any) =>
      {
         console.dir(error);
      });
   }




   /**
    * Retrieve all documents from the specified collection using the 
    * getDocuments method of the DatabaseProvider service
    *
    * @public
    * @method retrieveCollection
    * @return {none}
    */
   retrieveCollection() : void
   {
      this._DB.getDocuments(this._COLL)
      .then((data) =>
      {

         // IF we don't have any documents then the collection doesn't exist
         // so we create it!
         if(data.length === 0)
         {
            this.generateCollectionAndDocument();
         }

         // Otherwise the collection does exist and we assign the returned 
         // documents to the public property of locations so this can be 
         // iterated through in the component template
         else
         {
            this.locations = data;
         }
      })
      .catch();
   }




   /**
    * Navigate to the manage-document component to begin adding a new document
    *
    * @public
    * @method addDocument
    * @return {none}
    */
   addDocument() : void
   {
      this.navCtrl.push('manage-document');
   }




   /**
    * Update a document by passing the data to the manage-document component
    *
    * @public
    * @method updateDocument
    * @param  obj          {Object}           The document data we wish to update
    * @return {none}
    */
   updateDocument(obj) : void
   {
      let params : any = {
         collection   : this._COLL,
         location     : obj
      };
      this.navCtrl.push('manage-document', { record : params, isEdited : true });
   }




   /**
    * Delete a document from the Cloud Firestore collection using the
    * deleteDocument method of the DatabaseProvider service
    *
    * @public
    * @method deleteDocument
    * @param  obj          {Object}           The document ID for the document we wish to delete
    * @return {none}
    */
   deleteDocument(obj) : void
   {
      this._DB.deleteDocument(this._COLL,
      						obj.id)
      .then((data : any) =>
      {
         this.displayAlert('Success', 'The record ' + obj.city + ' was successfully removed');
      })
      .catch((error : any) => 
      {
         this.displayAlert('Error', error.message);
      });
   }




   /**
    * Provide feedback to user after an operation has succeeded/failed
    *
    * @public
    * @method displayAlert
    * @param  title          {String}           Heading for alert message
    * @param  message        {String}           Content for alert message
    * @return {none}
    */
   displayAlert(title      : string, 
                message    : string) : void
   {
      let alert : any     = this._ALERT.create({
         title      : title,
         subTitle   : message,
         buttons    : [{
          text      : 'Got It!',
          handler   : () => 
          {
            this.retrieveCollection();
          }
        }]
      });
      alert.present();
   }

}

The JSDoc syntax/commenting for the component logic should describe each method in full so let's move on to adding the necessary mark-up for the HomePage component template.

Open the ionic-firestore/src/pages/home/home.html template and add the following markup:

<ion-header>
  <ion-navbar>
    <ion-title>
      Cloud Firestore
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

   <button 
      ion-button 
      block 
      color="primary" 
      (click)="addDocument()">
   	  Add a new record
   </button>

  
   <ion-list>
   	  <ion-item *ngFor="let location of locations">
   	  	 <h2>{{ location.city }}</h2>
   	  	 <p>
   	  	    Population: {{ location.population }}<br>
            Established: {{ location.established }}
   	  	 </p>


   	  	 <button 
            ion-button 
            color="secondary" 
            (click)="updateDocument(location)">
         	  Update this record
         </button>


   	  	 <button 
            ion-button 
            color="danger" 
            (click)="deleteDocument(location)">
         	  Delete this record
         </button>


   	  </ion-item>
   </ion-list>
  
</ion-content>

A fairly straightforward set up as all we do here is display a button at the top of the template to add new documents then, underneath this, use an NgFor loop to iterate through all of the retrieved documents from the database collection and render these to the page with an associated update and delete button underneath each record.

Now let's shift our attention to the ManageDocumentPage component which will be used to handle adding new documents and displaying the data for an existing document which we can then update within our collection.

Managing our document

Within the ManageDocumentPage component we need to add the necessary logic to handle the following requirements:

  • Receive and parse navigation parameters for existing records
  • Implement Angular FormBuilder validation
  • Add the ability to determine whether we are creating a new document or are, instead, updating an existing document
  • Display values for an existing document in the correct fields in the form of the component template
  • Clearing the form fields of data once a document has been successfully saved
  • Alerting the user to the success/failure of managing that particular document

To accomplish this open the ionic-firestore/src/pages/manage-document/manage-document.ts component and change the existing code to the following (the extensive use of commenting should help you understand what is happening at each stage of the class):

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AlertController, IonicPage, NavController, NavParams } from 'ionic-angular';
import { DatabaseProvider } from '../../providers/database/database';


@IonicPage({
	name: "manage-document"
})
@Component({
  selector: 'page-manage-document',
  templateUrl: 'manage-document.html',
})
export class ManageDocumentPage {


   
   /**
    * @name form
    * @type {object}
    * @public
    * @description     Defines an object for handling form validation
    */
   public form          : any;
   


   /**
    * @name records
    * @type {object}
    * @public
    * @description     Defines an object for returning documents from Cloud Firestore database
    */
   public records       : any;
   


   /**
    * @name city
    * @type {string}
    * @public
    * @description     Model for city form field
    */
   public city          : string          = '';
   


   /**
    * @name population
    * @type {string}
    * @public
    * @description     Model for population form field
    */
   public population    : string          = '';
   


   /**
    * @name established
    * @type {string}
    * @public
    * @description     Model for established form field
    */
   public established 	: string          = '';
   


   /**
    * @name docID
    * @type {string}
    * @public
    * @description     property that stores an edited document's ID
    */
   public docID         : string          = '';
   


   /**
    * @name isEditable
    * @type {boolean}
    * @public
    * @description     property that stores value to signify whether 
                       we are editing an existing document or not
    */
   public isEditable    : boolean         = false;
   


   /**
    * @name title
    * @type {string}
    * @public
    * @description     property that defines the template title value
    */
   public title 		: string		   = 'Add a new document';
   


   /**
    * @name _COLL
    * @type {string}
    * @private
    * @description     property that stores the value for the database collection
    */
   private _COLL 		: string 			= "Britain";

          
   constructor(public navCtrl        : NavController,
               public params         : NavParams,
               private _FB 	         : FormBuilder,
               private _DB           : DatabaseProvider,
               private _ALERT        : AlertController)
   {

      // Use Formbuilder API to create a FormGroup object
      // that will be used to programmatically control the
      // form / form fields in the component template
      this.form 		= _FB.group({
         'city' 		        : ['', Validators.required],
         'population' 	        : ['', Validators.required],
         'established'	        : ['', Validators.required]
      });


      // If we have navigation parameters then we need to 
      // parse these as we know these will be used for 
      // editing an existing record
      if(params.get('isEdited'))
      {
          let record 		        = params.get('record');

          this.city	            = record.location.city;
          this.population   	  = record.location.population;
          this.established      = record.location.established;
          this.docID            = record.location.id;
          this.isEditable       = true;
          this.title            = 'Update this document';
      }
   }



   /**
    * Saves form data as newly added/edited record within Firebase Realtime
    * database and handles uploading of media asset to Firebase Storage 
    *
    * @public
    * @method saveDocument
    * @param  val          {any}              Form data
    * @return {none}
    */
   saveDocument(val : any) : void
   {
      let city	            : string		= this.form.controls["city"].value,
	 	      population        : string 		= this.form.controls["population"].value,
  		    established       : string		= this.form.controls["established"].value;
      

      // If we are editing an existing record then handle this scenario
      if(this.isEditable)
      {

         // Call the DatabaseProvider service and pass/format the data for use
         // with the updateDocument method
         this._DB.updateDocument(this._COLL, 
                               this.docID, 
                               {
	                               city    		 : city,
	                               population    : population,
	                               established   : established
	                           })
         .then((data) =>
         {
            this.clearForm();
            this.displayAlert('Success', 'The document ' +  city + ' was successfully updated');
         })
         .catch((error) =>
         {
            this.displayAlert('Updating document failed', error.message);            
         });
      }

      // Otherwise we are adding a new record
      else
      {

         // Call the DatabaseProvider service and pass/format the data for use
         // with the addDocument method
         this._DB.addDocument(this._COLL, 
                            {
	                           city    		 : city,
	                           population    : population,
	                           established   : established
	                        })
         .then((data) =>
         {
            this.clearForm();
            this.displayAlert('Record added', 'The document ' +  city + ' was successfully added');
         })
         .catch((error) =>
         {
            this.displayAlert('Adding document failed', error.message);         
         });
      }
   }



   /**
    * Provide feedback to user after an operation has succeeded/failed
    *
    * @public
    * @method displayAlert
    * @param  title          {String}           Heading for alert message
    * @param  message        {String}           Content for alert message
    * @return {none}
    */
   displayAlert(title      : string, 
                message    : string) : void
   {
      let alert : any     = this._ALERT.create({
         title      : title,
         subTitle   : message,
         buttons    : ['Got it!']
      });
      alert.present();
   }



   /**
    * Clear all form data 
    *
    * @public
    * @method clearForm
    * @return {none}
    */
   clearForm() : void
   {
      this.city  					= '';
      this.population				= '';
      this.established 				= '';
   }


}

With the logic in place it now falls on us to add the necessary HTML markup to the ionic-firestore/src/pages/manage-document/manage-document.html template like so:

<ion-header>
   <ion-navbar>
      <ion-title>
         {{ title }}
      </ion-title>
   </ion-navbar>
</ion-header>



<ion-content>
   <form
      [formGroup]="form"
      (ngSubmit)="saveDocument(form.value)">

   	  <ion-item>
         <ion-label stacked>City:</ion-label>
         <ion-input
            type="text"
            formControlName="city"
            [(ngModel)]="city"></ion-input>
   	  </ion-item>


      <ion-item>
         <ion-label stacked>Population:</ion-label>
         <ion-input
            type="text"
            formControlName="population"
            [(ngModel)]="population"></ion-input>
   	  </ion-item>


      <ion-item>
         <ion-label stacked>Established:</ion-label>
         <ion-input
            type="text"
            formControlName="established"
            [(ngModel)]="established"></ion-input>
   	  </ion-item>


   	  <ion-item>
         <button
           ion-button
           block
           color="primary"
           text-center
           padding-top
           padding-bottom
           [disabled]="!form.valid">
            <div *ngIf="!isEditable">
               Add a new document
            </div>

            <div *ngIf="isEditable">
               Update this document
            </div>
            </button>
   	  </ion-item>

   </form>


</ion-content>

There shouldn't be anything too challenging or difficult to understand with the above.

We're simply using Angular's Formbuilder API to manage validating the form fields (along with the use of NgModel directives to handle clearing those fields once our form has been successfully submitted) along with use of the ngIf directive to determine what titles will be displayed on the form submit button.

Okay, so with the HomePage, ManageDocumentPage components and the DatabaseProvider service all coded in full we can now test that the application actually works (always an important part of developing any project!) by running the project in our browser with the following command from the Ionic CLI:

ionic serve

All things being configured correctly in both Firebase and the Ionic project you should see your project loading and subsequently start being able to add/display new documents - like so:

In summary

During this tutorial we've explored using the beta version of Firebase's NoSQL database: Cloud Firestore service to create a very simple CRUD application with Ionic.

There's a lot that we haven't touched on with additional features of the Cloud Firestore service such as:

  • Transactions
  • Aggregation queries
  • Full text search
  • Enabling offline data persistence

What we have done though is gain a solid understanding of how data is managed within the Cloud Firestore service and subsequently built a strong foundation for managing that data through a combination of Ionic and the Firebase Web Library.

There are lots of ways in which this tutorial could be built on so feel free to explore and play!

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for information about working with forms and components in Ionic (doesn't cover Firebase).

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top