Nest JS differentiate 2 distinct routes of the same controller - javascript

I have a Nest JS application with 2 routes that simply returns the data(preferences) from the database.
I want to create 2 routes:
List item preferences: /api/items
List feature preferences: /api/features
I'm new to NestJS. These 2 routes should be under one module. Is this achievable?
The 2nd route works but I get an error saying 404 Not Found.
#Module({
controllers: ['PreferenceController'],
providers: ['PreferenceService']
})
#Controller()
export class PreferenceController{
constructor(private readonly preferenceService: PreferenceService) {}
#Get('preference/:x/:y/:z')
getItem(#Param() param) {
return this.preferenceService.getItem(param);
}
#Get(':id')
getFeature(#Param() param) {
return this.preferenceService.getFeature(param);
}
}
routes = [
path: '/api',
children: [
{
path: '/items',
module: 'PreferenceModule'
},
{
path: '/features',
module: 'PreferenceModule'
}
]
]

Related

Angular - Dynamic routing with router param vaiable

I want to do something like this in my routing.module.ts (see lines marked by-->)
export const routes: Routes = [
path: 'test/:action',
component: CreateComponent,
--> canActivate: ':action' == 'read' ? [Guard1] : [Guard1, Guard2],
data: {
--> screenAction: ':action' == 'read' ? 'read' : ':action',
}
]
I have to use the variable :action because it is used later in the router param. Thanks for your help !
Well, what you ask is possible but implementing it might be a little complex.
First of all, you need to define multiple routes instead of 1. Where you can apply the conditions you require.
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: AppComponent },
{
path: 'dummy1/:action',
component: Dummy1Component,
canActivate: [GuardRotaterGuard],
},
{
path: 'dummyx/:action',
component: Dummy2Component,
canActivate: [Guard1Guard],
},
{
path: 'dummyz/:action',
canActivate: [Guard1Guard, Guard2Guard],
component: Dummy2Component,
},
];
The route dummy1/:action is like a gateway route. Every request to dummy2 component should go from here. And then you need a decider/rotator guard that can decide and rotate the route depending on the route parameters.
It should look like below :
canActivate(
next: ActivatedRouteSnapshot,
statex: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
const { action } = next.params;
const state = { comesFrom: 'rotater' };
console.log("rotater",action);
if (action === 'read') { // you can decide whatever you want here
this.router.navigate(['/dummyx/read'], { state });
}else{
this.router.navigate(['/dummyz', action], { state }); // pass the action to the other route as a parameter
}
return true;
}
And here is a Stackblitz example in action.

NestJs - mongoose - Dynamic collection naming

