I need some help on how to approach the logic for my single page app with VueJS. This is difficult for me to explain, so I hope I can do my best:
I have a template/component with one API GET request via Axios like so:
Component named "Similar Entities"
<template>
<div v-if='gde.length > 0'>
<h4>Similar Entitie(s)</h4>
<ul>
<li v-for='item in gde'>
<a href='#'>{{item['Similar Entities']}}</a>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
import api from '#/api.js';
export default {
name: 'SimilarEntities',
data: function() {
return {
gde: []
};
},
created: function() {
axios
.get(api.getSimilarEntities + this.$route.params.ID)
.catch(error => {
console.log(error);
})
.then(response => {
this.gde = response.data;
});
}
};
</script>
The above template returns HTML with a bullet list like so:
Building
Hardened Aircraft Shelter
(and the gde object looks like this)
[
{
"ID":100,
"Name":"Aircraft Hangar",
"Similar Entities":"Building"
},
{
"ID":100,
"Name":"Aircraft Hangar",
"Similar Entities":"Hardened Aircraft Shelter"
}
]
The above 'Similar Entities' component is part of a larger template (which is part of a larger template!) which uses another Axios.get request to populate a product details page like so:
Component named 'Appearance'
<template>
<div>
<div>
<p>INfo about this product</p>
<ul>
<li v-for='item in ga'></li>
</ul>
<similar-entities></similar-entities>
</div>
</div>
</template>
<script>
import axios from 'axios';
import api from '#/api.js';
import SimilarEntities from '#/components/SimilarEntities.vue';
export default {
name: 'Appearance',
components: {
SimilarEntities
},
data: function() {
return {
ga: []
};
},
created: function() {
axios
.get(api.getAppearance + this.$route.params.ID)
.catch(error => {
console.log(error);
})
.then(response => {
this.ga = response.data;
});
}
};
</script>
The above 'Appearance' template returns HTML like so:
Appearance
An aircraft hangar is a place where aircraft are stored....blah blah blah
- Building
- Hardened Aircraft Shelter
Overall, the $route.params object for the single page component with router info is like so:
{
"ID":100,
"Name":"Aircraft Hangar"
}
The problem is I can't figure out how to hyperlink the Similar Entities items in the Similar Entities component (in this case, Building and Hardened Aircraft Shelter) to their respective product details page because they do not contain their associated IDs.
Note: There is an endpoint available that I can use called getNames which contains the product names for all of the products (including the name of our Building and Hardened Aircraft Shelter products) that contains the following data:
{
"ID":100,
"Name":"Aircraft Hangar"
},
{
"ID": 101,
"Name": "Building"
},
{
"ID": 102,
"Name": "Carport"
}
...snip
Should I put the getNames api in some kind of global state? Then with the $route.params object I can grab the key named Name and then somehow look at the global state of the getNames object and dynamically bind the Name to the hyperlink element of the Similar Entities component? This way, the Building and Hardened Aircraft Shelter links in the Similar Entities component gets linked ? All I am trying to do is get the user to the correct product page when they click on a Similar Entities link (in this demo, it's Building or Hardened Aircraft Shelter).
Thanks for any tips. I am probably overthinking this....
Related
In Home.vue I get data from db.json and store it into jobs: [] array.
export default {
name: 'Home',
data() {
return {
jobs: [],
}
},
components: {
},
mounted() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
}
}
Also in Home.vue I show this data, but only in a short list with:
v-for="job in jobs.slice(0, 5)"
In AllJobs.vue I want to show the full data from db.json and in AddJob.vue I will make a form to be able to add data to db.json.
In App.vue I have the router-links:
<template>
<div class="container">
<div class="navigation">
<h1 class="title">{{ $route.name }}</h1>
<nav>
<router-link :to="{ name: 'Latest open positions' }">Home</router-link>
<router-link :to="{ name: 'All open positions' }">Jobs</router-link>
<router-link :to="{ name: 'Add a new job' }">Dashboard</router-link>
</nav>
</div>
<router-view/>
</div>
</template>
How I pass data from Home.vue into AllJobs.vue?
Should I get another fetch method into AllJobs.vue to get data?
Should I get data into App.vue and then pass it into files that I need?
What is the best approach?
When it comes to handling API requests and sharing data between components, what you need is some state management solution like pinia.
You can fetch and save your data in a store and then use it in any component:
jobs.js
export const useJobsStore = defineStore('jobs', {
state: () => ({ jobs: [] }),
actions: {
fetchJobs() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
},
},
})
App.vue
import { mapActions } from 'pinia
import { useJobsStore } from './jobs.js'
export default {
methods: {
...mapActions(useJobsStore, ['fetchJobs'])
},
mounted() {
this.fetchJobs()
}
}
Home.vue and AllJobs.vue
import { mapState } from 'pinia'
import { useJobsStore } from './jobs.js'
export default {
computed: {
// this makes this.jobs available in script and template
...mapState(useJobsStore, ['jobs'])
}
}
One thing which is debatable is where to call fetchJobs action
In App.vue or main.js - this will fetch data as soon as you open the app, but can be unnecessary if the page you visit doesn't even use the data.
In each page that uses the data - solves the previous problem, but fetches the same data multiple times.
In each page that uses the data (with caching) - you can modify fetchJobs to make a request only if the data haven't been fetched already. This way the app will fetch the data as soon as you visit some page which uses it. And if you visit another page, it will use the cached value instead of making a request
There isn't a singe best approach, which one to pick depends on your needs
I'm creating very simple app and I have a problem with getting info from furnitures.js:
export default [
{ id: 1, name: 'Kanapa Sydney', dim1: '2,25m', dim2: '1,45m x 1,95m'},
{ id: 2, name: 'Kanapa Alex', dim1: '1,95m', dim2: '1,45m x 1,95m'}
]
File ProductDetail contain app-prodrend component. The only thing I know is Id (from route params) and I want to display (id, name, dim1 and dim2) in this component (app-prodend).
ProductDetail.vue
<template>
<div class="prod-det">
<app-header style="background-color: black"></app-header>
<app-prodrend style="position: absolute; margin-top: 50vh" :prod="prods"></app-prodrend>
</div>
</template>
<script>
import header from '../Header';
import prodrend from './ProdDetRen';
export default {
data() {
return {
id: this.$route.params.id
}
},
components: {
appHeader: header,
appProdrend: prodrend
},
computed: {
prods(id) {
return 'kook'
}
}
}
</script>
ProdDetRen.vue
<template>
<h1>dawdwa {{ prod.id }}</h1>
</template>
<script>
export default {
props: ['prod']
}
</script>
I tried to make a getter in furn.js file:
furnDetail(state, index) {
const record = state.products.find(element => element.id == index);
return {
id: index,
name: record.name,
dim1: record.dim1,
dim2: record.dim2
}
}
I have no idea what to do. Thanks in advance
The solution i would recommend is using Vuex to manage your data. Vuex will give you the ability to create getters that you can import into the components that require data. With that data you can filter the data down to only the item you are looking for.
Ive created a repo on my Git that you can have a look at to get a better idea of what i mean.
Visit https://github.com/FloydWatson/furnitureapp
Ive utilized Vuex for state management and vue-router to create the routing paths that have an id in them.
Within the page furniture.vue I get the list of furniture and find the item im looking for using route parameters
computed: {
...mapGetters(["allFurniture"]),
// get the details here
loadedItem() {
return this.allFurniture.find((item) => item.id == this.$route.params.id);
},
},
This is done in a computed field so that if the data was updated our Vuex would let the page know and our data would dynamically update.
Ive tried to leave some comments in there so you can see whats going on easier. Feel free to ask me if theres any other clarification you need.
Happy coding
I'm using quasar in SSR mode and I would like to implement meta data. Quasar has a plugin for meta data and I wanted to use it.
For now my project is quite simple, I have a page product (/product/:id) which display product information.
In the page Product, I use prefetch to fetch Data from a remote API base on the param id.
<template>
<q-page padding>
<div v-if="product">
<h1>{{product.name}}</h1>
<span v-html="product.description"/>
</div>
</q-page>
</template>
<script>
import store from '../store'
export default {
name: 'Product',
async preFetch ({ store, currentRoute }) {
let id = currentRoute.params.id
if (typeof store.getters['product/getProduct'](id) === 'undefined') {
let promise = store.dispatch('product/fetchProduct', id)
await promise.then({})
}
},
computed: {
product () {
return this.$store.getters['product/getProduct'](this.$route.params.id)
}
}
}
</script>
So far everything work find. I have my data setted in the store before rendering. But now I wanted to had a meta title. But in meta how can I have acces to the store and route.params.id.
<script>
export default {
name 'Product',
...
meta: {
title: store.getters['product/getProductMetaTitle'](route.params.id)
}
}
</script>
I'm in the process of setting up a VueJs SPA. I'm using vue-router and I'm trying to find the best solution to the following problem. I have a series of routes. Each of which needs to call an API to get the meta data for the given ID.
/industry/:id/overview
/industry/:id/top-stories
/industry/:id/top-tweets
/brand/:id/overview
/brand/:id/top-stories
/brand/:id/top-tweets
I've been looking at using created or beforeRouteEnter/beforeRouteUpdate and I'm a bit lost. Ideally, I would only fetch new data when a new /industry/:id is reached, not when navigating between pages within the same ID. Also, I'd like to avoid having to define the fetch to grab data in every page component. Also don't want to over complicate this, so my question is, Is there a standard method for tackling this issue?
Clarification:
When I say meta here, I mean data returned from an API about the given industry or brand which I pull using the ID in the route. The api call includes the name of the industry/brand which I want to have on page as soon as the page is presented to the user.
I have something similar. I tackle this using the following approach:
I use the same component for all /industry/:id Vue likes to reuse components wherever it can so if two routes (for example /industry/:id/overview and /industry/:id/top-stories) are using the same component it will stay the same.
What does change, however, is the route meta. So if you add a page key to the meta object in the route objects, and probably add a computed property called page that return this.$route.meta.page, you can use v-if attributes to conditionally render any component. So you might have something like <div v-if="page === 'overview'"></div><div v-else-if="page==='top-stories'"></div>
What this allows you to do is fetch all the data from the API during created or mounted lifecycle and store it as the state. Since the route change doesn't reload the component the state stays the same.
Here is a code example
// router.js
const Project = () =>
import(/* webpackChunkName: "projects" */ "./views/projects/_id");
export default new Router({
mode: "history",
routes: [
{
path: "/projects/:project_id/views",
name: "ViewProject",
component: Project,
meta: {
page: "views",
}
},
{
path: "/projects/:project_id/export",
name: "ExportProject",
component: Project,
meta: {
page: "exports"
}
},
{
path: "/projects/:project_id/recommendations",
name: "ProjectRecommendations",
component: Project,
meta: {
page: "recommendations"
}
},
]
});
And here is the template
<template>
<div v-if="project">
<h1>{{ project.name }}</h1>
<router-link :to="/project/someid/views">Views</router-link>
<router-link :to="/project/someid/exports">Exports</router-link>
<router-link :to="/project/someid/recommendations">Recommendations</router-link>
<ul v-if="page==='views">
<li v-for="(view, i) in project.views" :key="i">{{ views }}</div>
</ul>
<ul v-else-if="page==='exports">
<li v-for="(export, i) in project.exports" :key="i">{{ export }}</div>
</ul>
<ul v-else-if="page==='recommendations">
<li v-for="(recommendation, i) in project.recommendations" :key="i">{{ recommendation }}</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
project: null
}
},
computed: {
page() {
return this.$route.meta.page;
}
},
mounted() {
this.getProject()
},
methods: {
getProject() {
axios
.get(`/projects/someid`)
.then(res => this.project = res.data)
}
}
}
</script>
I'm teaching myself Vue.js 2. My task is to pull a list of posts from the Hacker News API. Upon clicking a list post, a new view is to display some of the data from that specific post.
I'm having a very tough time understanding how to get the REST API data populated in the 2nd view upon routing to the 2nd view, from the 1st view.
(I'm using vue-router and vue-resource(?), and not Vuex (it's such a small application).)
Below are the Item.vue and List.vue. From the List.vue, I'm trying to route to the Item.vue by clicking on a list item. For example, click on a list item called "Guy Has Tough Time with Vue", then open a 2nd view to display a title, score, URL, and comments of the post "Guy Has Tough Time with Vue".
List.vue (creates list, XHR request)
<template>
<div class="list-container">
<h1>List Vue</h1>
<ul class="item-list" v-for="(item, index) in this.items">
<li>
<router-link class="list-item" :to="/views">
{{index + 1}}. {{item.title}}
<div class="points">
Points: {{item.points}}
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'List',
props:[]
data(){
return {
items: []
}
},
mounted: function(){
console.log("created");
this.fetchList();
},
methods: {
fetchList(){
this.$http.get('http://hn.algolia.com/api/v1/search?query=javascript&hitsPerPage=25').then((response) => {
this.items = response.data.hits;
})
}
}
}
Item.vue (Receives item-specific data from List.vue)
<template>
<video id="bgvid" playsinline autoplay loop>
<source src="./src/assets/rad.mp4" type="video/mp4">
<div class="item-container">
<h1>Item Vue</h1>
<div class="post-title"></div>
<div class="post-score"></div>
<div class="post-url"></div>
<ul class="post-comments">
<li class="sngl-comment">
</li>
</ul>
</div>
</video>
</template>
<script>
export default {
name: 'Item',
data(){
return {
item: {}
}
},
mounted: function(){
console.log("created");
},
methods: {
}
}
</script>
Thanks in advance!
The problem is that the first view isn't a direct descendant of the second view so you can't pass data to it through props. I actually wouldn't be using vuex for this, instead I would pass an id through the route for the specific list item and fetch that individual item by the id, as an example:
const View1 = {
template: `<div>
<ul>
<li v-for="item in list"><router-link :to="'view2/'+item.id">{{item.name}}</router-link></li>
</ul>
</div>`,
data() {
return {
list: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 3,
name: 'baz'
}]
}
}
}
const View2 = {
template: `<div>Fetch item {{id}} <br /><router-link to="/">back</router-link></div>`,
created(){
console.log('Fetch data for item '+ this.id);
},
computed:{
id(){
return this.$route.params.id
}
}
}
const routes = [{
path: '/',
component: View1
}, {
path: '/view2/:id',
component: View2
}]
const router = new VueRouter({
routes // short for `routes: routes`
})
var app = new Vue({
router
}).$mount('#app')
Here, I've set the route on View2 to be: view2/:id (see: Dynamic route matching), now in the View2 component I can access that id via this.$route.params.id (which I've put in a computed). Once I've got that id I can simply make an ajax request for the data for the specific item.
And here's the JSFiddle: https://jsfiddle.net/craig_h_411/3hwr6mcd/
Using Vuex
If you are unable to retrieve a record by the specific id for some reason and you don't want to duplicate the call, you will need to share state between non-descendant components and that's where Vuex comes in.
There is a misconception with vuex that it's complicated, but if you only want to share state between a couple of components, it's really quite simple (and it's less than 10kb in size) so there really isn't much use trying to avoid it, all you need to add to your project is:
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = {
state:{
item: {}
},
mutations:{
setItem(state, value){
state.item = value;
}
}
}
export default new Vuex.Store(store)
Then in your app.js (or wherever you have you main Vue instance) you would do something like:
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'
new Vue({
store, // this will be available in components via this.$store
render: h => h(App)
}).$mount('#app')
You will also have to make a few changes, such as committing the state before pushing to the route, adding a computed to get the state from the store and removing the id from the route as it's no longer needed.
I've updated the JSFiddle to show you how that would work: https://jsfiddle.net/craig_h_411/bvswc0kb/