Last year I wrote an article that explained how to synchronize between platforms and Couchbase with only AngularJS and PouchDB. Back then AngularJS was booming and probably my favorite framework of all time. A year later AngularJS is still around, but is slowly being replaced by Angular 2, the next version of the popular framework. After using Angular 2, I look back at AngularJS 1.0 and wonder what was going through my brain because Angular 2 is pure awesomeness.
This time around we’re going to see how to create a simple web application that syncs using only Angular 2, PouchDB, and Couchbase Mobile.
The Requirements
The few requirements to being successful with this project are as follows:
- Node.js 4.0+
- Angular 2 CLI
- Typings
- Couchbase Sync Gateway
The Angular 2 CLI can be installed using the Node Package Manager (NPM) which is included with Node.js. NPM is also used when it comes to gathering various project dependencies. We won’t be using Couchbase Server in this example, but we could. Instead we’re only going to use Couchbase Sync Gateway and its prototyping features.
Configuring Couchbase Sync Gateway
Couchbase Sync Gateway is necessary to handle all of the synchronization. Without it we only have local storage in our web application, and in all honesty, there are much better solutions than PouchDB if you only wanted local storage.
Download the latest Sync Gateway and deploy it with the following configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "log":["CRUD+", "REST+", "Changes+", "Attach+"], "databases": { "example": { "server":"walrus:", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } } } }, "CORS": { "Origin": ["http://localhost:4200"], "LoginOrigin": ["http://localhost:4200"], "Headers": ["Content-Type"], "MaxAge": 17280000 } } |
Save this to a file called sync-gateway-config.json if you wanted. This is a very basic configuration where we are using an in-memory database called example. There are no read and write permissions so anything goes. At the bottom of this configuration we have some items relating to cross origin resource sharing (CORS). Because we’ll be serving our application locally, we need to identify this to prevent JavaScript errors.
Creating a New Angular 2 Project
To make things easy to understand we’re going to create a project with an incredibly simplistic design. We’re going to create a simple user management application where we can add people and they will be synchronized to wherever you want. For example, check the animation below:

