I have an API from which I'm getting json array of events. In the json, I'm getting a date for which I am showing a timer using RxJS timer. When one of the timer is finished, I'm calling the API again to get latest array. Issue is when I'm calling the API after timer is finished, that API is being called 4 times instead of 1 time.
I also tried keeping the subs
variable in another array and unsubscribing all of them in loop. But on doing this, getEvents()
method was being called continuously without stopping. So if I can know what is the issue in current code that would be great.
//helper.service.ts
getCounter() {
return timer(0, 1000);
}
getEvents() {
const url = '/app/events'
this.auth.get(url).then(x => {
this.eventArr = x?.data.events
this.showTimer()
}).catch(err=>{
console.log(err)
})
}
async showTimer() {
let unsubscribe = new Subject()
for (let i = 0; i < this.eventArr.length; i ) {
let x = this.eventArr[i]
const dateFuture = x?.inaugurateDt * 1000
const now = new Date().getTime()
const t = dateFuture - now
x.timer = Math.floor(t / 1000)
const subs = this.getCounter().pipe(takeUntil(unsubscribe)).subscribe(() => {
x.timer--;
if (x.timer <= 0) {
this.unSubscribeCoundown(unsubscribe)
this.getEvents()
}
});
}
}
}
CodePudding user response:
when you call getEvents after x.timer =0 then again showtimer function is calling and this loop continues for infinite time.
you have to mention some condition in the getEvents function so that you can check whether the showtime function should be called or not
CodePudding user response:
I created a working example that:
- includes multiple simultaneous countdowns
- unsubscribes all countdowns, whenever the first countdown reaches 0
- execute
getEvents()
exactly once after each global unsubscription - mocks the backend-API with the
getValuesFromMockAPI()
-method (for testing-purposes)
Stackblitz example with additional features
Apart from the example below, I created an extended example on stackblitz where the countdowns are displayed on the UI and unsubscribing 'onDestroy()' is added.
The model-class
In order not to divert the focus from the essential, I assumed that there is just a simple EventDto
-model, that contains a property with the number of seconds for the countdown:
class EventDto {
timeInSec = 0; // number of seconds for countdown
}
The main logic
For a better understanding please note the comments in the code.
unsubscribeAllCountdowns = new Subject<void>();
ngOnInit(): void {
this.getEvents();
}
getEvents() {
console.log('*** Get Events ***');
const url = '/app/events'
this.getValuesFromMockAPI(url)
.then(arr => this.showTimers(arr))
.catch(err=> console.log(err));
}
showTimers(eventArray: EventDto[]) {
// Create an array of countdown-observables:
const eventsAsObservables = eventArray.map((x, i) => this.getCountdown(i, x.timeInSec));
// Execute observables until all of them are terminated resp. unsubscribed:
forkJoin(eventsAsObservables).pipe(
// Unsubscribe this pipe after one execution:
first(),
// After all observables are done, execute 'getEvents' exactly once:
tap(() => this.getEvents())
)
.subscribe();
}
getCountdown(eventIndex: number, timeInSec: number) {
// First emission after 1000ms, then one emission every 1000ms:
return timer(1000, 1000).pipe(
// Do the countdown by decrementing the previous value:
scan((acc, curr) => acc - 1, timeInSec),
tap(timeRemainingInSec => console.log(`Event-Type [${eventIndex}]: ${timeRemainingInSec} sec`)),
// If timer <= 0 is reached: Unsubscribe all running counters:
tap(timeRemainingInSec => timeRemainingInSec > 0 || this.unsubscribeAllCountdowns.next()),
// Unsubscribe when the 'unsubscribe'-subject is invoked:
takeUntil(this.unsubscribeAllCountdowns)
);
}
/* Mock backend API: */
getValuesFromMockAPI(url: string): Promise<EventDto[]> {
return firstValueFrom(of([{ timeInSec: 5 } as EventDto, { timeInSec: 19 }, { timeInSec: 30 }, { timeInSec: 44 }]));
}