NGRX Cannot access 'USER_FEATURE' before initialization - ngrx

I currently have this:
user.state.ts
export interface IFeatureUserState {
checkoutRow: IEntityState<CheckoutRow>;
...
};
export const USER_FEATURE = 'users';
export const userFeatureState = createFeatureSelector<IFeatureUserState>(
USER_FEATURE
);
export const userReducers: ActionReducerMap<IFeatureUserState> = {
checkoutRow: checkoutRowReducer,
...
};
user-store.module.ts
#NgModule({
imports: [
StoreModule.forFeature(USER_FEATURE, userReducers),
EffectsModule.forFeature([
CheckoutRowEffects,
...
NgrxAutoEntityModule.forFeature();
providers: [
{ provide: CheckoutRow, useClass: EntityService },
...
]);
user-layout.module.ts <- Lazy loaded module
#NgModule({
imports: [
UserStoreModule
...
]
app.module.ts
#NgModule({
StoreModule.forRoot({}, {
metaReducers: [clearState],
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
},
}),
AuthStoreModule, // Another feature with same setup as USER_FEATURE but with authentication models.
EffectsModule.forRoot(),
NgrxAutoEntityModule.forRoot(),
...
});
However i get this error:
Anyone know what i do wrong? My goal is to use NGRX with features and separate my store into 2 modules, one for root/auth handling and one for user store functionality.

my problem solved by moving
export const USER_FEATURE = 'users';
to {nameof}actions.ts

Related

Why reducer does not seem to fire in ngrx? How to wire in the reducer to the store?

I am trying to dispatch the addHero action in my service:
I see the "obtaining" and "dispatched" messages for each Hero,
but the reducer does not seem to be called.
This is called from an ngOnInit hook.
What is the obvious thing I overlook?
Update: I have wrapped the appReducer to a function which logs each reducer call and then calls the real reducer. I neither see the log of it, so probably I am missing something about how to wire in the reducer.
My attempt was this line in the imports part of module.ts:
StoreModule.forRoot(appReducer)
(end of update)
export class GetTheActualListOfHeroesService {
store: Store<AppStore>;
run(): void {
console.log("GetTheActualListOfHeroesService")
obtainHeroesService().forEach(hero => {
console.log("obtaining", hero)
this.store.dispatch(addHero(hero))
console.log("dispatched")
}
)
};
constructor(appStore: Store<AppStore>) {
this.store = appStore;
}
}
The reducer is below. I do not see "addHero" in the log.
export const initialState: AppStore = {
heroes: [],
filterString: "",
selectedHero: undefined
}
export const appReducer = createReducer(
initialState,
on(addHero, (state: AppStore, hero) => {
console.log("addHero", hero)
state.heroes.push(hero);
console.log(state)
return state;
}),
);
The action is defined thus:
export const addHero = createAction('add Hero', props<Hero>());
And here is my module definition:
#NgModule({
declarations: [
HeroesComponent,
HeroeditorComponent,
HeroitemComponent,
HeroListComponent,
HeroFilterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
StoreModule.forRoot(appReducer)
],
providers: [
SelectedHeroRepository,
HeroFilterRepository,
GetTheActualListOfHeroesService,
IsThisHeroSelectedForEditingService,
SelectHeroForEditingService,
InitializeStatesService,
SelectHeroesWithMatchingNamesService,
],
bootstrap: [HeroesComponent]
})
export class Angulartest { }
The solution was this:
StoreModule.forRoot({ heroes: appReducer })
And the reducer became:
export const initialState: Heroes = []
export const appReducer = createReducer(
initialState,
on(addHero, (state: Heroes, hero) => {
console.log("addHero", hero)
const newstate = state.concat([hero])
console.log(state)
return newstate;
}),
);

Nest can't resolve dependencies: multiple database connections using mongoose package

