如何同步多个角度可观察数据服务

时间:2018-04-04 07:52:59

标签: angular rxjs observable

假设我使用可由服务器端点支持的可观察数据服务来构建我的Angular应用程序:

@Injectable()
export class TodoService {
  todos: Observable<Todo[]>
  private _todos: BehaviorSubject<Todo[]>;

  constructor(private http: Http) {
    this._todos = <BehaviorSubject<Todo[]>>new BehaviorSubject([]);
    this.todos = this._todos.asObservable();
  }

  loadData() {
    this.http.get(dataUrl).subscribe(data => this._todos.next(data));
  }
}

现在假设我的数据引用了其他一些模型,并通过其他一些服务(某种形式的多对多关系)公开:

interface ToDo {
  title: string;
  status: StatusEnum;
  requiredResouces: Resource[];
}
interface Resource {
  name: string;
  todos: ToDo[];
}

@Injectable()
export class ResourcesService {
  resources: Observable<Resource[]>
  private _resources: BehaviorSubject<Resource[]>;

  ...
}

现在,假设我将一个方法添加到“链接”待办事项和资源的服务,该服务将能够将更新状态推送到主题,但其他服务将不知道该更改。例如:

export class TodoService {

  ...

  addResourceRequirement(todo: ToDo, resource: Resource) {
    this.http.post(`${url}/${todo.id}/`, {addResource: resource.id})
      .subscribe(() => this.loadData());
  }
}

会导致任何“todos”观察者刷新,但任何“资源”观察者仍会显示旧状态......

您将使用哪种设计/模式/架构来同步这两种服务?

(我知道有一些架构可以避免这种困难 - 特别是基于磁通的解决方案 - NgRX等......但我对专门针对可观察数据服务模式的解决方案感兴趣)

2 个答案:

答案 0 :(得分:5)

您的模式已基本完成,您只需要避免在启动时创建observable,同时请记住BehaviorSubject扩展Observable,因此您可以按原样使用它,使用隐式getter如果你想让它易于使用。

@Injectable()
export class TodoService {
  private _todos: BehaviorSubject<Todo[]>;

  constructor() {
    this._todos = new BehaviorSubject<Todo[]>([]);
  }

  public get todos(): Observable<Todo[]>{
    return this._todos;
  }
}

然后,当您想要添加数据时,只需在_todos主题中发出一个新值,如果您想在每次添加一些数据时发出所有内容,请参阅RxJs: Incrementally push stream of data to BehaviorSubject<[]>了解增量阵列发射。< / p>

修改

如果服务依赖于todos数据,您只需使用mapmergeMap等运算符构建一个新的Observable,以便使用{{构建一个新的Observable 1}}作为源,允许您在根数据更改时发出新值。

示例:

todos

这样,如果@Injectable() export class ResourcesService{ private _resources: Observable<Resources[]>; constructor(private todoService: TodoService){ this._resources = this.todoService.todos.map(todos => { // I suppose you want to aggreagate all resources, you can do it using reduce or anything, I'll use forEach in this example const resources: Resource[] = []; todos.forEach(todo => resources.push(...todo.requiredResouces); }); } //And then implicit getter for _resources. } 发出一个新数组,TodoService将发出一个状态已完成的新待办事项数组,而不需要任何其他操作。

另一种方法

如果您的资源来自另一个端点(意味着您在更新数据后需要另一个请求来获取它们),那么使用重新加载器模式可能会更好:

ResourcesService

然后,每当您创建数据服务时,您只需将其与重新加载器observable合并:

@Injectable()
export class DataReloaderService{

  public readonly reloader: BehaviorSubject<void> = new BehaviorSubject<void>(null);

  public reload():void{
    this.reloader.next(null);
  }
}

最后,在您的服务中进行修改:

@Injectable()
export class ResourceService {

  private _resources: Observable<Resource[]>;

  constructor(private reloaderService: DataReloaderService, private http: HttpClient){
    this._resources = this.reloaderService.reloader.mergeMap(() => this.http.get(...));
  }

}

注意:您不应该订阅服务,Observables的目的是构建一个冷链,您只需在显示部分订阅,第二个模式确保所有Observable都链接对于中央重新加载器(您也可以创建多个重新加载器,例如每个模型系列一个),订阅会使您松散,导致只有奇怪的方式来编辑数据。如果一切都依赖于你的api,那么你的observable应该只使用这个api,每当你编辑一些内容时调用重新加载器以确保所有链接的数据都被更新。

答案 1 :(得分:0)

我不确定我是否理解这个问题。

但是假设你有两个科目列出资源和待办事项。

import {combineLatest} from "rxjs/observable/combineLatest";

mergeResourcesAndTodos() {
    return combineLatest(
        this.todoService.todos
        this.resourceService.resources
      ).map((data: any[]) => {
       const todos=data[0]
       const resources=data[1]
       //and here you can map anyway you want like a list of todos that have a list of resources or the other way around, here I will do an example with a list of todos with resources.
         return todos.map(todo=>{
              const resource= resources.find(resource=>resource.id===todo.resourceId)
              todo.resource=resource;
              return todo
          })
     })
  }