Auto Refresh with Manual Override

June 2nd 2017 ReactiveX Ionic 2/3

Let's create an Ionic 2 page that auto-refreshes at regular intervals (e.g. 5 seconds) with a twist: there's also a button for manual refresh which triggers an immediate refresh, but also ensures the standard interval between the manual refresh and the next auto-refresh (i.e. next auto-refresh will happen 5 seconds after the manual one).

Click postpones next auto-refresh

We'll implement it in two different ways: with standard JavaScript functions and with RxJS.

Classic Approach

We'll use Ionic lifecycle events to start and stop refreshing when entering and exiting the page respectively:

ionViewDidEnter() {
  this.initRefresh();
}

ionViewDidLeave() {
  this.stopRefresh();
}

To schedule auto-refresh with standard JavaScript, we'll use the setInterval method:

private timeoutId: number;

private initRefresh() {
  this.refresh();
  this.timeoutId = setInterval(() => this.refresh(), 5 * 1000);
}

We manually invoke refresh first, because setInterval will only call it after the interval passes once. We'll need the timeoutId value to stop the schedule:

private stopRefresh() {
  clearInterval(this.timeoutId);
}

To implement manual refresh, we'll simply reinitialize the schedule:

manualRefresh() {
  this.stopRefresh();
  this.initRefresh();
}

This will first prevent any further calls from the existing schedule, then immediately invoke refresh and reschedule it with unchanged interval. We can bind the method to a refresh button:

<button ion-button (click)="manualRefresh()">Refresh</button>

For testing purposes we can only log refresh calls to console (code depends on Moment.js):

import * as moment from 'moment';

private refresh() {
  console.log(`Refresh at ${moment().format('LTS')}`);
}

This could result in the following output to the console:

Refresh at 3:24:00 PM
Refresh at 3:24:05 PM
Refresh at 3:24:07 PM
Refresh at 3:24:12 PM

Reactive Approach

With Angular heavily depending on RxJS, it becomes tempting to use the same library for this functionality as well. We'll create an observable that will emit every time we want to refresh the trigger. Let's start by handling the manual clicks first:

import { Subject } from 'rxjs/subject';
import { Observable } from 'rxjs/Rx';

private refreshSubject: Subject<void>;
private refreshObservable: Observable<void>;

ionViewDidEnter() {
  this.refreshSubject = new Subject<void>();
  this.refreshObservable = this.refreshSubject.asObservable();
}

manualRefresh() {
  this.refreshSubject.next();
}

Instead of only emitting once per click, we will start a new timer on every click and abort the previous one, using the switchMap operator. Unlike setInterval, timer allows us to emit the first value immediately, no matter the interval duration:

ionViewDidEnter() {
  this.refreshSubject = new Subject<void>();
  let manualObservable = this.refreshSubject.asObservable();
  this.refreshObservable = manualObservable.switchMap<void, void>(val =>
    Observable.timer(0, 5 * 1000)
  );
  this.refreshSubject.next();
}

We need to simulate the first click on page load, otherwise auto-refresh wouldn't start until the user triggered it manually for the first time.

The observable is now ready. We can subscribe to it when entering the page and unsubscribe from it when leaving the page:

import { Subscription } from 'rxjs/subscription';

ionViewDidEnter() {
  this.refreshSubject = new Subject<void>();
  let manualObservable = this.refreshSubject.asObservable();
  this.refreshObservable = manualObservable.switchMap<void, void>(val =>
    Observable.timer(0, 5 * 1000)
  );
  this.refreshSubscription = this.refreshObservable.subscribe(() => this.refresh());
  this.refreshSubject.next();
}

ionViewDidLeave() {
  this.refreshSubscription.unsubscribe();
}

The reactive implementation is certainly more difficult to understand for someone without previous RxJS experience. However, due to its declarative nature, it will be easier to maintain and modify if the logic becomes more complex.

Copyright
Creative Commons License