我是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
我确实错过了有用的方法吗?
答案 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)
这些重要的方法是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调用。