In the above animation we have two web browsers. Add a person to one and it will synchronize to the other. This is something that would be particularly difficult without Couchbase.
Let’s start by creating a fresh Angular 2 project using the Angular CLI. Execute the following from your Command Prompt (Windows) or Terminal (Mac and Linux):
1 |
ng new PouchDBProject |
It may take a while, but when the project is created we want to install PouchDB and any necessary dependencies. Execute the following to install the latest PouchDB:
1 |
npm install pouchdb --save |
The maintainers of PouchDB have mixed emotions when it comes to TypeScript, a critical technology when it comes to Angular 2 development. For this reason there are no reliable type definitions available. This is not a big deal though.
Using Typings, we are going to install the following:
1 |
typings install dt~require --save --global |
The above type definitions will allow us to use the require
keyword within TypeScript. With it we can import the downloaded PouchDB into our project.
At this point we can start developing our project!
Developing an Angular 2 Provider to Maintain the Data
When working with data it is always a good idea to create an Angular 2 service, also known as a provider. This allows us to have a singleton instance and a class that is segregated from the rest of our code. It is great from a maintainability perspective.
You can create the provider manually or using the CLI. From the CLI, execute the following:
1 |
ng g service pouchdb |
Two files should be created in your project’s src/app directory. You should have a pouchdb.service.ts and a pouchdb.service.spec.ts file. They may be named something similar, but the name isn’t really important.
The spec file is a unit testing file, something we won’t worry about in this particular example. Instead we’re going to start developing in the pouchdb.service.ts file.
Open the file and include the following TypeScript code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { Injectable, EventEmitter } from '@angular/core'; var PouchDB = require("pouchdb"); @Injectable() export class PouchDBService { private isInstantiated: boolean; private database: any; private listener: EventEmitter = new EventEmitter(); public constructor() { } public fetch() { } public get(id: string) { } public put(id: string, document: any) { } public sync(remote: string) { } public getChangeListener() { } } |
Before we start filling in each of the methods, let’s figure out what we’re trying to do. We only want one database instance while the application is running. This can be accomplished via the constructor
method:
1 2 3 4 5 6 |
public constructor() { if(!this.isInstantiated) { this.database = new PouchDB("nraboy"); this.isInstantiated = true; } } |
In this case the local database is called nraboy. Now PouchDB does have a really great API. It just isn’t Angular 2 optimized, meaning working with PouchDB in its vanilla state in our project could have some hiccups.
In the scenario where we want to fetch all local documents, we might do something like this:
1 2 3 |
public fetch() { return this.database.allDocs({include_docs: true}); } |
We use the include_docs
attribute so the documents get included in the results rather than just their id values. So maybe we only want to get a single document. We would do something like the following:
1 2 3 |
public get(id: string) { return this.database.get(id); } |
So far we have been using the PouchDB API exactly how the documentation recommends. How about changing it up a bit? The API offers a way to create or update documents, but let’s combine this in a single method?:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public put(id: string, document: any) { document._id = id; return this.get(id).then(result => { document._rev = result._rev; return this.database.put(document); }, error => { if(error.status == "404") { return this.database.put(document); } else { return new Promise((resolve, reject) => { reject(error); }); } }); } |
The above put
method will check to see if a document exists based on its id. If it exists, copy the revision into the new data and save it again. If the document doesn’t exist, create it without a revision.
You probably noticed the EventEmitter
that was included near the top. This is necessary for subscribing to change events in the Angular 2 pages. Take the following sync
method for example:
1 2 3 4 5 6 7 8 |
public sync(remote: string) { let remoteDatabase = new PouchDB(remote); this.database.sync(remoteDatabase, { live: true }).on('change', change => { this.listener.emit(change); }); } |
Here we are defining a remote data source, which will be Couchbase Sync Gateway, choosing to do a live two-way sync, and emitting the changes every time they are discovered.
The change listener can be accessed on every page by accessing the following:
1 2 3 |
public getChangeListener() { return this.listener; } |
The the above we’d just end up subscribing to the listener.
While the provider is created, it isn’t available across all pages yet. We need to import it into the project’s @NgModule
found in the src/app/app.module.ts file. This file will look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { PouchDBService } from "./pouchdb.service"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, HttpModule ], providers: [PouchDBService], bootstrap: [AppComponent] }) export class AppModule { } |
Notice we’ve imported the provider class and added it to the providers
array of the @NgModule
block. Now we can use the provider throughout the application.
Applying the Database Provider within the Angular 2 Application
To keep things simple, this will be a single page application. We’re going to spend the rest of our time in the project’s src/app/app.component.ts and src/app/app.component.html files.
Open the project’s src/app/app.component.ts file and include the following TypeScript code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { Component, OnInit, NgZone } from '@angular/core'; import { PouchDBService } from "./pouchdb.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { public people: Array; public form: any; public constructor(private database: PouchDBService, private zone: NgZone) { this.people = []; this.form = { "username": "", "firstname": "", "lastname": "" } } public ngOnInit() { } public insert() { } } |
You’ll notice that we’re importing the PouchDBService
along with a few other things. We’ll explore those as we hit them.
Inside the AppComponent
we have a public array called people
which will be bound to our UI. It will contain all the data being saved and synced. The form
variable will represent an object containing each of our form elements. We could split it up into strings, but an object is more convenient because we’re going to end up saving the entire object as a document in Couchbase.
In the constructor
method we inject our PouchDB service and NgZone
. We use NgZone
to refresh the Angular 2 zone which sometimes gets bent out of shape when working with events and other kinds of listeners. Not too big a deal as you’ll see soon. Finally, the constructor
method initializes our two variables.
It is bad practice to load data in the constructor
method so instead we use the ngOnInit
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public ngOnInit() { this.database.sync("http://localhost:4984/example"); this.database.getChangeListener().subscribe(data => { for(let i = 0; i { this.people.push(data.change.docs[i]); }); } }); this.database.fetch().then(result => { this.people = []; for(let i = 0; i { console.error(error); }); } |
Most of the application grunt work happens above. In the above code we are defining our remote Sync Gateway host and database name. It doesn’t need to match that of our local database. We then subscribe to our listener and loop through the changes as they come in. All changes are added to the public array within an NgZone
so they refresh on the screen.
Since change events only trigger as they happen, we need to fetch all the documents when we first initialize the application.
This brings us to the insert
method:
1 2 3 4 5 6 7 8 9 10 |
public insert() { if(this.form.username && this.form.firstname && this.form.lastname) { this.database.put(this.form.username, this.form); this.form = { "username": "", "firstname": "", "lastname": "" } } } |
If our form elements are not blank then we can save the data as a document in the database. Once saved, the change event will trigger and the data will be added to the public array and displayed on the screen.
This is all seamless between the web application and Couchbase Sync Gateway.
Taking it for a Test Drive
There is a lot to take in when it comes to this Angular 2 and PouchDB guide. I uploaded a working project to GitHub if you’d like to take it for a spin.
Download the project and execute the following from your Terminal or Command Prompt:
1 |
npm install |
The above command will grab all the project dependencies. Make sure to update the src/app/app.component.ts file to reflect the correct hostname of your Sync Gateway and you should be good to go.
The project can be executed by running:
1 |
ng serve |
While serving, the project can be accessed from http://localhost:4200.
Conclusion
You just saw how to build a simple web application that syncs data using PouchDB and Couchbase. This is an Angular 2 application that is a step up from the guide I wrote last year which used AngularJS 1.0.
Hey mate! thank you for the tutorial, really appreciate it. Just one quick one, i’m trying to install “require” as per the suggested command but not having much luck. I’m running Angular 5.
typings install dt~require –save –global
On “ng serve” i get the following:
“ERROR in src/app/services/pouchdb.service.ts(3,17): error TS2304: Cannot find name ‘require’.”
Any ideas?
I’m having the same problem! I managed to find some outdated npm packages, including the old angular-cli that needed to be removed and replaced with @angular/cli, I’m wondering if it’s something similar because when I installed the typings, I got a notice that it’s deprecated in favor of using @types. I’ll give that a try…
PouchDB works with TypeScript now, without using require.js. See the PouchDB documentation: https://pouchdb.com/guides/setup-pouchdb.html#typescript
Steps:
1. Install PoutchDB
> npm install pouchdb –save
2. Install PouchDB types
> npm install pouchdb @types/pouchdb
3. Edit tsconfig.json and enable synthetic default imports
{
“compilerOptions”: {
“allowSyntheticDefaultImports”: true
}
}
4. Import the PouchDB in you Typescript class: pouchdb.service.ts.
import PouchDB from ‘pouchdb’;