react multiple layouts (route roots) with plainroutes - javascript

I'm trying to use plainroutes in order to create a react-router profile. I'm not convinced plainroutes is the most readable way to write code, but I'm trying to give it a fair shot since everyone seems excited about it. I'm having an extremely frustrating time trying to define multiple layouts for my components. Take the following working code:
Working plainroutes example
export const createRoutes = (store) => ({
path : '/',
component : CoreLayout,
childRoutes : [
LoginView(store),
SignupView(store),
Activate(store),
ForgotPw(store),
ConfirmReset(store)
]
}
)
Nothing unexpected happening here. The actual routes are built into the views using the following structure (LoginView for example):
Directory structure of childRoute objects
-/Login
--index.js
--/components
--Loginview.jsx
--Loginview.scss
The index.js files contain little route blocks looking like this:
Example childroute
export default (store) => ({
path : 'activate',
component: ActivateView
})
I'll also include the source of the Login component below, as referred to above. Please note I did try adding path: 'login' to this component but it made no difference.
Login import
export default {
component: LoginView
}
When a user visits /login they see the login component. Easy right? Yep. Now you might notice all those routes look like a group of authentication-related views. I want those views to share a layout. In this case, CoreLayout. That's also working with the above code.
Next step is that I want to add a user dashboard for after users login. This dashboard needs to use a different layout which I'm calling LoggedIn. Naturally, I expected that adding another json object with a similar pattern could accomplish this, so I produced the below code:
Broken multiple layout attempt
export const createRoutes = (store) => ({
path : '/login',
component : CoreLayout,
indexRoute : Login,
childRoutes : [
SignupView(store),
Activate(store),
ForgotPw(store),
ConfirmReset(store)
]
},
{
path : '/',
component : LoggedIn,
indexRoute : Home,
childRoutes : [
]
}
)
The above code does not work as intended. The only path that works are that paths in the second element of the set (the one with the / route). I tried moving some routes from the first element down and the do work when put in the second element... but this obviously doesn't solve my problem.
The most frustrating thing to me is that it seems to me as if I am following the SO posts which deal with this, though its a little difficult to tell for sure because they don't use plainroutes. I'm linking one here for reference.

Typing this out actually helped me work through the problem. Nothing like a good rubber duck. It looks like I misunderstood the nature of the router object. Apparently it needs to be a legitimate object, where I was under the impression it was accepting a collection. Thus, you need to define everything within the scope of a single parent. See below for how I solved it for my particular example:
export const createRoutes = (store) => (
{
path : '/',
component : CoreLayout,
indexRoute : Home,
childRoutes : [
{
path : '/',
component : AuthLayout,
childRoutes : [
LoginView(store),
SignupView(store),
Activate(store),
ForgotPw(store),
ConfirmReset(store)
]
},
{
path : '/admin',
component : LoggedIn,
indexRoute : Home,
childRoutes : [
]
}
]
}
)

Related

In Angular, how can one component to have multiple HTML templates?

I am developing an ecommerce application, and one major feature is that this app should have multiple themes. The total number of themes could be 100 or even more. However, these themes all have the same data (For example: all home page have same banner images, new product, feature product data.) .
I know I can use ng-template or TemplateRef to determine which piece of HTML should display. But since I have over 100 themes, both ng-template or TemplateRef methods will load a lot of extra files. So I think I need some sort of lazy load, when a component loads the data then lazy loads the correct HTML template. So how can I have this work?
Looks like it is possible, all our routes are handled by lazy loaded modules. This is our out-of-the-box route config:
const routes: Routes = [
{ path: '', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }
];
While module lazy has this route config:
const routes: Routes = [
{ path: 'home', component: HomeComponent },
]
While HomeComponent is taken from the declarations of module lazy.
Then define another module, called for example lazy-two with the same route config, and its own HomeComponent.
Finally, you can switch between the modules by using this code:
lazyLoad() {
const routes: Routes = [
{
path: '',
loadChildren: () => import('./lazy-two/lazy-two.module')
.then(m => m.LazyTwoModule)
}
];
this.router.resetConfig(routes);
this.router.navigateByUrl('/home');
}
This will lazy load module lazy-two and refresh the route to /home - you will see the component of the new module displayed.
I couldn't create a stackblitz, some errors occurred probably because of lazy loading. So I ran it locally on my machine and pushed the code to GitHub
EDIT I managed to make a StackBlitz
I recommend used ComponentFactoryResolver to create the components that you need to render.
this.templates = [
{
id: "template-1",
component: Template1,
},
{
id: "template-2",
component: Template2,
},
];
ngOnInit() {
this.templates.forEach((element) => {
this.containerReference.createComponent(
this.factoryResolver.resolveComponentFactory(element.component)
);
});
}
in the .html you should have
<ng-container #containerReference><ng-container>
what about using the same component and styling it different when you select the template?

Angular routing bug - this.router.navigateByUrl() and this.router.navigate()

