Do you know how to change a component dynamically with object prop
App.vue
<template>
<div id="app">
<component :is="current['test'].target.name"> </component>
<input type="button" value="click me" #click="change" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Comp from "./components/Comp.vue";
export default {
name: "App",
components: {
HelloWorld,
Comp,
},
data() {
return {
current: {},
};
},
created() {
this.current["test"] = {
index: 0,
target: {
name: "Comp",
},
};
},
methods: {
change() {
const r =
this.current["test"].target.name === "HelloWorld"
? "Comp"
: "HelloWorld";
this.current["test"].target = {
name: r,
};
console.log(this.current["test"]);
},
},
};
</script>
Comp.vue
<template>
<p>Template 2</p>
</template>
HelloWorld.vue
<template>
<p>Template 1</p>
</template>
https://codesandbox.io/s/clever-water-dgbts?file=/src/components/HelloWorld.vue:0-42
The value of the object will change correctly but not the component.
Thank you
The issue here is that the property test is not defined on the object current in the data definition - you're setting the definition in the created() function. This means that Vue does not know to create the reactive getter/setter for that property.
Change your data definition to:
data() {
return {
current: {
test: {
index: 0,
target: {
name: "Comp"
}
}
}
};
}
It is because of the way Vue does its reactivity (requiring pre-defined properties) that I would recommend steering clear of accessing properties as dictionary items i.e. use:
current.test.target.name
instead of
current['test'].target.name
For more information on Vue reactivity see this page: link
Related
I'm having trouble getting a route param to pass directly into a component. I followed multiple sets of directions in the docs (including using the Composition API as in the following code), but I'm still getting undefined when the CourseModule.vue first renders.
Route Definition
{
path: '/module/:id',
name: 'Course Module',
props: true,
component: () => import('../views/CourseModule.vue'),
},
CourseModule.vue:
<template>
<div class="AppHome">
<CustomerItem />
<CourseModuleItem :coursemodule-id="this.CoursemoduleId"/>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import CustomerItem from '../components/customers/customer-item.vue';
import CourseModuleItem from '../components/coursemodules/coursemodule-item.vue';
export default {
setup() {
const route = useRoute();
alert(`CourseModule.vue setup: ${route.params.id}`);
return {
CoursemoduleId: route.params.id,
};
},
components: {
CustomerItem,
CourseModuleItem,
},
mounted() {
alert(`CourseModule.vue mounted: ${this.CoursemoduleId}`);
},
};
</script>
coursemodule-item.vue:
<template>
<div id="module">
<div v-if="module.data">
<h2>Course: {{module.data.ModuleName}}</h2>
</div>
<div v-else-if="module.error" class="alert alert-danger">
{{module.error}}
</div>
<Loader v-else-if="module.loading" />
</div>
</template>
<script>
import Loader from '../APILoader.vue';
export default {
props: {
CoursemoduleId: String,
},
components: {
Loader,
},
computed: {
module() {
return this.$store.getters.getModuleById(this.CoursemoduleId);
},
},
mounted() {
alert(`coursemodule-item.vue: ${this.CoursemoduleId}`);
this.$store.dispatch('setModule', this.CoursemoduleId);
},
};
</script>
The output from my alerts are as follows:
CourseModule.vue setup: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
coursemodule-item.vue: undefined
CourseModule.vue mounted: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
As you can see, the path parameter works fine in the top level Vue, but not it's still not getting passed into the component.
your kebab-cased :coursemodule-id props that you're passing to the CourseModuleItem component becomes a camelCased coursemoduleId props
Prop Casing (camelCase vs kebab-case)
try this
// coursemodule-item.vue
...
props: {
coursemoduleId: String,
},
...
mounted() {
alert(`coursemodule-item.vue: ${this.coursemoduleId}`);
this.$store.dispatch('setModule', this.coursemoduleId);
},
I would like to set properties to buttons that I use in various parts of my project, and therefore I cannot insert static text in each button, but I have tried to use the state of vuex so that if I have to change the name it I change only in the state for all buttons, instead of going to change it by looking for it in every single button.
This solution does not work for me because nothing appears in the buttons, instead the words "Foo" and "Bar" should appear (indeed I would like them to appear to me). In practice it does not take the btnType property.
This is one of my components:
<template>
<div>
<b-button
v-for="(btn, idx) in buttons"
:key="idx"
:class="btn.class"
variant="info"
:name="btn.btnType"
>{{ btn.btnType }}</b-button
>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
computed: {
...mapState({
buttonFoo: "buttonFoo",
buttonBar: "buttonBar",
}),
},
data() {
return {
buttons: [
{
btnType: this.buttonFoo,
state: true,
class: "button-class1",
},
{
btnType: this.buttonBar,
state: false,
class: "button-class2",
},
],
};
},
};
</script>
And this is my index file:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
buttonFoo: "Foo",
buttonBar: "Bar"
},
mutations: {},
etc...
});
data evaluates before computed property, hence can not access computed property in data.
better to do it in mounted
export default {
computed: {
...mapState({
buttonFoo: "buttonFoo",
buttonBar: "buttonBar",
}),
},
data() {
return {
buttons: []
};
},
mounted(){
this.buttons=[
{
btnType: this.buttonFoo,
state: true,
class: "button-class1",
},
{
btnType: this.buttonBar,
state: false,
class: "button-class2",
},
],
}
};
I saw many answers telling to use vue-devtools to access the Vue Object but is there a way to do it in the browser console? Like in the tutorial, we enter
> app.data.sock
in console to get the data
Let's say:
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
HelloWorld.vue
<template>
<div class="hello">
<ul>
<li v-for="title in titles" :key="title.id">{{ title.name }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
titles: [{
id: 0,
name: "a"
},
{
id: 1,
name: "b"
}]
}
}
}
</script>
How do I access the data of 'titles' in HelloWorld.vue? In other word, how to get this.data.titles[0].name in the HelloWorld.vue in the browser console? Thank you
You can access the value of data function in created our mounted hooks of Vue lifecycle or can create a function in methods. I am calling your data in created hook
<script>
export default {
name: 'HelloWorld',
data() {
return {
titles: [{
id: 0,
name: "a"
},
{
id: 1,
name: "b"
}]
}
},
created(){
console.log(this.data.tiles)
}
}
</script>
I learned vue's custom directive today, and start wondering if I can write a custom directive that has same function as v-model. but I find the difficulty to do the two way binding in the directive's hooks, any help?
Yes,
you should pass value props to your component and then emit input for changing value
e.g.:
We have input component:
<template>
<input :value="innerValue" #input="change($event.target.value)">
</template>
<script>
export default {
name: "TextField",
props: ["value"],
computed: {
innerValue() {
return this.value;
}
},
methods: {
change(e) {
console.log(e);
this.$emit("input", e);
}
}
};
</script>
and we use it in parent component:
<template>
<div id="app">
<text-field v-model="value"/>
</div>
</template>
<script>
import TextField from "./components/TextField";
export default {
name: "App",
components: {
TextField
},
data: () => ({
value: ""
})
};
</script>
EDIT --- SOLVED
It turns out that isn't really a problem, Vue will auto-bind for you so there's no need to bind manually.
END EDIT ---
I'm trying to pass a method to a callback(or event) to a child component.
Everything works great, except that the function executes in the wrong context.
In react, I would bind the functions in the constructor, I'm not sure what's the solution is in Vue.
Example
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this); // refers to the child component
// can't access this.todos
}
},
components: {
Tasks,
Header
}
}
</script>
Here's the solution to it, turns out you can use the 'created' life cycle hook, this is similar to react when binding in a constructor
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this.todos); // can now access the correct 'this'
}
},
created() {
this.markAsDone = this.markAsDone.bind(this);
},
components: {
Tasks,
Header
}
}
</script>
Sub component code
<template>
<ul>
<li
:class="{isDone:todo.isDone}"
:key="todo.id"
v-for="todo in todos">
<input type='checkbox' #change="markAsDone(todo.id)"/>
{{todo.text}}
</li>
</ul>
</template>
<script>
export default {
name: 'Tasks',
props: ['todos'],
methods: {
markAsDone(id) {
this.$emit('onMarkAsDone', id);
}
}
}
</script>
You can return function in a markAsDone method, like this:
markAsDone() {
return id => {
console.log(this.todos);
}
},
and then when passing method as a prop call it:
:onMarkAsDone="markAsDone()"
Then you can call the prop method inside your Child-Component.
That should give you requested functionality without using bind in created() hook.