无法在两个订阅者之间共享http请求



我有一个应用程序,它每10秒就会得到一个随机笑话。我使用rxjs中的间隔运算符进行倒计时,10秒后我发出http请求以获得一个随机笑话。问题是我使用异步管道在模板中显示笑话两次。我知道这样做将创建两个新订阅,并发出两次http请求。为了处理这个问题,我尝试使用shareReplay或publish+refCount,这样只会发出一个http请求。但它调用了两次getRandomJoke服务函数。如何解决此问题。

代码和演示-https://stackblitz.com/edit/angular-ivy-xvtsrw

random-joke.component.html

<div class="joke-title">{{joke | async}}</div>
<div class="joke-title">{{joke | async}}</div>
<div class="footer">Next Joke in : {{countdown | async}}</div>

random-joke.component.ts

export class RandomJokesComponent implements OnInit {
joke: Observable<any>;
restartTimer = new Subject();
restartInterval = new Subject();
intervalForJokes: Observable<any>;
countdown: any;
countDownTill = 10;
constructor(private fetchService: FetchUtilService) {}
ngOnInit() {
this.startTimer();
this.getJokesInInterval();
}
getJokesInInterval() {
this.restartInterval.next();
let intervalForJokes = interval(10000);
this.joke = intervalForJokes.pipe(
tap(()=> console.log('getting interval')),
takeUntil(this.restartInterval),
publish(),
refCount(),
switchMap(() =>
this.fetchService.getRandomJoke().pipe(
tap(() => this.startTimer()))
)
);
}
startTimer() {
this.restartTimer.next();
this.countdown = timer(0, 1000).pipe(
takeUntil(this.restartTimer),
map(i => this.countDownTill - i)
);
}
}

提取服务

getRandomJoke(): Observable<any> {
console.log('getting');
return this.http.get(this.apiUrl).pipe(
tap(result => console.log(result)),
// shareReplay(),
publish(),
refCount(),
map((result: any) => result && result.value.joke)
);
}

您的实现过于复杂。使用shareReplay,并使用startWith(0)立即开始获取笑话。FetchUtilService.getRandomJoke可以简化为http请求逻辑+错误处理。

ngOnInit() {
this.startTimer();
this.joke = interval(10000).pipe(
startWith(0),
switchMap(() =>
this.fetchService.getRandomJoke().pipe(tap(() => this.startTimer()))
),
shareReplay(),
);
}
@Injectable()
export class FetchUtilService {
private apiUrl = 'https://api.icndb.com/jokes/random';
constructor(private http: HttpClient) {}
getRandomJoke(): Observable<any> {
console.log('getRandomJoke invocation');
return this.http
.get(this.apiUrl)
.pipe(map((result: any) => result && result.value.joke));
}
}

演示:https://stackblitz.com/edit/angular-ivy-qpugke

奖励:鼠标点击时重新启动计时器

ngOnInit(): void {
const click$ = fromEvent(document, 'click').pipe(
debounceTime(400),
startWith(null), // init timer
);
this.timer$ = click$.pipe(
switchMap(() => interval(1000)), // kills interval on click and starts new one
);
const readyToFetch$ = this.timer$.pipe(
map((seconds) => seconds > 0 && seconds % 10 === 0), // fetch every 10 seconds, skip 0 to avoid joke's fetching on timer restart
);
this.joke$ = readyToFetch$.pipe(
startWith(true), // initial fetch
filter((readyToFetch) => readyToFetch),
switchMap(() => this.fetchService.getRandomJoke()),
shareReplay(),
);
}

演示:https://stackblitz.com/edit/angular-ivy-uak1sx

如果你想使用http调用的相同响应,你最好考虑使用Subjects。

机制可以是以下。

首先,您创建一个服务,该服务公开一个方法(例如,callRemoteService(来激发http调用,以及一个Observable(例如httpResp(来发出响应。httpResp$是使用Subject的asObservable()方法将私有Subject(例如_resp$(转换为Observable而获得的。

然后,当链接到http调用的Obverable发出时,您也会发出内部Subject_resp$

多个客户端可以订阅公共httpResp$Observable,并在http调用返回时收到相同的通知。

代码可能看起来像这个

private _resp$ = new Subject<any>();
public httpResp$ = this._resp$.asObservable();
public callRemoteService() {
return this.http.get(this.apiUrl).pipe(
tap({
next: (data) => this._resp$.next(data),
error: (err) => this._resp$.error(err)
})
).subscribe()
}

你可能会在这篇文章中找到更多的灵感。它谈到了React,但其中所示的机制也可以应用于Angular。

最新更新