Dynamically update props - vue.js

As simplified below, my app has a template with a custom component.
The data is passed from Template A to custom component as props (":list")
Template A:
<template>
...
<custom-component
v-for="list in listGroup"
:key="list.id_list"
:list="list"
/>
</template>
<script>
export default {
data() {
return {
listGroup: []
};
},
components: {
'custom-component':require("...").default
}
</script>
The custom component
<template>
...
</template>
<script>
export default {
props:["list];
...
}
</script>
Problem to solve:
A new item is added to the list sent as props.
I need the list (:list="list") to be dynamically updated so that the props in the custom component automatically reflect that update.
Thanks.

There are two ways to achieve that one way is to use a state management library(Vuex is recommended) the other is to use events.
Here is an example of using events:
create a file event-bus.js with the following content
import Vue from "vue";
export const EventBus = new Vue();
then in your component where you want to update list use this EventBus.$emit('eventName', data);
remember to import event-bus file
the listen to the event in the other component
EventBus.$on('eventName', function (details) {
//update list here
});

Related

Access component parameters inside caller component

I have a component which I call inside another component as:
COMPONENT 1
<template>
<FiltersComponent />
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
So, this FiltersComponents have some parameters I want to access into my component one
COMPONENT 2 DATA
data() {
return {
TestList: [] as string[],
Test2List: null as string[] | null,
}
},
How can I access that TestList and Test2List inside COMPONENT 1?
There are multiple possibilities: If one component is a child component or sibling of the other, you might want to take a loop at props (passing data down) and events (passing data up). Otherwise, if they are not siblings or children, you can use a store like vuex.
To use the docs example:
vue entry point: (e.g., app.js):
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
someProperty: 'someValue'
}
},
mutations: {
update (state, value) {
state.someProperty = value
}
}
})
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
In your component's script-section:
this.$store.commit('update', 'YOUR_VALUE')
Other component:
const val = this.$store.state.someProperty
However, this is only a very basic example. You should definitely check out the docs, especially the sections about state, getters and mutations.
Always store the state as high in the component tree as you need, meaning that if you need these lists inside component 1, store them there. Then, you can use props and events to access and update the data inside component 2.
Alternatively, you can use a centralized store like Vuex.
You can achieve the same using ref,
Check the below example
<template>
<FiltersComponent ref="childComponent" /> <!-- Adding ref over here -->
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
then in any of your methods or mounted section in Component 1. You can access Component2 data like
this.$refs.childComponent.TestList
or
this.$refs.childComponent.Test2List
So simple isn't it? ;)

Call a function from another component using composition API