Hi I am trying to set up multiple database connections using mongoose nestjs package with named connections and following along the documentation found here (https://docs.nestjs.com/techniques/mongodb#multiple-databases): but I am getting a runtime error on startup:
Error: Nest can't resolve dependencies of the RetailLocationModelModel (?). Please make sure that the argument partnersConnection at index [0] is available in the MongooseModule context.
This only happens when I use named connections. If I remove the connection name 'partners' from forFeature parameter (even though keeping it at forRootAsync), it is working fine. Probably because the model connects to the default connection and since there is only one, it automatically connects with 'partners'.
// dependencies
"#nestjs/axios": "^0.0.8",
"#nestjs/common": "^8.4.5",
"#nestjs/config": "^1.0.1",
"#nestjs/core": "^8.4.5",
"#nestjs/mongoose": "^8.0.1",
#Module({
imports: [DatabaseModule, RetailPartnersModule],
})
export class AppModule {}
#Module({
imports: [ConfigModule, MongooseModule.forRootAsync(B2CPartnersDbAsyncOptions)],
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule {}
export const B2CPartnersDbAsyncOptions = {
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
const user = configService.get<string>(MONGODB_USERNAME)
const password = configService.get<string>(MONGODB_PASSWORD)
const database = configService.get<string>('b2c_partners_acc')
const host = configService.get<string>(MONGODB_URL)
const uri = `mongodb+srv://${user}:${password}#${host}/${database}`
return {
uri,
useNewUrlParser: true,
useUnifiedTopology: true,
connectionName: 'partners',
}
},
inject: [ConfigService],
}
#Module({
imports: [
MongooseModule.forFeature(
[{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
'partners'
),
],
controllers: [RetailPartnersController],
providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}
export class RetailLocationsRepository {
constructor(
#InjectModel(RetailLocationModel.name) private model: Model<RetailLocationDocument>
) {}
}
Note that I cannot add 'partners' as second parameter in InjectModel, as TS complains it only expects 1 argument (even though official docs say that I can pass the connection name as extra argument. When manually updating typings to support 2 parameters, I still get the same runtime error of unresolved dependencies
Update:
When I go into the mongoose module provided by the package and log the result of the static methods forRootAsync and forFeature, forRootAsync does not provide the partnersConnection token, where forFeature is trying to inject it
// Mongoose.module.ts
class MongooseModule {
static forRootAsync(options) {
return {
module: MongooseModule_1,
imports: [mongoose_core_module_1.MongooseCoreModule.forRootAsync(options)],
};
}
}
where logging forRootAsync.imports[0].providers yields:
{
provide: 'MongooseModuleOptions',
useFactory: [Function (anonymous)],
inject: [ [class ConfigService] ]
},
{
provide: 'DatabaseConnection',
useFactory: [Function: useFactory],
inject: [ 'MongooseModuleOptions' ]
},
{ provide: 'MongooseConnectionName', useValue: 'DatabaseConnection' }
And with forFeature:
static forFeature(models = [], connectionName) {
const providers = mongoose_providers_1.createMongooseProviders(connectionName, models);
const result =
{
module: MongooseModule_1,
providers: providers,
exports: providers,
};
console.log('result forFeature1: ', result.providers)
return result;
}
logs to:
[{
provide: 'RetailLocationModelModel',
useFactory: [Function: useFactory],
inject: [ 'partnersConnection' ]
}]
So it seems that the partnersConnection token is not being set properly in the forRootAsync static function, as the connection is named to the default value of 'DatabaseConnection'
I verified this by changing the connectionName of the RetailLocationsModule to 'Database' and the runtime error is resolved.
#Module({
imports: [
MongooseModule.forFeature(
[{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
'Database'
),
],
controllers: [RetailPartnersController],
providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}
Therefore either there is a bug in forRootAsync or I am missing something.
Instead of supplying connectionName to the factory, supply it to the options object of MongooseAsyncOptions:
export function createDbConfig(
dbName: string
): (configService: ConfigService) => MongooseModuleAsyncOptions {
return (configService: ConfigService): MongooseModuleOptions => {
const logger = new Logger(createDbConfig.name)
const user = configService.get<string>(MONGODB_USERNAME, '')
const password = configService.get<string>(MONGODB_PASSWORD, '')
const database = configService.get<string>(dbName, '')
const host = configService.get<string>(MONGODB_URL, '')
const mongoProtocol =
configService.get<string>(NODE_ENV) === 'local' ? Protocols.local : Protocols.production
const uri = `${mongoProtocol}://${user}:${password}#${host}/${database}`
logger.verbose(`Connecting to the Mongo database URI: ${uri}`)
return {
uri,
useNewUrlParser: true,
useUnifiedTopology: true,
retryAttempts: 0,
// connectionName: 'partners' <= remove from here
}
}
}
export const B2CPartnersDbAsyncOptions: MongooseModuleAsyncOptions = {
imports: [ConfigModule],
useFactory: createDbConfig(DB_NAME.default),
inject: [ConfigService],
connectionName: 'partners', // <= put it here instead
}
Then in the module where you use #InjectConnection, supply the name of the connection ('partners'), as well as with every MongooseModule.forFeature, e.g.
#Injectable()
export class DatabaseService {
constructor(#InjectConnection('partners') private _connection: Connection) {}
public getStatus(): DatabaseHealthStatus {
return this._connection && this._connection.readyState === 1
? DatabaseHealthStatus.CONNECTED
: DatabaseHealthStatus.DISCONNECTED
}
public get connection(): Connection {
return this._connection
}
}
#Module({
imports: [
MongooseModule.forFeature(
[{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
'partners'
),
],
controllers: [RetailPartnersController],
providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}

How to setup storybook at root level lerna monorepo

I am working on a project which is set up with lerna mono repo, we have multiple stencilJS projects for an individual component inside packages of monorepo.
My project sructure is:
I am new to the storybook, now I have to set up the storybook at the root level which all the packages storybook.
I followed an article on the internet and I have set up something which works only for a single package component, due to the current style of setup.
Due to defineCUstomElements in preview.js it is loading the first project package loader I am able to see only the first project stories. Css is not loading for second project stories.
Can someone help me to set up a storybook at the root level which works for all packages?
My example
storybook/main.js
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../packages/plugin-*/src/components/plugin-*/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
'#storybook/addon-links',
'#storybook/addon-essentials',
'#storybook/addon-viewport',
'#storybook/addon-notes',
'#storybook/addon-docs'
]
}
storybook/preview.js
import { defineCustomElements } from '../packages/stencilProj1/loader';;
defineCustomElements();
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
};
package/stencilProj1/component1.stories.ts
import readme from './readme.md'
import React from 'react';
import ComponentButton from '../../../dist/collection/components/ComponentButton /ComponentButton';
export default {
title: 'Button',
component: ComponentButton,
argTypes: {
label: { control: 'text' },
type: { type: 'select', options: ['primary'] },
disabled: { control: 'boolean' }
},
parameters: {
markdown: readme
},
};
const Template = ({ label, type, disabled = false }) => {
return <component-button type={type} disabled={disabled}>{label}</component-button>;
};
export const Primary = Template.bind({});
Primary.args = {
type: 'primary',
label: 'Primary Button',
};
After a couple of months of attempts, I finally solved this puzzle :) And want to share my solution with community
I have a lerna v4 monorepo for react v17 + mui v5 components written in typescript and flavored with storybook v6 and webpack v5
mui has its wrappers in preview.js, that's why I added the path to .storybook in babel-loader
module.exports = {
core: {
builder: "webpack5",
},
framework: "#storybook/react",
stories: ["../components/**/*.stories.#(ts|tsx)"],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
{
name: "#storybook/preset-create-react-app",
options: {
craOverrides: {
fileLoaderExcludes: ["less"],
},
},
},
],
webpackFinal: config => {
const {
module: {
rules: [, , , , , { oneOf }],
},
} = config;
const babelLoader = oneOf.find(({ test }) => new RegExp(test).test(".ts"));
babelLoader.include = [/components\/(.*)\/src/, /.storybook/];
babelLoader.options.sourceType = "unambiguous";
return config;
},
};
it is also worth to mention my tsconfig has these lines
"rootDirs": [
"./src",
],
Have you tried to import project 2's defineCustomElement with "as" to rename it and use it?
(something along the line of following inside preview.js)
import { defineCustomElements } from '../packages/stencilProj1/loader';
import { defineCustomElements as defineSecondProject } from '../packages/stencilProj2/loader';
defineCustomElements();
defineSecondProject();
This is very manual and even if this works, if you have many repo might not be good solution but I've done something similar to load multiple component loaders and that seem to work OK for my use case in the past.

