神刀安全网

Using Couchbase NoSQL In A NativeScript Angular 2 Mobile App

Not too long ago I created a Couchbase NoSQL plugin with Mehfuz Hossain from Telerik for the NativeScript framework.  Since developing the plugin it has received a lot of positive feedback and great developer adoption.  Shortly after its release I published a blog post on how to use the Couchbase plugin in a NativeScript app .  The blog post I had written focused on using NativeScript’s proprietary vanilla framework.  Since then, Angular 2 has been released for NativeScript, so it makes sense to make an Angular 2 version of the tutorial.

We’re going to see how to create a cross platform NativeScript application that syncs, using Angular 2 and Couchbase.

Using Couchbase NoSQL In A NativeScript Angular 2 Mobile App

The Requirements

There are a few requirements that must be met in order to be successful with this tutorial:

Angular 2 support first became available in NativeScript 2.0.  Anything higher will be good enough for this tutorial.  For data synchronization we’ll need Couchbase Sync Gateway.  It is open source.  You can optionally use Couchbase Server, but we won’t go there for this tutorial.

Creating a New NativeScript Android and iOS Project

Let’s start by creating a new project with all the dependencies.  Using a Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:

tnscreateCouchbaseProject --ng cd CouchbaseProject tnsplatformaddios tnsplatformaddandroid 

A few things to note in the above.  By using the --ng tag we are saying that this will be an Angular 2 project with TypeScript.  Also note that if you’re not using a Mac, you cannot build for the iOS platform.

With the project created, we need to install the Couchbase NoSQL plugin .  Using the Terminal or Command Prompt, execute the following:

tnspluginaddnativescript-couchbase 

This will install the plugin for both Android and iOS.

At this point we can focus on developing the actual application using Angular 2.  Like most of the tutorials I have floating around, we’re going to keep things simple with a todo-like application.  I’d rather not overwhelm anyone on the concepts I’m trying to prove.  The application will have two screens, one for saving data to the NoSQL database and one for showing the data.

Before we jump into the development, we need to create a few files and directories:

mkdir app/components mkdir app/components/list mkdir app/components/create touch app/components/list/list.component.ts touch app/components/list/list.xml touch app/components/create/create.component.ts touch app/components/create/create.xml touch app/couchbaseinstance.ts 

If you’re using a Windows computer and not a Mac or Linux machine, you won’t have the mkdir and  touch commands.  Go ahead and make those files and directories manually.

Creating the Shared Couchbase Service

To use Couchbase in our application we have to create a singleton instance, sometimes referred to as a shared service.  If we don’t do this, we’re going to experience some strange behavior.

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

import {Couchbase} from 'nativescript-couchbase';   export class CouchbaseInstance {       privatedatabase: any;     privatepull: any;     privatepush: any;       constructor() { }       init() {         this.database = new Couchbase("nraboy-database");           this.database.createView("people", "1", (document, emitter) => {             emitter.emit(JSON.parse(document)._id, document);         });     }       getDatabase() {         return this.database;     }       startSync(continuous: boolean) {         this.push = this.database.createPushReplication("http://192.168.57.1:4984/test-database");         this.pull = this.database.createPullReplication("http://192.168.57.1:4984/test-database");           this.push.setContinuous(continuous);         this.pull.setContinuous(continuous);           this.push.start();         this.pull.start();     }       stopSync() {         this.push.stop();         this.pull.stop();     }   } 

So what is going on in the above chunk of code?

We only want the Couchbase plugin initialized once which is why we don’t place the initialization code in the constructor.  During the initialization we do a one time creation of the MapReduce view that will allow us to later query for data.

In each of our components we can make use of the getDatabasestartSync , and  stopSync functions.  We want to make sure that we’re using the same database in all components.  In terms of synchronization, we’re planning to continuously sync in two directions at the Sync Gateway defined.  You’ll want to use the IP address of whatever your own Sync Gateway is.

Creating the List Component

The purpose of this component is for reading data from the database and displaying it on the screen.  It will also start the synchronization of data in our application.

We’ll start by creating our TypeScript logic.  Open the project’s app/components/list/list.component.ts file and include the following code:

