Reputation: 5988
I am using the following libraries in the relevant application: Angular 4.x, ngrx 4.x, rxjs 5.4.x
I have an api that I need to poll every 5 minutes. The user is also able to manually refresh the data. That data is stored in an ngrx store. I am using ngrx effects so the data is retrieved by dispatching an action of type 'FETCH'.
I want to setup a rxjs stream where it will dispatch the 'FETCH' action to the ngrx store. It will be a sliding 5 minute timer that resets when the user manually updates the store. The stream should initially emit a value when subscribed.
I'm not sure how I can reset the timer. In plain javascript I would do something like the following:
console.clear();
let timer;
let counter = 0;
function fetch() {
console.log('fetch', counter++);
poll();
}
function poll() {
if (timer != null) {
window.clearTimeout(timer);
}
timer = window.setTimeout(() => {
console.log('poll');
fetch();
}, 5000);
}
function manualGet() {
console.log('manual');
fetch();
}
fetch();
<button onClick="manualGet()">Get Data</button>
Question: How do I emit on an interval that is reset when another stream emits like the example again?
Upvotes: 4
Views: 2049
Reputation: 20249
My data collection was in the form of an observable that would make the request when subscribed/resubscribed so I adjusted Ingo Bürk's answer for my needs:
// observable that does the requesting goes here
const dataCollection$ = defer(()=>of(this.count++));
const clickEvent$ = fromEvent(button, 'click');
const clickOrWait$ = clickEvent$.pipe(
// startWith null to start the timer before button is clicked
startWith(null),
// emit immediately so clicking button immediately emits
switchMap(()=>timer(0,5000)),
// skip the first immediate emit so we don't infinitely repeat
skip(1),
);
const result$ = dataCollection$.pipe(
repeat({delay:()=>clickOrWait$}),
);
result$.subscribe((res)=>console.log("value output: "+res));
My particular use case was to use this polling to handle data in an angular component using async/without subscribe. example:
@Component({
imports: [
AsyncPipe,
],
selector: 'app-root',
standalone: true,
template: "<p>count: {{result$ | async}}</p> <button #button>Click me!</button>"
})
export class AppComponent implements AfterViewInit {
@ViewChild('button') button: any;
result$: Observable<number> = EMPTY;
count = 1
public ngAfterViewInit() {
// HttpClient call could go here
const dataCollection$ = defer(()=>of(this.count++));
const clickEvent$ = fromEvent(this.button.nativeElement, 'click');
const clickOrRepeat$ = clickEvent$.pipe(
startWith(null),
switchMap(()=>timer(0,5000)),
skip(1)
);
this.result$ = dataCollection$.pipe(
repeat({delay:()=> clickOrRepeat$}),
);
}
}
Stackblitz: https://stackblitz.com/edit/stackblitz-starters-5beot4?file=src%2Fmain.ts
Upvotes: 0
Reputation: 20033
You want two components to your stream – a timer and some user input. So let's start with the user input. I'll assume some button which can be clicked:
const userInput$ = Observable.fromEvent(button, 'click');
Now we want to start a timer which resets everytime userInput$
emits. We can do that using
userInput$.switchMap(() => Observable.timer(0, 5000));
However, we also want this stream to start without the user having to first click the button. But that's also not a problem:
userInput$.startWith(null);
Now we put it all together:
Observable.fromEvent(button, 'click')
.startWith(null)
.switchMap(() => Observable.timer(0, 5000))
.subscribe(() => dispatchFetch());
Note that I am following your examples of using a 5 second timer, not a 5 minute timer (which you mentioned in the question.)
Upvotes: 7
Reputation: 5988
After writing it out in vanilla JS I realized that the source of the timer should be the data. I was struggling to figure out what the source would be. Clearly it couldn't be the timer since I needed to reset it.
I'm open to better options but here is how I solved it:
console.clear();
let counter = 0;
const data = new Rx.BehaviorSubject(null);
function fetch() {
data.next(counter++);
}
function manualGet() {
console.log('manual');
fetch();
}
// setup poll
data.switchMap(() => Rx.Observable.timer(5000))
.subscribe(() => {
console.log('poll');
fetch();
});
// subscribe to the data
data.filter(x => x != null).
subscribe(x => { console.log('data', x); });
// do the first fetch
fetch();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.5/Rx.min.js"></script>
<button onClick="manualGet()">Get Data</button>
With ngrx I am listening for the success action related to the fetch event.
Upvotes: 0