Working With Shared Providers In An Ionic 2 Mobile App

When developing an Ionic 2 application there are often scenarios where it probably isn’t a good idea to include repetitive source code in multiple application pages.  A common example of this would be when it comes to database interaction in an Ionic 2 application.  Sure you could establish a connection to the database on every page and query it, but it would probably make more sense to use it like a shared provider.

We’re going to see how to create a SQLite shared provider, often referred to as a shared service or sometimes a singleton class, in an Ionic 2 Android and iOS application using Angular 2.

To make what we’re about to cover as easy to understand as possible, we’re going to start with a fresh Ionic 2 project.  From your Terminal (Mac and Linux) or Command Prompt (Windows), execute the following:

There are a few things to note in the above commands.  The first thing to note is the --v2 tag found in the project creation command.  This tags mean that we’ll be creating an Ionic 2 project that uses TypeScript.  You must be using the Ionic 2 CLI to make this possible, not the Ionic Framework 1 CLI.  The second thing to note is that if you’re not using a Mac, you cannot build for the iOS platform.

Since SQLite will be the basis around our example, we need to add the Apache Cordova SQLite plugin.  Using your Command Prompt or Terminal, execute the following:

If you want to know the basics behind the SQLite plugin and Ionic 2, check out the previous post I wrote regarding using SQLite in an Ionic 2 application.

Creating a Database Shared Provider

So the goal here is to set up the SQLite foundation once and use it with a minimal amount of effort in each of the pages.  Let’s start by creating a provider for our database.  From your Terminal or Command Prompt, execute the following:

The above command will generate a provider at app/providers/database/database.ts.  It will be pre-populated with a lot of code that we don’t need.

Open the app/providers/database/database.ts file and replace the code with the following:

There is a quite a bit happening above so let’s break it down.

Since we’re using SQLite we can use of Ionic Native to make it a bit more Angular 2 friendly.  We import the Ionic Native components along with the Injectable component so we can inject it in our pages and providers list.

In the Database class we create a private variable for keeping track of our storage solution.  In the constructor method we initialize the storage solution and create a new database table only if it doesn’t already exist.  This constructor method is called every time we inject this provider into a page, but our conditional logic prevents trying to open more than one instance of the database.

Finally we have two functions, a getPeople function and a createPerson function.  The function for retrieving data will return a promise.  The success response of the promise will be an array because we are transforming the SQLite results to an array of objects.  The function for creating data will take two strings and return a row id.

So how do we make use of this database provider?

Using the Database Shared Provider in Your Application Pages

To keep this example simple we’re only going to make use of the HomePage which was created when we started a new project.  This page will have a TypeScript logic file and HTML UI file.

Starting with the TypeScript file, open the project’s app/pages/home/home.ts file and include the following code:

We start by importing the provider that we had just created.  In the HomePage class we define a public variable which will be bound to the page UI.  It will contain data from the database.

The constructor method will do two things.  It is where we inject the database provider for use in other functions and it is where we initialize the public variable.

It is not a good idea to load data in the constructor method.  Instead we’re going to load the data from the onPageDidEnter method.  This method will call the load method which will call the getPeople method from the database provider.  Notice how we don’t use any SQL here or any data parsing.  Welcome to the magic of adding functionality to providers.

Finally there is the create method that will insert new data and then load it once it has been saved.

Now let’s look at the HTML file that is paired with our TypeScript logic.  Open the project’s app/pages/home/home.html file and include the following markup:

In the navigation bar we have a button that will call the create method.  We also have a list that will loop through our public array of objects.  When adding new items to the database, the list will automatically be refreshed.

Add the Shared Providers to the Core Application Logic File

To be able to use the providers on every page of the application, they must be added to the applications root TypeScript file.  This is not too difficult to do.

Open the project’s app/app.ts file and include the following code:

Let’s break down the above code.

First we import the Database provider for use in this particular file.  Where the magic really happens is in the providers property of the @Component block.  Any providers added here will be available throughout the rest of the application pages.

Conclusion

