即使没有订户也能保持可观察的连接

时间:2017-04-28 17:16:13

标签: angular rxjs rxjs5

编辑2:更新@karser's answer中所述的shareReplay(1)效果更好。

编辑1:最终效果最好的是:

@Injectable()
export class MyGlobalService {

  private resource$;
  private resource$Connected;

  constructor(private http: Http) {
    this.resource$Connected = false;
    this.resource$ = this.http
      .get('/api/resource')
      .map((res: Response) => res.json())
      .publishReplay(1);
  } 

  getResource(): Observable<any> {
    if (!this.resource$Connected) {
      this.resource$.connect();
      this.resource$Connected = true;
    }
    return this.resource$;
  }

}

它只会使AJAX调用一次,并且在某些消费者需要资源之前它不会调用。

原始问题:

我尝试缓存角度HTTP呼叫,并将最新结果多播到所有当前和未来的订阅者。 ajax结果不会在应用程序生命周期内发生变化,因此我不想对已有的资源进行任何额外调用。因此,我希望它继续保持联系&#34;即使所有订阅者都取消订阅。这可能吗?

我最初的尝试是:

// in a global service 

getResource(): Observable<any> {
  return this.http
    .get('/api/resource')
    .map((res: Response) => res.json())
    .publishLast()
    .refCount();
}

这适用于同一组件中的多个async管道,但如果该组件被销毁(因此refCount变为0),则在稍后的组件实例化时将重复HTTP请求

为了解决这个问题,我开始手动缓存结果:

resourceResults: any;

getResource(): Observable<any> {
  if (resourceResults) {
    return Observable.of(this.resourceResults);
  }
  return this.http
    .get('/api/resource')
    .map((res: Response) => res.json())
    .do(x => this.resourceResults = x)
    .publishLast()
    .refCount();
}

这很好用,但我觉得有更多的方法可以做到。

我尝试过使用connect(),但这似乎与我的第一个例子有同样的问题。一旦所有订阅者都取消订阅,使用connect()会导致HTTP请求再次发生

resource$ = this.http
  .get('/api/resource')
  .map((res: Response) => res.json())
  .publishLast()
  .refCount();

getResource(): Observable<any> {
  this.resource$.connect();
  return this.resource$;
}

有什么想法吗?

3 个答案:

答案 0 :(得分:1)

publishReplay / connect有效。这是the working plunker

if ($databaseOject->num_rows > 0) {
        echo "<table><tr><th>id</th><th>apartament</th><th>nume</th><th>apartament</th><th>persoane</th><th>mp</th><th>retim</th><th>incalzire</th><th>apacaldamc</th><th>apacaldalei</th><th>apacaldadif</th><th>aparecemc</th><th>aparecelei</th><th>aparecedif</th><th>curent</th><th>gaz</th><th>administrator</th><th>cheltuieliadministrare</th><th>acoperis</th><th>timp</th></tr>";
// output data of each row
        while($row = $databaseOject->fetch_assoc()) {
            echo "<tr><td>".$row["id"]."</td><td>".$row["apartament"]."</td><td>".$row["nume"]."</td><td>".$row["persoane"]."</td><td>".$row["mp"]."</td><td>".$row["retim"]."</td><td>".$row["incalzire"]."</td><td>".$row["apacaldamc"]."</td><td>".$row["apacaldalei"]."</td><td>".$row["apacaldadif"]."</td><td>".$row["aparecemc"]."</td><td>".$row["aparecelei"]."</td><td>".$row["aparecedif"]."</td><td>".$row["curent"]."</td><td>".$row["gaz"]."</td><td>".$row["administrator"]."</td><td>".$row["cheltuieliadministrare"]."</td><td>".$row["acoperis"]."</td><td>".$row["timp"]."</td></tr>";
    }
        echo "</table>";
    } else {
        echo "0 results";
}

输出:

import {Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {Observable} from "rxjs/Observable";

@Injectable()
export class YourService {
    resource:Observable<any>;

    constructor(private http: Http) {
        this.resource = this.http.get('https://api.github.com/users/karser')
            .map((res: Response) => res.json())
            .do(res => console.log('response', res))
            .publishReplay(1);
        this.resource.connect();
    }
}

更新:RxJS 5.4有shareReplay运算符,显然做同样的事情。见the updated plunkr

Subscribing
response Object {login: "karser", id: 1675033…}
Unscibscribed
Subscribing once again

来自pull request

  

this.http.get('https://api.github.com/users/karser') .map((res: Response) => res.json()) .shareReplay(1); 返回一个可被多播的源的观察者   超过shareReplay。该重播主题在出错时被回收   来源,但不是在完成源。这使得   ReplaySubject非常适合处理缓存AJAX结果之类的事情   它是可以重试的。然而,它的重复行为与分享不同   它不会重复源可观察,而是会重复   来源可观察的价值观。

答案 1 :(得分:0)

首先,如果您想在整个应用程序生命周期内共享此响应,则应将其放入服务中,并确保所有组件共享同一个实例。

理论上你可以使源Observable永远不会完成。更准确地说,您可以使链条永远不会传播完整的信号,但是如果您尝试将此Observable与forkJoin()toArray()等运算符一起使用,则不建议这样做并可能导致意外行为类似的,要求源Observable正确完成。

相反,您可以使用publishReplay(1)来保持源Observable发出的最后一个值refCount()在查询缓存时保持对源Observable的单个订阅,然后take(1)只接受一个值并完成。

const cache = this.http.get('/api/resource')
  .map((res: Response) => res.json())
  .publishReplay(1)
  .refCount()
  .take(1);

因此,您可以一次订阅多个观察者,并且只会执行一个HTTP请求。然后,任何后续订阅都将点击已经具有缓存值的.publishReplay(1),该值立即传播,take(1)立即完成链。因此,不会订阅this.http.get('/api/resource'),因此不会提出任何请求。

答案 2 :(得分:0)

不要使用refCount。手动创建可连接的可观察对象,对其进行连接,订阅以及可连接的ReplaySubject将保留最后发布的值。这样的事情:

let obs$ = Rx.Observable.interval(5000)
  .multicast(new Rx.ReplaySubject(1));

obs$.connect();

// unsuscribe when service is destroyed???
let mainSubecription = obs$.subscribe(x=>console.log(x));

// create subscription (like async pipe)
let subscription;
setTimeout(()=> {
  subscription = obs$.subscribe(x=>console.log('second', x));
}, 15000);


// remove subscription (like async pipe)
setTimeout(()=> {
 subscription.unsubscribe();
}, 40000);

// the main subscription still gets data