Below is a code for a header and a body (different components). How do you call the continue function of the component 2 and pass a parameter when you are inside component 1, using composition API way...
Component 2:
export default {
setup() {
const continue = (parameter1) => {
// do stuff here
}
return {
continue
}
}
}
One way to solve this is to use events for parent-to-child communication, combined with template refs, from which the child method can be directly called.
In ComponentB.vue, emit an event (e.g., named continue-event) that the parent can listen to. We use a button-click to trigger the event for this example:
<!-- ComponentB.vue -->
<script>
export default {
emits: ['continue-event'],
}
</script>
<template>
<h2>Component B</h2>
<button #click="$emit('continue-event', 'hi')">Trigger continue event</button>
</template>
In the parent, use a template ref on ComponentA.vue to get a reference to it in JavaScript, and create a function (e.g., named myCompContinue) to call the child component's continueFn directly.
<!-- Parent.vue -->
<script>
import { ref } from 'vue'
export default {
⋮
setup() {
const myComp = ref()
const myCompContinue = () => myComp.value.continueFn('hello from B')
return {
myComp,
myCompContinue,
}
},
}
</script>
<template>
<ComponentA ref="myComp" />
⋮
</template>
To link the two components in the parent, use the v-on directive (or # shorthand) to set myCompContinue as the event handler for ComponentB.vue's continue-event, emitted in step 1:
<template>
⋮
<ComponentB #continue-event="myCompContinue" />
</template>
demo
Note: Components written with the Options API (as you are using in the question) by default have their methods and props exposed via template refs, but this is not true for components written with <script setup>. In that case, defineExpose would be needed to expose the desired methods.
It seems like composition API makes everything a lot harder to do with basically no or little benefit. I've recently been porting my app to composition API and it required complete re-architecture, loads of new code and complexity. I really don't get it, seems just like a massive waste of time. Does anyone really think this direction is good ?
Here is how I solved it with script setup syntax:
Parent:
<script setup>
import { ref } from 'vue';
const childComponent = ref(null);
const onSave = () => {
childComponent.value.saveThing();
};
</script>
<template>
<div>
<ChildComponent ref="childComponent" />
<SomeOtherComponent
#save-thing="onSave"
/>
</div>
</template>
ChildComponent:
<script setup>
const saveThing = () => {
// do stuff
};
defineExpose({
saveThing,
});
</script>
It doesn't work without defineExpose. Besides that, the only trick is to create a ref on the component in which you are trying to call a function.
In the above code, it doesn't work to do #save-thing="childComponent.saveThing", and it appears the reason is that the ref is null when the component initially mounts.

How to get state item via props in vuejs with vuex

I'm sorry for my bad english.
I've fromSelected props in vue. I want to get state selected item via props value.
I've from component.
<template>
<h1>this.$store.state.fromSelected</h1>
</template>
<script>
export default {
props: ['fromSelected']
}
</script>
in vuex state like this
const state = {
'one': null,
}
I use my from component in root component like this
<from from-selected="one"></from>
When I use this.$store.state.fromSelected I want to get this.$store.state.one in from component.
It seems that the problem you are facing has to do with the flow of the data in Vue and Vuex. There are two different problems that need to be addressed here:
Problem nr. 1 - Data Flow and Scopes:
In your template code: <h1>this.$store.state.fromSelected</h1> you are trying to access the component property in the Vuex state. It will not exist there as it only exists in the components local scope. Here is an illustration of how the flow of data would work:
Problem nr. 2 - Static and Dynamic Props:
In the line <from from-selected="one"></from> you are not prepending the property with a colon and therefore the prop will be considered a string literal and not an expression where you could pass in a variable. Read more here: https://v2.vuejs.org/v2/guide/components-props.html#Static-and-Dynamic-Props
Solution:
The solution would be to pass in the value from the Vuex state as a dynamic property to the component; like this:
// Javascript
const store = new Vuex.Store({
state: {
one: "This comes from the Vuex state"
},
})
new Vue({
el: "#app",
store: store,
components: {
from: {
template: '<span>{{ fromSelected }}</span>',
props: ['fromSelected']
}
}
})
// Template
<div id="app">
<from :from-selected="$store.state.one"></from>
</div>
Try it here: https://jsfiddle.net/vngrcu5v/
Ifound. I must write like this.
// From.vue component
<template>
<span>{{this.fromSelected}}</span>
</template>
<script>
export default {
props: ['fromSelected']
}
</script>
// Root component
import from from 'from';
<from :from-selected="this.$store.state.one"></from>
// State
export default {
'one': null,
}
Did you try to put fromSelected with same syntax?
<from fromSelected="one"></from>

vue-chartjs load data from parent component

I have a component for a LineChart, here is the code :
<script>
import { Line } from 'vue-chartjs'
export default {
extends: Line,
props: ['data', 'options'],
mounted () {
this.renderChart(this.data, this.options)
}
}
</script>
I want to use this component in another one as I can affect data to data and options value of the component Chart.vue.
I'm new to VueJS and can't understand an example like that in vue-chartjs doc.
Here is my component that will be the parent one, and what I've done from now :
<template>
<div class="dashboard">
<chart></chart>
</div>
</template>
<script>
import Chart from '#/components/Chart'
export default {
name: 'DashBoard',
components: {
'chart': Chart
},
mounted () {},
data () {
return {
datacollection: null
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
Where does your data come from?
Your example code is weird, as you are not passing your data as props.
So no wonder, that nothing shows up.
You have to pass your datacollection to your chart component.
<chart :data="datacollection" />
And keep in mind, that if you are using an API your data will arrive async. So the component mounts, renders the chart but your data is not there. So you need to add a v-if to be sure that your api call is finished.

How to catch events across multiple child Vue components

I am building a form framework in vue. I have components for each field type. Each field type component uses this.$emit to communicate changes with the parent component.
I am able to trigger events in the parent component using v-on directives as follows:
<template>
<div v-if="fieldsLoaded">
<form-select :field="fields.title" v-on:updated="validate" ></form-select>
<form-input :field="fields.first_name" v-on:updated="validate" ></form-input>
</div>
</template>
However, I don't want to have to manually specify that every component should trigger the validate method individually.
How can I have the parent component listen for the updated emit across all its child components?
Edit: I'm looking for something like the below, though $on only catches emits that occur within the same component, rather than its children
created: function(){
this.$on('updated',validate)
}
The best way is to use event bus or even better in my opinion vuex.
For the first case take a look here
For the second here
With event bus you can emit an event, and listen to that event whenever you want(at parent,child even in the same component)
Vuex It serves as a centralized store for all the components in an application and you can have properties in that store,and you can use and manipulate them.
Example with event Bus:
main.js:
import Vue from 'vue'
import App from './App.vue'
export const eventBus = new Vue();
new Vue({
el: '#app',
render: h => h(App)
})
User Component
<template>
<button #click="clicked">Click me to create event</button>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'User',
methods: {
clicked() {
eventBus.$emit('customEvent', 'a text to pass')
}
}
}
</script>
Admin component
<template>
<p>The message from event is: {{message}}</p>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'Admin',
data: () => ({
message: ''
})
created() {
eventBus.$on('customEvent', dataPassed => {
this.message = dataPassed
}
}
}
</script>
Take a look to this tutorial to learn Vuex
For your case you can use v-model like following:
<template>
<div v-if="fieldsLoaded">
<form-select v-model="fields.title" :validate="validate" ></form-select>
<form-input v-model="fields.first_name" :validate="validate" ></form-input>
</div>
</template>
v-model is essentially syntax sugar for updating data on user input events.
<input v-model="something">
is just syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
You can pass a prop : value in the child components, and before changing input field call a function to validate which is also passed as a prop.
Vue.component('form-select', {
props: ['options', 'value', 'onChange', 'validate'], //Added one more prop
template: '#your-template',
mounted: function () {
},
methods: {
change (opt) {
if (this.validate !== undefined) {
var isValid = this.validate(this.value)
if(!isValid) return;
}
this.$emit('input', opt)
},
},
})