In this Minimal Reproductible Example I don't understand why the reducer has something (we can observe it in the ReduxDevTools) but the selector puts undefined in the component.
Could someone have keys to share about this behavior?
Here are the parts of the MRE:
import { createAction } from '@ngrx/store';
export const 碌AppInitializerEntered = createAction(`[frontend] 碌AppInitializerEntered`);
<pre>{{ feature$ | async | json }}</pre>
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { $feature } from '../selectors';
import { tap } from 'rxjs/operators'
@Component({
selector: 'workspace-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IndexComponent {
feature$ = this.store.pipe(select($feature), tap(feature => {
console.log({ feature })
}))
constructor(private store: Store<{}>) { }
}
import { createReducer, on } from '@ngrx/store';
import { produce } from 'immer';
import { 碌AppInitializerEntered } from '../../actions';
export interface AppInitializer {
status: 'initial' | 'entered';
}
export const appInitializer = createReducer(
{
status: 'initial' as AppInitializer['status']
},
on(碌AppInitializerEntered, (state): AppInitializer => produce(state, (draft) => {
draft.status = 'entered';
}))
);
import { InjectionToken } from '@angular/core';
import { Action, ActionReducerMap, MetaReducer } from '@ngrx/store';
import { appInitializer, AppInitializer } from './app-initializer/index.reducer';
export interface FeatureState {
appInitializer: AppInitializer;
}
export interface State {
frontend: FeatureState;
}
export const reducers = new InjectionToken<ActionReducerMap<FeatureState, Action>>('frontend', {
factory: () => ({ appInitializer })
});
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { FeatureState, State } from '../reducers';
export const $feature = createFeatureSelector<State, FeatureState>('frontend');
export const $appInitializer = createSelector($feature, (feature) => feature?.appInitializer);
export const $appInitializerEntered = createSelector($appInitializer, (appInitializer) => appInitializer?.status);
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Store, StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { 碌AppInitializerEntered } from './actions';
import { IndexComponent } from './components/index.component';
import { reducers } from './reducers';
@NgModule({
bootstrap: [IndexComponent],
declarations: [IndexComponent],
imports: [
BrowserModule,
StoreModule.forRoot(reducers, {
runtimeChecks: {
strictActionImmutability: true,
strictActionSerializability: true,
strictStateImmutability: true,
strictStateSerializability: true
}
}),
StoreDevtoolsModule.instrument({
maxAge: 5000,
name: 'frontend'
})
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: (store: Store<{}>) => () => store.dispatch(碌AppInitializerEntered()),
multi: true,
deps: [Store]
}
]
})
export class AppModule {
}
"@angular/animations": "^10.1.0",
"@angular/common": "^10.1.0",
"@angular/compiler": "^10.1.0",
"@angular/core": "^10.1.0",
"@angular/forms": "^10.1.0",
"@angular/platform-browser": "^10.1.0",
"@angular/platform-browser-dynamic": "^10.1.0",
"@angular/platform-server": "^10.1.0",
"@angular/router": "^10.1.0",
"@ngrx/effects": "^10.0.1",
"@ngrx/router-store": "^10.0.1",
"@ngrx/store": "^10.0.1",
"@nrwl/node": "^10.4.4",
"angular-oauth2-oidc": "^10.0.3",
"immer": "^8.0.0",
"lodash.random": "^3.2.0",
"rxjs": "~6.5.5",
"tslib": "^2.0.0",
"zone.js": "^0.10.2"
yarn run v1.21.1
$ nx report
> NX Report complete - copy this into the issue template
nx : Not Found
@nrwl/angular : 10.4.4
@nrwl/cli : 10.4.4
@nrwl/cypress : 10.4.4
@nrwl/eslint-plugin-nx : 10.4.4
@nrwl/express : Not Found
@nrwl/jest : 10.4.4
@nrwl/linter : 10.4.4
@nrwl/nest : Not Found
@nrwl/next : Not Found
@nrwl/node : 10.4.4
@nrwl/react : Not Found
@nrwl/schematics : Not Found
@nrwl/tao : 10.4.4
@nrwl/web : Not Found
@nrwl/workspace : 10.4.4
typescript : 4.0.5
Done in 1.68s.
I originally posted this issue here rather than in StackOverflow because I tried to follow the NgRx documentation very carefully to do this MRE so maybe I missed a documentation somewhere where we can bring more visibility on, but I finally also posted it on StackOverflow here.
The state is structured as
{ appInitializer: { ... } }
While the feature selector selects the feature leaf.
To resolve this issue, either change the factory for the root reducer, or select the appInitializer leaf in the feature selector.
export const reducers = new InjectionToken<ActionReducerMap<any, Action>>(
'frontend',
{
// 馃憞 use the `frontend` property here, otherwise it will be `appInitializer`
factory: () => ({ frontend: appInitializer }),
}
);
// or
export const $feature = createFeatureSelector<State, FeatureState>('appInitializer ');
:facepalm: so sorry to have disturbed you @timdeschryver for such an easy issue... Thank you very much for your help, NgRx (and Immer :wink:) are awesome!
No problem, and thanks!
Arf :persevere: @timdeschryver I updated the MRE:
appInitializer. I think in this use case your first proposition is not available, am I wrong?import { createFeatureSelector } from '@ngrx/store';
import { FeatureState, State } from '../reducers';
export const $feature = createFeatureSelector<State, FeatureState>('appInitializer');
Unfortunately this brings the following error:
ERROR in apps/frontend/src/app/selectors/index.ts:4:68 - error TS2345: Argument of type '"appInitializer"' is not assignable to parameter of type '"frontend"'.
4 export const $feature = createFeatureSelector<State, FeatureState>('appInitializer');
~~~~~~~~~~~~~~~
In VScode, with the original code I posted, there are no errors and the auto-completion works well everywhere which confuses me a lot :cry: ... Maybe you have any keys to share helping me to understand how to fix it?
Okay so I investigated a little bit more and here is what I have done to reproduce the bug:
git clone https://github.com/hadrien-toma/ngrx-app-initializercd ngrx-app-initializer && yarn installyarn starthttp://localhost:4200/ displays:
apps/frontend/src/app/selectors/index.ts, comment the line 5 and uncomment the line 6 ERROR in apps/frontend/src/app/selectors/index.ts:6:105 - error TS2339: Property 'status' does not exist on type 'FeatureState'.
6 export const $appInitializerStatus = createSelector($appInitializer, (appInitializer) => appInitializer.status);
http://localhost:4200/ displays:
yarn start and re-run it, we are not able to see nothing in the HTML because the compiler fails (instead of beeing successfull and triggering "after" an error).I am lost :persevere: ...
@hadrien-toma we're trying to keep the GitHub issues for bugs, I would suggest joining the NgRx Discord Server for further help.
Hello @timdeschryver, thanks for coming back, with my investigation of yesterday I consider it is a bug, do you think I am wrong?
After the help of Tim on the Discord, I updated the type of the selector from FeatureState to AppInitializer, to not only select the leaf but also to type it correctly. After that all is working. Thank you again for your time and help @timdeschryver :pray: !