I'd like to use dynamic collection names based on current year.
For example: From 'products' to 'products2020'.
Using NESTJS, I have to import "module.forFeature" with an specifyc collection name.
import { Module } from '#nestjs/common'
import { MongooseModule } from '#nestjs/mongoose'
#Module({
imports: [
MongooseModule.forFeature([
{
name: 'Products',
schema: ProductsSchema
}
])
],
controllers: [ProductsController],
providers: [ProductsService]
})
And the same happens with injection at service:
import { Injectable } from '#nestjs/common'
import { InjectModel } from '#nestjs/mongoose'
import { Model } from 'mongoose'
#Injectable()
export class ProductsService {
constructor(
#InjectModel('Products')
private readonly productsModel: Model<Products>
) {}
}
And finally, here's my schema:
import { Schema } from 'mongoose'
export const ProductsSchema = new Schema(
{
_id: { Type: String, required: true },
code: String
},
{
collection: 'Products'
}
)
Is there some way to achieve dynamic naming?
Thanks a lot !
I stumble into a similar issue, and the way I resolved was using the MongooseModule.forFeatureAsync method. The model and schema declaration are the same as in the nestjs docs.
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: UsersModel.name,
imports: [EnvironmentModule],
inject: [EnvironmentService],
useFactory: (envService: EnvironmentService) => {
const env = envService.getEnv();
const schema = UsersSchema.set(
'collection',
`${env.countryCode}-users`,
);
return schema;
},
},
]),
...
],
providers: []
...
I've looking for a solution to this kind of problem but i've hit a wall and there was no clear way to do it.
Below (minimal) code instantiate Services each bound to a specific model depending on a country parameter. i.e ServiceX bound to Model of Database X, ServiceY bound to the same Model in Database Y
But here is what i managed to do. You can absolutely do a work around to fit your needs
First comes the model/interface. Commonly used between different services
export interface User extends Document {
readonly username: string;
readonly password: string;
}
export const UserSchema = new mongoose.Schema(
{
_id: mongoose.ObjectId,
username: String,
password: String
},
{ collection: 'accounts', autoCreate: true }
);
Service definition is indeed the same for every model in different database/collection
#Injectable()
export class XUserService implements OnModuleInit{
constructor(
private userModel: Model<User>,
) {
}
////////////////////////////////////////////////////////////////////////////
async onModuleInit(): Promise<any> {
console.log(`inside service dbname=: ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
// await new this.userModel({_id: mongoose.Types.ObjectId(), username: 'test', password: 'test', flag: this.c}).save()
}
async insert(){
console.log(`inside service dbname=: ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
await new this.userModel({
_id: mongoose.Types.ObjectId(),
username: this.userModel.db.name,
password: '0000'
}).save();
}
async findOne(): Promise<User>{
console.log(`inside service in : ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
return this.userModel.findOne()
}
}
For Module, i made a DynamicModule
Import DBConnections
Create a Model for each need, ( for my case, one model in each Database )
Create and bind each Model to a Service, so the instantiation of the service will be correct
#Module({
})
export class XUserModule{
static register( /*use can pass parameter here*/): DynamicModule{
return{
module: XUserModule,
imports: [
DatabaseModule
],
controllers: [
XUserController
],
providers: [
// Create Models here, #1 and #2 in two different database
{
provide: 'dz-'+'UserModel',
useFactory: (connection: Connection)=> {
return connection.model('User', UserSchema )
},
inject: [ dbname.shooffood('dz')+'Connection' ]
},{
provide: 'ca-'+'UserModel',
useFactory: (connection: Connection)=> {
return connection.model('User', UserSchema )
},
inject: [ dbname.shooffood('ca')+'Connection' ]
},
// Create Providers/Services for each Model and Inject the Model to the Service by `TokenString`
{
provide: 'dz' + XUserService.name,
useFactory: (m: any)=> {
console.log(m);
return new XUserService(m);
},
inject: [ 'dz-'+'UserModel' ]
},{
provide: 'ca' + XUserService.name,
useFactory: (m: any)=> {
console.log(m);
return new XUserService(m);
},
inject: [ 'ca-'+'UserModel' ]
}
],
// Export your service with the same `provide` name for later usage.
exports: [
'dz' + XUserService.name,
'ca' + XUserService.name
]
}
}
}
Just FYI, database module looks like
Constants dbname are connection names and uri are the connection string.
const databaseProviders = [
{
provide: dbname.admin+'Connection',
useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.admin),
},{
provide: dbname.system+'Connection',
useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.system),
},{
provide: dbname.shooffood('dz')+'Connection',
useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.dzfood),
},{
provide: dbname.shooffood('ca')+'Connection',
useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.cafood),
}
];
#Module({
providers: [
...databaseProviders
],
exports: [
dbname.admin+'Connection',
dbname.system+'Connection',
dbname.shooffood('dz')+'Connection',
dbname.shooffood('ca')+'Connection'
]
})
export class DatabaseModule {}
As for Controller, there is only one that handle each service via request param :country. But first i had to list all possible Models and services to include in the Application.
#Controller(':country')
export class XUserController {
private byCountryServices = new Map();
constructor(
// Inject all required services by `tokenString`
#Inject('dz' + XUserService.name) private dzUserService: XUserService,
#Inject('ca' + XUserService.name) private caUserService: XUserService,
) {
// Add to `<key, value>` Map for easy by param access
this.byCountryServices.set('dz', this.dzUserService );
this.byCountryServices.set('ca', this.caUserService );
}
#Get('post')
async post(
#Param('country') c: string
): Promise<string>{
await this.byCountryServices.get(c).insert()
return 'inserted in ' + c;
}
#Get('get')
async get(
#Param('country') c: string
): Promise<string>{
console.log('param: ' + c)
return await this.byCountryServices.get(c).findOne()
}
}
Finally you import the module in AppModule with
XUserModule.register()

Angular : pass optional data within route

I'm using angular 5 , I ve a long routing file :
const homeRoutes: Routes = [
{
path: 'home', component: HomeComponent,
children: [
{
path: 'registration',
component: RegistrationComponent,
children: [
{
path: 'synthese',
component: SyntheseComponent
},
{
path: 'queue',
component: QueueComponent,
children: [
{
path: 'queue-modal',
component: QueueModalComponent
},
{
path: 'confirm',
component: ConfirmComponent
}
]
}
]
},
{
...
And i want to pass data within the "registration" path .
As i was told , i need to write it like this : path: 'registration/:mydata',
and after that subscribe to data of ActivatedRoute
The problem that i'm not always passing data , it s only in some cases.
How may i make it with minimum of impact ??
You can use querystring parameters (AKA queryParams) instead of route parameters. You do not need to define query params in the route.
Here is an example of a relative href:
registration/?mydata=123
This is how you can define queryParams for a routerLink:
<a [routerLink]="['registration']" [queryParams]="{ mydata: 123 }">Go to Registration</a>
This is how you can read that param value:
myValue: any;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.myValue = params['mydata'];
});
}

Angular Routing: Define Multiple Paths for Single Route

I've seen several answers to this, but I'm not sure if they necessarily "fit" my scenario (I'm not trying to create parent/child routing relationships or anything like that). In my case I have a component that's responsible for adding new widgets, or editing existing widgets. My routes are defined like so:
const routes: Routes = [
{
path: 'widget/add',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
{
path: 'widget/:id/edit',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
];
path is a string so it can't accept an array of values or anything like that. Is there a way to specify multiple paths for a single route so I don't have to duplicate the other parts of the route (the component, canActivate, and data parts)?
Note: The paths cannot be changed due to application requirements (i.e. I can't just make a single manage path).
Edit: My ManageWidgetComponent already has the correct logic for handling Create vs. Edit. That part's fine.
I think you could do something like this :
const routes: Routes = ['widget/add', 'widget/:id/edit'].map(path => {
return {
path: path,
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
};
});
you can use this format :
{
path: 'widget/:id/add-edit',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
and in ManageWidgetComponent use fallowing code to check if there is a value for "id"? if there is no value for id so you are adding a new user and if there is a value for "id" so you are editing a user.
this.route.params.subscribe((params: any) => {
if (params.id) {
//////you are editing a user
}else{
///// you are adding a new user
}
}
);
By convention, there is a one to one relationship between a route and a path (they are basically the same thing), So you can't have different paths for a single routes.
But you can have different paths that loads the same component (and that's what you're doing in the example above)
A way to solve this problem would be :
{
path: 'widget/:id',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
Then you can navigate to widget/add or widget/123
And in ManageWidgetComponent:
this.route.params.subscribe((params) => {
if (params.id === 'add') {
// Add new user
} else {
// Edit user with id = +params.id
}
});

How to get data before Router is loaded after Auth Service using angular 2+?

I hope this is not duplicated.
I have this component to Login with a user, that calls a service and gives me a Token.
When the login is success I save the token to localStorage and I get the user Data with getUserData service and save it to a localStorage with saveUderData service.
onLoginSuccess(token: Token) {
this.authService.saveToken(token);
this.authService.getUserData().subscribe(
userData => this.onGetUserDataSuccess(userData)
);
}
onGetUserDataSuccess(userData: UserDataModel) {
this.authService.saveUserData(userData);
this.router.navigate(['/'])
}
The problem is that when I go to the main component the data is not
loaded and I have to refresh the page.
I have this in my main component.
if (localStorage.getItem('_UserToken') && localStorage.getItem('_UserData') && (!this.authService.userToken || !this.authService.userToken.access_token)) {
this.authService.saveToken(JSON.parse(localStorage.getItem('_UserToken')));
this.authService.saveUserData(JSON.parse(localStorage.getItem('_UserData')));
this.userData = this.authService.userData;
}
with this.userData I get the data.
I solved it in another component with Resolve but here I can't do it because i don't know how.
I tried this in onLoginSuccess(token: Token)
this.route.data.subscribe(
userData => console.log(userData['user'])
);
but the userData is undefined
here my routes.
const appRoutes: Routes = [
{path: '', canActivate: [AuthGuardService], data: { title: 'Inicio'}, resolve: {user: UserDataResolve},
children: [
{path: '', component: HomeComponent},
{path: '', component: MenuComponent},
]},
{path: 'login', component: LoginComponent, data: { title: 'Iniciar SesiĆ³n'} },
];
and here is my Resolve
#Injectable()
export class UserDataResolve implements Resolve<UserDataModel> {
constructor(private authService: AuthService) {}
resolve(route: ActivatedRouteSnapshot): Observable<UserDataModel> {
return this.authService.getUserData();
}
}
I hope you can understand me, my english is not the best. thanks =).
Try using a getter instead of a simple property in your component:
get userData(): string {
this.authService.userData;
}
That way any time changes are detected it will get the current userData from your service.

Categories

Resources