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