經過 todo list
例子簡單展現一個 Angular
中的數據流管理方案;如圖:html
項目結構如圖:git
咱們的目的很簡單:github
TODO item
;TODO item
;TODO item
,使其數據可以進行同步,而不須要進行數據從新請求;從最外層組件開始,咱們會有一個 container
做爲入口,而且會配備一個 service
做爲其數據管理層api
...
@Injectable({
providedIn: 'root'
})
export class DataFlowService {
dataFlowSubject: BehaviorSubject<DataFlowData>;
dataFolwData: DataFlowData;
constructor() {
if (!this.dataFolwData) {
this.dataFolwData = this.initDataFlowData();
this.mockList(10);
}
this.dataFlowSubject = new BehaviorSubject<DataFlowData>(this.dataFolwData);
}
getDataFlowSubject = () => {
this.updateData();
return this.dataFlowSubject;
}
initDataFlowData = () => ({
list: []
});
updateData = () => {
this.dataFlowSubject.next(this.dataFolwData);
}
mockList = (num: number) => {
const list: TodoList[] = [];
if (num > 0) {
for (let i = 0; i < num; i++) {
list.push({
id: cuid(),
text: `todo-${i}`,
status: TodoStatus.TODO
});
}
}
this.dataFolwData.list = list;
}
...
}
複製代碼
@Component({
selector: 'data-flow',
templateUrl: './data-flow.component.html',
styleUrls: ['./data-flow.component.scss']
})
export class DataFlowComponent implements OnInit {
dataFlowSubject: BehaviorSubject<DataFlowData>;
todoList: TodoList[];
constructor( private dataFlowService: DataFlowService ) {
this.dataFlowSubject = this.dataFlowService.getDataFlowSubject();
}
ngOnInit() {
this.dataFlowSubject.subscribe((data: DataFlowData) => {
this.todoList = data.list;
});
}
}
複製代碼
咱們將 service
註冊在 root
,使其成爲一個當 app
生成就存在的單例,而且初始化數據,mock 假數據,使用 rxJS
生成一個 BehaviorSubject
的訂閱器,而後將初始化的數據,放入生成的訂閱器做爲初始數據;網絡
再在構造 component
的時候獲取到 service
中的訂閱器,在組件初始化的鉤子函數中訂閱上咱們的更新方法,這樣 component
與 service
就創建了聯繫;antd
這裏不難看出,咱們在 service
中存儲了一個持久化的數據,也就是咱們的總的一個數據,咱們後面就只會修改這個數據,而後再進行重繪進行組件的更新,雖然這裏的數據是 mock 的假數據,可是在真實項目中,咱們也能夠將取回來的數據存在這裏,而後對其作 增 刪 改 查
的操做,這樣子能夠避免一些沒必要要的網絡消耗,可是也會存在必定的弊端,就是數據量不能建議太大;app
因此接下來咱們給其增長上 add
函數來增長 TODO
,異步
add = (item: TodoList) => {
this.dataFolwData.list.push(item);
this.updateData();
}
複製代碼
addTodo = () => {
if (this.addText) {
this.dataFlowService.add({
id: cuid(),
text: this.addText,
status: TodoStatus.TODO
});
this.addText = '';
}
}
複製代碼
因爲咱們在 service
的 add
中使用了 updateData
觸發訂閱器的 next
,將最新的數據傳出,component
就會接收到數據就會根據數據進行重繪;async
同理,咱們也能夠寫上刪除的方法,也是調用 updateData
觸發訂閱器更新數據,
@Component({
selector: 'todo-item',
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.scss']
})
export class TodoItemComponent implements OnInit {
@Input() todoItem: TodoList;
constructor( private dtService: DataFlowService ) {
}
ngOnInit() {}
deleteItem = () => {
this.dtService.deleteItem(this.todoId);
}
}
複製代碼
deleteItem = (id: string) => {
this.dataFolwData.list = this.dataFolwData.list.filter((item: TodoList) => item.id !== id);
this.updateData();
}
複製代碼
爲了更明顯的驗證 data-flow service
的數據是持久化的,而且可以重複使用的,新建一個組件 detail container
展現詳情數據以及能夠更改數據,而後將數據同步到 data-flow service
,再次回到 data-flow container
,查看咱們的修改是否生效;
@Component({
selector: 'item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss']
})
export class TodoDetailComponent implements OnInit {
detailSubject$: DetailServiceSubject;
detailId: string;
detail: TodoList;
isLoading: boolean;
todoStatus = TodoStatus;
constructor( private route: ActivatedRoute, private router: Router, private detailService: DetailService, private dataFlowService: DataFlowService ) {
this.detailSubject$ = this.detailService.getDetailSubject();
this.detailSubject$.subscribe(this.updateDetail);
}
ngOnInit() {
const newId = this.route.snapshot.paramMap.get('id');
this.isLoading = true;
if (newId === this.detailId) {
this.isLoading = false;
} else {
this.detailId = newId;
this.detailService.getDetail(this.detailId);
}
}
get status() {
return statusMapping[this.detail.status] || '無狀態';
}
updateDetail = (data: TodoList) => {
this.detail = data;
this.isLoading = false;
}
changeValue = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
this.detail.text = value;
}
delete = () => {
this.dataFlowService.delete(this.detailId);
this.router.navigate(['/todolist-demo']);
}
change = () => {
this.dataFlowService.updateItem(this.detail);
this.router.navigate(['/todolist-demo']);
}
changeStatus = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
this.detail.status = value as TodoStatus;
}
}
複製代碼
@Injectable({
providedIn: 'root'
})
export class DetailService {
detailSubject: DetailServiceSubject;
detailData: TodoList;
constructor( private dataFlowService: DataFlowService ) {
this.detailData = null;
this.detailSubject = new BehaviorSubject<TodoList>(this.detailData);
}
getDetailSubject = () => {
this.updateData();
return this.detailSubject;
};
getDetail = (id: string) => {
if (get(this.detailData, 'id', null) === id) {
this.updateData();
} else {
this.dataFlowService.asyncGetItemById(id, (data: TodoList) => {
this.detailData = data;
this.updateData();
})
}
};
updateData = () => {
this.detailSubject.next(this.detailData);
}
}
複製代碼
updateItem = (item: TodoList) => {
this.dataFolwData.list = this.dataFolwData.list.map(todo => {
if (todo.id === item.id) return item;
return todo;
})
}
asyncGetItemById = (id: string, callback) => {
const detail = this.dataFolwData.list.filter(item => item.id === id)[0];
setTimeout(function() { callback(detail) }, 1000);
};
changeStatus = (item: TodoList) => {
this.dataFolwData.list = this.dataFolwData.list.map((d: TodoList) => {
if (d.id === item.id) {
return item;
}
return d;
});
this.updateData();
}
複製代碼
一樣的,container 層面的 component,都將數據放在 service 中進行處理和更新,detail component
也使用這樣的方式,進行數據的訂閱和更新;
在 detail component
中,聲明瞭 dataFlowService
,是爲了將數據的更改同步到列表頁面的數據中去,這樣子在回到剛纔那個頁面的時候,數據纔會獲得更改,固然這裏只是同步必要的數據,好比 狀態 文字 刪除
,若是有其餘的數據僅僅只是 detail 獨有的,可是不是 list 擁有的數據,就不須要同步;
效果如圖所示:
在進入 detail
頁面時,會有一個 loading 的提示,這是製做的一個假的異步獲取的代碼,在 detail component
中有一個判斷,若是當前的 id 與 前一個 id 不相同則進行請求數據,相同就直接取消掉 loading 效果,這是由於,在構造這個組件的時候,用的是 BehaviorSubject
,這個訂閱器在訂閱的時候會將最後一次數據傳過來做爲數據使用,因此當 id 相同時在 detail component
中,再也不須要去進行獲取數據的操做,這也就減小了一些網絡請求,增長了用戶體驗;
經過service進行數據管理,在component中訂閱數據更新方法,獲取到(新)數據進行頁面更新,進而減小對網絡的請求,提高用戶體驗,也可以有一個較爲清晰的數據流向,可以容易的理解數據流向就可以容易理解業務上的流程;