Is it possible to use Vue.js "stuff" inside of templates? I am trying to do this, but every time I try, nothing renders and a I get a worthless message in the console that says
[Vue warn]: Error when rendering anonymous component:
Nothing gets rendered to the screen and there is no indication as to what Vue.js is having a problem with. Below is the code for the template:
const list = {
template: '<table><tr v-for="item in list"><td><router-link v-on:click.native="doSomething" v-bind:to="\'/item/\' + item.id">{{ item.title }}</router-link></td></tr></table>'
}
const viewItem = {
template: '<div>Not Implemented</div>'
}
const router = new VueRouter({
routes: [
{ path: '/item/:id', component: viewItem },
{ path: '/list', component: list }
]
})
const app = new Vue(
{
router: router,
data: {
list: [],
test: "testing"
},
methods: {
getList: //method to get data and populate list. This working correctly.
doSomething: //method for fetching details.
}
}
Even doing something simple like {{ test }} in the template results in the same type of problem. If I use some static HTML, things work alright. If you can't do this inside of a template, how can you accomplish non-static HTML?
You may of course use Vue inside of templates.
The error you are receiving is because you incorrectly setting the data property. You have set list and test to the parent component, while you are trying to access them in your child list component. This produces an error while Vue tries to render the component, and thus the component is not rendered at all.
To fix just change your list component to the following:
const list = {
data () {
return {
list: [],
test: "Now you should see me :)"
}
},
template: '<table><tr v-for="item in list"><td><router-link v-on:click.native="doSomething" v-bind:to="\'/item/\' + item.id">{{ item.title }}</router-link></td></tr></table>'
}
You will also need to move your methods to the component where you are using them.
Related
AS title sates, I don't so much need a solution but I don't understand why I'm getting the undesired result;
running v2 vue.js
I have a vue component in a single component file.
Basically the vue should render data (currently being imported from "excerciseModules" this is in JSON format).
IT's dynamic so based on the url path it determines what to pull out of the json and then load it in the page, but the rendering is being done prior to this, and I'm unsure why. I've created other views that conceptually do the samething and they work fine. I dont understand why this is different.
I chose the way so I didn't have to create a ton of routes but could handle the logic in one view component (this one below).
Quesiton is why is the data loading empty (it's loading using the empty "TrainingModules" on first load, and thereafter it loads "old" data.
Example url path is "https...../module1" = page loads empty
NEXT
url path is "https..../module 2" = page loads module 1
NEXT
url path is "https..../module 1" = page loads module 2
//My route
{
path: '/excercises/:type',
name: 'excercises',
props: {
},
component: () => import( /* webpackChunkName: "about" */ '../views/training/Excercises.vue')
}
<template>
<div class="relatedTraining">
<div class="white section">
<div class="row">
<div class="col s12 l3" v-for="(item, index) in trainingModules" :key="index">
<div class="card">
<div class="card-content">
<span class="card-title"> {{ item.title }}</span>
<p>{{ item.excercise }}</p>
</div>
<div class="card-action">
<router-link class="" to="/Grip">Start</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
console.log('script');
let trainingModules; //when initialized this is empty, but I would expect it to not be when the vue is rendered due to the beforeMount() in the component options. What gives?
/* eslint-disable */
let init = (params) => {
console.log('init');
console.log(trainingModules);
trainingModules = excerciseModules[params.type];
//return trainingModules
}
import { getRandom, randomImage } from '../../js/functions';
import { excerciseModules } from '../excercises/excercises_content.js'; //placeholder for JSON
export default {
name: 'excercises',
components: {
},
props: {
},
methods: {
getRandom,
randomImage,
init
},
data() {
return {
trainingModules,
}
},
beforeMount(){
console.log('before mount');
init(this.$route.params);
},
updated(){
console.log('updated');
},
mounted() {
console.log('mounted');
//console.log(trainingModules);
}
}
</script>
I can't tell you why your code is not working because it is an incomplete example but I can walk you through a minimal working example that does what you are trying to accomplish.
The first thing you want to do, is to ensure your vue-router is configured correctly.
export default new Router({
mode: "history",
routes: [
{
path: "/",
component: Hello
},
{
path: "/dynamic/:type",
component: DynamicParam,
props: true
}
]
});
Here I have a route configured that has a dynamic route matching with a parameter, often called a slug, with the name type. By using the : before the slug in the path, I tell vue-router that I want it to be a route parameter. I also set props: true because that enables the slug value to be provided to my DynamicParam component as a prop. This is very convenient.
My DynamicParam component looks like this:
<template>
<div>
<ul>
<li v-for="t in things" :key="t">{{ t }}</li>
</ul>
</div>
</template>
<script>
const collectionOfThings = {
a: ["a1", "a2", "a3"],
b: ["b1", "b2"],
c: [],
};
export default {
props: ["type"],
data() {
return {
things: [],
};
},
watch: {
type: {
handler(t) {
this.things = collectionOfThings[t];
},
immediate: true,
},
},
};
</script>
As you can see, I have a prop that matches the name of the slug available on this component. Whenever the 'slug' in the url changes, so will my prop. In order to react to those changes, I setup a watcher to call some bit of code. This is where you can make your fetch/axios/xhr call to get real data. But since you are temporarily loading data from a JSON file, I'm doing something similar to you here. I assign this data to a data value on the component whenever the watcher detects a change (or the first time because I have immediate: true set.
I created a codesandbox with a working demo of this: https://codesandbox.io/s/vue-routing-example-forked-zesye
PS: You'll find people are more receptive and eager to help when a minimal example question is created to isolate the problematic code. You can read more about that here: https://stackoverflow.com/help/minimal-reproducible-example
I’ve been experimenting with vue.js and I'm having difficulty accessing JS object values in components when routing.
Using this repo to experiment, https://github.com/johnayeni/filter-app-vue-js, I'm just trying to replicate a basic a “product list” and “product description” app, but I can't get it working. The repo's homepage (the SearchPage.vue component) serves as the "product list," and I'm just trying to add the "product description" component to display only one item at a time.
I've added a "description page" component (calling it "item.vue") to allow a user to click on one of the languages/frameworks that will then route to item.vue to just display that specific object's associated information (item.name, item.logo, etc.), i.e., and not display any of the other languages.
Following some tutorials, here's what I've tried:
First, I added ids to the JS objects (found in data/data.js), i.e., id:'1'.
const data = [
{
id: '1',
name: 'vue js',
logo: 'http://... .png',
stack: [ 'framework', 'frontend', 'web', 'mobile' ],
},
{
id: '2',
name: 'react js',
logo: 'http://... .png',
stack: [ 'framework', 'frontend', 'web', 'mobile' ]
},
...
];
export default data
Then, I wrapped the item.name (in ItemCard.vue) in router-link tags:
<router-link :to="'/item/'+item.id"> {{ item.name}} </router-link>
I then added a new path in router/index.js:
{
path: './item/:id',
component: item,
props: true
}
But, when that router-link is clicked I can only access the ".id" (via $route.params.id), but I can't get .name or .logo. How do I access the other values (i.e. item.name, item.logo, etc.)? I have a feeling I'm going down the wrong track here.
Thank you so much for your help.
The only reason you have access the id because it's an url param: ./item/:id.
You have a couple options here, which depends on what you're trying to accomplish:
As suggested by #dziraf, you can use vuex to create a store, which in turn would give you access to all the data at any point in your app:
export default {
computed: {
data() {
return this.$store.data;
}
}
}
Learn more here: https://vuex.vuejs.org/
As an alternative, you can just import your data, and grab the correct item by its id:
import data from './data.js';
export default {
computed: {
data() {
return data.find(d => d.id === this.$route.params.id);
}
}
}
Just depends on what you're trying to do.
I guess you just need a wrapper component that takes the desired item from the URL and renders the proper item. Let's say an ItemWrapper:
<template>
<item-card :item="item"></item-card>
</template>
<script>
import ItemCard from './ItemCard.vue';
import data from '../data/data';
export default {
components: {
ItemCard,
},
props: {
stackNameUrl: {
required: true,
type: String,
},
},
data() {
return {
item: {},
}
},
computed: {
stackName() {
return decodeURI(this.stackNameUrl);
}
},
created() {
this.item = data.find( fw => fw.name === this.stackName);
}
}
</script>
<style>
</style>
This component takes a prop which is a stack/fw name uri encoded, decodes it, finds the fw from data based on such string, and renders an ItemCard with the fw item.
For this to work we need to setup the router so /item/vue js f.i. renders ItemWrapper with 'vue js' as the stackNameUrl prop. To do so, the important bit is to set props as true:
import Vue from 'vue';
import Router from 'vue-router';
import SearchPage from '#/components/SearchPage';
import ItemWrapper from '#/components/ItemWrapper';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'SearchPage',
component: SearchPage
},
{
path: '/item/:stackNameUrl',
name: 'ItemWrapper',
component: ItemWrapper,
props: true,
},
]
});
Now we need to modify SearchPage.vue to let the stack boxes act as links. Instead of:
<!-- iterate data -->
<item-card v-for="(item, index) in filteredData" :key="index" :item="item"></item-card>
we now place:
<template v-for="(item, index) in filteredData" >
<router-link :to="'/item/' + item.name" :key="index">
<item-card :key="index" :item="item"></item-card>
</router-link>
</template>
So now every component is placed within a link to item/name.
And voilá.
Some considerations:
the :param is key for the vue router to work. You wanted to use it to render the ItemCard itself. That could work, but you would need to retrieve the fw from data from the component created(). This ties your card component with data.js which is bad, because such component is meant to be reusable, and take an item param is much better than go grabbing data from a file in such scenario. So a ItemWrapper was created that sort of proxies the request and pick the correct framework for the card.
You should still check for cases when an user types a bad string.
Explore Vue in depth before going for vuex solutions. Vuex is great but usually leads to brittle code and shouldn't be overused.
I have a classic Vue Component like this
Vue.component('bar', {
template: `<div class="bar"></div>`,
data () {
return {
blocks: [
]
}
}
});
And also i have Vue Instance like this.
new Vue ({
el: '#app',
data: {
value: 1
},
methods: {
add: function() {
// here to do
}
}
});
When add function work, i have to add to data component blocks value. I can't use Vuex and what is this other solutions?
Since this is a parent -> child communication, you can very simply use props:
Store blocks on the parent component.
Pass blocks as a prop to bar component.
Do any additional processing using computed properties.
Display data using child's template.
Here's some guiding:
Define your component with props:
Vue.component('bar', {
template: `<div class="bar">{{blocks.length}}</div>`,
props: ['blocks']
});
Define blocks on the parent (in your example, the main vue component):
new Vue ({
el: '#app',
data: {
blocks: []
},
methods: {
add: function() {
// do something with blocks
}
}
});
Pass data down to the component:
<bar :blocks="blocks"></bar>
You can use the way which called “Event bus”
https://alligator.io/vuejs/global-event-bus/
Or you can create your own state management
https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
But i highly recommend you too use vuex, since it will become hard to manage events and state when the project grows
I have a basic Vue2 component and I am trying to pass data from the child to the parent component using $emit. Note, my-component contains a table where when I click on a row, onRowClick fires successfully, outputting 'foobar' to the console. For some reason, I'm not able to get the parent method to fire on $emit and success isn't being logged to console. Any idea why this is?
import Vue from 'vue';
import MyComponent from "./components/MyComponent.vue";
window.onload = function () {
var main = new Vue({
el: '#app-v2',
components: { MyComponent },
methods: {
onCouponApplied(data) {
console.log("Success, coupon applied, " + data);
this.saying = "Sunglasses are " + data; //Sunglasses are cool
}
},
data: {
contactEditPage: {
saying: ""
}
}
});
}
MyComponent.vue
export default {
methods: {
onRowClick(event){
this.$emit('applied', 'Cool');
console.log("foobar");
}
HTML
<div id="app-v2">
<my-component #applied="onCouponApplied"></my-component>
{{ saying }} //trying to output: sunglasses are cool
</div>
I faced the same problem. I fixed this issue by using $root.
For example in parent Vue component
mounted(){
this.$root.$on('event-name',function(value){
// code
});
},
And child component should be like this:
this.$root.$emit('event-name',[data]);
Hope can help you.
I would like to use the same component for different routes in a Vue.js application.
I currently have something like this:
main.js
const routes = [
{ path: '/route-1', name: 'route-1', component: MyComponent },
{ path: '/route-2', name: 'route-2', component: MyComponent },
{ path: '/route-3', name: 'route-3', component: MyComponent },
]
const router = new VueRouter({
routes
})
myComponent.vue
<ul>
<li><router-link to="/route-1">Route 1</router-link></li>
<li><router-link to="/route-2">Route 2</router-link></li>
<li><router-link to="/route-3">Route 3</router-link></li>
</ul>
When I type the route manually in the browser, everything is working well, but when I try to navigate between the routes using some of these router-generated-links, nothing happens. The route changes but the content is still the same. Any idea how I can solve this?
Thanks!
This is expected behaviour as Vue is trying to be optimal and reuse existing components. The behaviour you want to achieve used to be solved with a setting called canReuse, but that has been deprecated. The current recommended solution is to set a unique :key property on your <router-view> like so:
<router-view :key="$route.path"></router-view>
Check out this JSFiddle example.
You can use watch property, so your component will not waste time to reloading:
index.js
You might have something like this
const routes = [
{
path: '/users/:id',
component: Vue.component('user', require('./comp/user.vue').default)
}
]
user.vue
created(){
// will fire on component first init
this.init_component();
},
watch: {
// will fire on route changes
//'$route.params.id': function(val, oldVal){ // Same
'$route.path': function(val, oldVal){
console.log(this.$route.params.id);
this.init_component();
}
},
methods: {
init_component: function(){
// do anything you need
this.load_user_data_with_ajax();
},
}
Just to make a note. If anybody is working with SSR template, things are a bit different. #mzgajner's answer does indeed recreate the component but will not trigger the asyncData again.
For that to happen, modify entry-client.js like this.
OLD:
const activated = matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
NEW:
const activated = matched.filter((c, i) => {
/*
In my case I only needed this for 1 component
*/
diffed = ((prevMatched[i] !== c) || c.name == 'p-page-property-map')
return diffed
})