Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Working With Shared Providers In A NativeScript Angular App

TwitterFacebookRedditLinkedInHacker News

When building a NativeScript application with Angular, there are certain scenarios where you might want to share functions and variables across the pages of the application. There are other scenarios where you might want to pull similar functions into a class for code cleanliness. Both of these scenarios would find value in using Angular shared providers.

Shared providers can be injected into the constructor methods of each page that you wish to use them. The providers can act as a singleton where the data and functions are global to the application rather than local to any specific page.

We’re going to see how to create a provider for managing interactions with a database in an Angular NativeScript application.

If you’ve been keeping up you’ll remember that I wrote a tutorial for using a SQLite database in a NativeScript Angular application as well as using a pre-populated SQLite database. In both these examples the database interactions were mashed into the components. We’re going to make it slick in a sense that these database interactions will happen from a single location which is more ideal in a larger application.

Let’s start by creating a fresh NativeScript project. From the Terminal (Mac and Linux) or Command Prompt (Windows), execute the following commands:

tns create MyProject --ng
cd MyProject
tns platform add ios
tns platform add android

In the above, notice the --ng flag. This indicates that we are creating an Angular TypeScript application. While we’re adding the iOS build platform, we cannot build for iOS unless we’re using a Mac with Xcode installed.

Since SQLite will be the basis of this particular tutorial, we need to install the SQLite plugin for NativeScript. This can be done by executing the following:

tns plugin add nativescript-sqlite

Since the focus here is around creating an Angular provider, the application we build will only have a single page. Within the new project, create the following directories and files:

mkdir -p app/providers/database
touch app/providers/database/database.ts

If you don’t have access to mkdir and touch in your command line, just create the files and directories manually.

With the provider file created, we need to populate it with some code. Open the project’s app/providers/database/database.ts file and include the following TypeScript code:

import { Injectable } from "@angular/core";
var Sqlite = require("nativescript-sqlite");

@Injectable()
export class Database {

    private db: any;
    private isInstantiated: boolean;

    public constructor() {
        if(!this.isInstantiated) {
            (new Sqlite("my.db")).then(db => {
                db.execSQL("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)").then(id => {
                    this.db = db;
                    this.isInstantiated = true;
                }, error => {
                    console.log("CREATE TABLE ERROR", error);
                });
            }, error => {
                console.log("OPEN DB ERROR", error);
            });
        }
    }

    public insert(data: any): Promise<any> {
        return this.db.
            execSQL("INSERT INTO people (firstname, lastname) VALUES (?, ?)", [data.firstname, data.lastname]);
    }

    public fetch(): Promise<any> {
        return new Promise((resolve, reject) => {
            this.db.all("SELECT * FROM people").then(rows => {
                let people = [];
                for(var row in rows) {
                    people.push({
                        "id": rows[row][0],
                        "firstname": rows[row][1],
                        "lastname": rows[row][2]
                    });
                }
                resolve(people);
            }, error => {
                reject(error);
            });
        });
    }

}

There is a lot of code in the above file, so it is probably a good idea to break it down so it is easier to understand.

import { Injectable } from "@angular/core";
var Sqlite = require("nativescript-sqlite");

Since we’re going to be injecting this provider in each of our pages, we need to import the appropriate Injectable component. As mentioned in my previous SQLite tutorials, the free version of this plugin doesn’t include type definitions for TypeScript. This doesn’t limit what we can do with the library, it only makes things slightly inconvenient, nothing more. This is why we include the plugin library differently than a standard TypeScript library.

There are two variables that we maintain in this provider. The db variable will hold the open database instance that will be shared globally in the application. The isInstantiated variable will let us know if the database has already been instantiated. After all, we don’t want to keep opening the database if it is already open.

public constructor() {
    if(!this.isInstantiated) {
        (new Sqlite("my.db")).then(db => {
            db.execSQL("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)").then(id => {
                this.db = db;
                this.isInstantiated = true;
            }, error => {
                console.log("CREATE TABLE ERROR", error);
            });
        }, error => {
            console.log("OPEN DB ERROR", error);
        });
    }
}

The above constructor method will trigger every time the provider is injected. The isInstantiated check happens to prevent the database from being opened every time. When we try to open the database, we will also try to create a table if it doesn’t already exist. When this initialization is finished, we store the open database instance and say that it is stored.

Next we have an insert method:

