Access component parameters inside caller component - vue.js

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? ;)

Related

Isolated stores for each root component

I have a project where i need to use a store to manage a root component with many child components,
now i need to make copies of that root component with different props values, but the state is shared across the entire app, is there a way to scope the store it to root component?
example to illustrate the problem:
App.vue
<script>
import ComponentA from './components/ComponentA.vue'
</script>
<template>
<ComponentA name="comp1"/>
<ComponentA name="comp2"/>
<ComponentA name="comp3"/>
</template>
ComponentA.vue
<script>
import { store } from './store.js'
export default{
props: ["name"],
data(){
return { store }
}
}
</script>
<template>
{{name}}:{{store.count}}
<button #click="store.increment()">increment</button>
</template>
store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment(){
this.count++
}
})
I don't think that's possible without some sort of refactor.
The global store is helpful if you don't want to deal with prop drilling (and event emitting), but in order to scope it, the component ant its children will need to know which store to use. I think the easiest way to implement that would be to make use of provide/inject. You can use provide/inject to share a store instance
import { reactive } from 'vue'
export function createStoreInstance() {
return reactive({
count: 0,
increment(){
this.count++
}
})
}
then, in your component you would generate the instance during creation
<script>
import { createStoreInstance } from './store.js'
export default {
components: {
GrandChild
},
provide() {
return {
store: this.store
}
},
beforeCreate(){
this.store = createStoreInstance()
},
methods:{
increment(){
this.store.count ++
}
}
}
</script>
and in the child component just get the store
<script>
export default {
inject: [ 'store']
}
</script>
<template>
<p>
{{store}}
</p>
</template>
here is a SFC playground link
You can also use a global store that would accommodate keyed instances, and then use provide inject to pass the key to the children, but I think that would make the store more complicated, but there might be use cases where that may be a better approach (like if you want to be able to save the store state)

Dynamically update props

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
});

Sharing data between components in vue.js

