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

Ami*_*mit 11 observable rxjs angular

假设我使用可观察的数据服务构建我的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));
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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[]>;

  ...
}
Run Code Online (Sandbox Code Playgroud)

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

export class TodoService {

  ...

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

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

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


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

Sup*_*miu 5

模式几乎完成了,您只需要避免在启动时创建可观察的对象,还请记住,它是BehaviorSubjectextends Observable,因此可以按原样使用它,如果要使其易于使用,可以使用隐式getter。

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

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

  public get todos(): Observable<Todo[]>{
    return this._todos;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,当您要添加数据时,只需在_todos主题中发出一个新值,请参阅RxJs:如果您希望每次添加一些数据时都发出所有内容,则将数据流递增地推入BehaviorSubject <[]>以进行递增数组发出。

编辑

如果服务对todos数据有依赖性,则只需使用mapmergeMap等操作符来构建新的Observable,todos以使用源来构建新的Observable ,从而允许您在根数据更改时发出新值。

例:

@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.

}
Run Code Online (Sandbox Code Playgroud)

这样,如果您TodoService发出一个新数组,ResourcesService则将发出一个新的待办事项数组,状态为完成,而无需任何其他操作。

另一种方法

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

@Injectable()
export class DataReloaderService{

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

  public reload():void{
    this.reloader.next(null);
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,无论何时创建数据服务,只需要将其与可观察到的重新加载器合并即可:

@Injectable()
export class ResourceService {

  private _resources: Observable<Resource[]>;

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

}
Run Code Online (Sandbox Code Playgroud)

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

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());
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:您不应该订阅服务,Observables的目的是建立一个冷链,您只需在显示部分进行订阅,第二种模式可确保您的所有Observables都链接到中央重新加载器(您也可以创建多个重新加载程序(例如,每个模型系列一个),订阅会使您失去这一点,从而导致只能以奇怪的方式编辑数据。如果所有内容都取决于您的api,那么您的可观察对象应仅使用此api,并在每次编辑内容时都调用reloader,以确保所有链接的数据也得到更新。