Pionas
Pionas

Reputation: 346

Angular 7 used NGRX and save token in localStorage

In my Angular application i used NGRX store and i have some problem with saved user token. Sometimes i reload my page and lost everything.

In app.component.ts implement OnInit and add there:

this.store.select('auth').subscribe(event => {
  if (event.token) {
    window.localStorage.setItem('token', JSON.stringify(event.token));
  }
});

if (window.localStorage.getItem('token')) {
  const token = JSON.parse(window.localStorage.getItem('token'));
  this.store.dispatch(new AuthActions.SetToken(token));
}

And created Effect:

@Effect()
this.actions$.pipe(
    ofType<AuthActions.TrySignin> (
        AuthActions.AuthActionTypes.TRY_SIGNIN
    ),
        switchMap(action => {
            return this.httpClient.put('http://localhost:8080/api/signin', {
                username: action.payload.username,
                password: action.payload.password
            }, {
                    observe: 'body',
                    responseType: 'text'
                }).pipe(
                    map(
                        token => {
                            this.router.navigate(['/']);
                            return new AuthActions.SetToken(token);
                        }
                    ),
                    catchError(error => {
                        return of(new AuthActions.AuthFailed(error));
                    }
                    )
                );

        }
        )
);

It is correct?

Upvotes: 4

Views: 10973

Answers (4)

Lars
Lars

Reputation: 776

By default, your application state gets reset when you refresh the page.

What you need to do is, save your 'auth' state to a persistant storage, like localstorage/sessionstorage.

And restore the state from localstorage/sessionstorage on startup.

I've created a library to handle this for you easily: https://github.com/larscom/ngrx-store-storagesync

  1. Run:

npm install --save @larscom/ngrx-store-storagesync

  1. Configure the meta reducer

The configuration would be something like the following for your setup

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule, ActionReducerMap, ActionReducer, MetaReducer } from '@ngrx/store';
import { storageSync } from '@larscom/ngrx-store-storagesync';
import * as fromAuth from './auth/reducer';

export const reducers: ActionReducerMap<ISomeState> = { 
  auth: fromAuth.reducer
};

export function storageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return storageSync<ISomeState>({
    features: [
      // saves the auth state to sessionStorage
      { stateKey: 'auth' }
    ],     
    storage: window.sessionStorage
  })(reducer);
}

const metaReducers: Array<MetaReducer<any, any>> = [storageSyncReducer];

@NgModule({
  imports: [BrowserModule, StoreModule.forRoot(reducers, { metaReducers })]
})
export class AppModule {}

That's it, if you reload the page, the state will restore from the sessionStorage (in this case)

Upvotes: 3

Sachin S
Sachin S

Reputation: 62

@Injectable()
export class FormEffects {
  constructor(
    private actions$: Actions<Action>,
    private localStorageService: LocalStorageService
  ) {}

  @Effect({ dispatch: false })
  persistForm = this.actions$.pipe(
    ofType<ActionFormUpdate>(FormActionTypes.UPDATE),
    tap(action =>
      this.localStorageService.setItem(FORM_KEY, { form: action.payload.form })
    )
  );
}

Upvotes: 0

timdeschryver
timdeschryver

Reputation: 15505

I would suggest you to not do this inside your components. They will become harder to test, plus you could end up with the same code in different components.

Instead use you can do this inside effects as Maciej suggested, for another example see https://github.com/tomastrajan/angular-ngrx-material-starter/blob/master/src/app/examples/form/form.effects.ts#L20

But personally, I like to use a meta-reducer for this - see https://github.com/timdeschryver/ngrx-family-grocery-list/blob/master/src/app/groceries/reducers/groceries.reducer.ts#L165

For example:

export function persistStateReducer(_reducer: ActionReducer<State>) {
  const localStorageKey = '__auth';
  return (state: State | undefined, action: Action) => {
    if (state === undefined) {
      const persisted = localStorage.getItem(localStorageKey);
      return persisted ? JSON.parse(persisted) : _reducer(state, action);
    }

    const nextState = _reducer(state, action);
    localStorage.setItem(localStorageKey, JSON.stringify(nextState));
    return nextState;
  };
}

Upvotes: 4

Maciej Wojcik
Maciej Wojcik

Reputation: 2171

why not save token into localstorage directly in effect?

This approach has a weakness that you have to remember to subscribe to store and save token whenever it appears. Second bad thing abouth this is, whenever auth state emits data your's subscribe will save token even if there was correct before.

Getting token code smells good.

Upvotes: 1

Related Questions