Vuejs + vue-router, pass data between views like http post - vue.js

I'm try to pass data between Vuejs views with vue-router.
//View1.vue
route: {
data: function (transition) {
transition.next({
message: "this is it!!"
});
}
}
I call next wiew with a click action button with:
//View1.vue
methods:{
showResult: function(){
this.$router.go('/View2');
}
}
but the data are not filled in the next view:
//View2.vue
<template>
<p>Message: {{ message }}</p>
</template>
Does somebody knows what's wrong with my usage of vue-router? I don't think I need to pass through services for this, right?
Working examples on jsfiddle (or jsbin, etc) are welcome :D

If View2 is a child component you can pass it using props:
//View1.vue
<view2-component :passedData='message'></view2-component>
Alternatively, I believe if you set data on the $route object from View1, since that object is shared between all vue instances, I believe it will be available application-wide.
//View1.vue
this.$router.myProps.message = message
But arguably the better way to share data is use a POJO - plain old javascript object and bind it to both views. To do this you typically need a shared state object and you can if you wish use Vuex for this although it is a little more complicated than a POJO.

I know this has already been answered, but if someone is here looking for a way to pass data to a route from a router, I use Meta Data.
Not sure if this is what the questioner meant or not but I think it is?
I personally prefer this to props just because I am more used to using it.
It allows for data to be easily passed and received without having to modify children.
Anyway here is a snippit and link!
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Homepage',
meta: {
logo:{
"/imgs/Normal-Logo.png"
}
}
},
{
path: '/admin',
name: 'Admin',
meta: {
logo:{
"/imgs/Admin-Logo.png"
}
}
},
]
})
In any children who want to use vars:
<logo :src="this.$route.meta.logo"/>
Ref:
https://router.vuejs.org/guide/advanced/meta.html

Related

How to use VueRouter with Storybook

I'm trying to write a story for a component that references this.$route.params. I'm not sure how to synthetically define this.$route in the context of a story. I think the solution is to use decorators, but all the examples in the docs focus on rendering, like adding a wrapping <div> etc. I'm not sure how to inject values.
I also found this project which appears designed for this exact situation, but it hasn't been maintained in years and README references outdated syntax that doesn't match modern versions of Storybook, so I don't think it's an option.
Here's what doesn't work:
import AssetShow from '../app/javascript/src/site/components/assets/Show'
export default {
title: 'Site/AssetShow',
component: AssetShow,
parameters: {
}
};
export const CustomerSpotlight = () => ({
components: { AssetShow },
template: '<AssetShow />',
});
import Vue from 'vue'
import VueRouter from 'vue-router'
import StoryRouter from 'storybook-vue-router';
CustomerSpotlight.decorators = [
(story) => {
Vue.use(VueRouter)
return {
components: { story },
template: '<story />'
}
}
];
The component I'm writing the story for has this:
mounted() {
axios.get(`.../bla/${this.$route.params.id}.json`)
},
...which causes Storybook to throw this error: TypeError: Cannot read properties of undefined (reading 'params')
I suppose that your intention is to do something with the story's component based on the route parameters?
If that is the case, then I don't think you need to define the route.params within the story context. I suggest either keeping that code within the component itself, or create an option within the story for the user to simulate adding parameters to the path. Which you can simply have as an input text / select field that you send down to the component as a prop.

How to implement permalinks in a serverless Vue SPA

I made this little app which is a simple Vue serverless SPA. I wish to pass an array of strings and a an array of numbers through the URL so that I can share "states" of the websites with colleagues. I understand vue-routercan update the route's parameters as per their documentation, but I do not have enough perspective to see how to implement this to solve my problem. I would love some help or guidance so I actually learn from this. Thank you all.
EDIT: after Mr. Luis Brito's hint.
I added the following to my code (props)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: require('#/views/Home').default,
props: { myNumbers: [1,2,3]}
},
]
})
in a component I did
mounted () {
this.$router.push({ name: 'myNumbers', params: {myNumbers: [1,2,3,4] }})
const myNumbers = this.$route.params.myNumbers
console.log(myNumbers);
}
But now my App throws a Vue-Router error [vue-router] Route with name 'myNumbers' does not exist but it does console log the numbers I pushed. Is it possible to make my app look for props and only if they are there to do something with them? Otherwise I get a white screen.
The way I see to solve this problem is use vue-router with a route that passes props to the component, that prop can be an object containing the two arrays that you mentioned.
Referer to Vue Router - Obect Mode
Here is an example for the router:
const routes = {
path: '/promotion/from-newsletter',
component: Promotion,
props: { myNumbers: [1,2,3,5,8], myStrings: ['first', 'second', 'third']
}
}
On the component side, you can access those props on created() lifecycle hook, or mounted()
props: ['myNumbers','myStrings '],
mounted() {
if ( this.myNumbers !== undefined && this.myStrings !== undefined ) {
console.log(thys.myNumbers, this.myStrings);
}
}
So that http://localhost:8080/?myStrings=layer1,layer2,layer3&myNumbers=-1,2,3-4 would console log layer 1-3 and the numbers.
EDITED
TO pass the values programatically, would be better to use Function Mode to capture the URL params and pass it to component. Another way would be to create a component where you can input the numbers and strings that you want, and then call the router and pass those values to the route for the final destination component.

