Detecting Online Connections Using Angular 7 and NgRx
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
- The purpose of the “of(navigator.onLine)” observable is to get an initial value
- If the window.online event fires, set isOnline to “true”
- If the window.offline event fires, set isOnline to “false”
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.