I am creating angular application that will handle documentation for my GitHub apps, in more complex way than just readme files. I want to redirect user after clicking in topnav dropdown to selected route, but there's problem with router. (I need to redirect with some parameters, but it doesn't work even with simple path reditect). Those methods redirect to target route for like 1 seconds (like excepted), then user got redirected back to root page.
Code:
/* First element is project name, second is category/part of application name */
choices = ["typing_speed_test", "overview"]
json = json
constructor(private router: Router){}
onProjectClick($event){
this.choices[0] = $event.target.innerHTML.trim();
this.choices[1] = "overview";
this.redirect();
}
onCategoryClick($event){
this.choices[1] = $event.target.innerHTML.trim();
this.redirect();
}
redirect(){
this.router.navigateByUrl("404");
}
Routes:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'project/:project_name/:category', component: SubpageComponent },
{ path: '404', component: NotFoundComponent },
//{ path: '**', redirectTo: '404', pathMatch: 'full' }
];
Link to gif with problem: https://imgur.com/a/x2mPxvh
Full code in github repo: https://github.com/Dolidodzik/DocsForMyApps (if you used code from here to answer this question, please point that in your answer)
I think I may did some stupid mistake, because I am pretty new in Angular, but I couldn't do it, because every google question showed me solves for ERRORS, when redirect doesn't work at all, not to bugs like in my situation.
You need to remove the href="#" from your anchor links in your navigation bar. It's causing the browser to reload:
<a class="dropdown-item" *ngFor="let item of categories() | keyvalue">
{{item.key}}
</a>
Also this is a bit weird solution:
this.choices[0] = $event.target.innerHTML.trim();
You better just send the item variable in your function call in your template, and you can then read this in your component event handler:
onProjectClick(item){
this.choices[0] = item.key;
this.choices[1] = "overview";
this.redirect();
}
the problem is your lins look like this:
link
default link behavior causes your app reload from the start. you should use [routerLink]="['someUrl']" instead of href. If you need that href in some cases, consider calling $event.preventDefault() to cancel the native browser navigation

How to define new router?

For this question I am referring to the vuestic-admin template from https://github.com/epicmaxco/vuestic-admin
This template is the most suitable template for me. However, I want to make some changes. To be exactly, I want to add a new page, but dont want this page shown in the sidebar. To do this I need to define new router. My problem is: I dont quite understand how to add new router.
Hier is what I have done:
Firstly, I made a .vue file in components/contact/Contact.vue. Its code:
<template>
<div class="contact">
<div class="row">
<div class="col-md-12">
<p>contact</p>
</div>
</div>
</div>
</template>
<script>
export default { name: 'contact' }
</script>
<style lang="scss"></style>
Secondly, I added a new .js file in store/modules/contact.js. Its code:
import lazyLoading from './lazyLoading'
export default {
name: 'Contact',
path: '/contact',
component: lazyLoading('contact/Contact'),
meta: {
default: false,
title: 'menu.contact',
iconClass: 'vuestic-icon vuestic-icon-extras'
}
}
Thirdly, in the store/modules/menu/index.js file, I changed the state defination by adding pages:
import contact from './contact'
const state = {
pages: [
contact
],
items: [
dashboard,
statistics,
forms,
tables,
ui,
extra,
auth
]
}
Fourthly, in router/index.js file, I made following changes:
export default new Router({
routes: [
...generateRoutesFromMenu(menuModule.state.items),
{path: '*', redirect: { name: getDefaultRoute(menuModule.state.items).name }},
...generateRoutesFromMenu(menuModule.state.pages),
{path: '*', redirect: { name: getDefaultRoute(menuModule.state.pages).name }}
]
})
By compiling the changes above I get the console error: Uncaught TypeError: Cannot read property 'name' of undefined
Based on my knowledge (and my guess) the problem should be in the fourth step.
Could anybody please show me a way out of this problem? Thanks a lot!
Looking at the source here for getDefaultRoute(),
function getDefaultRoute (menu = []) {
let defaultRoute
menu.forEach((item) => {
if (item.meta.default) {
defaultRoute = item
} else if (item.children) {
let defaultChild = item.children.find((i) => i.meta.default)
defaultRoute = defaultChild || defaultRoute
}
})
return defaultRoute
}
I would say that you don't need the line
{path: '*', redirect: { name: getDefaultRoute(menuModule.state.pages).name }}
since for contact you have set default: false and there are no children, which means the function returns null (hence the error message you received).
The framework seems to assume a single list of menu items and one of them to be set as default. But I think your approach should work.
The only potential problem is that it looks like you are changing framework code, which means a bit more work when upgrading to next version of the framework.
A note about upgrading
I just ran the basic install, and looking at the project I have to revise my remark about a potential upgrade problem.
Essentially, this is a template not a framework. The difference is that the installer creates starter code in your project's src folder, which you can freely modify and save to a github repository.
There is no package under node_modules that would overwrite your router changes when you run npm install on your project at a future date.
If epicmaxco create a new version of the template that you wish to use, you would simply create a new project and copy the changes you previously made within src to the new project (perhaps run a diff of the old and new src folders).

