I'm using react-router-dom and react-router-config v4 in a browser app. I need to have a multilevel tab menu from graph of routes (each route either has a component to render or child routes to be shown as next level of menu).
I'd like to get following menu structure:
From a decsription like this:
const routes = [
{
path: "/Tab1",
name: "Tab 01"
component: Tab1
},
{
path: "/Tab2",
name: "Tab 02"
component: Tab12
},
{
path: "/Tab3",
name: "Tab 03"
component: null,
routes: [
{
path: "/Tab3/SubTab1",
name: "SubTab 01"
component: SubTab1
},
{
path: "/Tab3/SubTab2",
name: "SubTab 02"
component: SubTab2
},
{
path: "/Tab3/SubTab3",
name: "SubTab 03"
component: null,
routes: [
...
]
},
]
},
...
];
I came up with a satisfactory solution (following TypeScript snippet is lengthy, but most of it is route graph definition).
import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter, Route, Link, } from "react-router-dom";
import { renderRoutes, RouteConfig, MatchedRoute } from 'react-router-config'
import { Location } from "history";
declare module 'react-router-config'{
interface RouteConfig{
tabName?: string;
defaultSubpath?: string;
}
interface MatchedRoute<T>{
location: Location;
}
}
// todo optimize with memoization?
function getActiveRoutes(match: MatchedRoute<any>):RouteConfig[]{
const currentPath = match.location.pathname;
const routes = match.route.routes;
let activeRoutes:RouteConfig[] = [];
fillActiveRoutes(routes);
return activeRoutes;
function fillActiveRoutes(current: RouteConfig[]){
for(const route of current){
activeRoutes.push(route);
let isActive = false;
if(!route.routes || route.routes.length === 0){
isActive = route.path === currentPath;
} else if(route.routes) {
let isActive = fillActiveRoutes(route.routes);
}
if(isActive === false){
activeRoutes.pop();
} else {
break;
}
}
}
}
const ChildLinks = (match: MatchedRoute<any>) => {
let activeRoutes = getActiveRoutes(match);
return(<div>
{match.route.routes.map((route) => {
let isActive = activeRoutes.some(x => x === route);
let to = route.defaultSubpath || route.path;
let key = 'main-tabs-link-' + route.path;
let label = isActive ? ` [${route.tabName}] ` : ` ${route.tabName} `;
return (<Link to={to} key={key}> {label} </Link>);
})
}
</div>);
}
const EmptyRenderer:React.StatelessComponent<MatchedRoute<any>> = (match: MatchedRoute<any>) => (<div>
{ChildLinks(match)}
{renderRoutes(match.route.routes)}
</div>);
const Root:React.StatelessComponent<MatchedRoute<any>> = (match: MatchedRoute<any>) => (<div>
<h1>Root</h1>
{ EmptyRenderer(match) }
</div>);
const StaticDiv: (content:string) => React.StatelessComponent<MatchedRoute<any>> = (content:string) =>
() => (<div>{content}</div>)
const routes:RouteConfig[] = [
{ component: Root,
routes: [
{ path: '/A/',
tabName: 'A',
exact: true,
component: StaticDiv("A")
},
{ path: '/B/',
tabName: 'B',
defaultSubpath: '/B/2/',
exact: false,
component: EmptyRenderer,
routes: [
{
path: '/B/1/',
exact: true,
tabName: "B1",
component: StaticDiv("B1")
},{
path: '/B/2/',
exact: true,
tabName: "B2",
component: StaticDiv("B2")
},{
path: '/B/3/',
exact: true,
tabName: "B3",
component: StaticDiv("B3")
}]
},
{ path: '/C/',
tabName: 'C',
defaultSubpath: '/C/3/Z/',
exact: false,
component: EmptyRenderer,
routes: [
{
path: '/C/1/',
exact: true,
tabName: "C1",
component: StaticDiv("C1")
},{
path: '/C/2/',
exact: true,
tabName: "C2",
component: StaticDiv("C2")
},{
path: '/C/3/',
defaultSubpath: '/C/3/Z/',
exact: false,
tabName: "C3",
component:EmptyRenderer,
routes: [
{
path: '/C/3/X/',
exact: true,
tabName: "C3X",
component: StaticDiv("C3X")
},{
path: '/C/3/Y/',
exact: true,
tabName: "C3Y",
component: StaticDiv("C3Y")
},{
path: '/C/3/Z/',
exact: true,
tabName: "C3Z",
component: StaticDiv("C3Z")
}]
}]
}
]
}
]
export const Example = () => (<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>);
Lib versions used:
"dependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-router": "^4.2.0",
"react-router-config": "^1.0.0-beta.4",
"react-router-dom": "^4.2.2"
},
"devDependencies": {
"#types/react": "^16.0.38",
"#types/react-dom": "^16.0.4",
"#types/react-router-config": "^1.0.6",
"typescript": "^2.7.1"
}
Related
I'm new to Ionic 5 and trying to use Angular 9 lazy loading with navController.navigateForward, but it's not working.
I don't know if it's something in relation to the way I'm setting up the routers, or what.
And I couldn't find official information about navigateForward anywhere.
When I click "go to details" (below), I get an error Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'details/'
This is the TabsPage:
tabs-routing.module.ts router:
const routes: Routes = [
{
path: '',
component: TabsPage,
children: [
{
path: '',
redirectTo: 'films',
pathMatch: 'full'
},
{
path: 'films',
children: [
{
path: '',
loadChildren: () => import('../films/films.module').then(m => m.FilmsPageModule),
pathMatch: 'full'
}
]
},
{
path: 'people',
children: [
{
path: '',
loadChildren: () => import('../people/people.module').then(m => m.PeoplePageModule),
pathMatch: 'full'
}
]
},
{
path: 'planets',
children: [
{
path: '',
loadChildren: () => import('../planets/planets.module').then(m => m.PlanetsPageModule),
pathMatch: 'full'
}
]
}
]
}
];
films.page.html :
<ion-header>
<ion-toolbar color="primary">
<ion-title>Films</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button expand="full" (click)="openDetails()">Go to Details</ion-button>
<ion-button expand="full" (click)="goToPlanets()">Switch to Planets</ion-button>
</ion-content>
films.page.ts :
import { Component, OnInit } from '#angular/core';
import { NavController, NavParams } from '#ionic/angular';
#Component({
selector: 'app-films',
templateUrl: './films.page.html',
styleUrls: ['./films.page.scss'],
providers: [NavController, NavParams]
})
export class FilmsPage implements OnInit {
constructor(public navCtrl: NavController, public navParams: NavParams) { }
ngOnInit() {
}
openDetails() {
// original code adapted to ionic 5
// this.navCtrl.push('FilmDetailsPage');
this.navCtrl.navigateForward('/details/'); // not working !!
}
goToPlanets() {
// original code adapted to ionic 5
// this.navCtrl.parent.select(2);
this.navCtrl.navigateRoot('/tabs/planets'); // working fine
}
}
films-routing.module.ts router:
const routes: Routes = [
{path: '', component: FilmsPage, children: [
// if I don't comment this, I get an error
// {path: '', redirectTo: 'details'},
{path:'details', children: [
{
path: '', loadChildren: ()=> import('../film-details/film-details.module').then(m => m.FilmDetailsPageModule), pathMatch: 'full'
}
]
}
]}
];
film-details.page.html :
<ion-header>
<ion-toolbar>
<ion-title>filmDetails</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>
I did the same with a detail page but we did it with a variable id.
Your case should be this
const routes: Routes = [
{
path: '',
component: FilmsPage
},
{
path: 'details',
children: [
{
path: '',
loadChildren: () =>
import('../film-details/film-details.module')
.then(m => m.FilmDetailsPageModule),
pathMatch: 'full'
}]
}
];
To set the id instead of "detail" to the route so you can access it via deep linking for the future.
const routes: Routes = [
{
path: '',
component: FilmsPage
},
{
path: ':filmsID',
children: [
{
path: '',
loadChildren: () =>
import('../film-details/film-details.module')
.then(m => m.FilmDetailsPageModule),
}]
}
];
I have the following issue with Jest:
I have this reducer:
[REMOVE_FILTER]: (state: FiltersState, action: Action<string>): FiltersState => {
const { [action.payload!]: deleted, ...activeFilters } = state.activeFilters;
return { ...state, activeFilters, createFilterSelection: undefined, filterCreateOpen: false };
}
When I am trying to test it, it says that I do not have coverage for
...activeFilters } = state.activeFilters;
Here is my test:
test(REMOVE_FILTER, () => {
const action: IAction<string> = {
type: REMOVE_FILTER,
payload: "subprovider"
};
expect(
testReducer({ reducer, state, action })
).toEqual({
...state,
activeFilters: { name: null, branded: null },
createFilterSelection: undefined,
filterCreateOpen: false
});
});
Can someone suggest what I am doing wrong?
I am using:
Jest 23.6.0
Typescript 3.4.0
Redux 4.0.0
React-Redux: 6.0.0
Redux Actions: 2.6.1
Thank you!
P.S: Here is the Jest config:
{
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"globals": {
"window": true,
"document": true
},
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__test__/.*)\\.test\\.(ts|tsx)$",
"notify": true,
"collectCoverageFrom": [
"**/*.{ts,tsx}"
],
"coveragePathIgnorePatterns": [
"(/__e2e__/.*)",
"(/__specs__/.*)",
"(/__test__/.*)",
"(/interfaces/.*)",
"(index.ts)",
"(src/server/app.ts)",
"(src/server/config.ts)",
"(/mock/.*)",
"(data/mock.ts)",
"(automapperConfiguration.ts)",
"(src/app/store/store.ts)",
"(src/app/containers/brand-configuration/.*)"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"setupTestFrameworkScriptFile": "<rootDir>/jestSetup.js",
"testURL": "http://localhost/"
}
The above TS code gets transpilled to:
[REMOVE_FILTER]: (state, action) => {
const _a = state.activeFilters, _b = action.payload, deleted = _a[_b], activeFilters = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
return Object.assign({}, state, { activeFilters, createFilterSelection: undefined, filterCreateOpen: false });
}
I want to create a whole new web application using ngx-admin theme.
I have created a new component named "OPERATION". But, I'm not able to get the routing for it.
Need help in routing..
Thanks in advance...
first you need to import your component and you need to add your component route to the routes in the app-routing.module.ts.
below is the routes in ngx admin app-routing.module.ts.
const routes: Routes = [
{
path: 'pages',
loadChildren: () => import('./pages/pages.module')
.then(m => m.PagesModule),
},
{
path: 'auth',
component: NbAuthComponent,
children: [
{
path: '',
component: NbLoginComponent,
},
{
path: 'login',
component: NbLoginComponent,
},
{
path: 'register',
component: NbRegisterComponent,
},
{
path: 'logout',
component: NbLogoutComponent,
},
{
path: 'request-password',
component: NbRequestPasswordComponent,
},
{
path: 'reset-password',
component: NbResetPasswordComponent,
},
],
},
{ path: '', redirectTo: 'pages', pathMatch: 'full' },
{ path: '**', redirectTo: 'pages' },
];
I have problem with HTML like the images below:
Error :
I clicked a button to move to the page with path: /page/:beecow, the last HTML will show if '/page/:beecow' have an error. The last HTML is 'market' in app-routing
I'm using angular version 4.3.5
// Here is my app-routing
const appRoutes: Routes = [
{
path: '',
component: LandingPageComponent,
pathMatch: 'full'
},
{
path: 'page', // Navigate to here
loadChildren: './page/page.module#PageModule'
},
{
path: 'market',
loadChildren: './market/market.module#MarketModule'
},
{
path: '**', redirectTo: ''
}
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// Here is my page-routing
const routes: Routes = [{
path: '', component: PageComponent,
canActivate: [AuthGuardService],
children: [
{
path: 'upload-product',
component: ProductComponent,
pathMatch: 'full',
data: {
authorities: ['ROLE_STORE'],
pageTitle: 'beecow.store.item.productTitle'
},
canActivate: [RoleGuardService],
canDeactivate: [NavigationService]
},
{
path: ':beecow', // if The component have error, html expanded
component: ManageItemComponent
}
]
}];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageRoutingModule { }
I am trying to set URLs to my items got via json.
So I have a structure like that:
<div class="releases-component">
<div *ngFor="let release of releases" [routerLink]="['/releases', { id:release.id }]">
<img src="{{release.image}}" alt="Image release">
<h3>{{release.name}}</h3>
<span>{{release.year}}</span>
</div>
</div>
And I've got json of type:
[
{
"id":"release-1",
"name": "Release1 name",
"image": "./cover1.jpg",
"year": "2014"
},
{
"id":"release-2",
"name": "Release2 name",
"image": "./release2.jpg",
"year": "2015"
}
]
My router:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'releases', component: ReleasesComponent },
{ path: 'distro', component: DistroComponent },
{ path: 'contacts', component: ContactsComponent }
];
The problem is that when I click on any *ngFor generated div I get url like
http://localhost:3000/releases;id=release-1
And I want it to look like
http://localhost:3000/releases/release-1
Didn't manage to find a working solution in Angular2 to me.
There are few small syntax mistakes in the code above, here is the code with the fixes.
Structure:
<div class="releases-component">
<div *ngFor="let release of releases" [routerLink]="['/releases',release.id ]">
<img src="{{release.image}}" alt="Image release">
<h3>{{release.name}}</h3>
<span>{{release.year}}</span>
</div>
</div>
Router:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'releases/:id', component: ReleasesComponent },
{ path: 'distro', component: DistroComponent },
{ path: 'contacts', component: ContactsComponent }
];
Here is a one page similar working example plunkr code: http://plnkr.co/edit/UyNkK9?p=preview,
you can see the url changes on this plunkr url,
http://run.plnkr.co/plunks/UyNkK9/
Here is how the url will look after you click on releases, http://run.plnkr.co/plunks/UyNkK9/releases/release-1
html:
<div class="releases-component">
<div *ngFor="let release of releases"
(click)=onSelect(release)>
<img src="{{release.image}}" alt="Image release">
<h3>{{release.name}}</h3>
<span>{{release.year}}</span>
</div
</div>
routes:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'releases/:id', component: ReleasesComponent },
{ path: 'distro', component: DistroComponent },
{ path: 'contacts', component: ContactsComponent }
];
component:
import {Router} from '#angular/router';
...
...
constructor(private router: Router) {}
onSelect(release): any {
this.router.navigate(['/release', release.id]);
}