如何多次使用Observable?

时间:2018-01-05 06:09:35

标签: angularjs rxjs

我是Angular和RxJS的新手。我想知道多次使用Observable值的最佳/正确方法是什么。

我的设置: 我有一个组件调用使用REST服务的服务。 服务器返回我想要的结果后

  • 将此结果用于手头的服务
  • 并将结果返回给组件。
// foo.component.ts

onEvent() {
    this.fooService.foo()
        .subscribe((data: FooStatus) => doSomething());
}

// foo.service.ts

private lastResult: FooStatus;

constructor(protected http: HttpClient) {
}

foo(): Observable<FooResult> {
    return this.http.get('/foo')
        .map((data: FooStatus) => {
            this.lastResult = data; // use the data...
            return data;            // ... and simply pass it through
        });
}

多次使用subscribe()将无效,因为请求会多次发送。这是错误的。

目前我使用map()拦截结果。但我对此并不满意,因为我引入了副作用。看起来像是一个代码味道。

我尝试了

foo(onSuccess: (result: FooResult) => void, onFailure: () => void): void {
    ...
}

但是看起来更糟糕的是,我放弃了Observable魔法。而且我不想自己在每个服务方法中编写这些回调。

另一种方式是我考虑在服务中调用subscribe()然后创建一个新的Observable然后我可以返回到组件。但我无法让它工作......似乎也很复杂。

有更优雅的解决方案吗? Observable我确实错过了有用的方法吗?

3 个答案:

答案 0 :(得分:1)

有很多方法可以做到这一点,答案取决于您的使用情况。

此codepen https://codepen.io/mikkel/pen/EowxjK?editors=0011

// interval observer
// click streams from 3 buttons
console.clear()
const startButton = document.querySelector('#start')
const stopButton = document.querySelector('#stop')
const resetButton = document.querySelector('#reset')

const start$ = Rx.Observable.fromEvent(startButton, 'click')
const stop$ = Rx.Observable.fromEvent(stopButton, 'click')
const reset$ = Rx.Observable.fromEvent(resetButton, 'click')

const minutes = document.querySelector('#minutes')
const seconds = document.querySelector('#seconds')
const milliseconds = document.querySelector('#milliseconds')

const toTime = (time) => ({
  milliseconds: Math.floor(time % 100),
  seconds: Math.floor((time/100) % 60),
  minutes: Math.floor(time / 6000)
})

const pad = (number) => number <= 9 ? ('0' + number) : number.toString()

const render = (time) => {
  minutes.innerHTML = pad(time.minutes)
  seconds.innerHTML = pad(time.seconds)
  milliseconds.innerHTML = pad(time.milliseconds)
}

const interval$ = Rx.Observable.interval(10)

const stopOrReset$ = Rx.Observable.merge(
    stop$,
    reset$
)

const pausible$ = interval$
   .takeUntil(stopOrReset$)
const init = 0   
const inc = acc => acc+1
const reset = acc => init

const incOrReset$ = Rx.Observable.merge(
    pausible$.mapTo(inc),
    reset$.mapTo(reset)
)

app$ = start$
      .switchMapTo(incOrReset$)
      .startWith(init)
      .scan((acc, currFunc) => currFunc(acc))
      .map(toTime)
      .subscribe(val => render(val))

您会注意到重置$ observable用于另外两个可观察对象,incOrReset$stopOrReset$

您还可以引入.multicast()运算符,它将明确允许您多次订阅。请参阅此处的说明:https://www.learnrxjs.io/operators/multicasting/

答案 1 :(得分:0)

查看share,它允许您共享对基础源的单个订阅。你可以做类似下面的事情(它没有经过测试,但应该给你一个想法):

private lastResult: FooStatus;
private fooObs: Observable<FooStatus>;
constructor(protected http: HttpClient) {
    this.fooObs =  this.http.get('/foo').share();
    this.fooObs.subscribe(((data: FooStatus) => this.lastResult = data);
}

foo(): Observable<FooResult> {
    return this.fooObs.map(toFooResult);
}

答案 2 :(得分:0)

正如@Mikkel在他的回答中提到的那样,方法很酷,但你可以使用Observable中的以下方法来实现这一目标。

这些重要的方法是publishReplay和refCount,

使用它们,如下所示,

private lastResult: FooStatus;
private myCachedObservable : Observble<FooResult> = null;

constructor(protected http: HttpClient) {
}

foo(): Observable<FooResult> {

    if(!myCachedObservable){
       this.myCachedObservable = this.http.get('/foo')
        .map((data: FooStatus) => {
            this.lastResult = data; // use the data...
            return data;            // ... and simply pass it through
        })
       .publishReplay(1)
       .refCount();
       return this.myObsResponse;
    } else{
    return this.myObsResponse;
    }
}

现在,在您的组件中,只需订阅从上述方法返回的observable并注意网络请求,您将看到只有一个网络请求正在进行此http调用。