Testing ngrx store: No provider for MockStore

I am following the Official Documentation for Testing ngrx Stores: https://ngrx.io/guide/store/testing
Even the simplest implementation of injecting a MockStore has the following Error:
NullInjectorError: R3InjectorError(CompilerModule)[MockStore -> MockStore]:
NullInjectorError: No provider for MockStore!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'MockStore', 'MockStore' ] })
My code looks like this:
import { TestBed } from '#angular/core/testing';
import { provideMockStore, MockStore } from '#ngrx/store/testing';
describe('Auth Guard', () => {
// #ts-ignore
let store: MockStore;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// any modules needed
],
providers: [
provideMockStore(),
// other providers
],
});
store = TestBed.inject(MockStore);
});
it('should create', () => {
expect(store).toBeTruthy();
});
});
I am Running #ngrx/store#8.6.0
UPDATED
based on discussion store = TestBed.inject(Store); instead of MockStore is enough for the desired behavior.
ORIGINAL
It is too early,
get it in the test:
it('should create', inject([Store], (store) => {
expect(store).toBeTruthy();
}));
not sure, but you can try to call compileComponents.
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
// any modules needed
],
providers: [
provideMockStore(),
// other providers
],
}).compileComponents();
store = TestBed.inject(MockStore);
});

Providing root reducer in #ngrx/store 4.0

