Search code examples
angularngrxngrx-storengrx-effects

I am unable to get the correct value from the ngrx store in a simple angular app


I want that when an API call returns a user the state is updated and the home.component.html shows the new value. I am also using ngrx devtools and I can see the state change, but the html does not update. This is what I have.

home.component.ts

export class HomeComponent implements OnInit {
  meUser$ = this.store.select(selectMeUser);

  constructor(private store: Store<AppState>) { }

  ngOnInit(): void {
    this.store.dispatch(loadMeUser());
  }

home.component.html

<p *ngIf="meUser$ | async as me">
   Hi, {{ me.firstname }}
</p>

user.actions.ts

export const loadMeUser = createAction('[User] Load Me User');
export const loadMeUserSuccess = createAction(
    '[User] Load Me User Success', 
    props<{ user: User }>()
);
export const loadMeUserFailure = createAction(
    '[User] Load Me User Failure',
    props<{ error: string }>()
);

user.reducers.ts

export interface UserState {
    meUser: User | null; 
    status: string;
    error: string;
}

export const initialstate: UserState = {
    meUser: null,
    status: 'pending',
    error: ''
}

export const userReducer = createReducer(
    initialstate, 
    on(loadMeUser, (state) => ({ ...state, status: 'loading' as StatusType })),
    on(loadMeUserSuccess, (state, { user }) => ({
        ...state, 
        meUser: user,
        error: '',
        status: 'success'
    })),
    on(loadMeUserFailure, (state, { error }) => ({
        ...state, 
        error: error, 
        status: 'error'
    }))
)

user.selectors.ts

export const selectUser = (state: AppState) => state.user;
export const selectMeUser = createSelector(
    selectUser,
    (state: UserState) => state?.meUser
)

user.effects.ts

@Injectable()
export class UserEffects {
    constructor(
        private actions$: Actions,
        private store: Store<AppState>,
        private userService: UserService
    ) {}

    loadMeUser$ = createEffect(() => 
        this.actions$.pipe(
            ofType(loadMeUser),
            exhaustMap(() => {
                return this.userService.getMeUser().pipe(
                    map((data) => {
                        return loadMeUserSuccess({ user: data });
                    }),
                    catchError((error) => of(loadMeUserFailure({ error: error })))
                )
            })
        )
    )
}

In the devtools I can see the state result is success and if I add a console.log inside the effect the data is correct, if I change it to meUser$ = this.store.select(selectMeUser).subscribe((data) => console.log(data)) I get undefinied.

app.module.ts

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...,
    StoreModule.forRoot({ users: userReducer }),
    StoreDevtoolsModule.instrument({
      maxAge: 25,
      trace: true,
      traceLimit: 25
    }),
    EffectsModule.forRoot([UserEffects])
  ],
  providers: [
    ...
  ],
  bootstrap: [AppComponent, ...]
})
export class AppModule { }

What am I missing?


Solution

  • There's a typo

    export const selectUser = (state: AppState) => state.user;
    

    user should be userS:

    export const selectUser = (state: AppState) => state.users;
    

    The reason is that it's configured as users while configuring the store:

    StoreModule.forRoot({ users: userReducer }),
    

    To prevent this, you can make use of feature keys https://ngrx.io/guide/store/selectors#selecting-feature-states or build your state with feature creators https://ngrx.io/guide/store/feature-creators