public insert(data: any): Promise<any> {
    return this.db.
        execSQL("INSERT INTO people (firstname, lastname) VALUES (?, ?)", [data.firstname, data.lastname]);
}

From each of the pages we would pass a data object which would be inserted into the database. It may not be a good idea to work with database queries in every page of your application. Working with JavaScript objects might make more sense.

Finally we have the fetch method, responsible for gathering data from the database:

public fetch(): Promise<any> {
    return new Promise((resolve, reject) => {
        this.db.all("SELECT * FROM people").then(rows => {
            let people = [];
            for(var row in rows) {
                people.push({
                    "id": rows[row][0],
                    "firstname": rows[row][1],
                    "lastname": rows[row][2]
                });
            }
            resolve(people);
        }, error => {
            reject(error);
        });
    });
}

Notice in this method we’re not just returning the query result like in the insert method. This is because we want to do something useful and parse the data into an object before returning it to the page.

So the provider is now complete and really isn’t any different than what we saw in the first SQLite tutorial that I wrote.

Now how do we use this provider?

The first thing we need to do is import the provider in the applications @NgModule block. For NativeScript, this block is found in the app/main.ts file. Open the project’s app/main.ts file and include the following TypeScript:

// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";
import { Database } from "./providers/database/database";

@NgModule({
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    imports: [NativeScriptModule],
    providers: [Database]
})
class AppComponentModule {}

platformNativeScriptDynamic().bootstrapModule(AppComponentModule);

In the above code, we really only added the following line:

providers: [Database]

Let’s take a look at our application page now. The goal here isn’t very complex, just to show us using the provider that we just created. The rest is up to your imagination.

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

import { Component, OnInit } from "@angular/core";
import { Database } from "./providers/database/database";

@Component({
    selector: "my-app",
    templateUrl: "app.component.html",
})
export class AppComponent implements OnInit {

    public people: Array<any>;

    public constructor(private database: Database) {
        this.people = [];
    }

    public ngOnInit() {
        setTimeout(() => {
            this.fetch();
        }, 500);
    }

    public insert() {
        this.database.insert({firstname: "Nic", lastname: "Raboy"}).then(result => {
            this.fetch();
        });
    }

    public fetch() {
        this.database.fetch().then(result => {
            this.people = result;
        });
    }

}

We’re going to break down this file since it is quite large.

The first thing we want to do is import the Database provider that we created:

import { Database } from "./providers/database/database";

Yes we had already imported the provider into the @NgModule, but it is not the same. It needs to be done in both places.

public constructor(private database: Database) {
    this.people = [];
}

In the constructor method, we’re not only initializing our data array, but we’re injecting the provider into our component. This allows us to use it throughout our particular component.

public ngOnInit() {
    setTimeout(() => {
        this.fetch();
    }, 500);
}

It is frowned upon to load data in the constructor method, so instead we make use of the ngOnInit method. This however, is a special scenario. Because the database load process is asynchronous, we really have to wait a moment before we can start using the database. This is why I added a short timeout. Again, this is a unique scenario because of the particular provider goals. If you’re not doing anything asynchronous in the constructor, don’t worry about it.

public insert() {
    this.database.insert({firstname: "Nic", lastname: "Raboy"}).then(result => {
        this.fetch();
    });
}

public fetch() {
    this.database.fetch().then(result => {
        this.people = result;
    });
}

Using the provider functions are as easy as calling them in the functions as seen above. You can ignore that I’ve called my component functions the same name as my provider functions. They don’t need to be named the same.

To wrap things up, the corresponding UI can be found in the project’s app/app.component.html file. Open this file and include the following markup:

<ActionBar title="SQL App">
    <ActionItem text="Add" ios.position="right" (tap)="insert()"></ActionItem>
    <ActionItem text="Refresh" ios.position="left" (tap)="fetch()"></ActionItem>
</ActionBar>
<StackLayout>
    <ListView [items]="people">
        <template let-person="item">
            <Label text="{{ person.firstname }} {{ person.lastname }}"></Label>
        </template>
    </ListView>
</StackLayout>

The UI is just a simple ListView and an ActionBar. Nothing too complicated, right?

Conclusion

You just saw how to create an Angular provider in a NativeScript application. While this was only one usage scenario, there are plenty of great things you can do with providers. Another good example is using a provider for passing complex data between pages of an application.

If you want to learn more about SQLite in a NativeScript application, check out one of my many other tutorials.

A video version of this article can be found below.

Nic Raboy

Nic Raboy

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