[Ionic 2从入门到精通] 7.6 本地和远程PouchDB和Cloudant后台

虽然我们完成了应用的大部分功能,但是本节课将是最大最难的那个。我们将使用PouchDB来存储信息而不是直接将他扔到信息数组里面去。
PouchDB是一个浏览器内的NoSQL数据库,灵感来自CouchDB项目。他最大的功能是允许存储离线数据,当应用再次上线之后会自动从远程数据库同步数据。和使用Ionic提供的SqlStorage一样,使用PouchDB保证你存储的本地数据不会被随机擦除。
普及NoSQL不是本书的目的,但是为了给你一点基础,NoSQL数据库通常以类JSON的键值对风格格式来存储数据,不同的样式要不同的去思考。所以,所以当你来到NoSQL的时候,需要放弃你可能已有大量的的SQL固有观念(假设你之前用过)。我们存储的数据非常简单,所以我们不需要担心如何合理的使用NoSQL数据库。
所以PouchDB会负责本地数据的存储,但是我们还要用到Cloudant来创建一个远程数据库。我们将同步本地的PouchDB数据库和远程的Cloudant数据库,这样无论何时应用上线的时候都可以从Cloudant获取最新数据,任何本地新数据都将推送到Cloudant。幸运的是,这两个工具都被设计为相互协作且设置好了远程同步,这在平常事一个非常复杂的任务,现在却变得非常简单了。
Cloudant是一个DBaas(Database as a Service数据库即是服务),所以我们只要创建一个帐号(低端使用免费)来使用,当然也要设置数据库。使用类似Cloudant这样的的东西非常简单,且扩展非常简单(因为所有后端架构都帮你处理好了),但是如果你喜欢的话,你也可以轻松的使用类似CouchDB或者Couchbase安装到你的服务端来和PouchDB同步。这个我不会深入,因为差别不大。

创建Cloudant Database

在进入编码之前,我们要在IBM Bluemix的Cloudant上设置我们的后端。Bluemix让我们可以访问IBM的Open Cloud Architecture,他可以用来创建,部署和管理基于云的应用。他提供大量可用的服务,其中一个就是Cloudant。
首先你的创建一个IBM Bluemix帐号。当你创建好帐号登录进去之后,可以看到这样的界面:

Bluemix

选择Create App选项,选择Mobile,给你的应用取个名字然后点击Finish。之后就会看到这样的画面:

Bluemix

从左边的Service菜单选择Cloudant NoSQL DB,然后在Cloudant Dashboard上点击View your Data。你现在应该来带了Cloudant的仪表盘了。点击右上的Create Database选项创建一个新的数据库然后命名为camperchat或者其他你喜欢的。

Bluemix

现在,选择刚才新建的数据库查看具体细节,应该可以看到如下画面:

6.7.4.jpg

点击右上的API连接就得到了你的Cloudant Database URL了,这个我们要稍后提供给PouchDB的,所以最好拿个本子记下来吧。同时需要去Permissions部分生产一个API密钥(确保给他提供Write和Replicate权限) -- 他可以让PouchDB访问你的数据库,使用API密钥比用你的用户名和密码好一些。请记住密码因为一旦离开屏幕你就再也找不到了。
这里我们还有一件事情要做。我们需要激活CORS(Cross Origin Resource Sharing)这样我们就可以从我们的应用中向数据库发起请求了。来到左边菜单的Account,选择CORS然后选择All Domains(*) 选项。
这样就在Cloudant上设置好了,可以跟PouchDB一起使用了。

整合PouchDB

之前我们创建好了Data服务,也只是存放了一点Facebook API返回的值。现在我们要扩展Data服务用来操作PouchDB的数据存储和获取。
> 修改 src/providers/data.ts 为如下:

import { Injectable } from '@angular/core';
import PouchDB from 'pouchdb';

@Injectable()
export class Data {
    fbid: number;
    username: string;
    picture: string;
    db: any;
    data: any;
    cloudantUsername: string;
    cloudantPassword: string;
    remote: string;

    constructor(){
        this.db = new PouchDB('camperchat');
        this.cloudantUsername = 'YourAPIUsernameHere';
        this.cloudantPassword = 'YourAPIPasswordHere';
        this.remote = 'https://YOUR-URL-HERE-bluemix.cloudant.com/camperchat';
        //Set up PouchDB
        let options = {
            live: true,
            retry: true,
            continuous: true,
            auth: {
                username: this.cloudantUsername,
                password: this.cloudantPassword
            }
        };
        this.db.sync(this.remote, options);
    }

    addDocument(message){
    }

    getDocuments(){
    }

    handleChange(change){
    }
}

我们已经通过npm安装好了PouchDB,想要在这个服务里使用的话我们得先导入:

import PouchDB from 'pouchdb';

在构造器中,我们处理了PouchDB的设置和远程Cloudant数据库的同步。首先我们我们新建了一个PouchDB,或者获取一个已存在的引用:

this.db = new PouchDB('camperchat');

然后我们定义了一些用于连接到Cloudant数据库的变量:

this.cloudantUsername = 'YourAPIUsernameHere';
this.cloudantPassword = 'YourAPIPasswordHere';
this.remote = 'https://YOUR-URL-HERE-bluemix.cloudant.com/camperchat';

