Data Architecture in Angular 4
在应用里,获取数据的方式有很多种:
• AJAX HTTP Requests
• Websockets
• Indexdb
• LocalStorage
• Service Workers
• etc.
形成了如何有效处理多种来源方式问题。
多年来,MVC应用里面的一种常用数据结构,Models包含主要逻辑,View展示数据,controller负责把两者连接在一起。
问题是,MVC模式没有把直接转换到web应用的这一步做的很好。
所以出现了其他数据结构:
- MVW: Model-View-Whatever, 数据双向绑定,也是Angualr1.x用的模式。
整个应用共享数据结构,一个部分的改变会传递到整个应用。
- Flux:单向数据流(unidirectional data flow),store保存数据,view渲染store里的数据,动作改变store里的数据。
数据流是单向的。
- Observables:数据流(streams of data),从流里订阅数据,然后操作数据(perform operations to react to changes),RxJs是最流行的JS流库。
Falcor 是一个强大框架用来绑定客户端和服务端的数据。
让客户端和服务端通过只通过一个json资源进行连接,
客户端不需要重复向服务端发送多条请求。
需要在数据端(服务端?)设置好要发送的json,然后客户端接收即可。
(github地址)[https://github.com/Netflix/falcor]
在Angular4,数据结构可以随项目定制。
服务端 Part 1: services
Underscore.js 是一个库提供JS数据结构的操作,比如array和object。
Angular会使用RxJS,也就是流 stream。
流有如下特点
- promise 只能处理一个函数,streams 能处理多个。
- Imperative code pull data 命令式代码提取数据
reactive streams“push”data 响应式编程,流是推入数据,数据订阅,然后流会推送新的改变的订阅。 - RxJS是函数性的,比如map,reduce, filter这些函数来操作。
- 流是是可随意组合的,composable, 流就像管道一样布满数据,你可以订阅任意一截管道,或者把它们组合成新的管道。
example 聊天机器人101
可以和3个机器人聊天,机器人会有简单的反馈机制。
需要3个top-levle父组件,3个模型,3个服务。
组件
3个父组件:
状态栏,聊天主题,聊天框
模型
User:用户
Message:信息
Thread: 主题,信息库以及聊天数据
服务
每一个模型都有对应的服务。
服务负责
1, 提供应用订阅的数据流,
2, 数据流操作
总之,
- 服务负责维护流,发出模型。
- 组件负责订阅流和渲染最近的数据流
比如聊天主题组件从主题服务监听最近的主题。
会深入运用Angular 4 和RxJs
模型搭建 User, Thread, Message
User,
包含id, name , avatarsrc
export class User {
id: string;
constructor(
public name: string,
public avatarSrc: string) {
this.id = uuid();
}
}
public name: string,
有两个用处:
1,name成为User clsss的全局变量
2,放在constructor里,会给新实例默认配置。
Thread
p279 p302
创建主题模型,一些用户在此交互信息。
注意在这,导入了message数据从message模型,
lastMessage: Message;
用于预览。
Message
message模型的constructor可以让模型实例化更为自由
constructor(obj?: any) {
this.id = obj && obj.id || uuid();
this.isRead = obj && obj.isRead || false;
this.sentAt = obj && obj.sentAt || new Date();
this.author = obj && obj.author || null;
this.text = obj && obj.text || null;
this.thread = obj && obj.thread || null;
表明创建新数据实例时,可以输入数据或者不输入(用默认值)
let msg1 = new Message();
let msg2 = new Message({
text: "Hello Nate Murray!"
})
用户服务 UserService
p281, p304
需要让应用接收到目前用户。所以只需要一个流。
新建用户服务。UserService
import { Injectable } from '@angular/core';
import { Subject, BehaviorSubject } from 'rxjs';
import { User } from './user.model';
@Injectable 可以把其他依赖注入到这个constructor。
便于测试,让Angular更好处理对象的生命周期。
创建流
p281 p304
currentUser: Subject<User> = new BehaviorSubject<User>(null);
定义一个currentUse
流,它是Subject
流的实例变量,
subject
流是一个读写流,继承自Observable
and Observer
new BehaviorSubject<User>(null);
BehaviourSubject
用来保存最新数值,保存User,第一个值时null。
BehaviourSubject
用来保存最新数值。因为响应式,信息会被立刻发布。
信息有可能丢失。
所以把BehaviourSubject
用来保存最新数值。就可以保证数据不会丢失。
创建用户
有两种增加用户方法
1. 把用户直接加到流
UsersService.currentUser.subscribe((newUser) => {
console.log('New User is: ', newUser.name);
})
let u = new User('Nate', 'anImgSrc');
UsersService.currentUser.next(u);
使用了next
方法往currentUser流里推入数据
- 创建一个
setCurrentUser(newUser: User)
方法
public setCurrentUser(newUser: User): void {
this.currentUser.next(newUser);
}
注意这里同样使用next
方法推入,但是currentUser只用了一次。
两种方法依据情况使用。
也有其他方法,在MessageService里会涉及。
MessagesService
需要5条流,3个数据管理,2个动作。
数据管理流
- newMessages 把每个新消息发出一次
- messages 发出一系列目前消息
- updates 操作message
the newMessages stream
只会把新消息发出一次的newMessages流
newMessages: Subject<Message> = new Subject<Message>();
把信息添加到newMessages流
addMessage(message: Message): void {
this.newMessages.next(message);
}
同主题其他用户的信息
接受一个主题和用户,
造出主题下的所有信息
把主题下的本用户信息筛选掉,返回其他用户的信息。
返回的是新流
messagesForThreadUser(thread: Thread, user: User): Observable<Message> {
return this.newMessages
.filter((message: Message) => {
// 通过thread.id造出本主题下的信息
return (message.thread.id === thread.id) &&
// 排除本用户的信息
(message.author.id !== user.id);
});
}
the Messages stream
Messages流 保存了一些列messages,
messages: Observable<Message[]>;
操作
p287 p310
维护messages的状态
用update流来操作这个messages流
update流的函数会接受一条流,并返回一条流,
update流接收函数,具体的函数放在constructor
updates: Subject<any> = new Subject<any>();
constructor() {
this.messages = this.updates
// watch the updates and accumulate operations on the messages
.scan((messages: Message[],
operation: IMessagesOperation) => {
return operation(messages);
},
initialMessages)
这里用了新的流函数:scan,
scan函数 返回累计值,
Rx.Observable.prototype.scan(accumulator, [seed])
接受参数:
accumulator (Function):
acc: Any - 传入数据
currentValue: Any - 目前数据
index: Number - 目前序号 index
source: Observable - the current observable instance
[seed] (Any): The initial accumulator value.
当使用this.updates.scan是,会生成新的流,一条订阅于update的流。
输入:需要累积的message和
需要的操作(IMessagesOperation)
返回新的message流
流共享
默认流是不能共享,如果一个订阅者读取一个数据,那个读取数据就消失了。
要设置共享,
两个操作: publishReplay
and refCount
。
publishReplay
用于在不同订阅者间分享一个订阅,然后回复n条数据。refCount
让return更容易发表。
把messages添加到message流
p289 p312
创建一个让流接受messages添加到列表。
创建一个create流,
create: Subject<Message> = new Subject<Message>();
在constructor里配置create流
this.create
.map( function(message: Message): IMessagesOperation {
return (messages: Message[]) => {
return messages.concat(message);
};
})
.subscribe(this.updates);
表示每一个输入的message,通过IMessagesOperatio把message添加到列表。
然后把这个更updata流挂钩。.subscribe(this.updates);
view端
chat-threads 组件导入模型和服务
<chat-thread
*ngFor="let thread of threads | async"
[thread]="thread">
</chat-thread>
NgFor 遍历threads,把thread传递到chat-thread组件
加入async,可以使用RxJS Observable。
在chat-thread组件里
OnInit 能够坚挺某个具体的事件周期。
ngOnInit 导入因为thread属性不能用于constructor