代码之家  ›  专栏  ›  技术社区  ›  Amit

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

  •  13
  • Amit  · 技术社区  · 7 年前

    假设我使用服务器端端点支持的可观察数据服务构建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[]>;
    
      ...
    }
    

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

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

    将导致任何“待办事项”观察器刷新,但任何“资源”观察器仍将显示旧状态。。。

    您将使用什么设计/模式/体系结构来同步这两个服务?


    (我知道有一些架构可以从整体上避免这一困难,尤其是基于流量的解决方案-NgRX等……但我对一个专门针对可观测数据服务模式的解决方案感兴趣)

    2 回复  |  直到 7 年前
        1
  •  5
  •   Supamiu    7 年前

    您的模式几乎完成了,您只需要避免在引导时创建可观察的,同时,请记住 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<[]> 对于增量阵列发射,如果希望在每次添加数据时发射所有内容。

    编辑

    如果服务依赖于 todos 数据,您只需使用 map ,则, mergeMap ,etc运算符,以建立新的可观察 待办事项 作为源,允许在根数据更改时发出新值。

    示例:

    @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 将发出状态为“完成”的新TODO数组,而不需要任何其他操作。

    另一种方法

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

    @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(...));
      }
    
    }
    

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

    export class TodoService {
    
      private _todos: BehaviorSubject<Todo[]>;
    
      constructor(private reloaderService: DataReloaderService, private http: HttpClient) {
        this._todos = this.reloaderService.reloader.mergeMap(() => this.http.get(dataUrl));
      }
    
      public get todos(): Observable<Todo[]>{
        return this._todos;
      }
    
      addResourceRequirement(todo: ToDo, resource: Resource) {
        this.http.post(`${url}/${todo.id}/`, {addResource: resource.id})
          .subscribe(() => this.reloader.reload());
      }
    }
    

    注意事项 :您不应该在服务中订阅,Observables的目的是构建冷链,您只需在显示部分订阅,第二种模式确保您的所有Observables都链接到中心重载器(您也可以创建多个重载器,例如每个模型族一个),订阅会使您失去这一点,从而导致只编辑数据的奇怪方式。如果一切都依赖于您的api,那么您的可观察对象应该只使用此api,在您编辑某些内容时调用重载程序,以确保所有链接的数据也都得到更新。

        2
  •  1
  •   Eduardo Vargas    7 年前

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

    但让我们假设您有两个主题,都有资源列表和待办事项。

    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
              })
         })
      }