Skip to content

Instantly share code, notes, and snippets.

@federicofazzeri
Last active March 20, 2017 20:21
Show Gist options
  • Save federicofazzeri/ae76fc20252411447a1f6b49f60dbea6 to your computer and use it in GitHub Desktop.
Save federicofazzeri/ae76fc20252411447a1f6b49f60dbea6 to your computer and use it in GitHub Desktop.
Angular2 app that saves data on an any in-browser database (thanks to PouchDb) and syncs it with a remote couchDb as soon as the connection is restored

Angular2 app that saves data on an any in-browser database (thanks to PouchDb) and syncs it with a remote couchDb as soon as the connection is restored

1 - Install pouchdb and pouchdb types

npm install pouchdb --save
npm install @types/pouchdb --save --save-exact

2 - fix CORS issues

npm install -g add-cors-to-couchdb
add-cors-to-couchdb

3 - In your module:

import { OfflineDbComponent } from './offline-db.component';
import { UpdateNoteComponent } from './update-note.component';
import { OfflineDbService } from './offline-db.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

providers: [
      OfflineDbService         
  ],
  declarations: [
      OfflineDbComponent,
      UpdateNoteComponent
  ]

4 - Update config.ts

/************* service configuration ***************
remoteDbName >> your remote couch db name
localDbName >> your local pouch db name
remoteDbUrl >> your couch db remote host
interval >> sync the db every x seconds
*******************************************/
export const localDbName: string = 'mylocaldb';
export const remoteDbName: string = 'myremotedb';
export const remoteDbUrl: string = 'http://localhost:5984';
export const interval: number = 5;
import { Component, OnInit } from '@angular/core';
import { OfflineDbService } from './offline-db.service';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { Scheduler } from 'rxjs/Rx';
import { localDbName, remoteDbName, remoteDbUrl, interval} from './config';
@Component({
selector: 'offline-db',
template: `
<div class="alerts">
<p *ngIf="!onLine">Connecting...</p>
<p *ngIf="updateError">An error occurred while updating your local database</p>
</div>
<form [formGroup]="myForm" (ngSubmit)="addNote()">
<label for="note">add a note</label>
<textarea name="note" [formControl]="myForm.controls['note']"></textarea>
<button type="submit" [class.show]="myForm.valid">add</button>
</form>
<ul>
<li *ngFor="let doc of notes">
<update-note [note]="doc.note" (updatedNote)="updateNote(doc, $event)"></update-note>
<button (click)="deleteNote(doc)">X</button>
</li>
</ul>`,
styles: [`[type=submit] { display: none; }
label { display: block; }
ul, li { list-style: none; margin: 0; padding:0; }
li { padding:10px 0; margin-bottom: 10px; background-color: #FAFAFA; }
.show { display: inline-block; }`],
providers: [OfflineDbService]
})
export class OfflineDbComponent implements OnInit {
notes: Object[];
onLine: boolean = false;
updateError: boolean = false;
myForm: FormGroup;
constructor(private db: OfflineDbService, private fb: FormBuilder) {
this.db.initService(localDbName, remoteDbName, remoteDbUrl, interval);
//update the view when the db change
this.db.docsStream
.map(data => JSON.parse(data))
.subscribe(data => this.onChange(data));
//alert if offline
this.db.isOnlineStream
.map(data => JSON.parse(data))
.subscribe(data => this.onLine = data.onLine);
}
ngOnInit() {
//setup the form
this.myForm = this.fb.group({ note: ['', Validators.required] });
//start syncing
this.db.initSync();
}
private onChange(data) {
if(data.docs){
this.notes = data.docs;
} else {
this.updateError = true;
//hide the error message after 2 seconds
Scheduler.asap.schedule( () => this.updateError = false, 2000);
}
}
addNote() {
if(this.myForm.valid) {
this.db.addItem({note: this.myForm.value.note});
this.myForm.setValue({note: ''});
}
}
deleteNote(note) {
this.db.removeItem(note);
}
updateNote(note, text) {
this.db.updateItem(note, {note: text});
}
}
/********************************************************
install pouchdb and pouch db types
npm install pouchdb --save
npm install @types/pouchdb --save --save-exact
fix CORS issues
npm install -g add-cors-to-couchdb
add-cors-to-couchdb
********************************************************/
import { Injectable } from '@angular/core';
import { Scheduler } from 'rxjs/Rx';
import { Subject } from 'rxjs/Subject';
import * as PouchDB from 'pouchdb';
@Injectable()
export class OfflineDbService {
private remoteDb;
private localDb;
private interval;
private isOnlineSource = new Subject<string>();
public isOnlineStream = this.isOnlineSource.asObservable();
private notifyOffline(msg){
this.isOnlineSource.next(JSON.stringify(msg));
}
private docsSource = new Subject<string>();
public docsStream = this.docsSource.asObservable();
private pushDocs(msg){
this.docsSource.next(JSON.stringify(msg));
}
private dispatchDocs(data={}):void {
this.localDb.allDocs({ include_docs: true })
.then( res => this.pushDocs(Object.assign({}, data, { docs: res.rows.map( row => row.doc)})) )
.catch( err => this.pushDocs({ error: err }) );
}
constructor() { }
public initService(localDbName, remoteDbName, remoteDbUrl, interval=10) {
this.remoteDb = new PouchDB(remoteDbUrl + '/' + remoteDbName);
this.localDb = new PouchDB(localDbName);
this.interval = interval * 1000;
//emit a stream of database changes
this.localDb.changes({live: true, since: 'now', include_docs: true})
.on('change', change => this.dispatchDocs({ updated: true, change: change }))
.on('error', error => this.pushDocs({ updated: false, error: error }));
//stream db docs on initialization
this.pushDocs({ docs: this.dispatchDocs() });
}
// periodically sync the db in the background
public initSync = () =>
this.localDb.sync(this.remoteDb)
.on('complete', () => {
this.notifyOffline({onLine: true});
Scheduler.asap.schedule( () => this.initSync(), this.interval);
})
.on('error', err => {
this.notifyOffline({onLine: false});//emit a notification that the app is offline
Scheduler.asap.schedule( () => this.initSync(), this.interval);
});
public addItem(item) {
return this.localDb.post(item);
}
public removeItem(item) {
return this.localDb.remove(item._id, item._rev);
}
public updateItem(item, updates) {
return this.localDb.put(Object.assign(item, updates));
}
}
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
@Component({
selector: 'update-note',
template: `
<p [class.hide]="showForm" (click)="setShowForm(true)">{{note}}</p>
<form [class.hide]="!showForm" [formGroup]="myForm" (ngSubmit)="editNote()">
<textarea name="note" [formControl]="myForm.controls['note']"></textarea>
<button type="submit" [class.show]="myForm.valid">edit</button>
</form>`,
styles: [`.hide { display: none; }`]
})
export class UpdateNoteComponent implements OnInit {
@Input() note;
@Output() updatedNote = new EventEmitter();
showForm:boolean = false;
myForm: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit() {
this.myForm = this.fb.group({
note: ['', Validators.required]
})
this.myForm.setValue({note: this.note});
}
editNote() {
if(this.myForm.valid) {
this.updatedNote.emit(this.myForm.value.note);
}
this.setShowForm(false);
}
setShowForm(val) {
this.showForm = val;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment