Detecting Online Connections Using Angular 7 and NgRx

by | Dec 3, 2018

If you’ve ever seen the Gmail website say “Not connected, retrying in 3s” and wondered how they detected that it was offline (not connected to the internet), this post is for you!   Using Angular 7 and NgRx, I plan to show you how to detect online connection status with a simple effect and an “isOnline” property in state.

Note that there is software called Offline.js that does this for you but I found that it wasn’t 100% reliable in an Angular application.  Sometimes it said it was online but it was actually offline.  Also, it didn’t handle the retry of requests at all.  It may be that the interceptor that I had interfered with it but I’m not sure.

The code for this example is available on github.

 

Initialize the Project Using Angular 7 CLI

The first thing I do is create a project using the Angular 7 CLI and add NgRx using my previous post here.  I don’t want to make the post about initializing a project so if you have questions, see the code on github.

 

Add the NgRx Code

 

Actions

These are the actions I’ll need for NgRx:

import { Action } from "@ngrx/store";

export enum NetworkActionTypes {
  StartOnlineOfflineCheck = "[Network] StartOnlineOfflineCheck",
  SetIsOnline = "[Network] SetIsOnline"
}

export class StartOnlineOfflineCheck implements Action {
  readonly type = NetworkActionTypes.StartOnlineOfflineCheck;
}

export class SetIsOnline implements Action {
  readonly type = NetworkActionTypes.SetIsOnline;

  constructor(public payload: boolean) {}
}

export type NetworkActions = StartOnlineOfflineCheck | SetIsOnline;
  • StartOnlineOfflineCheck is needed to initiate the listening of window events (online/offline)
  • SetIsOnline will update the store with the new value of isOnline to true or false

Reducers

This “network” reducer will handle the SetIsOnline action:

import * as networkActions from "../actions/network.actions";

export interface State {
  isOnline: boolean;
}

export const initialState: State = {
  isOnline: navigator.onLine
};

export function reducer(
  state = initialState,
  action: networkActions.NetworkActions
): State {
  switch (action.type) {
    case networkActions.NetworkActionTypes.SetIsOnline:
      return handleIsOnline(state, action);

    default:
      return state;
  }
}

function handleIsOnline(
  state: State,
  action: networkActions.SetIsOnline
): State {
  return {
    ...state,
    isOnline: action.payload
  };
}

export const getIsOnline = (state: State) => state.isOnline;
  • Note that I’m defaulting the “isOnline” value to navigator.onLine for an accurate value initially, see below for more about navigator.onLine

This is the main reducer:

import {
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
  MetaReducer
} from "@ngrx/store";

import { environment } from "../../../environments/environment";
import * as fromNetwork from "./network.reducer";

export interface State {
  network: fromNetwork.State;
}

export const reducers: ActionReducerMap<State> = {
  network: fromNetwork.reducer
};

export const metaReducers: MetaReducer<State>[] = !environment.production
  ? []
  : [];

export const getNetworkState = createFeatureSelector<fromNetwork.State>(
  "network"
);

export const getIsOnline = createSelector(
  getNetworkState,
  fromNetwork.getIsOnline
);
  • The getIsOnline selector will be the way we access the “isOnline” slice of state in the app.component

Effect

The meat of the code lies in the effect I’m about to add.  It is code lifted from a stack overflow post.

import { SetIsOnline } from "./../actions/network.actions";
import { Observable, merge, of, fromEvent } from "rxjs";
import { switchMap, mapTo, map } from "rxjs/operators";
import { Injectable } from "@angular/core";
import { Actions, Effect, ofType } from "@ngrx/effects";
import { Action } from "@ngrx/store";

import * as networkActions from "../actions/network.actions";

@Injectable()
export class NetworkEffects {
  constructor(private actions$: Actions) {}

  @Effect()
  startOnlineOfflineCheck$: Observable<Action> = this.actions$.pipe(
    ofType(networkActions.NetworkActionTypes.StartOnlineOfflineCheck),
    switchMap(() => {
      return merge(
        of(navigator.onLine),
        fromEvent(window, "online").pipe(mapTo(true)),
        fromEvent(window, "offline").pipe(mapTo(false))
      );
    }),
    map(isOnline => {
      return new SetIsOnline(isOnline);
    })
  );
}
  • The “merge” command will take the 3 observables and whenever they emit, it will create an action “SetIsOnline” which will update the state with the new value

Add the User Interface

In app.component, I’ll add the following code to get the NgRx “isOnline” value and start the listening in the NetworkEffect:

import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";

import * as fromRoot from "./store/reducers";
import { StartOnlineOfflineCheck } from "./store/actions/network.actions";
import { Observable } from "rxjs";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
  isOnline$: Observable<boolean>;

  constructor(private store: Store<fromRoot.State>) {}

  ngOnInit(): void {
    this.isOnline$ = this.store.select(fromRoot.getIsOnline);

    this.store.dispatch(new StartOnlineOfflineCheck());
  }
}

The HTML will look like this:

<h1>Detect Online/Offline</h1>
<h2>Online: {{isOnline$ | async}}</h2>

Output

The output should look as follows for online/offline states:

On Chrome, you can test this by hitting F12 to get into developer tools and going to the Network tab.  Look for the “Offline” checkbox and click it on and off to see it work.  Unfortunately, IE has no similar checkbox to test it with, that was removed in IE11.

 

Conclusion

As you can see from this article, it isn’t hard to set up in NgRx an “isOnline” state property that updates whenever the internet connection goes down or up.  What you do with that boolean value is up to you.  Most likely you would give the user some sort of indication that it is up or down at a minimum.