Angular NgRx subscribing to state leads to undefined

153 views Asked by At

I set up a basic NgRx example in Angular 17.

this is my app.config.ts where I register my reducer in my store

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes), provideStore({ counterReducer} ), provideStoreDevtools({
    maxAge: 25, // Retains last 25 states
    logOnly: !isDevMode(), // Restrict extension to log-only mode
    autoPause: true, // Pauses recording actions and state changes when the extension window is not open
    trace: false, //  If set to true, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code
    traceLimit: 75, // maximum stack trace frames to be stored (in case trace option was provided as true)
  }),]
};

app.state.ts

export interface AppState {
  value: number;
}

counter.reducer.ts

import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';
import { AppState } from "./app.state";

export const initialState : AppState = {
  value: 0
};

export const counterReducer = createReducer(
  initialState,
  on(increment, (state) => ({ ...state, value: state.value + 1 })),
  on(decrement, (state) => ({ ...state, value: state.value - 1 })),
  on(reset, () => initialState)
);

export const selectCounterValue = (state: AppState) => state.value;

my-counter.component.html: This is where I want to show my current value of my State. I am aware that I dont have to subscribe to the count when using async pipe. Just for debugging cases I want to print to console

<button (click)="increment()">Increment</button>

<div>Current Count: {{ (count$ | async ) }}</div>

my-counter.component.ts This where i want to subscribe to my AppState.value. Basically i want to print it out when the state changes.

import {Component, OnDestroy, OnInit} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {Store} from "@ngrx/store";
import {increment} from "../store/counter.actions";
import {AppState} from "../store/app.state";
import {state} from "@angular/animations";
import {CommonModule} from "@angular/common";
import {selectCounterValue} from "../store/counter.reducer";

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  imports: [CommonModule],
  standalone: true,
})
export class MyCounterComponent implements OnInit, OnDestroy{
  count$: Observable<number>;
  subscription: Subscription | undefined;

  constructor(private store: Store<AppState>) {
    this.count$= store.select(selectCounterValue);
  }

  increment() {
    this.store.dispatch(increment());
  }

  ngOnInit(): void {
    this.subscription = this.count$.subscribe(value => {
      console.log(value);
    });
  }

  ngOnDestroy(): void {
    this.subscription?.unsubscribe();
  }

}

counter.reducer.ts:

import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';
import { AppState } from "./app.state";

export const initialState : AppState = {
  value: 0
};

export const counterReducer = createReducer(
  initialState,
  on(increment, (state) => ({ ...state, value: state.value + 1 })),
  on(decrement, (state) => ({ ...state, value: state.value - 1 })),
  on(reset, () => initialState)
);

export const selectCounterValue = (state: AppState) => state.value;

As you can see my console.log of my callback value is undefined devtools1

However the value in my store is updated correctly when clicking on my increment button devtools

I tried showing my entire state like this:

this.count$= store.select(state => state);

Now I always got a value when clicking but I just want to get the value and not the complete state devtools

What am I missing? I think the undefined has something to do with my select. Basically this is the example of https://ngrx.io/guide/store.

EDIT: I found a workaround but i'm not sure why this is working. Inside my app.config.ts when registering my store i had to give it a name like this:

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes), provideStore(), provideState({name: "game", reducer: scoreboardReducer}), provideStoreDevtools({
    maxAge: 25, // Retains last 25 states
    logOnly: !isDevMode(), // Restrict extension to log-only mode
    autoPause: true, // Pauses recording actions and state changes when the extension window is not open
    trace: false, //  If set to true, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code
    traceLimit: 75, // maximum stack trace frames to be stored (in case trace option was provided as true)
  }),]
};

After that i did it like this in my component:

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

... }

I checked my store via Redux Devtools and i got values with this approach. When I remove { game: AppState}> it doesn't work anymore and my store is empty.

1

There are 1 answers

2
Peca021 On

It appears that the issue might be related to how you are selecting the value from the store. To address this, consider utilizing the createSelector function provided by the NgRx library.
Additionally, if you aim to streamline the process and avoid manually creating selectors for individual pieces of state, you may find createFeature from NgRx beneficial.

You can take a look at the docs to see how they both works:
https://ngrx.io/api/store/createSelector
https://ngrx.io/api/store/createFeature