import {Component} from "@angular/core"; import {Router} from "@angular/router-deprecated"; import {Location} from "@angular/common"; import {CouchbaseInstance} from "../../couchbaseinstance";   @Component({     selector: "my-app",     templateUrl: "components/list/list.xml", }) export class ListComponent {       privatedatabase: any;     privaterouter: Router;     publicpersonList: Array<Object>;       constructor(router: Router, location: Location, couchbaseInstance: CouchbaseInstance) {         this.router = router;         this.database = couchbaseInstance.getDatabase();         this.personList = [];           couchbaseInstance.startSync(true);           this.database.addDatabaseChangeListener((changes) => {             var changeIndex;             for (var i = 0; i < changes.length; i++) {                 var documentId;                   documentId = changes[i].getDocumentId();                 changeIndex = this.indexOfObjectId(documentId, this.personList);                 var document = this.database.getDocument(documentId);                   if (changeIndex == -1) {                     this.personList.push(document);                 } else {                     this.personList[changeIndex] = document;                 }             }         });           location.subscribe((path) => {             this.refresh();         });           this.refresh();     }       create() {         this.router.navigate(["Create"]);     }       private refresh() {         this.personList = [];         var rows = this.database.executeQuery("people");         for (var i in rows) {             if (rows.hasOwnProperty(i)) {                 this.personList.push(JSON.parse(rows[i]));             }         }     }       private indexOfObjectId(needle: string, haystack: any) {         for (var i = 0; i < haystack.length; i++) {             if (haystack[i] != undefined && haystack[i].hasOwnProperty("_id")) {                 if (haystack[i]._id == needle) {                     return i;                 }             }         }         return -1;     }   } 

The above code is a lot to take in.  Let’s start breaking it down.

The first thing you see is a bunch of import statements:

import {Component} from "@angular/core"; import {Router} from "@angular/router-deprecated"; import {Location} from "@angular/common"; import {CouchbaseInstance} from "../../couchbaseinstance"; 

The first three imports are part of the Angular 2 framework and are responsible for the creation of our component and routing to other components.  The fourth import is the import of our shared service for Couchbase.

In the @Component section we define which XML template will be paired with the TypeScript.

Let’s work backwards on the methods.  Starting with the indexOfObjectId method:

private indexOfObjectId(needle: string, haystack: any) {     for (var i = 0; i < haystack.length; i++) {         if (haystack[i] != undefined && haystack[i].hasOwnProperty("_id")) {             if (haystack[i]._id == needle) {                 return i;             }         }     }     return -1; } 

We’re going to use the above method for finding objects in our list view.  This is useful when we are handling data changes.  When data comes in we can use this function to determine if it is already in the list or not.

private refresh() {     this.personList = [];     var rows = this.database.executeQuery("people");     for (var i in rows) {         if (rows.hasOwnProperty(i)) {             this.personList.push(JSON.parse(rows[i]));         }     } } 

The refresh function will query for all data in the database.  This is useful for every time we navigate to the list component.  All data queried will be pushed into the  personList variable which is bound to the XML layout.

create() {     this.router.navigate(["Create"]); } 

The create function will use the Angular 2 router to navigate to the soon to be created component for saving data.

Finally, this brings us to the constructor method that does a lot of initialization.  Essentially we are getting the database from the shared service and starting sync.  However, we are also creating our change listener:

this.database.addDatabaseChangeListener((changes) => {     var changeIndex;     for (var i = 0; i < changes.length; i++) {         var documentId;           documentId = changes[i].getDocumentId();         changeIndex = this.indexOfObjectId(documentId, this.personList);         var document = this.database.getDocument(documentId);           if (changeIndex == -1) {             this.personList.push(document);         } else {             this.personList[changeIndex] = document;         }     } }); 

Every time a change is detected, this listener will trigger.  Changes could be local changes or changes that have come from Couchbase Sync Gateway.  If the data already exists in the list, it will be updated, otherwise it will be added.

The last critical piece of the constructor method is the  subscribe found on the  Location component:

location.subscribe((path) => {     this.refresh(); }); 

We need this subscription for handling pop events on the navigation stack.  After saving locally and returning to the list view, we want to query for data and display it in the list view.

This brings us to the XML layout that goes with the TypeScript component.  Open the project’s app/components/list/list.xml file and include the following markup:

<ActionBartitle="Person List">     <ActionItemtext="Create" (tap)="create()" ios.position="right"></ActionItem> </ActionBar> <GridLayout>     <ListView[items]="personList">         <templatelet-item="item">             <Label[text]="item.firstname + ' ' + item.lastname"></Label>         </template>     </ListView> </GridLayout> 

Here we have a simple view that contains an action bar with a single button.  This button will take us to the component for creating data.  The core content is a ListView that is bound to the  personList of the TypeScript file.  Each object from that array is displayed on a per row basis.

Creating the Component for Adding NoSQL Data

Now we can start working on our second screen for creating new data to be saved into our NoSQL database.

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

import {Component} from "@angular/core"; import {Location} from "@angular/common"; import {CouchbaseInstance} from "../../couchbaseinstance";   @Component({     selector: "create",     templateUrl: "./components/create/create.xml" }) export class CreateComponent {       privatecouchbaseInstance: CouchbaseInstance;     privatedatabase: any;     privatelocation: Location;     publicfirstname: string;     publiclastname: string;       constructor(location: Location, couchbaseInstance: CouchbaseInstance) {         this.database = couchbaseInstance.getDatabase();         this.location = location;         this.firstname = "";         this.lastname = "";     }       save() {         if(this.firstname != "" && this.lastname != "") {             this.database.createDocument({                 "firstname": this.firstname,                 "lastname": this.lastname             });             this.location.back();         }     }   } 

