Angular + rxJS 的數據流向實例與講解

做者:markzzw 時間:2019-12-24    本文代碼: github    原文地址:原文地址css

簡介

經過 todo list 例子簡單展現一個 Angular 中的數據流管理方案;如圖:html

xmind

項目結構如圖:git

pro-tree

咱們的目的很簡單:github

  1. 生成若干 TODO item;
  2. 展現這些 TODO item;
  3. 修改 TODO item,使其數據可以進行同步,而不須要進行數據從新請求;

data-flow.componnet.ts & data-flow.service.ts

從最外層組件開始,咱們會有一個 container 做爲入口,而且會配備一個 service 做爲其數據管理層api

  • data-flow.service.ts
...
@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;
  }
  ...
}
複製代碼
  • data-flow.componnet.ts
@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 中的訂閱器,在組件初始化的鉤子函數中訂閱上咱們的更新方法,這樣 componentservice 就創建了聯繫;antd

這裏不難看出,咱們在 service 中存儲了一個持久化的數據,也就是咱們的總的一個數據,咱們後面就只會修改這個數據,而後再進行重繪進行組件的更新,雖然這裏的數據是 mock 的假數據,可是在真實項目中,咱們也能夠將取回來的數據存在這裏,而後對其作 增 刪 改 查 的操做,這樣子能夠避免一些沒必要要的網絡消耗,可是也會存在必定的弊端,就是數據量不能建議太大;app

因此接下來咱們給其增長上 add 函數來增長 TODO,異步

  • data-flow.service.ts
add = (item: TodoList) => {
  this.dataFolwData.list.push(item);
  this.updateData();
}
複製代碼
  • data-flow.componnet.ts
addTodo = () => {
  if (this.addText) {
    this.dataFlowService.add({
      id: cuid(),
      text: this.addText,
      status: TodoStatus.TODO
    });
    this.addText = '';
  }
}
複製代碼

因爲咱們在 serviceadd 中使用了 updateData 觸發訂閱器的 next,將最新的數據傳出,component 就會接收到數據就會根據數據進行重繪;async

add

同理,咱們也能夠寫上刪除的方法,也是調用 updateData 觸發訂閱器更新數據,

  • todo-item.component.ts
@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);
  }
}
複製代碼
  • data-flow.service.ts
deleteItem = (id: string) => {
  this.dataFolwData.list = this.dataFolwData.list.filter((item: TodoList) => item.id !== id);
  this.updateData();
}
複製代碼

delete

detail.component 和 data-flow.service 的關係

爲了更明顯的驗證 data-flow service 的數據是持久化的,而且可以重複使用的,新建一個組件 detail container 展現詳情數據以及能夠更改數據,而後將數據同步到 data-flow service ,再次回到 data-flow container ,查看咱們的修改是否生效;

  • detail.component
@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;
  }
}
複製代碼
  • detail.service
@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);
  }
}
複製代碼
  • data-flow.service.ts
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 擁有的數據,就不須要同步;

效果如圖所示:

syncdata

在進入 detail 頁面時,會有一個 loading 的提示,這是製做的一個假的異步獲取的代碼,在 detail component 中有一個判斷,若是當前的 id 與 前一個 id 不相同則進行請求數據,相同就直接取消掉 loading 效果,這是由於,在構造這個組件的時候,用的是 BehaviorSubject,這個訂閱器在訂閱的時候會將最後一次數據傳過來做爲數據使用,因此當 id 相同時在 detail component 中,再也不須要去進行獲取數據的操做,這也就減小了一些網絡請求,增長了用戶體驗;

總結

經過service進行數據管理,在component中訂閱數據更新方法,獲取到(新)數據進行頁面更新,進而減小對網絡的請求,提高用戶體驗,也可以有一個較爲清晰的數據流向,可以容易的理解數據流向就可以容易理解業務上的流程;

參考資料

  1. rxjs-BehaviorSubject
  2. angular-service
相關文章
相關標籤/搜索