NGRX Entity test in component - ngrx

I have a component with an entity selector 'selectAllProperties' in ngOnInit, and I want to test this component:
ngOnInit() {
this.store.dispatch(new LoadPropertiesRequested());
this.properties$ = this.store.pipe(select(selectAllProperties));
this.loading$ = this.store.pipe(select(selectPropertiesLoading));
this.logs$ = this.store.pipe(select(selectPropertiesLogs));
}
In my spec file, i initialized the store like in the ngrx doc:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({
...fromRoot.reducers,
feature: combineReducers(fromProperties.reducer),
})
],
declarations: [
SidebarPropertiesComponent,
SidebarElementComponent
]
})
.compileComponents();
}));
When I launch the tests, I have 'TypeError: Cannot read property 'ids' of undefined'.
All the others selectors do not produce errors
I also would like to mock the Observable returned by each selector.
Thanks

I have found the problem, in TestBed.configureTestingModule
instead of
imports: [
StoreModule.forRoot({
...fromRoot.reducers,
feature: combineReducers(fromProperties.reducer),
})
],
use
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
StoreModule.forFeature('properties', fromProperties.reducer),
],
No more 'TypeError: Cannot read property 'ids' of undefined'
I can mock the properties
it('should have 2 properties elements', () => {
store.dispatch(new LoadPropertiesSuccess({properties: propertiesMock}));
fixture.detectChanges();
const list = debugElement.queryAll(By.css('li'));
expect(list.length).toBe(2);
});

Related

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 {}

NGRX Cannot access 'USER_FEATURE' before initialization

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

Get the parent name of children's route Vue js

I have this route that has a children. I can retrieve the name of the route however it is only applicable to the name of the children.
const routes = [
{
path: '/',
name: 'Home', // <--- I want to get this route name
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('src/pages/Home/Index.vue') },
{ path: '/patient', component: () => import('src/pages/Home/Patient.vue') },
]
},
{
path: '/auth',
name: 'Auth', <--- I want to get this route name
component: () => import('layouts/AuthLayout.vue'),
children: [
{ path: '', component: () => import('pages/Login.vue') },
//{ path: '', component: () => import('pages/Login.vue') }
]
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/Error404.vue')
}
]
export default routes
Then I tried remove all named routes from the children and assigned a name to the parent but it gives me
undefined whenever I console.log($route.name) on the MainLayout.vue
I'm not sure if this is really the right way of getting the parent's route name but I have achieved it using route.matched
import { useRoute } from 'vue-router'
...
const path = computed(() => $route.matched[0].name ) //[0] first one
This should return the component name Home
I think you're looking for the immediate parent of the current active route .. yes?
In that case, you do as previously mentioned use this.$route.matched, but not as stated. The current route is the last item in $route.matched array, so to get the immediate parent you can use:
const parent = this.$route.matched[this.$route.matched.length - 2]
const { name, params, query } = parent
this.$router.push({ name, params, query })
In my vue.js 3 project I am using vite-plugin-pages and for some reason #Shulz's solution gives me route.matched[0].name: undefined. So, doing things as mentioned below helped:
In <template>
<router-link to='/the-page' :class='{ "active": subIsActive("/the-page") }'> The Page </router-link>
In <script>
const subIsActive = (input) => {
const paths = Array.isArray(input) ? input : [input];
return paths.some((path) => route.path.indexOf(path) === 0);
};
but, as I am using vite-plugin-pages I found another solution and I followed this approach to fix my issue.

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);
});

Angular 2 Router - named router-outlet navigation from code

Using #angular/router": "3.4.7".
Proposed solution here doesn't work anymore.
/**
The ProductComponent depending on the url displays one of two info
components rendered in a named outlet called 'productOutlet'.
*/
#Component({
selector: 'product',
template:
` <router-outlet></router-outlet>
<router-outlet name="productOutlet"></router-outlet>
`
})
export class ProductComponent{
}
#NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{
path: 'product',
component: ProductComponent,
children: [
{
path: '',
component: ProductOverviewComponent,
outlet: 'productOutlet'},
{
path: 'details',
component: ProductDetailsComponent,
outlet: 'productOutlet' }
]
}
]
)],
declarations: [
ProductComponent,
ProductHeaderComponent,
ProductOverviewComponent,
ProductDetailsComponent
exports: [
ProductComponent,
ProductHeaderComponent,
ProductOverviewComponent,
ProductDetailsComponent
]
})
export class ProductModule {
}
Manual navigation works as expected
http://localhost:5000/app/build/#/product-module/product (correctly displays overview component in named outlet)
http://localhost:5000/app/build/#/product-module/product/(productOutlet:details)
(correctly displays details component in named outlet)
THE PROBLEM
Cannot figure out the correct way to perform programatical navigation:
this.router.navigateByUrl('/(productOutlet:details)');
this.router.navigate(['', { outlets: { productOutlet: 'details' } }]);
Following errors occur:
Error: Cannot match any routes. URL Segment: 'details'.
You can navigate programatically like this
this.router.navigate([{ outlets: { outletName: ['navigatingPath'] } }], { skipLocationChange: true });
Note: skipLocationChange is use to hide the url from the address bar.
Refer the official document : https://angular.io/guide/router
You can try relative navigation as
this.router.navigate(['new'],{relativeTo:this.route})
For this you will have to inject current router snapshot and Activated route in component as
import { Router,ActivatedRoute } from "#angular/router";
constructor(private router:Router,private route:ActivatedRoute ){}

Resources