使用Angular4中的obervable来同时使用“初始请求”和“后续更新”

时间:2017-10-28 03:12:32

标签: angular rxjs

背景

我觉得这是一个非常基本的问题,但我一直想知道我是否遗漏了一些基本的东西。这是场景。

  1. 在页面加载时,我执行http请求以加载初始模型。
  2. 我还有一个websockets服务器将推送更新发送到同一模型
  3. 我可能有其他地方我不通过网络套接字/ http
  4. 更新此模型
  5. 多个组件可能需要同时使用相同的模型类型,但我只想要一个http请求。换句话说,所有订阅者都希望“某人”只做一个http请求,然后更新所有这些请求,但没有客户会自愿“敲响猫......” (https://en.wikipedia.org/wiki/Belling_the_cat
  6. 这是我想要实现的目标:

    1. 每个客户(例如组件)只订阅一个可观察的

    2. 如果到目前为止还没有人请求该模型(例如,observable为空),则会自动触发该初始http请求。例如在所有组件被渲染/初始化之后,DOM被呈现给用户,如果observable为空,则自动发起http请求。 (请注意,我需要区分空observable和返回null对象的http调用)。这应该在任何路线导航或“页面加载”上重复(Angular世界中非常含糊的术语,但你知道我的意思)。

    3. 如果webockets请求带有该版本的新版本,请更新相同的observable,因此客户端不关心它是来自初始http请求还是websockets请求

    4. 天真的实施

      如果延迟加载不是问题(例如,如果我没问题,模型将始终按概念“页面加载”加载一次),那么我想我可以做这样的事情

      //totally made RoutingLifeCycle up, but I assume Angular has something like it
      
      @Injectable()
      class ModelLoadingService implements RoutingLifeCycle {
      
         /**
         /* only keep the "last" copy of the model for future subscribers.
          */
         private modelSubject = new ReplaySubject<Model>(1);
      
         /**
         /* the observable clients will subscribe to.
          */
         model$ = this.modelSubject.asObservable() 
      
         /**
         /* Right before every conceptual "page load", where (except perhaps 
         /* the app component) many components get either destroyed or 
         /* initialized in a short time. 
         /* Again, I totally made this "onBeforeRouteChanged" method up but
         /* I assume Angular has something like this 
          */
         onBeforeRouteChanged() {
      
           //problem is that if there are no subscribers for it in this "page" 
           // then I wasted an http request. this is not "lazy"
      
           // Side question... IS THERE A BETTER WAY THAN THIS? 
           // I can't pass an Observable<Model> to the Subject.next method 
           // And I can't .startWith as the subject is kind of immutable,
           // And I can't "replace" model$ as the subscribers subscribed
           // To the old one, (is there a way to "move the subscribers" to 
           // A new observable?) I feel I'm taking crazy pills 
      
           getModel().subscribe(model => this.modelSubject.next(model)); 
         }
      
         /**
          * loads a model via a regular http request
          */
         getModel(): Observable<Model> {
           return http.get(...);
         }
      
         /**
          * receives a model from a websockets message and multicasts it 
          */       
         onWebsocketsMessage(message:string) {
           // omitted error handling etc... 
           this.modelSubject.next(JSON.parse(message) as Model); 
      
         }
      }
      

      另一种方法

      如果我想避免关心页面重新加载触发器,我想我可以使用像shareReplay(1, 1000)这样的东西(还添加处理上面例子中未处理的“get by ID”情况)

      getModelById(id:string):Observable<Model> {
        let modelObservable = this.modelMap.get(id);
        if(!modelObservable) {
          model = http.get(...).shareReplay(1, 1000);
          this.modelMap.set(id, modelObservable);
        }
        return modelObservable;
      }   
      

      然后我使用websockets主题merge,并且只公开那个唯一可观察的客户端可以用于该项目。这是正确的方法吗?

      问题

      1. 用Rx实现类似行为的惯用方法是什么?我是朝着正确的方向吗?或者还有很长的路要爬山。

      2. 上面我的问题怎么样? :)将Observable<Model>传递给Subject.next的好方法是什么? (observable.subscribe(model => subject.next(model));除外)

      3. 相关问题

        How to handle/queue multiple same http requests with RxJs?

2 个答案:

答案 0 :(得分:1)

你走在正确的轨道上。

您需要采取的步骤是:

  1. 在您的服务中创建一个存储模型的可观察主题。 2,创建一个从HTTP请求中获取模型的函数。
    • 在该函数中首先检查模型是否为空,否则返回您已有的可观察量。
    • 如果模型为空,请从HTTP requet中获取。
  2. 拥有需要该模型的每个组件,订阅#1
  3. 中的observable

答案 1 :(得分:1)

我没有纯粹的Rx解决方案,但我想分享一些从各种方面解决这个问题的想法。

Redux(命令查询分离)
我同意@dee zg,命令查询分离原则使得对数据流的推理更加容易。试图找到一个纯粹的Rx解决方案似乎类似于只用一个扳手来为您的汽车服务。我首先通过“缓存”解决了这个问题。服务,但结果是复杂和脆弱的。转移到Redux,解决方案感觉好多了。

处理状态
Ben Richards解释的类似单身模式是避免多个请求的关键,但它不仅仅是案例模型空/模型已满。问题是HTTP请求需要花费时间,如果第二个客户端在第一个客户端加载时订阅,则会出现重叠 可能吗,这是一个问题吗?取决于应用程序,但如果确实发生,可能很难调试。

已排序的请求
另一方面是可能存在所需的一系列资源,并且对于每个组件,顺序可以是不同的,如同多个请求的排队&#39;你引用的问题。
我认为序列应该由组件拥有并在组件中定义。使用工资单应用程序显示任何人都可以查看的摘要页面,并详细说明需要登录令牌才能访问的页面。两者都使用数据获取服务,但只有详细信息页面需要获取令牌 - 因此令牌提取不能与数据服务绑定。

我目前的结构

  • 组件调用一系列命令&#39;关于init的服务。该命令仅返回信号完成的承诺,以允许组件排序并等待资源。

    initialize () {
      configService.checkConfig()
        .then(_ => {
          dataService.checkFiles(this.page)
        })
    }
    
  • 服务定义命令,但不动作 - 它们将其与处理提取,失败和超时的方法一起交给商店操作。

    checkConfig () {
      return store.dispatch('waitForFetch', {
        resource: 'config',
        fetch: this.getConfig   // this method defines the request
      })
    }
    
  • Store操作处理每个资源的状态,类似于modelMap对象,但存储在Redux状态分支中,因此可以在devtools中轻松查看。

    const actions = {
      waitForFetch: (context, {resource, fetch, fail, timeout}) => {
        return 
          isLoaded(resource)  ? Promise.resolve()
        : isInitial(resource) ? doFetch(resource, fetch, fail)
        : isLoading(resource) ? doWaitForLoading(resource, timeout)
        : null
      }
    }
    

它并不完美,它不是Rx(订阅商店除外),但它灵活,易懂,可测试。