How can Vue router get current route path of lazy-loaded modules on page load?

I have a vue app with router set up like:
import index from './components/index.vue';
import http404 from './components/http404.vue';
// module lazy-loading
const panda= () => import(/* webpackChunkName: "group-panda" */ "./components/panda/panda.vue");
// ...
export const appRoute = [
{
path: "",
name: "root",
redirect: '/index'
},
{
path: "/index",
name: "index",
component: index
},
{
path: "/panda",
name: "panda",
component: panda
},
//...
{
path: "**",
name: "http404",
component: http404
}
];
So the panda module is lazy-loaded. However, when I navigate to panda page, a console.log() of this.$route.path in App.vue's mounted() lifecycle only outputs
"/"
instead of
"/panda"
But index page works well, it shows exactly
"/index"
as expected.
So how can Vue router get current path correctly of a lazy-loaded page, when page is initially loaded? Did I miss something?
Edit:
It can, however, catch the correct path after Webpack hot-reloads. It catches "/" on first visit of panda, but after I change something in source code, webpack-dev-server hot-reloads, then it gets "/panda".
So I guess it has something to do with Vue life-cycle.
There is a currentRoute property that worked for me:
this.$router.currentRoute
May be you need to use $route not $router
check here : https://jsfiddle.net/nikleshraut/chyLjpv0/19/
You can also do it by $router this way
https://jsfiddle.net/nikleshraut/chyLjpv0/20/
Use this.$route.path.
Simple and effective.
Hide Header in some components using the current route path.
get current route path using this.$route.path
<navigation v-if="showNavigation"></navigation>
data() {
this.$route.path === '/' ? this.showNavigation = false : this.showNavigation = true
}
If You have similar problem the correct answer is to use router.onReady and then calling your logic concerning path. Below the official Vue router docs:
router.onReady
Signature:
router.onReady(callback, [errorCallback])
This method queues a callback to be called when the router has completed the initial navigation, which means it has resolved all async enter hooks and async components that are associated with the initial route.
This is useful in server-side rendering to ensure consistent output on both the server and the client.
The second argument errorCallback is only supported in 2.4+. It will be called when the initial route resolution runs into an error (e.g. failed to resolve an async component).
Source: https://v3.router.vuejs.org/api/#router-onready
For vue 3 (Composition API)
It can be as simple as route.path if you define the variable route as: const route = useRoute()
Usage example
If you try the following, each time your route path changes it will console log the current path:
<script setup>
import {useRoute} from 'vue-router'
const route = useRoute()
watchEffect(() => console.log(route.path))
</script>

Angular 2 routes resolve different component

Here is my use case:
When i load url /product/123 i want to load component ProductComponent
This is my setup:
RouterModule.forRoot([
{
path: 'product/:productId',
component: ProductComponent
},
{
path: '**', component: NotFoundComponent
},
]),
Now I have added a resolver to check if that product id exists, so my setup looks like this:
RouterModule.forRoot([
{
path: 'product/:productId',
component: ProductComponent,
resolver: {
productResolver: ProductResolver
}
},
{
path: '**', component: NotFoundComponent
},
]),
My resolver checks if that productId parameter exists via api call. The problem i have is that when productId is not found I want to load NotFoundComponent rather than redirecting to different page (i dont want to change url like angular 2 documentation suggests).
Anyone knows how to do that? if not productId found via resolver load NotFoundComponent without changing url/navigate?
I think all you want to do is skip the location change when you navigate to your NotFoundComponent. I'm assuming you've injected the Router into your resolver and are doing something like this when the ID does not exist:
router.navigate(['someUrl']);
Or you might be using the navigateByUrl method. Either way, you can tell the router not to change the URL:
router.navigate(['someUrl'], {skipLocationChange: true});
Don't see why you'd need to load your component via router settings, I'd put it inside the Component that tries to fetch it from the service, and then if it doesn't get a result back toggle some boolean that controls whether the NotFoundComponent gets shown. Some pseudo-ish code below:
ngOnInit(){
if (this.route.snapshot.params && this.route.snapshot.params['id']){
myService.getTheThingById((success)=>{
this.isFound = true;
},(err)=> {
this.isFound = false;
});
}
Then assuming your NotFoundComponent has a selector in it like 'not-found-component' throw it in the template for the component that's calling the service.
<not-found-component *ngIf='!isFound'></not-found-component>
I once faced this problem.
What I did was, in the component, to create 2 other components (in your case, you have ProductComponent, NotFoundComponent, and the other one you want to navigate to, let's say ArticleComponent)
Then I inserted 2 of the components in the main one :
product.component.html
<app-notFound></app-notFound>
<app-article></app-article>
After that, in your ngOnInit, you see if the parameter is there. If he is, then you log it in a property, let's say isParam = true.
Your template becomes
<app-notFound *ngIf="!isParam"></app-notFound>
<app-article *ngIf="isParam"></app-article>
It may not be the best solution out there, but it worked for me !

Categories

Resources