You just saw how to create a provider that handles all interaction with the database in your Ionic 2 Android and iOS application.  This is convenient for reducing duplicate code and sharing a consistent service throughout our application pages.  While you don’t need to use providers, it makes your code more maintainable in the long run.

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.

  • Sam Zhao

    You mentioned SqlLite but didn’t use any
    The last part, you need to include the database provider as an dependency in the bootstrap function in order to make it global, not the @component part.

    • I use SQLite all over this tutorial. Did you read it? In regards to adding it to the bootstrap, for Ionic you don’t do this. You are supposed to add it to the providers list in the component of the app.ts file.

      Run through the tutorial, you’ll see.

      • Sam Zhao

        I saw you added the cordova-sqlite-storage plugin, but in the db part, you didn’t import it from “ionic-native” nor use window.sqlitePlugin/cordova.sqlitePlugin to initialize the db. All you did is “this.storage = new Storage(SqlStorage);”. I was confusing about it: is SqlStorage the same as SQLLite db? or SqlStorage is using SQLLite under the hood?
        The reason I was saying that is by refencing the Ionic Conference App which is maintained by Ionic team. If you check https://github.com/driftyco/ionic-conference-app/blob/master/app/app.ts, you will see that they are adding the global provider as a dependency in the bootstrap function, not on each @component provider part.

        I’ve read and learned a lot from your tutorials, they are valuable. I raised those questions simply want to make sure my understanding is correct.

        Thanks!

        • The SqlStorage component of Ionic Native acts like ngCordova did for Ionic 1. It is nothing more than a wrapper to the Apache Cordova SQLite library that I included in the project. It makes the SQLite plugin more Angular 2 friendly.

          In terms of the link you provided. That is the correct way to do things in Angular 2, but I believe Ionic may have changed things in the latest release. Or maybe both versions work, I didn’t check. Strictly looking at Angular 2, the correct way to inject providers is through the bootstrap, but there are a lot of things Ionic 2 does that isn’t really Angular. After reviewing many Ionic docs I came to the conclusion I used in my tutorial. I’d say use either if they both work 🙂

          Best,

  • Hugo Heneault

    Hi Nic,
    Thanks for this great post!

    It seems we can’t run it into the browser, can’t we?

    • I’ve requested the Ionic Framework guys chime in to answer this question. They can best answer browser vs device compatibility.

      I’ll check back soon to make sure you got the answers you need.

      Best,

    • Mike Hartington

      Since Nic used the native sqlite plugin, no, it cannot be used in the browser.
      https://github.com/driftyco/ionic-site/blob/master/docs/v2/faq/index.md#cordova-plugins-not-working-in-the-browser

      It’s not live, yet, but we have mentioned in our docs that if you try to use an ionic-native plugin in the browser, it will print out a helpful warning message.

      An alternative approach would be the sqlStorage class from the framework core.

      http://ionicframework.com/docs/v2/2.0.0-beta.10/api/platform/storage/SqlStorage/

      This will allow you to use the browsers own DB API, but if it detects that the sqlite plugin is installed, it will use that instead.

      https://github.com/driftyco/ionic/blob/master/src/platform/storage/sql.ts#L54

      Overall, while the ionic-native approach does give you the full support of the native plugin, there are other possible routes you can take that with this.

      • For clarity, what will be the recommended way when Ionic 2 enters stable release? Should people use SQLite or the previous SqlStorage?

        • Mike Hartington

          It’s more of a personal preference.

          While some folks will want to use the sqlite plugin and deploy a native build.
          Others will want to use something a bit more flexible that can work in both web and native.
          So we have offerings for both.

          There is not right or wrong choice here.

          • Awesome. Hopefully this clears things up for you @Hugo

  • jos

    I have just finished the project. I’d like to start off by saying thank you for taking the time to create this and several other tutorials.
    I have ran the application on android and it works perfectly, however when I run the application on ios. Nothing happens. The console outputs ‘undefined’ on both methods refresh() and add().

    Has anyone else experienced this?

    Nic, do you know why this may be? I’d like to give you more information but I haven’t got much to work with.

    • You know what, I recently noticed this as well for iOS. I think there may be a bug with how Ionic is handling service injections in iOS. Let me pull Mike from Ionic into this.

        • Mike Hartington

          Any sample project to look at? Angular is doing all the heavy lifting with service injections so not sure where that error is coming from. If you could put together a simple project setup, I’d happily take a look at it

          • Just copy and paste the code from this article. Shouldn’t take more than 2 minutes.

          • Mike Hartington

            Pretty sure this is not an ionic-framework specific issue. I only saw an error on the page load as you were trying to call this.load().

            ERROR: TypeError: Cannot read property ‘executeSql’ of undefined(…)

            Not able to dive deeper into it at the moment, but I’d suggest opening an issue on ionic-native for now, and posting a sample project as well. Thanks.

          • I don’t think this is an issue of Ionic Native. I’ve experienced it a few times with custom shared providers in iOS. It works fine for Android. The injection is not triggering the shared provider constructor method on iOS. If the constructor doesn’t trigger, in this case the database instance will not be created.

            I’ve not looked into how Ionic’s custom Angular 2 bootstrap method works, but my assumption is there may be a bug for iOS in how everything gets bootstrapped.

            This blog post has the sample project. You just need to copy and paste the code into a new project. @[email protected]_cby9rrXjOV:disqus or @@mhartington:disqus feel free to open an issue ticket for Ionic. I no longer open issue tickets.

            Best,

          • Matt Lawson

            Thanks for the write-up on this Nic, it’s been really useful. Just picking up on the point I believe raised by @disqus_cby9rrXjOV:disqus – I too have the problem where my first call to the database in home.ts happens before the database is opened by the database.ts provider constructor.

            Wrapping it in platform.ready() didn’t seem to help (but I’ve done it anyway). The only way I could prevent it was to wrap the first executeSql request in another openDatabase request (all other database request on other pages work, it just seems to be the first page e.g.: home.ts) – here’s a simplified example to explain:

            // database.ts
            public constructor(public platform: Platform) {
            platform.ready().then(() => {
            if(!this.isOpen) {
            this.db = new SQLite();

            }

            public getPeople() {
            return new Promise((resolve, reject) => {
            this.platform.ready().then(() => {
            if(!this.isOpen) {
            this.db = new SQLite();

            }

            The only slight issue with this is, it does try to open the db a second time (when the constructor openDatabase finally kicks in). As you mentioned:

            “The injection is not triggering the shared provider constructor method on iOS. If the constructor doesn’t trigger, in this case the database instance will not be created”

            I know this was a few months ago, but was there ever a solution for this issue or a ticket raised (I couldn’t find one)?

            Thanks again for the article, It was really useful.

          • There was no solution to this. I’ve tried including Ionic folks on this thread, but they are not interested in helping.

            In terms of the whole ready() stuff, it is just the nature of Apache Cordova native plugins. Not fun, but it is the way it goes. More unpleasant when working asynchronous plugins.

            You might check the Ionic forums, to see if others are having similar issues.

            Best,

          • Ryan How

            I haven’t executed your code, but the first thing I noticed (before reading any comments) is that openDatabase is a promise, so it probably hasn’t completed before the rest of the code executes. Maybe on android it is synchronous and on ios it is not?.

            I worked around this by having an init method which opens the database and returns the promise. So in your load method, call database.init().then(() => database.getPeople().then( … you get the idea… so it waits until the database has opened and created the table before calling getPeople.

            That is my understanding of how to do it anyway and how I overcame a similar problem in my project. (I found your post here because I was using SqlStorage in beta 11 which was removed and I was looking how to switch to SQLite)

        • jos

          awesome :). I managed to get it working on IOS by wrapping the initialisation by invoking platform.ready() call… and placing the appropriated method within it’s scope. It was initialising before the platform (IOS device) was ready and therefore created the default ionicstorage database and didn’t actually create any of the tables. However after I wrapped the call platform.ready() within app.js (could probably go somewhere else, I just wanted to ensure it got called for testing purposes) and it instantiated the object when the platform was ready and actually created the tables

          for reference:

          import {Platform} from ‘ionic-angular’;

          constructor(platform: Platform) {
          platform.ready().then(() => {
          this.dementiaSqlService.refreshDataSet();
          });
          }
          Method looks like this:
          public refreshDataSet() {
          this.storage = new Storage(SqlStorage);
          }

          refreshDataSet is within a provider

          • I could be wrong, but I was under the impression that Ionic 2 handled all of the platform.ready stuff for you now. Maybe @mhartington:disqus can tell us what needs to be wrapped with platform.ready and what doesn’t need to be.

            In either case, I’m glad you got it working 🙂

            Best,

  • Matthias

    I’m using Ionic 2.0.0-rc.2 and I had to change the following in home.ts to get it working:

    import {Database} from “../../providers/database”;

    @Component({
    templateUrl: ‘home.html’
    })

  • james

    Hi @nicraboy:disqus @disqus_Ik0In0IouM:disqus @mhartington:disqus @disqus_3cD4twTvOD:disqus @disqus_ukU1U6fNiJ:disqus @CodeBlog:disqus @hugoheneault:disqus @ryan_how:disqus @disqus_cby9rrXjOV:disqus I am trying to create a thread to keep on checking the network status of the app so that it can send send to my remote database. How an I handle this is ionic 2 using Ionic-native.

    Best Regards

  • Gemma Stephen

    Hi, do you have advice on creating multiple tables and extending this provider? In this example, you create the “people” table when you create the database (if it doesnt already exist) then the methods deal with adding data when required but of course, the table is already there.

    So in a larger app, you will need multiple tables. Is the best idea to create all empty tables when db is created? And create all methods in this provider for creating entries to the table throughout the app? Sounds a little messy as could have quite a few tables/methods so not sure what the best approach would be?

    Any help in understanding this is much appreciated!

    • You can create a new class for every table if you want. At a minimum you should have an initialization component that will create and configure your application when it is first run.

  • Sagar Wanojwar

    Hi Nic,

    I have a doubt about providers. I am actually overriding the backbutton behaviour in one of my pages. I am not able to call the provider function inside the back button callback function. I get the following error-
    Uncaught TypeError: Cannot read property ‘showExitDialog’ of undefined

    The code looks something like this-

    /********************************************************************************************************************/

    import { Component } from ‘@angular/core’;
    import { NavController } from ‘ionic-angular’;
    import { Platform } from ‘ionic-angular’;
    import { DataService } from ‘../../common/dataservice’;
    import {Events} from ‘ionic-angular’;

    @Component({
    selector: ‘page-services_details’,
    templateUrl: ‘services_details.html’,
    providers: [DataService]
    })
    export class ServicesDetail {

    constructor(public dataService: DataService, public events:Events, public navCtrl: NavController, public platform: Platform) {
    platform.ready().then(() => {
    this.platform.registerBackButtonAction(this.onBackButtonPressed,101);
    });
    }

    onBackButtonPressed(){
    console.log(‘onBackButtonPressed’);
    this.dataService.showExitDialog();
    }
    }

    /********************************************************************************************************************/

    Am I missing something or we cannot use the provider functions in ‘platform.registerBackButtonAction’