I am new to Nuxt/Vue and am getting confused about how data works between different pages. I want to display a light or dark logo depending on if I am on the index page or not. When navigating between pages the data doesn't update the route name so the logo doesnt change: Layout page below.
<template>
<main>
<img v-if="page != 'index'" src="~/assets/img/logo-white.svg">
<img v-else src="~/assets/img/logo-dark.svg">
<nuxt />
</main>
</template>
<script>
export default {
data () {
return {
page: this.$route.name
}
}
}
</script>
Any help would be great.
Thanks,
Jamie
In a nuxt layout, every page is rendered in place of the <nuxt /> tag.
You are setting the reactive property page in the layout which is mounted only once in the beginning, and all other pages are rendered inside it later. So when a page change occurs the layout doesn't re-mount and the value of page remains the same.
You can add a watcher on the route like this:
<script>
export default {
data () {
return {
page: this.$route.name
}
}
watch: {
'$route': function (value) {
this.page = value.name
}
}
}
</script>
So, now everytime the route changes, page will updated with the new name of the route.
Use a computed property instead of data, $route is reactive and will trigger a computed update.
computed: {
page() {
return this.$route.name
}
}
You could also just access $route in your template.
<img v-if="$route.name != 'index'" src="~/assets/img/logo-white.svg">
Related
I have a tricky communication issue between a router-link in my parent Vue component and a child component.
I would like to clear an input field on the child component, which is the main Index for my app. This component loads by default.
The parent App component contains the main navigation which contains a router-link routed back to the index like so:
<router-link :to="{ name: 'Index', query: { search: '' }}" class="nav-link">
Index
</router-link>
The Index component contains a search input to filter the index items list. The search value is tracked by the component's data.
data() {
return {
items: [],
tags: [],
search:'',
}
The issue is I cannot seem to clear the search input when the router-link for the Index is clicked. I assume because the Index is already mounted clicking the link cannot trigger any function in the component.
I have tried using $emit by wrapping the link text in <span #click="clearSearch()">, putting the $emit in the clearSearch function and picking it up in the Index, but this apparently doesn't work from parent to child??
As you can see in the router-link above I have also tried passing as query like query: { search: '' } and adding the following to the main.js route configuration.
props(route) {
return { search: route.query.search }
}
This sends the empty string in URL but I still cannot pick it up in the Index component to clear the search input, again I think because it is already mounted.
This seems like it should be an easy thing to do, but I am a bit stumped on it and don't know what else to try. I am still learning Vue so there might be something obvious I am missing. If anyone can help me out on how to achieve this I would much appreciate it. Thanks in advance.
An easy hack would be to use router.push() in a method instead of <router-link> and to make search a prop and clear it from the parent.
This way you can store search in the parent data and clear it on click.
// Parent component
<template>
<div #click="navigate" class="nav-link">
Index
</div>
...
<Index :search="search" />
</template>
<script>
export default {
data() {
return {
search: ''
}
},
methods: {
navigate() {
this.$router.push({ name: 'Index' });
this.search = '';
}
}
}
</script>
// Child component
<template>
{{ search }}
</template>
<script>
export default {
props: {
search: {
type: String,
default: '',
}
}
}
</script>
in my project i'm suffering from delay in API requests and that's because i have huge amount of data in API, so i added a cache but it still appears white page when page creates, so i was thinking of adding API call on app.vue so request will be faster... is there a way to do it?
now i'm making API request on each page in my project
code below:
//building.vue where buildings api used
<template>
<b-row class="icon-examples">
<b-col lg="3" md="6" v-for="(building, index) in buildings"
:key="index" >
<button type="button" class="btn-icon-clipboard" #click="GoToBuilding(building._id)"
>
<div>
<i class="ni ni-building"></i>
<router-link
class="question-routerToDetail"
:to="`/tables/${building._id}`"
> <span > B info - </span>
<span>{{building.building_number}}</span></router-link>
</div>
</button>
</b-col>
</b-row>
</template>
<script>
import BuildingsService from "../../../services/ApiService"
export default {
data() {
return {
};
},
components: {
props:
['buildings'],
BaseHeader,
// buildings:[]
},
}
}
</script>
app.vue:
<template>
<router-view :number="count" #send="getNewCount" #reset="onReset" :buildings="buildings">
<sidebar :number="count" #reset="onReset"/>
</router-view>
</template>
<script>
export default {
components: {
sidebar,
},
data() {
return {
buildings: []
};
},
created(){
BuildingsService.getBuildings().then((response) => {
this.buildings = response.data.response;
console.log(this.buildings , "skk")
});
}
}
</script>
can i add saved API request array in app.vue and use it on other pages? and is it gonna improve my API call?
thanks in advance
Edit: updated question depending on answer below.. but getting empty data with no console errors
Yes, you can call the API a single time in the component that contains router-view and pass down the results to rendered components through props. The rendered components must declare the props that will be passed down to them, but only if they will be using it.
<template>
<router-view :number="count"
#send="getNewCount" #reset="onReset"
:buildings="buildings">
<sidebar :number="count" #reset="onReset"/>
</router-view>
</template>
<script>
import BuildingsService from "../../../services/ApiService"
export default {
components: {
sidebar,
},
data() {
return {
buildings: []
};
},
created(){
BuildingsService.getBuildings().then((response) => {
this.buildings = response.data.response;
});
}
}
</script>
But if the API call returns a huge amount of data or is really slow, then I really suggest that you check into optimizing the backend queries of your API, paginating results, and loading them progressively with lazy load on scroll or something.
Also it would be good if you can keep something in your state to check if the data is being loaded or not, so that you can show a loader or something rather than a white page until you get the results.
One possible solution is to adapt getBuildings(), such that it will call the backend only once and store the result in a property of BuildingsService, e.g. cachedBuildings.
If cachedBuildings is empty, getBuildings() calls the backend and stores the result in cachedBuildings.
If cachedBuildings is not empty, getBuildings() returns cachedBuildings.
The advantage of this solution is, that you only adapt your code in one place and the caching is transparent for every component.
I have a 'views' page that imports two components, one of which is a NavBar that will display a loading animation until the other component is fully loaded in.
The way I'm trying to accomplish this, is I am trying to define a 'loading' var in the view, pass that var into the NavBar AND releases components. IF I change the loading to false from the releases component that should propagate over to the NavBar (to stop the loading animation).
views/Release.vue
<template>
<div>
<NavBar v-bind:loading="this.loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases v-bind:loading="this.loading"></Releases>
<Footer></Footer>
</div>
</div>
</template>
<script>
import Releases from "../components/Releases.vue";
import NavBar from "./components/NavBar.vue";
export default {
name: "releases",
data () {
return {
loading: 'loading'
}
},
components: {
NavBar,
Releases,
}
};
</script>
components/NavBar.vue
<template>
<div>
<div id="nav">
<a href='/link1'>Link 1</a>
<a href='/link2'>Link 2</a>
<a href='/link3'>Link 3</a>
<pulse-loader :loading="this.loading"></pulse-loader>
</div>
</div>
</template>
<script>
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
export default {
name: 'NavBar',
props: ['loading'],
components: {
PulseLoader
},
};
</script>
I have left out Releases.vue from this post for brevity, but no matter where I set
this.loading=false
It does not seem to propagate over to NavBar component.
What am I doing wrong here? Not sure If I need to use $emit for something like this?
No, you should NOT modify the prop loading from Releases.vue.
In Releases.vue when data loaded, call $emit:
this.loadReleases()
.then(() => {
// Your logic.
this.$emit('loaded', true);
});
In the view Release.vue
<template>
<div>
<NavBar :loading="loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases #loaded="updateLoading"></Releases>
<Footer></Footer>
</div>
</div>
</template>
<script>
import Releases from "../components/Releases.vue";
import NavBar from "./components/NavBar.vue";
export default {
name: "releases",
data () {
return {
loading: true,
}
},
components: {
NavBar,
Releases,
},
methods: {
updateLoading(val) {
this.loading = !val; // loading = false;
},
},
};
</script>
Please, use : instead of v-bind, # instead of v-on for making the code clear. And it's no need to use this on the template.
Do not use this in your templates.
<NavBar v-bind:loading="loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases v-bind:loading="loading"></Releases>
<Footer></Footer>
<pulse-loader :loading="loading"></pulse-loader>
In fact, eveything in the template refers to this component, and you can't refer to anything else directly from the template.
$emit is for sending data up to the parent, and the main use case for that is to tell the parent to update a property that then flows back down to the component. Your use case is updating children, and using v-bind is appropriate, as the NavBar owns the data.
I have your usual admin dashboard (Core-UI) which I'm modifying for my own needs. I have an "aside" component into which I want to load MonitorAside.vue whenever I am on the Monitor page (I am using vue-router)
Here's a small rundown.
src/containers/Full.vue imports all the main components (including aside) and then uses router-view to render the view based on the route.
src/components/Aside.vue is the component in question. It contains fixed content but I want its content to dynamically be changed if another component requires to do so.
src/views/Monitor.vue is the page in question and thus the page which dynamically needs to inject/swap the content of the aside component. Note that this component is not imported in Full.vue but is rendered through the router there.
src/views/asides/MonitorAside.vue is the component I want to be loaded into Aside.vue whenever I am on the Monitor page.
How would I go about doing this?
So whenever vue-router navigates to a different page, you want the content of your Aside component to change? You can watch the $route value and then render the content depending on your route names, something like:
<template>
<div>
<monitor-aside v-if="page === 'monitor'"></monitor-aside>
<div v-else>
<!-- your default aside content -->
</div>
</div>
</template>
<script>
import MonitorAside from '../views/asides/MonitorAside.vue'
export default {
data() {
return {
page: null
}
},
methods: {
setContent(routeName) {
if (routeName === 'Monitor') {
this.page = 'monitor';
} else {
this.page = null;
}
}
},
created() {
this.setContent(this.$route.name);
},
watch: {
'$route'(to, from) {
this.setContent(to.name);
}
}
components: {
MonitorAside
}
}
</script>
First of all : I'm using laravel spark and the given setup of vue that comes with spark.
I have a "home" component with the prop "custom". Within custom there's a "passwords" array. (Entry added by code of directive, it's initialized empty)
My component ( alist) which should be bound against the data
<template id="passwords-list-template">
<div class="password" v-for="password in list">
<ul>
<li>{{ password.name }}</li>
<li>{{ password.description }}</li>
</ul>
</div>
</template>
<script>
export default {
template: '#passwords-list-template',
props: ['list'],
};
</script>
Usage
<passwords-list :list="custom.passwords"></passwords-list>
Using vue devtools I can see that my data is updating, however my list is not. Also other bindings like
<div v-show="custom.passwords.length > 0">
Are not working ...
UPDATE : Parent component (Home)
Vue.component('home', {
props: ['user', 'custom'],
ready : function() {
}
});
Usage
<home :user="user" :custom="spark.custom" inline-template>
Update 2: I played around a little bit using jsfiddle. It seems like changing the bound data object using $root works fine for me when using a method of a component. However it does not work when trying to access it using a directive
https://jsfiddle.net/wa21yho2/1/
There were a lot of errors in your Vue code. First of all, your components where isolated, there wasn't an explicit parent-child relationship.Second, there were errors in the scope of components, you were trying to set data of the parent in the child, also, you were trying to set the value of a prop, and props are by default readonly, you should have written a setter function or change them to data. And finally, I can't understand why were you trying to use a directive if there were methods and events involve?
Anyway, I rewrote your jsfiddle, I hope that you find what you need there. The chain is Root > Home > PasswordList. And the data is in the root but modified in home, the last component only show it. the key here are twoWay properties, otherwise you wouldn't be able to modify data through properties.
Here is a snippet of code
Home
var Home = Vue.component('home', {
props: {
user: {
default: ''
},
custom: {
twoWay: true
}
},
components: {
passwordList: PasswordList
},
methods: {
reset: function () {
this.custom.passwords = [];
}
}
});
// template
<home :custom.sync="spark.custom" inline-template>
{{custom | json}}
<button #click="reset">
reset in home
</button>
<password-list :list="custom.passwords"></password-list>
<password-list :list="custom.passwords"></password-list>
</home>
Here is the full jsfiddle