Using Router Parameters in URLs with Angular 4 Router - javascript

How to use trying to use these kind of URLs with prefix/foo-bar-1123, prefix/foo-bar-1123/bazquux-123 with Angular 4 Router? The important bits are the numerical id's which I am trying to capture.
I have tried this, but the result is that I get a all data between // characted captured to single variable called categorySlug-:categoryId and subcategorySlug-:subcategoryId.
const routes = [{
path: 'prefix/:categorySlug-:categoryId',
component: MyComponent,
}, {
path: 'prefix/:categorySlug-:categoryId/:subcategorySlug-:subcategoryId',
component: MyComponent,
}]
export const MyRoute: ModuleWithProviders = RouterModule.forChild(routes)
Ultimately I'd like to end up with these kind of variables:
categorySlug=foo-bar
categoryId=1123
subcategorySlug=bazquux
subcategoryId=123
Is this something that the Router supports? Is is possible to extend Router to support these?

Create a custom UrlMatcher which can be used to implement matching and fetch id's from URL. This works at least in Angular 4.4.3.
const categoryUrlMatcher = (url: UrlSegment[], group: UrlSegmentGroup, route: Route): UrlMatchResult => {
if (url.length != 2) {
return null
}
if (url[0].path !== 'prefix') {
return null
}
const categoryMatch = url[1].path.match(/(\w+)-(\d+)$/)
if (categoryMatch) {
return {
consumed: url,
posParams: {
categorySlug: new UrlSegment(categoryMatch[1], {}),
categoryId: new UrlSegment(categoryMatch[2], {})
}}
}
return null
}
const routes: Routes = [
{
matcher: categoryUrlMatcher,
component: MyComponent,
}
]

Related

Check if currentRoute starts with some text(something/something/*...) in Angular

I want to stop loader from loading from few screens and therefore I applied ngIf at routes where loader isn't needed. Here is the code for app.component.ts :
<router-outlet>
<app-spinner></app-spinner>
<ngx-ui-loader *ngIf="!(currentRoute =='/dashboard' || currentRoute == '/vehicle/edit/')"></ngx-ui-loader>
</router-outlet>
app.component.html
this.currentRoute = "";
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.currentRoute = event.url;
}
});
I need to add * to vehicle/edit URL as there can be any vehicle ID while fetching the edit page like : /vehicle/edit/49042/1422, /vehicle/edit/49023/1421 and so on.
How to allow currentRoute accept /vehicle/edit/* ?
Ok, so to answer your question about route that accepts dynamic URLS/all URLS starting with /vehicle/edit/.
The "dummy" approach if you know that your nesting got limit, is to declare multiple routes with params, more-less like this:
const routes: Routes = [
{ path: '/vehicle/edit/', component: VehicleListComponent },
{ path: '/vehicle/edit/:id', component: VehicleEditComponent },
{ path: '/vehicle/edit/:parent/:id', component: VehicleEditComponent },
{ path: '/vehicle/edit/:grandparent/:parent/:id', component: VehicleEditComponent },
];
This will work, because Angular routing stops at very first matching path, so the order of your route declaration is important!
However, if you're dealing with very long nesting capability, better approach is to use custom route matcher:
import { UrlSegment } from '#angular/router';
const nestedCategoryMatcher = (url: UrlSegment[]) => {
// Check if this regex actually match your requirements
const regexMatcher = /^(vehicle\/edit)([\/][0-9]+.+)/;
if (!url.join('/').match(regexMatcher)) return null;
return ({ consumed: url });
}
const routes: Routes = [
{ path: '/vehicle/edit/', component: VehicleListComponent },
{ matcher: nestedCategoryMatcher, component: VehicleEditComponent },
];
And remember, that by using matcher, you will have to retrieve your "params" manually in your components by splitting URL into segments.
this.route.url
.subscribe(segments => {
const urlSegment: UrlSegment[] = (segments as UrlSegment[]);
console.log(urlSegment);
});

How to store nuxtjs dynamically generated routes in vuex store

I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]

Dynamically create route

My app is having some roles e.g Student, Teacher, etc. I have a route defined as
const routes : Routes = [
{ path : '', component : StudentDashboard }
]
I was wondering If I can replace StudentDashboard with TeacherDashboard dynamically based on the role. The data about role is present in a service.
I tried this
const routes : Routes = [
{ path : '', component : true ? StudentDashboard : TeacherDashboard }
]
This was not giving any compilation error. But how can I fetch the role from service so that I can replace condition in ternary expression.
What I am not looking for is
1) Re routing 2) Conditional child component
I am looking for manipulating route definition, I dont know if it is possible or not but giving it try
Why not just put both paths in your routing module:
const routes : Routes = [
{ path : 'teacher', component : TeacherDashboard },
{path:'student', component : StudentDashboard}
];
and when you are navigating you can check from from the service:
if(yourService.role === 'student') {
this.router.navigate(['/student']);
} else {
this.router.navigate(['/teacher']);
}
well if the path should be empty then you can use structural directive ngIf like:
<app-student *ngIf="role === 'student'"></app-student>
<app-teacher *ngIf="role === 'teacher'"></app-teacher>
In the component.ts you can get the role from service:
role: string;
ngOnInit() {
this.role = yourservice.role;
}
I would create another general component called 'dashboard' where in this class there are 2 variables that have type TeacherDashboard and StudentDashboard.
In app-routing I would set { path : ':id', component : DashBoardComponent }
in the Dashboard
student: StudentDashboardComponent;
teacher: TeacherDashboardComponent;
role: boolean;
// httpService is the service file where u call your api
counstructor(private router: Router, private http: HttpService) {}
ngOnInit() {
const id = this.router.url.substring(0);
this.http.searchTeacher(id).subscribe(
res => {
role = true; //true for teacher and false for student
}); // if u get err 404, it doesn't care
this.http.searchStudent(id).subscribe(
res => {
role = false; //true for teacher and false for student
},
err => {
console.log(err);
... do something...
});
-do your staff-
}
U have to adapt this code to your project, but I did that in my project and it work perfectly fine, u can also optimize all this stuff combining teacherDashboard with studentDashboard and with *ngIf on the role u can do all what u want to do.
I hope I was helpful.

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

AngularJS 2: new router and templateProvider practice from Angular 1

I'm new to angular 2. In angular 1 i used ui-router and templateProvider to use different templates in one controller, for example:
templateProvider: function ($localStorage) {
if($localStorage.isAdmin) { //just an example, here could be any condition, like url has some params
return '<admin></admin>';
} else if($localStorage.isManager) {
return '<manager></manager>';
}
},
how can i achieve something similar in angular 2?
for example i have such routes:
{ path: 'customers/:id', component: CustomerDetailsComponent },
{ path: 'customers/:id/edit', component: CustomerDetailsComponent }
how can i in CustomerDetailsComponent check it?
like:
ngOnInit(): void {
this.activatedRoute.params.subscribe(params => {
if (params['edit']) { //how to add a condition? to split edit from show view
/*someTemplateLogic*/
}
});
}
is it possible to do?
so: two different templates for routes:
{ path: 'customers/:id', component: CustomerDetailsComponent },
{ path: 'customers/:id/edit', component: CustomerDetailsComponent }
with edit and without - how to check which route i have, and set such template in one component
<admin *ngIf="isAdmin"></admin
<manager *ngIf="!isAdmin"></manager>
ngOnInit(): void {
this.router.events
.filter(f => f instanceof NavigationEnd)
.forEach(e => this.isAdmin = e.url.endsWith('/edit'));
}

Categories

Resources