Shweta Mandavgane
4 min readOct 10, 2023

Simple method to restore NgRx state after Page reload or F5 and prevent user logout

Imagine building an entire Angular website with NgRx, using login as the core feature. Everything runs smoothly until someone hits F5 or the browser’s Reload button, logging the user out. Complaints about lost unsaved data flood your service center. You discover that the NgRx store resets on refresh, leaving you stuck without a solution. Ever faced this?

If yes, you’ve come to the right place. I will be explaining you the basic reason behind this issue and very simple resolution to it.

Let’s first understand what is happening:
Your Angular application is a single-page application that starts with index.html. All the other HTML components that you write are dynamically loaded and rendered inside index.html. So when you reload the browser, index.html gets reloaded, which, in turn, resets the entire Angular application. Consequently, your NgRx store also initializes or resets to its initial value.

Now, that we understand the basic problem, let’s try to solve it.
Let’s understand NgRx basics using the below diagram from the offical NgRx docs. We are going to refer the example given at :
https://v8.ngrx.io/guide/store

Actions describe unique events that are dispatched from components and services.
State changes are handled by pure functions called
reducers that take the current state and the latest action to compute a new state.
Selectors are pure functions used to select, derive and compose pieces of state.
State is accessed with the
Store, an observable of state and an observer of actions.

Working of NgRx as per Official Website Source: https://v8.ngrx.io/guide/store

As per description above, primary method to update your store is through reducer. There it is! Our solution! We are going to use reducer or metareducers inside NgRx to initialize the store after reload.

In the code snippet found at https://v8.ngrx.io/guide/store, you can see the following import: ‘StoreModule.forRoot({ count: counterReducer })’. This indicates that the ‘counterReducer’ will be invoked whenever any action is performed on the store.
Find out this exact reducer/line in your NgRx code. If there are multiple reducers/ metareducers, you can select the first one in the list.Try adding console logs and check if it is indeed executed every time or there is any conditional execution.
You need to choose the one that executes every single time without conditions. If there is not one, your can write one and add it to the list.
We will be using this reducer in Step 2 of our solution later.

//Copy of code from https://v8.ngrx.io/guide/store for your reference
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
StoreModule.forRoot({ count: counterReducer })
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

Now that we know that the reducer is the place where you can setup your store during refresh, everything now becomes very easy.

Let’s see the steps to accomplish this:
Step 1: We need to save the store state before the component is reloaded. That means as soon as reload event is executed, we need to save the state to session/local storage. Refer the below code. You can copy this code as-is in your app.component.ts — just replace applicationState with your store contents. If your application state consists of multiple classes or sub-states, plan on storing your substate as separate session item.

@HostListener('window:beforeunload', ['$event'])
beforeunloadHandler(event: Event): void {
sessionStorage.setItem('applicationState', JSON.stringify(applicationState));
}

Step 2: Next step is to retrieve this stored state when reload is complete and the app.component.ts is loaded again and NgRx is loaded. This is where the reducer we found earlier will be used.
In the reducer, write the below code lines.
Note: When you store the objects containing Lists or DateTimes which are not a part of plain javacript, everything gets converted to plain javascript objects for example — List gets stored as Object Array, LocalDate/LocalDateTime get stored as javascript Date/DateTime. While retrieving you will have to add extra code to convert them back to the types you wanted to.

export function counterReducer(state, action) {
const application: ApplicationStateClass = JSON.parse(
sessionStorage.getItem('applicationState'));
//now if you have list or LocalDate or any custom classes not part of
// javascript reassign them too. Example as below:
application.someList = List(application.someList);
if(application.someDate){
const dateArray = application.someDate?.split('-');
if(dateArray) {
application.someDate = LocalDate.of(
Number(dateArray[0]),
Number(dateArray[1]), Number(dateArray[2]));
}
}
state = application;
return _counterReducer(state, action);
}

Step 3: Do not forget to add sessionStorage.clear() or clear your localStorage in ngOnInit() of app.component.ts, so that when your refreshed component is loaded, there is nothing in the session storage.

Run your code using npm start. Try refreshing your pages. You are still logged in!

Happy Coding!

If ever you face issues, let me know. I have extensive experience in resolving these issues and might help you solve yours!

Shweta Mandavgane

Computer Science Masters | 10+ Yrs IT Exp | Angular, React, Java, Springboot, Groovy, AWS Expert | Tech Enthusiast