I got an array of data in one component which I want to access in another component but cannot get it right
My idea was to just import component one in component two and thought I could access the data in that way but it didnt work.
here is what I got so far ...
Component 1:
export default {
data() {
return {
info: [
{
id: 1,
title: "Title One"
},
{
id: 2,
title: "Title Two"
},
Component 2:
<template>
<div>
<div v-for="item in info" v-bind:key="item.id">
<div>{{ item.title }} </div>
</div>
</div>
</template>
<script>
import ComponentOne from "../views/ComponentOne ";
export default {
components: {
ComponentOne
}, But after this I am a bit lost
Can anyone point my to the right direction it would be very much appreciated!
In order to access shared data, the most common way is to use Vuex. I'll get you going with the super basics with a module system as it does take a little reading.
npm install vuex --save
Create new folder called store in the src directory.
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import example from './modules/example'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
example // replace with whatever you want to call it
}
})
src/main.js
// add to your imports
import store from './store/index'
...
// Change your Vue instance init
new Vue({
router,
store, // <--- this bit is the thing to add
render: h => h(App)
}).$mount('#app')
/src/store/modules/example.js
// initial state
const state = {
info: []
}
// getters
const getters = {}
// actions
const actions = {
}
// mutations
const mutations = {
set (state, newState) {
state.info.splice(0)
state.info.push.apply(state.info, newState)
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
To update the store when you get info, from any component you can use this.$store.commit('example/set', infoArray) where the first parameter follows the pattern of module name/mutation function name, and the second parameter is the 'new state' that you want updated.
To access the data from the store, you can access it from your components as a computed property:
computed: {
info () {
return this.$store.state.example.info
}
}
Obviously you can use getters and actions and other stuff, but this will get you going and you can read up and modify the Vuex store once you get comfortable and understand how it works.
Let's say if you do not want to use any other state management like vuex then you can share with the use of mixins.
Well, you can achieve it with the use of Vue.mixins.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixins will be “mixed” into the component’s own options.
Mixins official docs
Hope this helps!

How can I send data from parent to child component by vuex store in vue component?

My parent component like this :
<template>
...
<search-result/>
...
</template>
<script>
import {mapActions} from 'vuex'
...
export default {
...
props: ['category'],
mounted() {
this.updateCategory(this.category)
},
methods: {
...mapActions(['updateCategory']),
}
}
</script>
My child component like this :
<template>
...
</template>
<script>
import {mapGetters} from 'vuex'
...
export default {
...
mounted() {
console.log(this.getCategory)
},
computed: {
...mapGetters(['getCategory'])
},
}
</script>
My modules vuex to send data between components like this :
import { set } from 'vue'
...
// initial state
const state = {
category: null
}
// getters
const getters = {
getCategory: state => state.category
}
// actions
const actions = {
updateCategory ({commit}, payload) {
commit(types.UPDATE_CATEGORY, payload)
}
}
// mutations
const mutations = {
[types.UPDATE_CATEGORY] (state, payload) {
state.category = payload
}
}
export default {
state,
getters,
actions,
mutations
}
I try like that, but it does not works
If the code executed, the result of console.log(this.getCategory) in the child component is null
For example category prop in the parent component = 7. Should the result of console.log(this.getCategory) in the child component = 7
Why it does not work? Why the result is null?
Note :
I can just send the category data via prop. But here I do not want to use prop. I want to send data via vuex store. So I want to set and get data only via vuex store
Child's mounted hook is executed before parent's mounted hook. ( why? See this link)
console.log(this.getCategory) happens before this.updateCategory(this.category).
Therefore, you get null in the console.
If you put console.log(this.getCategory) in updated hook, you would be getting the right value in the console later on.
Jacob goh has pointed out the problem.
To solve this issue you can make use of vm.$nextTick() in the child component's mounted hook to ensure that the entire view has been rendered and the parent's mounted hook is called.
<template>
...
</template>
<script>
import {mapGetters} from 'vuex'
...
export default {
...
mounted() {
this.$nextTick(() => {
console.log(this.getCategory);
})
},
computed: {
...mapGetters(['getCategory'])
},
}
</script>
Here is the working fiddle
You can learn more about why use vm.nextTick() here: Vue updates the DOM asynchronously

Two way binding between controller in routerview and parent

I have a single page application which consists of a:
A navigation component which contains a title and a menu
A router-view
I'd like each component being presented in the router view which correspond to a state in the router, to update the title of the navigation component. How to go about passing the parameters from the components in the router-view to the outer navigation component.
I'm using vue 2.0
I would recommending Vuex for this:
https://github.com/vuejs/vuex
This way you can access your Vuex store in the main component and display its title:
computed: {
title () {
return this.$store.state.title
}
}
Then you'd set up a mutation to update the title and call this wherever required in your components. Now as Vue is reactive the title within the nav component will update.
Below is an abbreviated example:
Vuex Store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
title: ''
},
mutations: {
updateTitle (state, title) {
state.title = title
}
}
})
Your App Root:
import Vue from 'vue'
import store from './vuex/store'
new Vue({
el: '#app',
store,
})
Nav component:
export default {
computed: {
title () {
return this.$store.state.title
}
},
...
Any other component which needs to update your title:
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'updateTitle' // map this.updateTitle() to this.$store.commit('updateTitle')
]),
},
// if you wanted to call it on mount
mounted () {
this.updateTitle('hello')
},
...
You can just have a contain-all Main component which stores the current route, and pass it to the navigation and the router view component as the their props, then the situations are:
when Main's stored route changes, the nav will rerender accordingly.
for the router view component, watch the prop change, this.$router.push('/some/where') when it changes.
when the user clicks an item on the nav, it emits an event, along with the data (the route the user wants to go to), the parent receives the event, changes its stored route, which, according to the last 2 bullets, changes how nav and router view looks.
The routes have to be part of the parent, since vue uses a parent-to-child-only one-way data flow. Children can't communicate with each other without help from their common parent unless we use some approach described in the last paragrath.
For details on how to implement parent-child data sharing, see doc.
Above is the simplest solution, which gets dirty quickly when your routes go nested. If so, you'll need a global event bus for non-parent-child data passing, or, vuex if you're looking at a large app and want your global states contained well.