Vue.js call component by variable name? - javascript

Is possible in Vue.js call component by variable name?
Components are registred:
import Component1 from 'component1'
import Component2 from 'component2'
import Component3 from 'component3'
...
components: {
Component1, Component2, Component3
},
And i am searching for something like this:
created() {
var componentName = 'Component1';
this.components[componentName]
}

You can access the components property like this:
this.$options.components[componentName]

Just a basic example on how you can use dynamic component:
link to jsFiddle to play around: link here
<div id="app">
<component v-bind:is="currentPage">
<!-- component changes when currentPage changes! -->
<!-- output: Updated About -->
</component>
</div>
new Vue({
el: '#app',
data: {
currentPage: 'home'
},
components: {
home: {
template: "<p>Home</p>"
},
about: {
template: "<p>About</p>"
},
contact: {
template: "<p>Contact</p>"
}
},
mounted(){
this.$options.components['about'].template = "<p>Updated About</p>"
this.currentPage = 'about'
}
})

Related

How to get a specific child component object property from parent component?

I would like to grab a child component's "meta" property from parent. Is it possible somehow ?
I know there is a solution with an emit method, but is there some easier way to make it happen ?
// Default.vue <-- parent component
<template>
<h1>{{ pagetitle }}</h1>
<router-view />
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'LayoutDefault',
computed: {
pagetitle () {
let title = this.$route.meta.title // <--- I want to access child's component meta here
// if title not provided, set to empty string
if (!title) title = ''
return title
}
}
})
</script>
// router/routes.js
const routes = [
{
path: '/',
component: () => import('layouts/Default.vue'),
children: [
{
path: 'dashboard',
name: 'dashboard',
meta: { title: 'Dashboard', auth: true, fullscreen: false }, // <--- TAKE THIS
component: () => import('pages/dashboard.vue')
}
]
}
]
// pages/dashboard.vue <-- child component
<template>
<div>
dashboard content
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Dashboard',
meta: { // <--- this should be reachable from the parent component (Default.vue)
title: 'Dashboard',
auth: true,
fullscreen: false
}
})
</script>
You can get component info via $route.matched.
Here's a PoC:
const Dashboard = Vue.defineComponent({
template: "<div>Some dashboard</div>",
meta: { title: "Dashboard" },
})
const router = new VueRouter({
routes: [{ path: "/", component: Dashboard }],
})
const app = new Vue({
router,
computed: {
// Note that this takes the *last* matched component, since there could be a multiple ones
childComponent: (vm) => vm.$route.matched.at(-1).components.default,
},
}).$mount('#app')
<div id="app">
<h1>{{ childComponent.meta.title }}</h1>
<router-view />
</div>
<script src="https://unpkg.com/vue#2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router#3/dist/vue-router.js"></script>
As suggested by Estus Flash in a comment, instead of taking the last matched component we can take the last matched component that has meta defined. To do that, replace the following:
vm.$route.matched.at(-1).components.default
with:
vm.$route.matched.findLast((r) => "meta" in r.components.default)
.components.default
Some approaches I could figure from the web:
Using ref by this.$refs.REF_NAME.$data (As done here: https://stackoverflow.com/a/63872783/16045352)
Vuex or duplicating the logic behind stores (As done here: https://stackoverflow.com/a/40411389/16045352)
Source: VueJS access child component's data from parent

Accessing instance data in Vue from Controller or Components

I'm using the Vue controller to update the content on my SPA. While I can import views and controllers and display them accordingly, I'm having a hard time updating some variables defined in the main Vue instance. I also tried to define them in the main view but I still cannot update them.
Here's my Vue instance:
new Vue({
router,
render: h => h(App),
data: () => {
return {
showYesOrNo: false,
showMultiChoice: false
}
}
}).$mount('#app')
This is my main view:
<script type="text/javascript">
import ContactContainer from "./views/ContactContainer.vue"
export default {
name: "app",
components:{
ContactContainer
},
data: () => {
return { // Redefined the variables here to see if I could access them in some way
showYesOrNo: false,
showMultiChoice: false
}
}
}
</script>
<template>
<div id="app">
<div class="container">
<div id="main_container">
<transition name="fade"><router-view name="YesOrNo" v-show="!showMultiChoice"/></transition>
<transition name="fade"><router-view name="MultiChoice" v-show="!showYesOrNo"/></transition>
<transition name="fade"><ContactContainer name="Contact" v-show="!showMultiChoice && !showYesOrNo"></ContactContainer></transition>
</div>
</section>
</div>
</div>
</template>
Now this is how I try to access the ShowYesOrNo and ShowMultiChoice variables. First in the controller:
const routes = [
{
path: '/YesOrNo',
components: {
YesOrNo: YesOrNoVue,
MultiChoice: MultiChoiceContainer
},
beforeEnter: (to, from, next) => {
console.log(this); //undefined
console.log(this.$parent.showYesOrNo) //undefined
console.log(vm.showYesOrNo) //undefined
next();
}
}
]
Then in the YesOrNo vue:
<template>
<!-- Some HTML -->
</template>
<script>
export default {
data: function(){
return {
name: 'YesOrNoVue'
}
},
created: function(){
console.log(this.showYesOrNo); //undefined
console.log(vm.showYesOrNo); //undefined
}
}
</script>
I'm a little confused with the visibility of these variables so I'd like to know what's the best approach to change them when the router-view is updated (i.e. when the vue is created).
Thanks.

Struggling to transmit data to a child component

I am really new to VueJS, and I'm trying to build some kind of system where I have four pages available with Vue-Router. These pages are "ranked" from 1 to 4, and if the new page has a higher rank than the old one, the transition is set to "transition-left", else, "transition-right".
My problem here is that I can't manage to pass the result of the comparison to the child component. Here is my main.js file:
import Vue from "vue";
import VueRouter from "vue-router";
import Landing from "./components/Landing.vue";
import Bio from "./components/Bio.vue";
import Shop from "./components/Shop.vue";
import Contact from "./components/Contact.vue";
import App from "./components/App.vue";
import "./assets/style.css";
Vue.config.productionTip = false;
Vue.use(VueRouter);
let transitionName = "";
const router = new VueRouter({
mode: "history",
routes: [
{
path: "/",
component: Landing,
meta: { order: 1 }
},
{
path: "/biography",
component: Bio,
meta: { order: 2 }
},
{
path: "/shop",
component: Shop,
meta: { order: 3 }
},
{
path: "/contact",
component: Contact,
meta: { order: 4 }
}
]
});
router.beforeEach((to, from, next) => {
if (to.meta.order > from.meta.order) {
transitionName = "slide-left";
} else {
transitionName = "slide-right";
}
next();
});
let vm = new Vue({
router,
el: "#app",
data: {
transitionName: transitionName
},
render: h => h(App)
});
And here is how I tried to pass the "transitionName" value to "App.vue":
<template>
<div id="app">
<transition :name="name">
<router-view></router-view>
</transition>
</div>
</template>
<script>
export default {
props: ["transitionname"],
data: function() {
return { name: this.transitionname };
}
};
</script>
I'm thinking that maybe I did the new Vue part wrong, and that's not how you pass datas to a child component, but I can't find a working way to pass it correctly.
As a result, everything works, but there aren't any transition, so I guess the :name="name" equals nothing.
How can I fix this?
Thank you in advance
Here's how I might approach what you want to achieve. Basically all you want to do is watch the $route in your App component.
watch:{
$route(to, from){
if (to.meta.order > from.meta.order) {
this.name = "slide-left";
} else {
this.name = "slide-right";
}
}
}
Here is an example of that working (without the actual transitions).

Synchronize variable between router in vue.js

I'd like to change value of a variable in a router-view by changing other variable in different routre-view synchronously. I wrote code like below to change variable isFoo in header and catch it in side bar, but it fails.
App.vue:
<template>
<v-app id="app">
<router-view name="sidebar"></router-view>
<router-view name="header"></router-view>
<router-view name="main"></router-view>
<router-view name="footer"></router-view>
</v-app>
</template>
<script>
export default {
name: 'app',
isFoo: false
}
</script>
and Sidebar.vue:
<template>
<div id="sidebar" :isOpen="isFoo"></div>
</template>
<script>
export default {
name: 'sidebar',
data () {
return {isFoo: this.$parent.$options.isFoo}
}
}
</script>
Header.vue:
<template>
<button v-on:click="foo()">Button</button>
</template>
<script>
export default {
name: 'header',
methods: {
foo: () => {
this.$parent.$options.isFoo = !this.$parent.$options.isFoo
}
}
}
</script>
Your question is essentially about how to share state across multiple components of your app, and is quite general.
Your code does not work because you have copied isFoo across your components instead of just referencing a single source of truth for that data. Also you should specify reactive data in the data property of each component, not directly within the $options of the component.
I've fixed your code to make it work:
const Header = {
template: '<button #click="$parent.isFoo = true">Click Me</button>'
}
const Sidebar = {
template: '<div>Sidebar: {{ $parent.isFoo }}</div>'
}
const router = new VueRouter({
routes: [
{
path: '/',
components: {
header: Header,
sidebar: Sidebar
}
}
]
})
new Vue({
router,
el: '#app',
data: {
isFoo: false
}
})
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://rawgit.com/vuejs/vue-router/dev/dist/vue-router.js"></script>
<div id="app">
<router-view name="header"></router-view>
<router-view name="sidebar"></router-view>
</div>
However I do not recommend this approach. You really shouldn't be accessing this.$parent because it tightly couples the components.
I'm not going to go into detail about better ways of doing this because there are lots of SO questions which cover this topic.

Vue.js loading single-file components

I'm new to Vue.js and want to use single-file components, but I don't understand the workflow.
For example, I have three components: App, Grid and List
App.vue
<template>
<div id="app">
<div id="grid"></div>
<div id="right"></div>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
message: 'Hello Vue!'
}
}
}
</script>
Grid.vue
<template>
<div id="left"></div>
</template>
<script>
export default {
name: 'grid',
data: function () {
return {
grid: 'some-data'
}
}
}
</script>
List.vue
<template>
<div id="right"></div>
</template>
<script>
export default {
name: 'list',
data: function () {
return {
text: 'some-text'
}
}
}
</script>
Main.js
import Vue from 'vue'
import App from './vue/App.vue'
import Grid from './vue/Grid.vue'
import PatternList from './vue/PatternList.vue'
new Vue({
el: '#app',
render: h => h(App)
});
new Vue({
el: '#grid',
render: h => h(Grid)
});
new Vue({
el: '#right',
render: h => h(PatternList)
});
It works but I hope this isn't the right way to create nested components.
Can anyone show the way it should be done? Thanks
You can register components using the Vue.component method:
import Vue from 'vue'
import App from './vue/App.vue'
import Grid from './vue/Grid.vue'
import PatternList from './vue/PatternList.vue'
Vue.component('grid', Grid);
Vue.component('pattern-list', PatternList);
new Vue({
el: '#app',
render: h => h(App)
});
And then add them to the App template directly using their tag name:
<template>
<div id="app">
<grid></grid>
<pattern-list></pattern-list>
</div>
</template>
This registers the components globally, meaning that any Vue instance can add those components to their templates without any additional setup.
You can also register components to a Vue instance, like so:
new Vue({
el: '#app',
render: h => h(App),
components: {
'grid': Grid,
'pattern-list': PatternList
}
});
Or within the script tag of a single-file component:
<script>
import Grid from './vue/Grid.vue'
export default {
name: 'app',
components: {
'grid': Grid,
'pattern-list': PatternList
}
});
</script>
This registers the components just to that Vue instance, meaning that those registered components will only be available to use within the template for that Vue instance. A child component would not have access to those registered components unless those components are also registered to the child Vue instance.

Categories

Resources