We’ll break this file down too.

Just like with the ListComponent we are including some core Angular 2 components and the Couchbase shared service.  In the  constructor method we are initializing all of the variables to be used in this application.

This brings us to the save method.  We start by making sure that  firstname and  lastname are not empty.  If this condition passes then we can call the  createDocument method and pass in an object to be saved.  Since this is NoSQL you can pass any kind of complex object you want.

When the saving is done, we can pop back in the navigation to the previous component.

Now let’s take a look at the XML layout that is paired with the CreateComponent .  Open the project’s  app/components/create/create.xml file and include the following markup:

<ActionBartitle="Create">     <NavigationButtontext="Back" ios.position="left"></NavigationButton>     <ActionItemtext="Save" (tap)="save()" ios.position="right"></ActionItem> </ActionBar> <StackLayout>     <TextFieldhint="First Name" [(ngModel)]="firstname"></TextField>     <TextFieldhint="Last Name" [(ngModel)]="lastname"></TextField> </StackLayout> 

Nothing too fancy happening in the above.  We have an action bar with a button for navigating backwards in the stack and a button for calling our save method.

The two text fields are bound to our TypeScript file using the Angular 2 [(ngModel)] tag.

Add Routing Information for the Angular 2 Router

Angular 2 requires some configuration logic to be put into place in order to perform routing between components.  Lucky for us, this isn’t complicated to do.

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

import {Component} from "@angular/core"; import {RouteConfig} from "@angular/router-deprecated"; import {NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "nativescript-angular/router"; import {CouchbaseInstance} from './couchbaseinstance';   import {ListComponent} from "./components/list/list.component"; import {CreateComponent} from "./components/create/create.component";   @Component({     selector: "my-app",     directives: [NS_ROUTER_DIRECTIVES],     providers: [NS_ROUTER_PROVIDERS],     template: "<page-router-outlet></page-router-outlet>" }) @RouteConfig([     { path: "/list", component: ListComponent, name: "List", useAsDefault: true },     { path: "/create", component: CreateComponent, name: "Create" }, ]) export class AppComponent {       constructor(couchbaseInstance: CouchbaseInstance) {         couchbaseInstance.init();     }   } 

To make routing possible we need to define all the components we wish to use in our application.  In our particular application we have a ListComponent and  CreateComponent .

With the components defined, we can use the @RouteConfig to configure them.  We set the  ListComponent as the default route, and then the  CreateComponent .

Finally, take notice of the following:

constructor(couchbaseInstance: CouchbaseInstance) {     couchbaseInstance.init(); } 

This routing component is called before any of our other components making it the perfect opportunity to initialize our shared Couchbase service.  It should only be done once.

Bootstrapping for the iOS Platform

Since we are using the action bar, we need to do some special bootstrapping in order to support iOS.  It is just a simple parameter that must be placed.

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

import {nativeScriptBootstrap} from "nativescript-angular/application"; import {AppComponent} from "./app.component"; import {CouchbaseInstance} from "./couchbaseinstance";   nativeScriptBootstrap(AppComponent, [CouchbaseInstance], { startPageActionBarHidden: false }); 

The actual bootstrapping logic for iOS is found in the { startPageActionBarHidden: false } parameter.  Take notice that we are creating our globally accessible shared Couchbase service here as well.

Running Couchbase Sync Gateway

If you wish to have synchronization support in your application you should have downloaded the open source Couchbase Sync Gateway.

The Sync Gateway requires a configuration file.  We won’t get into the details here, but let’s use this super simple configuration file:

{     "log":["CRUD+", "REST+", "Changes+", "Attach+"],     "databases": {         "test-database": {             "server":"walrus:",             "sync":`                 function (doc) {                     channel (doc.channels);                 }             `,             "users": {                 "GUEST": {                     "disabled": false,                     "admin_channels": ["*"]                 }             }         }     },     "CORS": {         "Origin": ["http://localhost:8100"],         "LoginOrigin": ["http://localhost:8100"],         "Headers": ["Content-Type"],         "MaxAge": 17280000     } } 

To sum up the above configuration file, we’re syncing all data with no read or write permissions.  If you wish to know more about creating Couchbase Sync Gateway configuration files, check out the developer portal .

It can be run by executing the following from your Command Prompt or Terminal:

/path/to/sync/gateway/bin/sync-gatewaysync-gateway-config.json 

Your application now has cross platform synchronization support.

Conclusion

You just saw how to create a NativeScript Angular 2 application that uses Couchbase NoSQL as its database.  If you’re interested in seeing the version without Angular 2, see the previous blog post I wrote on the subject.  Couchbase is an excellent solution because it is open source, and you’re not tied down to using a Backend as a Service (BaaS) that could potentially shut down like Facebook’s Parse service.

This project can be seen on GitHub along with the actual plugin.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Using Couchbase NoSQL In A NativeScript Angular 2 Mobile App

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址