Binding Vue component data to external state - javascript

I have a Vue component that needs to be embedded into a large legacy (not Vue) system in several places.
The component makes some AJAX requests and displays information based on an array of database record IDs, which it usually receives at page load time. The array is embedded directly into the page template by the server, and passed as props to the component, like so:
let config = {
records: {{ template_code_outputting_array }},
...otherConfigOptions
}
let app = new Vue({
el: '#app',
store,
render: h => h(ComponentName, {
props: config
})
})
My problem is that on one of the screens it needs to be embedded into, the database records can be selected dynamically by the user with a search-and-add type AJAX interface (rather than being pre-set at page load), so I need to keep the component in sync with the user's selection so it can be re-rendered as necessary.
I have a working solution but it's far from ideal; I'm currently directly calling a method on the component every time the user makes some change to their selection, passing the new selection so that the component can update its own internal copy:
app.$children[0].recordsChanged(newSelection)
But obviously this isn't reactive, and it feels kind of hacky. I feel like I shouldn't be directly addressing a component, and the function I'm calling is there solely for external use; nothing inside the component uses it.
I've tried binding values in data to global variables (which again isn't ideal but I don't have much choice) then adding a watch method to trigger a re-render on change. Obviously scalar values won't work as they'll be by value and not reference, but wrapping them in an object doesn't seem to work either. They bind fine and take the initial values provided, but updating the object (either directly mutating its values or replacing the object with a new one) doesn't seem to trigger the watch method or any reactivity.
Is there any best practice for doing this or is my current approach the best I can hope for?

An event bus (as suggested by AfikDeri in the comments) is a good solution. It provides a straightforward interface.
Reactive-izing your data is also possible, with some caveats. An Object used to initialize a data item will itself be made reactive. If the things that change your array do so in the approved ways, you should see the changes inside your Vue.
let stuff = [2, 3, 5];
const v = new Vue({
el: '#app',
data: {
reactiveStuff: stuff
},
components: {
myComponent: {
props: ['arr'],
template: '<div><div v-for="item in arr">{{item}}</div></div>'
}
}
});
setTimeout(() => {
stuff.push(7);
}, 1200);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<my-component id="app" :arr="reactiveStuff">
</my-component>

Related

Vuex/Redux store pattern - sharing single source of data in parent and child components that require variations of that data

