Search code examples
javascriptangulartypescriptngrxngrx-store

NgRx store - Selectors are not working for a root global store


I am very new to ngrx, and just trying to get my head around it, and get something working.

I have added ngrx (version 8.3) to my application.

I wish to have a few things in a root state (if possible), and then have separate states for each of my features. I started at the root state, but the selector I have is just never notified.

I have the following actions...

    // actions
    import { createAction, union } from '@ngrx/store';
    import { SecurityTokensState } from './app.reducer';

    export const setUrl = createAction(
      '[App url] ',
      (payload: string) => ({ payload })
    );

    export const setTokens = createAction(
      '[App setSecurityTokens] ',
      (payload: SecurityTokensState) => ({ payload })
    );


    export const actions = union({
      setUrl,
      setTokens
    });

    export type ActionsUnion = typeof actions;

And the following reducers..

    import * as rootActions from './app.actions';
    import { createReducer, on } from '@ngrx/store';

    /** Top level state */
    export interface State {
      /** State to do with Auth */
      tokens: SecurityTokensState;

      /** General / root app state (eg configuration) */
      app: AppState
    }

    /** App wide general state */
    export interface AppState {
      url: string;
      //extraLogging: boolean;
      //offlineExpiry: number; 
      //offlineTime: number;
    }

    /** Security token state */
    export interface SecurityTokensState {
      token: string,
      refreshToken: string;
    }

    const initialState: State = { tokens: undefined, app: { url: ""}  };

    export function rootReducer(state: State, action: rootActions.ActionsUnion): State {
      return reducer(state, action);
    }

    const reducer = createReducer(
      initialState,
      on(rootActions.setTokens,
        (state, { payload }) => ({ ...state, tokens: payload })
      ),
      on(rootActions.setUrl,
        (state, { payload }) => ({ ...state, app: updateUrl(state, payload)}))
    )

    /**  Helper to update the nested url */
    const updateUrl = (state: State, payload: string): AppState => {      
      const updatedApp = { ...state.app };
      updatedApp.url = payload;
      return updatedApp;
    }

And I created the following selector...

import { createFeatureSelector, createSelector } from "@ngrx/store";
import { AppState } from './app.reducer';

const getAppState = createFeatureSelector<AppState>('app');

export const getUrl = createSelector(
  getAppState,
  state => state.url
  );

In app.module, I have the following...

StoreModule.forRoot(rootReducer),

Now, in a component, I have

   import * as rootSelectors from '../state/app.selectors';
    ....

    public onUrlBlur(ev : any): void {   
       let val = ev.target.value;
       this.store.dispatch(rootActions.setUrl(val));   
      }

And I have the code to subscribe to the update

 this.subs.sink = 
    this.store.pipe(select(rootSelectors.getUrl)).subscribe(url => {
    this.urlEntered = url        
  });

Lastly, just as a placeholder, in one of my feature modules, I have added...

   StoreModule.forFeature('myfeature1', {})

I see the blur function being called, and in the redux dev tools I see

enter image description here

But for the state, all I see is

enter image description here

And the observable this.store.pipe(select(rootSelectors.getUrl)).subscribe(url => {` never fires

So my root state does just not seem to be in there, and I really can't see what I have done wrong.

Where have I messed up here?

Update

Have added a very similar example (with the same problem) here

When running, and go to the console, can see the following...

selector.ts:610 The feature name "appRoot" does not exist in the state, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('appRoot', ...) or StoreModule.forFeature('appRoot', ...). If the

What I don't understand if how to use the StoreModule.forRoot(rootReducer ),

In the error, it suggests using a string... eg StoreModule.forRoot('app', rootReducer ), but this gives a syntax error.

If I do the following:

 StoreModule.forRoot({appRoot: rootReducer} ),

I get a nested state:

enter image description here

But just passing the reducer:

StoreModule.forRoot(rootReducer ),

I get no state:

enter image description here

All the examples I see use just feature states, but I have a number of settings that are just application wide, and not in a feature module.

Also, as this state is not in a feature module, I am not sure if I should be using the createFeatureSelector:

const getAppState = createFeatureSelector<AppState>('appRoot');

Solution

  • The StoreModule.forRoot() function expects a ActionReducerMap, not a reducer function.

    See the docs for more info.

    To resolve the nested state problem, in your case this would look as follows:

    StoreModule.forRoot({
      tokens: tokensReducer,
      appRoot: appRootReducer
    })
    

    or you can do:

    StoreModule.forRoot({}),
    StoreModule.forFeate('appRoot', appRootReducer)