Is there any way to have dynamic routes/components in vuejs router?

Hi beautiful Vuejs developers out there!
I have a little problem with routing many Vue components/pages dynamically. In this scenario I am using nested routes to have a couple of routes for my layout components and hundreds of child routes for my pages and as you can imagine I'll have to type many child routes statically or manually, and then add more when I need more child routes in the future code changes but I need a solution to simplify/solve this problem with more efficient/better way like adding those routes from what user types after the layout in the url... here is my example code code:
const routes: RouteRecordRaw[] = [
{
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: 'dashboard',
component: () => import('pages/student/Dashboard.vue'),
},
{
path: 'profile',
component: () => import('pages/student/Profile.vue'),
},
],
},
}
As you see in this code I have a layout named Student and it has two children but I'll have to type manually hundreds of child routes for this layout and other layouts is there any way to dynamically set up those routes with what users enter after the layout name like /student/dashboard or /layout/page and match it with a component name? I mean like params in Angular, can I use the param value itself inside the router to say?
{
path: ':pagename',
component: (pagename) => import('pages/student/' + pagename + '.vue'),
},
let me know if there is an efficient way to solve this problem.
Thanks in advance!
I would, personally, not use this, or advise such an approach, nor have I done it, but this idea came to me when I read your question:
My best guess would be to have a handler component which renders a component dynamically based on a route parameter (say you have /:subpage as a child to /student and the handler component is set to that route), and an exception handler around that to show a 404 page when the user types in an inexistent/unsupported route.
For example, I would dynamically import the component by the route parameter into a predefined let (e.g. let SubpageComponent; outside the try catch block), have a try catch block around the dynamic import assignment for the respective error where catch would set the variable to a 404 page. Then I would add the SubpageComponent into the data() of the component doing the rendering of the route.
Edit
I've written out come code that, maybe, makes sense.
It's based on https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
your routes definition, changed
const routes: RouteRecordRaw[] = [
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: '/:subpage',
component: () => import('pages/student/SubpageRenderer.vue'),
props: true,
},
],
},
]
SubpageRenderer.vue
<script>
export default {
props: ['subpage'],
data() {
return {
currentSubpage: () => import(`./${subpage}.vue`)
}
}
}
</script>
<template>
<component :is="currentSubpage"></component>
</template>
Instead of using the currentSubpage import, you can also use the subpage route prop to bind :is if subpage is the name of a registered component.
Since this would get only "dashboard" from the route, you'd need some namespacing, like "student-dashboard" with the help of template literals. You could make currentSubpage into a template literal that creates the student-${subpage} name.
I'd probably recommend importing the options object of the component designated by the subpage route parameter instead of registering all the components - if you're registering them, you might as well use vue-router the usual way :)
Also, I only think this could work! It should be tested out, and perhaps casing should be kept in mind, and maybe the Layout suffix as well (subpage will probably be all lowercase, and you'll probably have the components named in PascalCase). After uppercasing the first letter, this could also obviously lead to both /student/Dashboard and /student/dashboard being valid routes

Same VueX store module registered from components on two pages

I have encountered a weird case when using VueX and Vue-Router and I am not too sure how to cleanly solve it.
I have a component (let's call it "ComponentWithStore") that registers a named store module a bit like this : (the actual content of the store don't matter. obviously in this toy example using VueX is overkill, but this is a very simplified version of a much more complexe app where using VueX makes sense)
// ComponentWithStore.vue
<script>
import module from './componentStore.js';
export default {
name: 'ComponentWithStore',
beforeCreate() {
this.$store.registerModule(module.name, module);
},
beforeDestroy() {
this.$store.unregisterModule(module.name);
}
}
</script>
Then I place this component in a view (or page) which is then associated to a route (let's call this page "Home").
// Home.vue
<template>
<div class="home">
Home
<ComponentWithStore/>
</div>
</template>
<script>
import ComponentWithStore from '#/components/ComponentWithStore.vue';
export default {
name: "Home",
components: { ComponentWithStore }
};
</script>
So far so good, when I visit the Home route, the store module is registered, and when I leave the Home route the store module is cleaned up.
Let's say I then create a new view (page), let's call it "About", and this new About page is basically identical to Home.vue, in that it also uses ComponentWithStore.
// About.vue
<template>
<div class="about">
About
<ComponentWithStore/>
</div>
</template>
<script>
import ComponentWithStore from '#/components/ComponentWithStore.vue';
export default {
name: "About",
components: { ComponentWithStore }
};
</script>
Now I encounter the following error when navigating from Home to About :
vuex.esm.js?2f62:709 [vuex] duplicate namespace myComponentStore/ for the namespaced module myComponentStore
What happens is that the store module for "About" is registered before the store module for "Home" is unregistered, hence the duplicate namespace error.
So I understand well what the issue is, however I am unsure what would be the cleanest solution to solve this situation. All ideas are welcome
A full sample may be found here : https://github.com/mmgagnon/vue-module-router-clash
To use, simply run it and switch between the Home and About pages.
As you have mentioned, the issue is due to the ordering of the hooks. You just need to use the correct hooks to ensure that the old component unregisters the module first before the new component registers it again.
At a high level, here is the order of hooks in your situation when navigating from Home to About:
About beforeCreate
About created
Home beforeDestroy
Home destroyed
About mounted
So you can register the module in the mounted hook and unregister it in either beforeDestroy or destroyed.
I haven't tested this though. It might not work if your component requires access to the store after it is created and before it is mounted.
A better approach is to create an abstraction to register and unregister modules that allows for overlaps.
Untested, but something like this might work:
function RegistrationPlugin(store) {
const modules = new Map()
store.registerModuleSafely = function (name, module) {
const count = modules.get(name) || 0
if (count === 0) {
store.registerModule(name, module)
}
modules.set(name, count + 1)
}
store.unregisterModuleSafely = function (name) {
const count = modules.get(name) || 0
if (count === 1) {
store.unregisterModule(name)
modules.delete(name)
} else if (count > 1) {
modules.set(name, count - 1)
}
}
}
Specify the plugin when you create your store:
const store = new Vuex.Store({
plugins: [RegistrationPlugin]
})
Now register and unregister your modules like this:
beforeCreate() {
this.$store.registerModuleSafely(module.name, module)
},
destroyed() {
this.$store.unregisterModuleSafely(module.name)
}

Aurelia: How navigate between child routes

I'm trying to navigate from one child route to another, but I continually get Route not found. My primary question: how to navigate between child views?
Below is the code, and I'll have additional questions below, too.
App Mode-View
App Class:
export class App {
configureRouter(config, router) {
config.title = 'My Site';
config.map([
{ route: ['','job-view'], name: 'jobView', moduleId: './job-view', nav: true, title:'Jobs'},
{ route: ['services'], name: 'services', moduleId: './services', nav: true, title:'Services'}
]);
this.router = router;
this.router.refreshNavigation();
}
}
Q.2: Why do we need to save router here if it's always accessible from aurelia-router?
App Page:
<template>
<require from='./nav-bar'></require>
<nav-bar router.bind="router"></nav-bar>
<div class="container">
<router-view></router-view>
</div>
</template>
Ok, so now that we have our root page view and nav defined, let's define the job-view MV.
JobView Class:
export class JobView {
configureRouter(config, router) {
config.map([
{ route: ['','jobs'], name: 'jobs', moduleId: './jobs', nav: false, title:'Jobs', settings:{icon:'glyphicon glyphicon-align-justify'} },
{ route: ['job/:id'], name: 'job', moduleId: './job', nav: false, title:'Job Details'}
]);
this.router = router; //WHY SAVE THIS?
this.router.refreshNavigation();
}
}
JobView Page:
<template>
<router-view></router-view>
</template>
Now, here are the child views. My assumption that is that routing that occurs should be relative to job-view. That's what I want, ideally.
Jobs Class (a bunch of code removed for brevity):
import {Router} from 'aurelia-router';
#inject(Router)
export class Jobs {
constructor(router) {
this.router = router;
}
toJob(id) {
// this.router.navigateToRoute("job", {id:id}); // ERROR - ROUTE NOT FOUND
this.router.navigate("#/job-view/job/".concat(id)); // THIS WORKS
}
}
Q.3: I've seen both router.navigateToRoute and router.navigate referenced, but no indication when to use either or what the difference is, and the document doesn't seen to explain. Which should be used? Docs
Jobs Page:
Details for jobs.html are irrelevant, so not listing them here.
Finally, the job view:
Job Class:
Nothing relevant for job.js, so not listing code. At most I may perform navigation back to jobs, but that's handled below in the page.
Job Page:
<!-- a bunch of html //-->
<!-- HOW TO USE ROUTER IN HTML, NOT BELOW URL HREF? //-->
Print Jobs
<!-- a bunch of html //-->
Q.4: Again, I'd like routing to relative, even in the HTML page. The above won't work, so what should I use?
There was a proposed answer in a similar question, but injecting job-view into job and using job-view's saved router didn't work either.
By the way, if I manually navigate to http://localhost:3000/#/job-view/job/3 the page loads fine, so it's clear something with the router.
Note to mod:
A similar question was ask at How to access child router in Aurelia? but it wasn't answered with a solution that works.
I will try to answer you questions one by one below.
I will start from Q2
Q.2: Why do we need to save router here if it's always accessible from
aurelia-router?
So in your App Mode-View App Class you are referencing router property in your view: <nav-bar router.bind="router"></nav-bar>, that's why you need to declare the property to use it then. In the second view you are not so you don't need it :-)
The property router is also added when you need do something with the router in main.ts / main.js - the starting point of you application. This is because the router is configured for the first time there, and injection will not work in constructor, so you need to save this property to get it in configureRouter(..) method (note: this was a case before beta 1, I don't know if it's still there now).
In your code you have a call for this.router.refreshNavigation(); this will ensure that your router is updated with new information regarding current location of the routing.
Q.3: I've seen both router.navigateToRoute and router.navigate referenced, but no indication when to use either or what the difference is, and the document doesn't seen to explain. Which should be used? Docs
The method router.navigate(fragment: string, options?: any) uses an URL fragment not a route name to navigate, so e.g. router.navigate('#/app/child', {...<options - not params od the URL>...}). This method must be used to navigate absolutely between routers, and to access parent URL etc.
If you only are navigating around the current router you will always use router.navigateToRoute(route: string, params?: any, options?: any). This method is using a route name, not URL, co we just put there a name of route in the custom routing map (custom means the current child routing map, or current main routing regarding the URL location we are on the page). Here you can pass URL params in more convenient way, as you can see. You can use a params object instead of concatenating the URL with params.
Q.4: Again, I'd like routing to relative, even in the HTML page. The above won't work, so what should I use?
In Aurelia we are not using href attribute of the a tag directly for navigation. As already answered by Brandon, you have to use route-href attribute, which is probably nowhere documented just appears around on forums and portals. This is equivalent of the router.navigateToRoute(route: string, params?: any, options?: any), so you cannot use it to navigate between routers in such case you can use custom attribute or just use click.triger="navTo('#/app/child')", where the navTo() method is implemented in your View-Model and looks like this:
public navTo(routeName: string) {
// Assuming you are injecting router somewhere in the constructor
this.router.navigateToRoute(routeName);
}
And finally your topic question:
Q.1: How navigate between child routes
Probably now you know the answer, just use: router.navigate(fragment: string, options?: any) with absolute URL.
Below example custom attribute to solve this:
import {inject} from "aurelia-dependency-injection";
import {Router} from "aurelia-router";
import {customAttribute} from "aurelia-framework";
#customAttribute('nav-href')
#inject(Element, Router)
export class NavHref {
private value: string = '';
constructor(private element: Element, private router: Router) {
let $this = this;
element.addEventListener('click', () => {
if ($this.value === 'back') {
$this.router.navigateBack();
} else {
// expression
$this.router.navigate($this.value);
}
});
}
public valueChanged(newValue: string, oldValue: string) {
this.value = newValue;
}
}
First you need to import it in your HTML, I named my file nav.href.ts:
<require from="common/nav.href"></require>
Then just use it in you HTML code:
<a nav-href="#/home/any-location">My link to any location</a>
Hope this will help you, cheers :-)
The way I've configured child routers in my Aurelia apps is in this fashion:
app.js
export class App {
configureRouter(config, router) {
config.map([
{ route: ['','home'], name: 'home', moduleId: 'home', nav: true },
{ route: 'work', name: 'work', moduleId: 'work/work-section', nav: true },
]);
this.router = router;
}
}
work/work-section.js
export class WorkSection {
configureRouter(config, router) {
config.map([
{ route: '', moduleId: './work-list', nav: false },
{ route: ':slug', name: 'workDetail', moduleId: './work-detail', nav: false }
]);
this.router = router;
};
}
The corresponding "work-section.html" is simply a Router View:
<template>
<router-view></router-view>
</template>
In this use case, I have my main app.js which defines a child router, "work", which sits in a subdirectory under src.
When the route /work is activated, the child router, "work-section" takes over, activating the "work-list" route, as the path segments end there: /work
"work-list.js" retrieves items from a REST API then passes the data to the view.
From there, I'm able to use route binding to get to a "work detail" in the "work-list.html" view:
<div repeat.for="sample of item.samples">
<a route-href="route: workDetail; params.bind: { slug: sample.slug }">
${sample.title}
</a>
</div>
Hope that helps you out. I'm not 100% certain if you're asking how to do a redirect, or how to nav to a child route from the view, so please correct me if I'm wrong and I'll do my best to update my answer for you.