I understand the benefits of using a store pattern and having a single source of truth for data shared across components in an application, and making API calls in a store action that gets called by components rather than making separate requests in every component that requires the data.
It's my understanding that if this data needs to change in some way, depending on the component using the data, this data can be updated by calling a store action with the appropriate filters/args, and updating the global store var accordingly.
However, I am struggling to understand how to solve the issue whereby a parent component requires one version of this data, and a child of that component requires another.
Consider the following example:
In an API, there exists a GET method on an endpoint to return all people. A flag can be passed to return people who are off sick:
GET: api/people returns ['John Smith', 'Joe Bloggs', 'Jane Doe']
GET: api/people?isOffSick=true returns ['Jane Doe']
A parent component in the front end application requires the unfiltered data, but a child component requires the filtered data. For arguments sake, the API does not return the isOffSick boolean in the response, so 2 separate requests need to be made.
Consider the following example in Vue.js:
// store.js
export const store = createStore({
state: {
people: []
},
actions: {
fetchPeople(filters) {
// ...
const res = api.get('/people' + queryString);
commit('setPeople', res.data);
}
},
mutations: {
setPeople(state, people) {
state.people = people;
}
}
});
// parent.vue - requires ALL people (NO filters/args passed to API)
export default {
mounted() {
this.setPeople();
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
// child.vue - requires only people who are off sick (filters/args passed to API)
export default {
mounted() {
this.setPeople({ isOffSick: true });
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
The parent component sets the store var with the data it requires, and then the child overwrites that store var with the data it requires.
Obviously the shared store var is not compatible with both components.
What is the preferred solution to this problem for a store pattern? Storing separate state inside the child component seems to violate the single source of truth for the data, which is partly the reason for using a store pattern in the first place.
Edit:
My question is pertaining to the architecture of the store pattern, rather than asking for a solution to this specific example. I appreciate that the API response in this example does not provide enough information to filter the global store of people, i.e. using a getter, for use in the child component.
What I am asking is: where is an appropriate place to store this second set of people if I wanted to stay true to a store focused design pattern?
It seems wrong somehow to create another store variable to hold the data just for the child component, yet it also seems counter-intuitive to store the second set of data in the child component's state, as that would not be in line with a store pattern approach and keeping components "dumb".
If there were numerous places that required variations on the people data that could only be created by a separate API call, there would either be a) lots of store variables for each "variation" of the data, or b) separate API calls and state in each of these components.
Thanks to tao I've found what I'm looking for:
The best approach would be to return the isOffSick property in the API response, then filtering the single list of people (e.g. using a store getter), thus having a single source of truth for all people in the store and preventing the need for another API request.
If that was not possible, it would make sense to add a secondary store variable for isOffSick people, to be consumed by the child component.

How to induce reactivity when updating multiple props in an object using VueJS?

I was witnessing some odd behaviour while building my app where a part of the dom wasn't reacting properly to input. The mutations were being registered, the state was changing, but the prop in the DOM wasn't. I noticed that when I went back, edited one new blank line in the html, came back and it was now displaying the new props. But I would have to edit, save, the document then return to also see any new changes to the state.
So the state was being updated, but Vue wasn't reacting to the change. Here's why I think why: https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive
Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object
The Object in my state is an instance of js-libp2p. Periodically whenever the libp2p instance does something I need to update the object in my state. I was doing this by executing a mutation
syncNode(state, libp2p) {
state.p2pNode = libp2p
}
Where libp2p is the current instance of the object I'm trying to get the DOM to react to by changing state.p2pNode. I can't use $set, that is for single value edits, and I think .assign or .extend will not work either as I am trying to replace the entire object tree.
Why is there this limitation and is there a solution for this particular problem?
The only thing needed to reassign a Vuex state item that way is to have declared it beforehand.
It's irrelevant whether that item is an object or any other variable type, even if overwriting the entire value. This is not the same as the reactivity caveat situations where set is required because Vue can't detect an object property mutation, despite the fact that state is an object. This is unnecessary:
Vue.set(state, 'p2pNode', libp2p);
There must be some other problem if there is a component correctly using p2pNode that is not reacting to the reassignment. Confirm that you declared/initialized it in Vuex initial state:
state: {
p2pNode: null // or whatever initialization value makes the most sense
}
Here is a demo for proof. It's likely that the problem is that you haven't used the Vuex value in some reactive way.
I believe your issue is more complex than the basic rules about assignment of new properties. But the first half of this answer addresses the basics rules.
And to answer why Vue has some restrictions about how to correctly assign new properties to a reactive object, it likely has to do with performance and limitations of the language. Theoretically, Vue could constantly traverse its reactive objects searching for new properties, but performance would be probably be terrible.
For what it's worth, Vue 3's new compiler will supposedly able to handle this more easily. Until then, the docs you linked to supply the correct solution (see example below) for most cases.
var app = new Vue({
el: "#app",
data() {
return {
foo: {
person: {
firstName: "Evan"
}
}
};
},
methods: {
syncData() {
// Does not work
// this.foo.occupation = 'coder';
// Does work (foo is already reactive)
this.foo = {
person: {
firstName: "Evan"
},
occupation: 'Coder'
};
// Also works (better when you need to supply a
// bunch of new props but keep the old props too)
// this.foo = Object.assign({}, this.foo, {
// occupation: 'Coder',
// });
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Hello {{foo.person.firstName}} {{foo.occupation}}!
<button #click="syncData">Load new data</button>
</div>
Update: Dan's answer was good - probably better than mine for most cases, since it accounts for Vuex. Given that your code is still not working when you use his solution, I suspect that p2pNode is sometimes mutating itself (Vuex expects all mutations in that object to go through an official commit). Given that it appears to have lifecycle hooks (e.g. libp2p.on('peer:connect'), I would not be surprised if this was the case. You may end up tearing your hair out trying to get perfect reactivity on a node that's quietly mutating itself in the background.
If this is the case, and libp2p provides no libp2p.on('update') hook through which you could inform Vuex of changes, then you might want to implement a sort of basic game state loop and simply tell Vue to recalculate everything every so often after a brief sleep. See https://stackoverflow.com/a/40586872/752916 and https://stackoverflow.com/a/39914235/752916. This is a bit of hack (an informed one, at least), but it might make your life a lot easier in the short run until you sort out this thorny bug, and there should be no flicker.
Just a thought, I don't know anything about libp2p but have you try to declare your variable in the data options that change on the update:
data: {
updated: ''
}
and then assigning it a value :
syncNode(state, libp2p) {
this.updated = state
state.p2pNode = libp2p
}

How can give notice to the brother level component? [duplicate]

Overview
In Vue.js 2.x, model.sync will be deprecated.
So, what is a proper way to communicate between sibling components in Vue.js 2.x?
Background
As I understand Vue.js 2.x, the preferred method for sibling communication is to use a store or an event bus.
According to Evan (creator of Vue.js):
It's also worth mentioning "passing data between components" is
generally a bad idea, because in the end the data flow becomes
untrackable and very hard to debug.
If a piece of data needs to be shared by multiple components, prefer
global stores or Vuex.
[Link to discussion]
And:
.once and .sync are deprecated. Props are now always one-way down. To
produce side effects in the parent scope, a component needs to
explicitly emit an event instead of relying on implicit binding.
So, Evan suggests using $emit() and $on().
Concerns
What worries me is:
Each store and event has a global visibility (correct me if I'm wrong);
It's too wasteful to create a new store for each minor communication;
What I want is to some scope events or stores visibility for siblings components. (Or perhaps I didn't understand the above idea.)
Question
So, what is the correct way to communicate between sibling components?
You can even make it shorter and use the root Vue instance as the global Event Hub:
Component 1:
this.$root.$emit('eventing', data);
Component 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
With Vue.js 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.
Define centralized event hub.
const eventHub = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Now in your component you can emit events with
this.eventHub.$emit('update', data)
And to listen you do
this.eventHub.$on('update', data => {
// do your thing
})
Update
Please see the answer by alex, which describes a simpler solution.
Disclaimer: this answer was written a long time ago and it may not reflect latest Vue development or trends. Take everything in this answer with a grain of salt and please comment if you find anything that's outdated, no longer valid, or unhelpful.
State scopes
When designing a Vue application (or in fact, any component based application), there are different types of data that depend on which concerns we're dealing with and each has its own preferred communication channels.
Global state: may include the logged in user, the current theme, etc.
Local state: form attributes, disabled button state, etc.
Note that part of the global state might end up in the local state at some point, and it could be passed down to child components as any other local state would, either in full or diluted to match the use-case.
Communication channels
A channel is a loose term I'll be using to refer to concrete implementations to exchange data around a Vue app.
Each implementation addresses a specific communication channel, which includes:
Global state
Parent-child
Child-parent
Siblings
Different concerns relate to different communication channels.
Props: Direct Parent-Child
The simplest communication channel in Vue for one-way data binding.
Events: Direct Child-Parent
Important notice: $on and $once were removed in Vue version 3.
$emit and v-on event listeners. The simplest communication channel for direct Child-Parent communication. Events enable 2-way data binding.
Provide/Inject: Global or distant local state
Added in Vue 2.2+, and really similar to React's context API, this could be used as a viable replacement to an event bus.
At any point within the components tree could a component provide some data, which any child down the line could access through the inject component's property.
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
app.component('todo-list-statistics', {
inject: ['todoLength'],
created() {
console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
}
})
This could be used to provide global state at the root of the app, or localized state within a subset of the tree.
Centralized store (Global state)
Note: Vuex 5 is going to be Pinia apparently. Stay tuned. (Tweet)
Vuex is a state management pattern + library for Vue.js applications.
It serves as a centralized store for all the components in an
application, with rules ensuring that the state can only be mutated in
a predictable fashion.
And now you ask:
[S]hould I create vuex store for each minor communication?
It really shines when dealing with global state, which includes but is not limited to:
data received from a backend,
global UI state like a theme,
any data persistence layer, e.g. saving to a backend or interfacing with local storage,
toast messages or notifications,
etc.
So your components can really focus on the things they're meant to be, managing user interfaces, while the global store can manage/use general business logic and offer a clear API through getters and actions.
It doesn't mean that you can't use it for component logic, but I would personally scope that logic to a namespaced Vuex module with only the necessary global UI state.
To avoid dealing with a big mess of everything in a global state, see the Application structure recommandations.
Refs and methods: Edge cases
Despite the existence of props and events, sometimes you might still
need to directly access a child component in JavaScript.
It is only meant as an escape hatch for direct child manipulation -
you should avoid accessing $refs from within templates or computed properties.
If you find yourself using refs and child methods quite often, it's probably time to lift the state up or consider the other ways described here or in the other answers.
$parent: Edge cases
Similar to $root, the $parent property can be used to access the
parent instance from a child. This can be tempting to reach for as a
lazy alternative to passing data with a prop.
In most cases, reaching into the parent makes your application more
difficult to debug and understand, especially if you mutate data in
the parent. When looking at that component later, it will be very
difficult to figure out where that mutation came from.
You could in fact navigate the whole tree structure using $parent, $ref or $root, but it would be akin to having everything global and likely become unmaintainable spaghetti.
Event bus: Global/distant local state
See #AlexMA's answer for up-to-date information about the event bus pattern.
This was the pattern in the past to pass props all over the place from far up down to deeply nested children components, with almost no other components needing these in between. Use sparingly for carefully selected data.
Be careful: Subsequent creation of components that are binding themselves to the event bus will be bound more than once--leading to multiple handlers triggered and leaks. I personally never felt the need for an event bus in all the single page apps I've designed in the past.
The following demonstrates how a simple mistake leads to a leak where the Item component still triggers even if removed from the DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" #click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Remember to remove listeners in the destroyed lifecycle hook.
Component types
Disclaimer: the following "containers" versus "presentational" components is just one way to structure a project and there are now multiple alternatives, like the new Composition API that could effectively replace the "app specific containers" I'm describing below.
To orchestrates all these communications, to ease re-usability and testing, we could think of components as two different types.
App specific containers
Generic/presentational components
Again, it doesn't mean that a generic component should be reused or that an app specific container can't be reused, but they have different responsibilities.
App specific containers
Note: see the new Composition API as an alternative to these containers.
These are just simple Vue component that wraps other Vue components (generic or other app specific containers). This is where the Vuex store communication should happen and this container should communicate through other simpler means like props and event listeners.
These containers could even have no native DOM elements at all and let the generic components deal with the templating and user interactions.
scope somehow events or stores visibility for siblings components
This is where the scoping happens. Most components don't know about the store and this component should (mostly) use one namespaced store module with a limited set of getters and actions applied with the provided Vuex binding helpers.
Generic/presentational components
These should receive their data from props, make changes on their own local data, and emit simple events. Most of the time, they should not know a Vuex store exists at all.
They could also be called containers as their sole responsibility could be to dispatch to other UI components.
Sibling communication
So, after all this, how should we communicate between two sibling components?
It's easier to understand with an example: say we have an input box and its data should be shared across the app (siblings at different places in the tree) and persisted with a backend.
❌ Mixing concerns
Starting with the worst case scenario, our component would mix presentation and business logic.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
});
}
}
}
</script>
While it might look fine for a simple app, it comes with a lot of drawbacks:
Explicitly uses the global axios instance
Hard-coded API inside the UI
Tightly coupled to the root component (event bus pattern)
Harder to do unit tests
✅ Separation of concerns
To separate these two concerns, we should wrap our component in an app specific container and keep the presentation logic into our generic input component.
With the following pattern, we can:
Easily test each concern with unit tests
Change the API without impacting components at all
Configure HTTP communications however you'd like (axios, fetch, adding middlewares, tests, etc)
Reuse the input component anywhere (reduced coupling)
React to state changes from anywhere in the app through the global store bindings
etc.
Our input component is now reusable and doesn't know about the backend nor the siblings.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Our app specific container can now be the bridge between the business logic and the presentation communication.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" #change="updateState"></my-input>
<my-input :initial="otherValue" #change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
#click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '#/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.saveState,
])
}
</script>
Since the Vuex store actions deal with the backend communication, our container here doesn't need to know about axios and the backend.
Okay, we can communicate between siblings via the parent using v-on events.
Parent
|- List of items // Sibling 1 - "List"
|- Details of selected item // Sibling 2 - "Details"
Let's assume that we want update Details component when we click some element in List.
In Parent:
Template:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Here:
v-on:select-item it's an event, that will be called in List component (see below);
setSelectedItem it's a Parent's method to update selectedModel;
JavaScript:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item // Here we change the Detail's model
},
}
//...
In List:
Template:
<ul>
<li v-for="i in list"
:value="i"
#click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JavaScript:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit('select-item', item) // Here we call the event we waiting for in "Parent"
},
}
//...
Here:
this.$emit('select-item', item) will send an item via select-item directly in the parent. And the parent will send it to the Details view.
How to handle communication between siblings depends on the situation. But first I want to emphasize that the global event bus approach is going away in Vue.js 3. See this RFC. Hence this answer.
Lowest Common Ancestor Pattern (or “LCA”)
For most cases, I recommend using the lowest common ancestor pattern (also known as “data down, events up”). This pattern is easy to read, implement, test, and debug. It also creates an elegant, simple data flow.
In essence, this means if two components need to communicate, put their shared state in the closest component that both share as an ancestor. Pass data from parent component to child component via props, and pass information from child to parent by emitting an event (example code below).
For example, one might have an email app: the address component needs to communicate data to the message body component (perhaps for pre-populating "Hello <name>"), so they use their closest shared ancestor (perhaps an email form component) to hold the addressee data.
LCA can be annoying if events and props need to pass through many "middlemen" components.
For more detail, I refer colleagues to this excellent blog post. (Ignore the fact that its examples use Ember, its concepts are applicable to many frameworks).
Data Container Pattern (e.g., Vuex)
For complex cases or situations where parent–child communication would involve too many middlemen, use Vuex or an equivalent data container technology.
Use namespaced modules when a single store becomes too complicated or disorganized. For example, it might be reasonable to create a separate namespace for a complex collection of components with many interconnections, such as a complex calendar.
Publish/Subscribe (Event Bus) Pattern
If the event bus (i.e. publish/subscribe) pattern makes more sense for your app (from an architecture standpoint), or you need to remove Vue.js's global event bus from an existing Vue.js app, the Vue.js core team now recommends using a third party library such as mitt. (See the RFC referenced in paragraph 1.).
Miscellaneous
Here's a small (perhaps overly simplistic) example of an LCA solution for sibling-to-sibling communication. This is a game called whack-a-mole.
In this game the player gets points when they "whack" a mole, which causes it to hide and then another mole appears in a random spot. To build this app, which contains "mole" components, one might think , “mole component N should tell mole component Y to appear after it is whacked”. But Vue.js discourages this method of component communication, since Vue.js apps (and html) are effectively tree data structures.
This is probably a good thing. A large/complex app, where nodes communicated to each-other without any centralized manager, might be very difficult to debug. Additionally, components that use LCA tend to exhibit low coupling and high reusability.
In this example, the game manager component passes mole visibility as a prop to mole child components. When a visible mole is "whacked" (clicked), it emits an event. The game manager component (the common ancenstor) receives the event and modifies its state. Vue.js automatically updates the props, so all of the mole components receive new visibility data.
Vue.component('whack-a-mole', {
data() {
return {
stateOfMoles: [true, false, false],
points: 0
}
},
template: `<div>WHACK - A - MOLE!<br/>
<a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
<a-mole :has-mole="stateOfMoles[1]" v-on:moleMashed="moleClicked(1)"/>
<a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
<p>Score: {{points}}</p>
</div>`,
methods: {
moleClicked(n) {
if(this.stateOfMoles[n]) {
this.points++;
this.stateOfMoles[n] = false;
this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
}
}
}
})
Vue.component('a-mole', {
props: ['hasMole'],
template: `<button #click="$emit('moleMashed')">
<span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
</button>`
})
var app = new Vue({
el: '#app',
data() {
return { name: 'Vue' }
}
})
.mole-button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<whack-a-mole />
</div>
What I usually do if I want to "hack" the normal patterns of communication in Vue.js, specially now that .sync is deprecated, is to create a simple EventEmitter that handles communication between components. From one of my latest projects:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
With this Transmitter object you can then do, in any component:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
And to create a "receiving" component:
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Again, this is for really specific uses. Don't base your whole application on this pattern, use something like Vuex instead.
In my case i have a table with editable cells. I only want one cell to be editable at a time as the user clicks from one to another to edit the contents.
The solution is to use parent-child (props) and child-parent (event).
In the example below i'm looping over a dataset of 'rows' and using the rowIndex and cellIndex to create a unique (coordinate) identifier for each cell. When a cell is clicked an event is fired from the child element up to the parent telling the parent which coordinate has been clicked. The parent then sets the selectedCoord and passes this back down to the child components. So each child component knows its own coordinate and the selected coordinate. It can then decide whether to make itself editable or not.
<!-- PARENT COMPONENT -->
<template>
<table>
<tr v-for="(row, rowIndex) in rows">
<editable-cell
v-for="(cell, cellIndex) in row"
:key="cellIndex"
:cell-content="cell"
:coords="rowIndex+'-'+cellIndex"
:selected-coords="selectedCoords"
#select-coords="selectCoords"
></editable-cell>
</tr>
</table>
</template>
<script>
export default {
name: 'TableComponent'
data() {
return {
selectedCoords: '',
}
},
methods: {
selectCoords(coords) {
this.selectedCoords = coords;
},
},
</script>
<!-- CHILD COMPONENT -->
<template>
<td #click="toggleSelect">
<input v-if="coords===selectedCoords" type="text" :value="cellContent" />
<span v-else>{{ cellContent }}</span>
</td>
</template>
<script>
export default {
name: 'EditableCell',
props: {
cellContent: {
required: true
},
coords: {
type: String,
required: true
},
selectedCoords: {
type: String,
required: true
},
},
methods: {
toggleSelect() {
const arg = (this.coords === this.selectedCoords) ? '' : this.coords;
this.$emit('select-coords', arg);
},
}
};
</script>

vue.js - dynamically generated component in v-for does not update bound property correctly

I came upon a problem in vue.js and I'm not sure how I should solve it. I'll try to explain the gist of it as clear and short as possible.
Situation
I load data from a rest API. I created a class using the javascript prototype syntax to create a class called DataLoader which handles all the loading and internal state changes. More often than not, the loaded data is displayed in a repetitive way (e.g. list or cards). I created a new component called DataLoaderWrapper for this purpose, taking a DataLoader object as property. The DataLoaderWrapper displays loading spinners according to the loading state of the DataLoader object. In addition, the component markup includes the following tag:
<slot :dataLoader='dataLoader' />
.. to allow for easy access to the loaded data. The idea is to use the component like so:
<DataLoaderWrapper :dataLoader="myDataLoader">
<template slot-scope="props">
<template v-for="item in props.dataLoader.data">
{{item}}
</template>
</template>
</DataLoaderWrapper>
Now just to clear this up right away; why do I use props.dataLoader instead of myDataLoader within the template? The basic idea is that the DataLoader object sometimes is generated dynamically and therefore the most direct access to the object instance is via the component bound property. (I also tried direct access to the loader and found no different behavior, but this way makes the component easier to use for me)
Now let's extend this basic concept.
Imagine you have a DataLoader object which is configured to load a list of music releases. This would look something like this:
<DataLoaderWrapper :dataLoader="myReleaseLoader">
...
</DataLoaderWrapper>
Furthermore, every release is published by one or more artists. The list of artists needs to be loaded separately. I can extend the example like this:
<DataLoaderWrapper :dataLoader="myReleaseLoader">
<template slot-scope="props">
<template v-for="release in props.dataLoader.data">
<h1>release.Title</h1>
Artists:
<DataLoaderWrapper :dataLoader="getArtistsLoader(release)">
<template slot-scope="nestedProps">
{{nestedProps.dataLoader.data.map(artist => artist.Name).join(', ')}}
</template>
</DataLoaderWrapper>
</template>
</template>
</DataLoaderWrapper>
The Problem
The releases are loaded correctly and the templates of myReleaseLoader are rendered correctly. As soon as this happens, a new DataLoader instance is created dynamically and ordered to load the artists for a given release. This happens for every release. While the artist loaders are loading, the loading spinner is displayed. The problem is, that the nestedProps variable does not get updated as soon as the DataLoader instance is done loading the data.
I've been trying to solve this issue for at least two days, meaning I've debugged and checked everything that came to my mind.
I am sure that the data is correctly loaded and existent. But the loading spinner of the DataLoaderWrapper component is still displayed and printing nestedProps.dataLoader reveals data: [] which does not line up with the state shown when inspecting the component. This brings me to the conclusion, that the nestedProps property is not updated correctly.
I think this could be because vue bindings are one-directional and an update in a child component will not trigger the parent to update.
Having the page loaded on my local machine with 'hot-reloading' on performs an update to the UI every time I save changes in my files without performing a full reload of the page. The loaders are in a different state when updating the UI now than they are when loading the page for the first time. The data does not get loaded again, because the getArtistsLoader(..) function implements a cache which guarantees that always the same loader is returned for a given release when requested, this means that the data is essentially already existent when re-rendering the page, resulting in the desired output being displayed. I wasn't able to get into the correct state otherwise.
I tried to work with computed properties, direct access to the loader and more direct access to the desired property (e.g. props.data instead of props.dataLoader.data), none of which solved the update issue.
How can I do this?
The problem is getArtistsLoader does not return data synchronously
const data = getArtistsLoader(release);
// data === undefined
so inside the DataLoaderWrapper, it gets undefined for the dataLoader props
Solution
DataLoaderWrapper can accept a promise as data, so it can update when promise resolved.
1. update DataLoaderWrapper to accept promise
I use an async prop to distinguish dataLoader is asynchronous or not
<template>
<div>
<slot :dataLoader="realData"/>
</div>
</template>
<script>
export default {
props: {
dataLoader: null,
async: Boolean
},
data() {
let realData = this.dataLoader;
if (this.async) {
realData = [];
this.dataLoader
.then(data => {
this.realData = data;
})
.catch(error => {
console.log(error);
});
}
return {
realData: realData
};
}
};
</script>
2. make getArtistsLoader return promise
function getArtistsLoader(release) {
retrun axios.get('/artists?release=' + release.id)
.then((response) => response.data)
}
3. add async prop
<DataLoaderWrapper async :dataLoader="getArtistsLoader(release)">
full demo

Communication between sibling components in Vue.js 2.0

Overview
In Vue.js 2.x, model.sync will be deprecated.
So, what is a proper way to communicate between sibling components in Vue.js 2.x?
Background
As I understand Vue.js 2.x, the preferred method for sibling communication is to use a store or an event bus.
According to Evan (creator of Vue.js):
It's also worth mentioning "passing data between components" is
generally a bad idea, because in the end the data flow becomes
untrackable and very hard to debug.
If a piece of data needs to be shared by multiple components, prefer
global stores or Vuex.
[Link to discussion]
And:
.once and .sync are deprecated. Props are now always one-way down. To
produce side effects in the parent scope, a component needs to
explicitly emit an event instead of relying on implicit binding.
So, Evan suggests using $emit() and $on().
Concerns
What worries me is:
Each store and event has a global visibility (correct me if I'm wrong);
It's too wasteful to create a new store for each minor communication;
What I want is to some scope events or stores visibility for siblings components. (Or perhaps I didn't understand the above idea.)
Question
So, what is the correct way to communicate between sibling components?
You can even make it shorter and use the root Vue instance as the global Event Hub:
Component 1:
this.$root.$emit('eventing', data);
Component 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
With Vue.js 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.
Define centralized event hub.
const eventHub = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Now in your component you can emit events with
this.eventHub.$emit('update', data)
And to listen you do
this.eventHub.$on('update', data => {
// do your thing
})
Update
Please see the answer by alex, which describes a simpler solution.
Disclaimer: this answer was written a long time ago and it may not reflect latest Vue development or trends. Take everything in this answer with a grain of salt and please comment if you find anything that's outdated, no longer valid, or unhelpful.
State scopes
When designing a Vue application (or in fact, any component based application), there are different types of data that depend on which concerns we're dealing with and each has its own preferred communication channels.
Global state: may include the logged in user, the current theme, etc.
Local state: form attributes, disabled button state, etc.
Note that part of the global state might end up in the local state at some point, and it could be passed down to child components as any other local state would, either in full or diluted to match the use-case.
Communication channels
A channel is a loose term I'll be using to refer to concrete implementations to exchange data around a Vue app.
Each implementation addresses a specific communication channel, which includes:
Global state
Parent-child
Child-parent
Siblings
Different concerns relate to different communication channels.
Props: Direct Parent-Child
The simplest communication channel in Vue for one-way data binding.
Events: Direct Child-Parent
Important notice: $on and $once were removed in Vue version 3.
$emit and v-on event listeners. The simplest communication channel for direct Child-Parent communication. Events enable 2-way data binding.
Provide/Inject: Global or distant local state
Added in Vue 2.2+, and really similar to React's context API, this could be used as a viable replacement to an event bus.
At any point within the components tree could a component provide some data, which any child down the line could access through the inject component's property.
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
app.component('todo-list-statistics', {
inject: ['todoLength'],
created() {
console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
}
})
This could be used to provide global state at the root of the app, or localized state within a subset of the tree.
Centralized store (Global state)
Note: Vuex 5 is going to be Pinia apparently. Stay tuned. (Tweet)
Vuex is a state management pattern + library for Vue.js applications.
It serves as a centralized store for all the components in an
application, with rules ensuring that the state can only be mutated in
a predictable fashion.
And now you ask:
[S]hould I create vuex store for each minor communication?
It really shines when dealing with global state, which includes but is not limited to:
data received from a backend,
global UI state like a theme,
any data persistence layer, e.g. saving to a backend or interfacing with local storage,
toast messages or notifications,
etc.
So your components can really focus on the things they're meant to be, managing user interfaces, while the global store can manage/use general business logic and offer a clear API through getters and actions.
It doesn't mean that you can't use it for component logic, but I would personally scope that logic to a namespaced Vuex module with only the necessary global UI state.
To avoid dealing with a big mess of everything in a global state, see the Application structure recommandations.
Refs and methods: Edge cases
Despite the existence of props and events, sometimes you might still
need to directly access a child component in JavaScript.
It is only meant as an escape hatch for direct child manipulation -
you should avoid accessing $refs from within templates or computed properties.
If you find yourself using refs and child methods quite often, it's probably time to lift the state up or consider the other ways described here or in the other answers.
$parent: Edge cases
Similar to $root, the $parent property can be used to access the
parent instance from a child. This can be tempting to reach for as a
lazy alternative to passing data with a prop.
In most cases, reaching into the parent makes your application more
difficult to debug and understand, especially if you mutate data in
the parent. When looking at that component later, it will be very
difficult to figure out where that mutation came from.
You could in fact navigate the whole tree structure using $parent, $ref or $root, but it would be akin to having everything global and likely become unmaintainable spaghetti.
Event bus: Global/distant local state
See #AlexMA's answer for up-to-date information about the event bus pattern.
This was the pattern in the past to pass props all over the place from far up down to deeply nested children components, with almost no other components needing these in between. Use sparingly for carefully selected data.
Be careful: Subsequent creation of components that are binding themselves to the event bus will be bound more than once--leading to multiple handlers triggered and leaks. I personally never felt the need for an event bus in all the single page apps I've designed in the past.
The following demonstrates how a simple mistake leads to a leak where the Item component still triggers even if removed from the DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" #click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Remember to remove listeners in the destroyed lifecycle hook.
Component types
Disclaimer: the following "containers" versus "presentational" components is just one way to structure a project and there are now multiple alternatives, like the new Composition API that could effectively replace the "app specific containers" I'm describing below.
To orchestrates all these communications, to ease re-usability and testing, we could think of components as two different types.
App specific containers
Generic/presentational components
Again, it doesn't mean that a generic component should be reused or that an app specific container can't be reused, but they have different responsibilities.
App specific containers
Note: see the new Composition API as an alternative to these containers.
These are just simple Vue component that wraps other Vue components (generic or other app specific containers). This is where the Vuex store communication should happen and this container should communicate through other simpler means like props and event listeners.
These containers could even have no native DOM elements at all and let the generic components deal with the templating and user interactions.
scope somehow events or stores visibility for siblings components
This is where the scoping happens. Most components don't know about the store and this component should (mostly) use one namespaced store module with a limited set of getters and actions applied with the provided Vuex binding helpers.
Generic/presentational components
These should receive their data from props, make changes on their own local data, and emit simple events. Most of the time, they should not know a Vuex store exists at all.
They could also be called containers as their sole responsibility could be to dispatch to other UI components.
Sibling communication
So, after all this, how should we communicate between two sibling components?
It's easier to understand with an example: say we have an input box and its data should be shared across the app (siblings at different places in the tree) and persisted with a backend.
❌ Mixing concerns
Starting with the worst case scenario, our component would mix presentation and business logic.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
});
}
}
}
</script>
While it might look fine for a simple app, it comes with a lot of drawbacks:
Explicitly uses the global axios instance
Hard-coded API inside the UI
Tightly coupled to the root component (event bus pattern)
Harder to do unit tests
✅ Separation of concerns
To separate these two concerns, we should wrap our component in an app specific container and keep the presentation logic into our generic input component.
With the following pattern, we can:
Easily test each concern with unit tests
Change the API without impacting components at all
Configure HTTP communications however you'd like (axios, fetch, adding middlewares, tests, etc)
Reuse the input component anywhere (reduced coupling)
React to state changes from anywhere in the app through the global store bindings
etc.
Our input component is now reusable and doesn't know about the backend nor the siblings.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Our app specific container can now be the bridge between the business logic and the presentation communication.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" #change="updateState"></my-input>
<my-input :initial="otherValue" #change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
#click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '#/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.saveState,
])
}
</script>
Since the Vuex store actions deal with the backend communication, our container here doesn't need to know about axios and the backend.
Okay, we can communicate between siblings via the parent using v-on events.
Parent
|- List of items // Sibling 1 - "List"
|- Details of selected item // Sibling 2 - "Details"
Let's assume that we want update Details component when we click some element in List.
In Parent:
Template:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Here:
v-on:select-item it's an event, that will be called in List component (see below);
setSelectedItem it's a Parent's method to update selectedModel;
JavaScript:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item // Here we change the Detail's model
},
}
//...
In List:
Template:
<ul>
<li v-for="i in list"
:value="i"
#click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JavaScript:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit('select-item', item) // Here we call the event we waiting for in "Parent"
},
}
//...
Here:
this.$emit('select-item', item) will send an item via select-item directly in the parent. And the parent will send it to the Details view.
How to handle communication between siblings depends on the situation. But first I want to emphasize that the global event bus approach is going away in Vue.js 3. See this RFC. Hence this answer.
Lowest Common Ancestor Pattern (or “LCA”)
For most cases, I recommend using the lowest common ancestor pattern (also known as “data down, events up”). This pattern is easy to read, implement, test, and debug. It also creates an elegant, simple data flow.
In essence, this means if two components need to communicate, put their shared state in the closest component that both share as an ancestor. Pass data from parent component to child component via props, and pass information from child to parent by emitting an event (example code below).
For example, one might have an email app: the address component needs to communicate data to the message body component (perhaps for pre-populating "Hello <name>"), so they use their closest shared ancestor (perhaps an email form component) to hold the addressee data.
LCA can be annoying if events and props need to pass through many "middlemen" components.
For more detail, I refer colleagues to this excellent blog post. (Ignore the fact that its examples use Ember, its concepts are applicable to many frameworks).
Data Container Pattern (e.g., Vuex)
For complex cases or situations where parent–child communication would involve too many middlemen, use Vuex or an equivalent data container technology.
Use namespaced modules when a single store becomes too complicated or disorganized. For example, it might be reasonable to create a separate namespace for a complex collection of components with many interconnections, such as a complex calendar.
Publish/Subscribe (Event Bus) Pattern
If the event bus (i.e. publish/subscribe) pattern makes more sense for your app (from an architecture standpoint), or you need to remove Vue.js's global event bus from an existing Vue.js app, the Vue.js core team now recommends using a third party library such as mitt. (See the RFC referenced in paragraph 1.).
Miscellaneous
Here's a small (perhaps overly simplistic) example of an LCA solution for sibling-to-sibling communication. This is a game called whack-a-mole.
In this game the player gets points when they "whack" a mole, which causes it to hide and then another mole appears in a random spot. To build this app, which contains "mole" components, one might think , “mole component N should tell mole component Y to appear after it is whacked”. But Vue.js discourages this method of component communication, since Vue.js apps (and html) are effectively tree data structures.
This is probably a good thing. A large/complex app, where nodes communicated to each-other without any centralized manager, might be very difficult to debug. Additionally, components that use LCA tend to exhibit low coupling and high reusability.
In this example, the game manager component passes mole visibility as a prop to mole child components. When a visible mole is "whacked" (clicked), it emits an event. The game manager component (the common ancenstor) receives the event and modifies its state. Vue.js automatically updates the props, so all of the mole components receive new visibility data.
Vue.component('whack-a-mole', {
data() {
return {
stateOfMoles: [true, false, false],
points: 0
}
},
template: `<div>WHACK - A - MOLE!<br/>
<a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
<a-mole :has-mole="stateOfMoles[1]" v-on:moleMashed="moleClicked(1)"/>
<a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
<p>Score: {{points}}</p>
</div>`,
methods: {
moleClicked(n) {
if(this.stateOfMoles[n]) {
this.points++;
this.stateOfMoles[n] = false;
this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
}
}
}
})
Vue.component('a-mole', {
props: ['hasMole'],
template: `<button #click="$emit('moleMashed')">
<span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
</button>`
})
var app = new Vue({
el: '#app',
data() {
return { name: 'Vue' }
}
})
.mole-button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<whack-a-mole />
</div>
What I usually do if I want to "hack" the normal patterns of communication in Vue.js, specially now that .sync is deprecated, is to create a simple EventEmitter that handles communication between components. From one of my latest projects:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
With this Transmitter object you can then do, in any component:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
And to create a "receiving" component:
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Again, this is for really specific uses. Don't base your whole application on this pattern, use something like Vuex instead.
In my case i have a table with editable cells. I only want one cell to be editable at a time as the user clicks from one to another to edit the contents.
The solution is to use parent-child (props) and child-parent (event).
In the example below i'm looping over a dataset of 'rows' and using the rowIndex and cellIndex to create a unique (coordinate) identifier for each cell. When a cell is clicked an event is fired from the child element up to the parent telling the parent which coordinate has been clicked. The parent then sets the selectedCoord and passes this back down to the child components. So each child component knows its own coordinate and the selected coordinate. It can then decide whether to make itself editable or not.
<!-- PARENT COMPONENT -->
<template>
<table>
<tr v-for="(row, rowIndex) in rows">
<editable-cell
v-for="(cell, cellIndex) in row"
:key="cellIndex"
:cell-content="cell"
:coords="rowIndex+'-'+cellIndex"
:selected-coords="selectedCoords"
#select-coords="selectCoords"
></editable-cell>
</tr>
</table>
</template>
<script>
export default {
name: 'TableComponent'
data() {
return {
selectedCoords: '',
}
},
methods: {
selectCoords(coords) {
this.selectedCoords = coords;
},
},
</script>
<!-- CHILD COMPONENT -->
<template>
<td #click="toggleSelect">
<input v-if="coords===selectedCoords" type="text" :value="cellContent" />
<span v-else>{{ cellContent }}</span>
</td>
</template>
<script>
export default {
name: 'EditableCell',
props: {
cellContent: {
required: true
},
coords: {
type: String,
required: true
},
selectedCoords: {
type: String,
required: true
},
},
methods: {
toggleSelect() {
const arg = (this.coords === this.selectedCoords) ? '' : this.coords;
this.$emit('select-coords', arg);
},
}
};
</script>

Categories

Resources