Communication between Vue components in Meteor
There is few methods of sending data between unrelated vue components. Some of these are universal, other typical for vue, another for Meteor. We compare all of them.
Daniel Gustaw
• 10 min read
What we have and what we want?
Lets consider the following situation.
- We have Meteor project with Vue
- We have two components that use the same data
- We want to observe changes made in one component in another
How to do this?
There are some very different approaches to gain this result:
a) Do no use Vue or Meteor and operate directly on DOM
b) Use EventBus known from Vue to emit and listen event
c) Use Vuex that allow many components to operate on global state
d) Use Meteor Tracker to recalculate property in second component
e) Use Minimongo and vue-meteor-tracker package
Below we show examples of application any of these methods and describe their pros and cons.
Full repository with all these method can be found there: https://github.com/gustawdaniel/vue-meteor-components-communication-tutorial
How to integrate Vue with Meteor?
We assume that you can create meteor project with Vue. If not then after on standard meteor project created by
meteor create .
you should use commands:
meteor remove autopublish insecure blaze-html-templates
meteor add akryum:vue-component static-html
meteor npm install --save vue-meteor-tracker vue
Replace /client/main.html
by
<head>
<title>Components communication</title>
</head>
<body>
<div id="app"></div>
</body>
and /client/main.js
by
import Vue from 'vue';
import VueMeteorTracker from 'vue-meteor-tracker'; // here!
Vue.use(VueMeteorTracker); // here!
import App from './App.vue';
Meteor.startup(() => {
new Vue({
el: '#app',
render: h => h(App)
});
});
Now in /client/App.vue
we can use standard Vue syntax like
<template>
<h1>App</h1>
</template>
<script>
export default {
name: "App"
}
</script>
In following sections we can see results in table with applied spectre.css, but we will not discuss this. You can see how to use it following by commits in attached repository.
Operation directly on DOM
In simplest cases when we do not want to operate on submitted data we can use old school direct dom modification. Listen on changes in first component, then select and directly modify innerHTML
of elements in other.
To obtain this result we can write the following code in first component
<template>
<input class="form-input" type="text" ref="input" @keyup="update">
</template>
<script>
export default {
methods: {
update() {
document.querySelector('#dom-target').innerHTML = this.$refs.input.value; // result of selector could be cached
}
}
}
</script>
As you can see we listen on keyup
on input element and execute method update
that looking for #dom-target
and directly modify their content.
In second file, we even do not use JavaScript
<template>
<p class="m-2" id="dom-target"></p>
</template>
This method is
- very simple
- easy to use and understand
- require small amount of code
but have also drawbacks:
- can cause hard to detect bugs
- wreck idea of independent components
- introduce global namespace of id or other constraining selectors
- we should avoid in bigger projects where many people collaborate
Use Event Bus
Concept of Event Bus relays on creation one (or many) independent globally available empty component that has only one task: emit and listen on events. This component can be injected to other components from our application and be used to signalize and invoke actions.
import Vue from 'vue';
export default new Vue();
Now out component initiating communication is more expanded
<template>
<input class="form-input" type="text" v-model="value" @keyup="update">
</template>
<script>
import EventBus from '../../EventBus';
export default {
data() {
return { value: null }
},
methods: {
update() {
EventBus.$emit('update', this.value)
}
}
}
</script>
We applied data property and use $emit
method on imported EventBus
to send signal. In component receiving we can use method $on
to listen on this.
<template>
<p class="m-2" id="dom-target">{{value}}</p>
</template>
<script>
import EventBus from '../../EventBus';
export default {
data() {
return {
value: null
}
},
mounted() {
EventBus.$on('update', (value) => {
this.value = value;
})
}
}
</script>
Advantages
- no constrain of relative components position
- in more complex project you can use many EventBuses
Disadvantages
- you always should manually emit event
- you should remember to add this mechanism to any component that operate on these data
Vuex as global state of application
Idea of separated components has great influence on code clarity and possibility of maintain. But sometimes we have data that should be accessible in any component and we want to share these data deliberately. To achieve this goal we can use Vuex. Is it not part of core vue library but officially supported. We shoud install vuex by command:
npm install vuex --save
To make it working we should do some preparation before using vuex
in components. Firstly we should register it as package that we use. We can to it in file /imports/client/plugins/vuex.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
Next we should create global set of shared properties and method to change state of these values called store
. I propose create file /imports/client/vuex/store.js
with content
import Vuex from 'vuex'
export default new Vuex.Store({
state: {
value: null
},
mutations: {
update (state, value) {
state.value = value;
}
}
});
You can see that our state contains value
and it can be “mutated” by function update
. But it is not all. We also should append both our plugin and store to /client/main.js
file. We can do this in following way
import Vue from 'vue';
import '../imports/client/plugins/tracker'
import '../imports/client/plugins/vuex'
import store from '../imports/client/vuex/store';
import App from '../imports/client/ui/App.vue';
Meteor.startup(() => {
new Vue({
el: '#app',
store,
render: h => h(App)
});
});
You can see that meteor-tracker
is moved to depart file in the same way like vuex
, and in constructor of our Vue
object there is injected store
that we just defined.
Now we are prepared to use vuex
in our components. When we typing then update should commit mutation with name update
and send to their second argument value of our input.
<template>
<input class="form-input" type="text" @keyup="update">
</template>
<script>
export default {
methods: {
update(event) {
this.$store.commit('update', event.target.value)
}
}
}
</script>
Store has defined behavior of this method. We know that it is setter of value property. And in second component we can simply get this value from vuex.
<template>
<p class="m-2" id="dom-target">{{$store.state.value}}</p>
</template>
Like in case with direct operation on dome we do not need additional JavaScript, but in this case it is achieved due to reactivity build in Vue.
Advantages:
- Efficient for shared states
- Allow to create global rules of manipulating on shared resources
- Require small amount of code when is implemented
Disadvantages:
- Relatively hard to setup in compare to previous methods
- Need many components that use the same value to have sense of usage
Independent reactivity from Meteor tracker
You can see that we described three methods that we can use with Vue but without Meteor. Now We look at Meteor Tracker and see how to combine reactivity from meteor with reactivity from vue. Now idea is the following: create shared dependency like EventBus, import it in two components. In first send signal to recalculate any function dependent from this dependency, in second component create dependent function. To store data we will use localStorage
because of this is definitely non reactive storage of data and we show that thanks Tracker we can not worry about manner of storing (it could be external API), but only take care about where reactive data are changed and where they are used.
Lets start from creating dependency in file /imports/client/Dependency.js
export default new Tracker.Dependency;
Now we want to import this dependency and use on it method changed()
to signalize that any function dependent from this should be recalculated. Additionally we are saving value to localStorage
and clear localStorage
on create of this component to avoid caching value between browser refreshes.
<template>
<input class="form-input" type="text" @keyup="update">
</template>
<script>
import dependency from '../../Dependency';
export default {
methods: {
update(event) {
window.localStorage.setItem('value', event.target.value);
dependency.changed();
}
},
created() {
window.localStorage.clear();
}
}
</script>
In second component we have standard data()
function but on created there is registered autorun
Tracker’s method. This method will be called always if any reactive (from Meteor point of view) variable inside of his function changed. By typing dependency.depend()
we command Meteor to recompute this function in any case when dependency.changed()
is invoked.
<template>
<p class="m-2" id="dom-target">{{value}}</p>
</template>
<script>
import dependency from '../../Dependency';
export default {
data() {
return {
value: null
}
},
created() {
Tracker.autorun(() => {
dependency.depend();
this.value = window.localStorage.getItem('value');
});
}
}
</script>
Sometimes we want to communicate Meteor’s data with Vue data. Because of theirs reactivity are build on different technologies Meteor Tracker is point where they can meet and interact with each others.
Disadvantages:
- Tracker is no such well known like other pure Vue methods
- If there are many points of update, you should remember about invoking
changed
in any of them
Advantages:
- Allow to very custom integration with Meteor, more flexible than
vue-meteor-tracker
package.
Automated tracking by vue-meteor-tracker package
One of most comfortable solution is usage vue-meteor-tracker
. In contrast to last example we need reactive (from Meteor perspective) resource. Thank to usage reactive variables we can automate process of sending signal about changing data to dependent functions. We can use Mini Mongo collection that simulate Mongo collection but lives only on front-end without connection with back-end. Of course real Mongo collection can be used too, but we chose Mini Mongo for simplicity. Lets create collection in/imports/client/Values.js
export const Values = new Mongo.Collection(null);
Now in input component we want to upsert data to this collection like the following:
<template>
<input class="form-input" type="text" @keyup="update">
</template>
<script>
import { Values } from '../../Values';
export default {
methods: {
update(event) {
Values.upsert('value', {$set: {value: event.target.value}})
}
},
created() {
Values.upsert('value', {$set: {value: null}});
}
}
</script>
There are upserts
on create of component and on update of input. Now in component that present data we can have code with meteor
property of Vue
component provided by vue-meteor-tracker
package.
<template>
<p class="m-2">{{valueObject.value}}</p>
</template>
<script>
import { Values } from '../../Values';
export default {
meteor: {
valueObject() {
return Values.findOne('value');
}
}
}
</script>
This property allow to define functions that are reactive in two ways. These functions are invoked by changes of variables reactive for Meteor but they return values reactive for Vue. Due this fact we do not have any listener. Only pure logic of saving data to reactive Meteor collection in first component and returning value from reactive for Meteor cursor in second component. Vue-meteor-tracker
transform this reactivity to Vue reactivity and valueObject
can be treated in second component like property returned by data()
method.
Disadvantages:
- you should learn and relay on external additional package
- there is some problems with dynamic changing subscription parameters
Advantages
- package is well documented
- intuitive and easy to use
- always used when you want to integrate Meteor with Vue
Summary
We presented some methods of communicating components. Some of them are universal, two last methods are typical for vue-meteor
integration. There is not best or worse. You will create best code if you will know and understood all and select right one in dependent from your needs.
All methods presented in this article with input and output components.
If you want to add method that I forgot or see other thing to improve do not hesitate and let mention about it in comment.
Sources:
Custom events in Vue
Vuex official guide
Meteor Tracker Docs
Vue Meteor Tracker
This blog post is published also there: https://medium.com/@gustaw.daniel/communication-between-vue-components-in-meteor-29006be3dae9
Full repository with all these method can be found there: https://github.com/gustawdaniel/vue-meteor-components-communication-tutorial
Other articles
You can find interesting also.
Telegram Bot in Typescript
Learn how to create a bot on Telegram, add command listening to it, and configure notification sending.
Daniel Gustaw
• 3 min read
Calculating the Difference Between JSON Files
Learn how to find missing translations in JSON files with dictionaries.
Daniel Gustaw
• 3 min read
CodinGame: Best fit to data - Rust - Regression Analysis
Linear and logarithmic regressions were derived here. Models were fitted in rust language. This article shows that sometimes it's worth improving the theoretical model before starting implementation.
Daniel Gustaw
• 6 min read