In #ngrx/store 2.0 we could provide the root reducer as a function and from there we split our logic inside the application. After I updated to #ngrx/store 4.0 I cannot use this feature any more from what I can see the reducers need to be a map of reducers which will create objects under the same keys in the state. Is there a way to use the old behavoir in #ngrx/store 4.0 In my state components are aware one of another and I need to be able to split my state dynamically also I need to be able to dispatch actions to the right reducer in my own way. Also app is splitted in multiple lazy loaded routes which in some cases reuse the data from another feature.
StoreModule.provideStore(reducer, {
auth: {
loggedIn: true
}
})
StoreModule.forRoot(reducers, {
initialState: {
auth: {
loggedIn: true
}
}
})
I need reducers to be a function which gets the full state and dispatches it to the correct reducer, Is there a way to achieve this behavior?
After I had a second look over ngrx repo I figured it out. To achieve the wanted result we need to replace the #ngrx/store reducer factory with a new implementation. I injected a new reducer factory and right now the application works as before. Simple code sample on how to replace the reducer factory it.
// This factory replaces #ngrx combine reducers so we can manage how we split the keys inside the state
export function combineReducersFactory(
reducers: any,
initialState: any = {}
): ActionReducer<any, Action> {
return function combination(state = initialState, action) {
const nextState: any = reducers(state, action);
return nextState !== state ? nextState : state;
};
}
export const NG_RX_STORE_PROVIDER = [
StoreModule.forRoot(rootReducer, createEmptyState()),
];
export const NG_RX_REDUCER_FACTORY = [
{
provide: REDUCER_FACTORY,
useFactory: () => combineReducersFactory
}
];
#NgModule({
imports: [
...NG_RX_STORE_PROVIDER
],
declarations: [...APP_COMPONENTS, ...AG_GRID_COMPONENTS],
providers: [...NG_RX_REDUCER_FACTORY]
})
export class AppModule {
}
You can set up a meta reducer to receive every event and manipulate the state from its root. Here is an example way to set it up:
const myInitialState = {
// whatever you want your initial state to be
};
export function myMetaReducer(
reducer: ActionReducer<RootStateType>
): ActionReducer<RootStateType> {
return function(state, action) {
if (iWantToHandleThisAction) {
state = doWhatIWantWith(state);
}
return reducer(state, action);
};
}
#NgModule({
imports: [
StoreModule.forRoot(myInitialState, { metaReducers: [myMetaReducer] })
]
})
export class AppModule {}
The StoreModule forRoot() function accepts a reducerFactory which can be used as follows:
export function myReducerFactory(reducers: any, initState: any) {
return (state = myInitialState, action) => myCustomReducer(state, action);
}
#NgModule({
// ...
imports: [
StoreModule.forRoot(null, { reducerFactory: myReducerFactory })
]
// ...
})
export class AppModule {
}
This works for me:
// your old reducer that handled slicing and dicing the state
export function mainReducer(state = {}, action: Action) {
// ...
return newState;
}
// new: metaReducer that just calls the main reducer
export function metaReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
return function (state, action) {
return MainReducer(state, action);
};
}
// new: MetaReducer for StoreModule.forRoot()
export const metaReducers: MetaReducer<any>[] = [metaReducer];
// modified: app.module.ts
#NgModule({
// ...
imports: [
// neglect first parameter ActionReducerMap, we don't need this
StoreModule.forRoot({}, {
metaReducers: metaReducers,
initialState: INITIAL_STATE // optional
}),
]
})

Resources