请记得要用你自己的Cloudant仪表盘上的参数替换上面这些值。接着我们创建了一个options对象来配置到Cloudant数据库的连接,然后我们调用了sync方法:

this.db.sync(this.remote, options);

这样将会设置从PouchDB数据库复制到Cloudant数据库,同时也会设置从Cloudant数据库到PouchDB数据库的复制。现在,如果我们给PouchDB添加一些数据的时候将会自动反射到远程Cloudant数据库,如果我们在远程Cloudant修改或者添加一些数据的时候,将会自动反射到我们的本地数据库。
在那之后我们创建了三个空函数,我们现在就来实现。
addDocument
> 修改 addDocument 为如下:

addDocument(message) {
    this.db.put(message);
}

我们只需要简单的调用数据库的put就可以向PouchDB添加文档(NoSQL条款里一个数据对象叫做一个“文档”,所以可以将文档想象成一小片数据,而不是一个Word文档)。这样我们可以向这个函数传入任何数据以存储到数据库。
getDocument
> 修改 getDocument 为如下:

getDocuments(){
    return new Promise(resolve => {
        this.db.allDocs({
            include_docs: true,
            limit: 30,
            descending: true
        }).then((result) => {
            this.data = [];

            let docs = result.rows.map((row) => {
                this.data.push(row.doc);
            });

            this.data.reverse();
            resolve(this.data);

            this.db.changes({live: true, since: 'now', include_docs:true}).on('change', (change) => {
                this.handleChange(change);
            });
        }).catch((error) => {
            console.log(error);
        });
    });
}

这个函数用户获取数据库中的所有文档(记住,文档只是一个数据对象)。这是一个一部操作,所以我们将他包装到一个promise里,然后在数据返回的时候在resolve。他允许我们在应用内其他地方使用getDocuments().then()语法。通过调用allDocs可以获取所有文档,这里我们也提供了一些选项:

include_docs: true,
limit: 30,
descending: true

这里的include_docs看起来有点迷糊,但是我们还是要指定这个这样文档内的所有数据都将被返回(信息,图片等)。如果没有提供这个参数的话只会返回文档的id。我们也设置了一个30的显示,和一个降序,这样一来只返回最新的30个文档(聊天信息)。
我们将这些结果传入到我们的处理器,每个返回的行我们都会压入到this.data数字。我们想让这些信息倒序这样的话最新的消息将会显示在最下面。之后我们resolve我们创建的promise然后回传数据,然后设置changes监听器。
changes监听器在每次侦测到数据库变更的时候(例如当其他用户添加了一个聊天信息的时候)都将调用this.handleChange函数,change本身也会被传入到函数里。我们现在来定义。
handleChange
> 修改 handleChange 为如下:

handleChange(change) {
    let changedDoc = null;
    let changedIndex = null;
    this.data.forEach((doc, index) => {
        if(doc._id === change.id){
            changedDoc = doc;
            changedIndex = index;
        }
    });

    //A document was deleted
    if(change.deleted){
        this.data.splice(changedIndex, 1);
    }
    else {
        //A document was updated
        if(changedDoc){
            this.data[changedIndex] = change.doc;
        }
        //A document was added
        else {
            this.data.push(change.doc);
        }
    }
}

我们将传入的change反射到我们的this.data数组,但是有点技巧。传入进来的change对象可能是一个文档更新了,新建了一个文档,或者删除了一个文档。
检查文档删除非常简单只要检查他是否有deleted属性就可以了。但是辨别他是否是更新,我们需要检查我们是否有了相同id的文档(如果找到了的话就更新他),如果没有的话我们就知道是新增文档了(我们需要将他添加到数组)。
现在我们完全设置好了Data服务,但是如果要使用他的话还有些事情要做。我们需要使用provider来存储新数据,而不是像现在这样直接将他添加到messages数组,同时在应用打开的时候我们需要加载最新的信息。我们现在就来完成这些。
> 修改 src/page/home/home.ts 如下:

import { Component, ViewChild } from '@angular/core';
import { Data } from '../../providers/data';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage {

    @ViewChild('chat') chat: any;
    chatMessage: string = '';
    messages: any = [];

    constructor(public dataService: Data){
        this.dataService.getDocuments().then((data) => {
            this.messages = data;
            this.chat.scrollToBottom();
        });
    }

    sendMessage(): void {
        let message = {
            '_id': new Date().toJSON(),
            'fbid': this.dataService.fbid,
            'username': this.dataService.username,
            'picture': this.dataService.picture,
            'message': this.chatMessage
        };

        this.dataService.addDocument(message);
        this.chatMessage = '';
    }
}

现在我们在构造器中调用了getDocument()函数来加载信息数据,一旦完成之后我们通过@ViewChild获取滚动内容的引用,将他滚动到底部。
最后,在sendMessage函数中,唯一的变更是我们调用了addDocument函数而不是直接把信息压入到messages数组。
终于完成了!【译者:我尿也憋坏了】现在聊天应用的全部功能都开发完了。他能够正常工作了,但是也丑爆了。下节课我们让他变漂亮点甚至给他加点动画的啥的!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容