Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Persist and Abstract Data in a NativeScript TypeScript Application

DZone's Guide to

Persist and Abstract Data in a NativeScript TypeScript Application

In this simple article, we'll see how to save and retrieve some user info by installing Couchbase, creating a TypeScript class, and implementing the singleton pattern.

· Database Zone
Free Resource

Traditional relational databases weren’t designed for today’s customers. Learn about the world’s first NoSQL Engagement Database purpose-built for the new era of customer experience.

Couchbase is a great tool to persist data inside your app. If you haven’t heard of it, it’s a document object storage that allows you to save your data.

If you are using NativeScript-Angular, there are already some great tutorials on how to get started and some more advanced features. In this article, we’ll focus on NativeScript with TypeScript.

What We’ll Cover

In this simple article, we will see how to save and retrieve some user information by:

  1. Installing Couchbase.
  2. Creating a TypeScript class that provides a layer of abstraction on top of Couchbase, making it a lot more easy to use (no complex instructions for data insertion and retrieval).
  3. Implementing the singleton pattern on the class.

By the end of the tutorial, we’ll be able to use instructions such as:

userSettings.name = "Frank" //boom! written in our db

text=”{{ userSettings.name }}” //boom! read from our db

...without having to write extensive queries to read and write!

Also, all of the code in this article is available in this GitHub repo. Feel free to clone it and play around.

Without further ado, let’s get started!

Install Couchbase Plugin

Let’s create a new NativeScript app using the TypeScript template.

tns create ns-couchbase-demo --tsc

In the root folder of our newly created NativeScript project, let’s give the following command to add the plugin and save it to our dependencies list:

tns plugin add nativescript-couchbase

Once the plugin is successfully installed, let’s create a class that we’ll use to store and retrieve the user information we need.

Abstraction Layer Class

Optional: It is usually a good idea to create an interface to define the properties we need to include.

Navigate to the app folder of our project and create a models folder. Here, we will define our Models.ts file (app/models/Models.ts):

export module Models{

        export interface IUserSettings {

                username :string;

                fullname :string;

                email :string;

}

}

Once we've defined our interface, let’s go ahead and create the class that actually implements it. We’ll call the class UserSettings.

Inside this class, we also need to store the information related to our Couchbase database.

let CouchBaseModule = require("nativescript-couchbase");

import { Models } from '../models/Models';

export class UserSettings implements Models.IUserSettings{

   private DATABASE_NAME = 'appname-db';

   private USER_SETTINGS_DOC_ID = 'usersettings';

   private _database;

   private _userSettingsDocument: Models.IUserSettings;

   private _userSettingsObj: Models.IUserSettings;

   private _instance :UserSettings;

}

Let’s break it down.

In the first two lines, we require the Couchbase plugin we previously installed along with the Models module we will use to take advantage of TypeScript’s strong typing.

Then, we define some other private properties that we’ll use later:

DATABASE_NAME: This is the name of the database. Make sure it doesn’t contain capital letters (and it’s less than 240 characters).

USER_SETTINGS_DOC_ID: Name of the Couchbase document.

database: This is the instance of the database connector.

_userSettingsDocument: Couchbase is a No document-oriented database. It does not have tables; instead, it uses a primary storage entity known as Document. Basically, a document is a collection of key-value pairs, where the value can be pretty much anything (string, numbers, arrays, etc.). This is what makes Couchbase the number one solution if you need to store a variety of non-relational data: simplicity and flexibility.

_userSettingsObj: This is the JavaScript object that we will use to sync the information with the Couchbase document.

We will cover why we declared the _instance variable later on when we implement the singleton pattern.

Now that we’ve got that covered, let’s see how our class constructor works:

constructor() {

       this._database = new CouchBaseModule.Couchbase(this.DATABASE_NAME);

       this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID);

       if (!this._userSettingsDocument) {

           console.log("Document does not exist yet :)");

           this._userSettingsObj = {

               username: "",

               email: "",

               fullname: "",

           }

           this._database.createDocument(this._userSettingsObj, this.USER_SETTINGS_DOC_ID);

           this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID);

       }

   }

Once again, let’s see in detail what’s going on in our constructor:

this._database = new CouchBaseModule.Couchbase(this.DATABASE_NAME); 

Here, we are instantiating Couchbase and telling to which database we want to connect.

this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID);

This line tells Couchbase to get us our document (the collection which we use to store our info).

 if (!this._userSettingsDocument) {

           console.log("Document does not exist yet :)");

           this._userSettingsObj = {

               username: "",

               email: "",

               fullname: "",

           }

           this._database.createDocument(this._userSettingsObj, this.USER_SETTINGS_DOC_ID);

           this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID);

       }

If our document doesn’t exist yet (for example, first time), we create an empty Object of type UserSettings and we also create a new document. The second parameter we assign to the createDocument function is the id of the document. If we don’t supply this parameter Couchbase will assign the document a UUID automatically.

Fantastic! Now that our constructor is done, let’s see how we can set and retrieve information about our user.

We’ll set up some getters and setters to achieve just that.

/*====== USERNAME GETTER AND SETTER ======*/

   get username(): string {

       this._userSettingsObj = this._database.getDocument(this.USER_SETTINGS_DOC_ID

);

       let username = this._userSettingsObj.username;

       return username;

   }

   set username(value: string) {

       this._userSettingsObj = this._database.getDocument(this.USER_SETTINGS_DOC_ID

);

       this._userSettingsObj.username = value;

       this._database.updateDocument(this.USER_SETTINGS_DOC_ID

, this._userSettingsObj);

   }

As usual, let’s break the code down.

In the getter, we do three things:

  1. We read data from our document into our object.

  2. Get the desired value (in this case the username).

  3. Return it.

Easy peasy!

In the setter, we do the following:

  1. We get the latest version of our userSettingsObject reading it from the database.

  2. We set the username property to the value passed to the function.

  3. We update our document in the database. Contrary to the createDocument function, the first parameter is the ID of the document and the second is the object itself.

But… why do we need to getDocument first? Why can’t we just update the document with the object?

I’m glad you asked!

Basically, whenever we update a document, Couchbase keeps track of the change. Each document has a _rev property; this value gets updated by Couchbase every time a write operation on the document takes place. If we try to update a document with an older _rev, Couchbase will reject it.

Singleton Pattern

Who doesn’t love design patterns? Today, we’ll go ahead and implement a very simple, yet effective one: the singleton. The singleton pattern, when correctly implemented, “restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system,” which is exactly our case.

Basically, we make sure that we only have one instance of our class reading and writing to our database.

How do we do that? A simple way to achieve that is with the following two steps:

  1. Making our constructor private (Introduced in ts 2.0).

  2. Implementing a getInstance method which returns the current instance if it exists, or a new one if it’s the first call.

Step 1:

private constructor() {....}

Step 2:

Now, we can go ahead and create our getInstance() method, which will look something like this:

public static getInstance() :UserSettings{

   if(!this._instance){

          this._instance = new UserSettings();

    }

return this._instance;

}

Great! We have abstracted data and implemented the singleton pattern, let’s go ahead and see how we can use our shiny new UserSettings class.

We will be editing the existing main-page.xml and main-page.ts  files.

First, we’ll define some basic UI layout. For this demo, we’ll be using a text field to get user input, a button to save the value to the database, and a label to display the current value in the database. As you can see, if no data is stored in the database, the label will simply display “No data stored in the db.”

This is what our main-page.xml file should look like:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">

    <Page.actionBar>

        <ActionBar title="My App" icon="" class="action-bar">

        </ActionBar>

    </Page.actionBar>

    <StackLayout>

        <TextField hint="username" text="{{ username }}" />

        <Button text="SAVE MY USERNAME" tap="onTap" class="btn btn-primary btn-active"/>

        <Label text="{{ dbusername || 'no data stored in the db' }}" textWrap="true"/>

    </StackLayout>

</Page>

Alright! Now that we’ve got a basic layout, we can proceed by adding some logic to the UI.

This is how our main-page.ts file should look:

import { Observable } from 'data/observable'; //bind data to our view

import { UserSettings } from './UserSettings'; //woohoo our amazing database interaction layer

import { EventData } from 'data/observable'; //use with typescript for intellisense

import { Page } from 'ui/page'; //use with typescript for intellisense

let userSettings = UserSettings.getInstance();

let mainPageViewModel;

class MainPageViewModel extends Observable{

    public username;

    public dbusername

    constructor(){

        super();

        this.dbusername = userSettings.username;

    }

}

export function navigatingTo(args: EventData) {

    let page = <Page>args.object;

    mainPageViewModel = new MainPageViewModel();

    page.bindingContext = mainPageViewModel;

}

export function onTap(){

    userSettings.username = mainPageViewModel.username;

    mainPageViewModel.set("dbusername",userSettings.username);

}

What have we done? In the first lines, we just imported a bunch of stuff, checking the comments to see what the modules do. Next, we have:

let userSettings = UserSettings.getInstance();

This is how we get a hold of our UserSettings class instance.

Moving on, we declared another small class:

class MainPageViewModel extends Observable{

    public username;

    public dbusername

    constructor(){

        super();

        this.dbusername = userSettings.username;

    }

}

As you can see, MainPageViewModel extends the Observable class. If you’re not familiar with Observables, know that they are basically JavaScript objects that trigger a notification if one of the properties change. You can find out more here.

Our view model has two properties: username (which we bound to our text field) and dbusername (which we bound to our label). Whenever we create a new instance of our class we assign to dbusername whatever value is present in our userSettings instance, which in turn goes and reads data in our database (remember our get username() getter method?).

export function navigatingTo(args: EventData) {

    let page = <Page>args.object;

    mainPageViewModel = new MainPageViewModel();

    page.bindingContext = mainPageViewModel;

}

This function gets called every time we navigate to our page. We instantiate our MainPageViewModel class and assign it to the mainPageViewModel variable and we bind it to our page. Plain and simple.

Last, but not least, we define the behavior for the button click:

export function onTap(){

    userSettings.username = mainPageViewModel.username;

    mainPageViewModel.set("dbusername",userSettings.username);

}

Once again, very simple: We write to the database whatever value our text field holds (mainPageViewModel.username is bound to the text field) and then we go and update the value of dbusername in order to update the label. An image is worth a thousand words, so here comes a GIF!

Learn how the world’s first NoSQL Engagement Database delivers unparalleled performance at any scale for customer experience innovation that never ends.

Topics:
couchbase ,database ,tutorial ,nativescript ,typescript ,persistence ,abstract data

Published at DZone with permission of Laura Czajkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}