12

We are currently working on a voting app based on the vue-cli webpack template. Since we want to store and manipulate our voting state in a consistent and maintainable fashion we intend to use vuex for state management. The interaction between frontend and backend is based on websockets and we want to use signalr since it has been proven very good in previous projects. Due to the fact that we are new to vue.js we need some advice how to integrate signalr, vuex and vue.js perfectly together.

Let's describe the scenario:

The frontend gets an event from our backend to recognize, that the voting poll is active and can receive the selected answers. After a period of time we inform the frontend that results are available and display them to the user. In some cases we might open another voting poll. It is important that we are able to disconnect in case the document gets hidden (page visibility api).

Our solution approach:

In general I'd like to implement a special signal.service for this purposes. This service is responsible for establishing the connection as well as sending and receiving the messages via websockets. Since we are not able to perform any changes to vuex store from a common module, we figured out that a vuex plugin would be suitable. The vuex plugin should wrap signalr.

In case we receive a VotingStartEvent we would unlock the respective question and display it to the user. If the user answered that question we commit the new state of this question (answered) to the vuex store. Inside our plugin we have a subscription to mutations and we would use this subscription to send our vote to the backend. The following snippet illustrates the idea:

var plugin = (store) => {
  // ...
  store.subscribe((mutation, state) => {
    console.log('inside subscription');
    // if vote has answered state, call connection.send() to submit the message to the backend
  });

  connection.received((message) => {
    // ...
    var event = parseEvent(message);
    if (event.type === START) {
      store.commit({type: 'voting', item: 'unlocked'});
    }
    // ...
  });
}

Is this approach good or do you see any room for improvement?

jimmy
  • 413
  • 3
  • 6
  • 17
  • I can't use SignalR in example, but if you want, for inspiration, I can write here live, working example, how I am integrating another WS library (Pusher) to Vue and updating data in Vuex. –  Nov 07 '17 at 14:49
  • 1
    There is no need for a plugin. Just dispatch an action when you receive an event in SignalR. https://stackoverflow.com/questions/44333164/vuex-websockets/44336198 – Bert Nov 07 '17 at 14:51
  • @WaldemarIce Thanks a lot! A short inspiration would be great :) – jimmy Nov 07 '17 at 15:05
  • @Bert Thanks for your advice! But in that scenario I need a reference to the store and I'd prefer separate modules for store and signalr handling. With a vuex plugin we could achieve this, but I am not sure if this is a good practice at all. – jimmy Nov 07 '17 at 15:21
  • That's what import/export are all about. Define SignalR in one module, import the store into it. Or, define them both in separate modules and then import them both into a third. Complete separation of concerns. – Bert Nov 07 '17 at 15:23
  • @Bert Okay. This should work in our scenario. Thanks! But what speaks against using a plugin and commit state changes directly inside this plugin and respond to specific mutations with a connection.send()? I've read that store references in other modules are not optimal. – jimmy Nov 07 '17 at 15:27
  • I don't think your approach is wrong in any sense. If using a plugin works for you and your team, by all means do so. I can't think of any major drawbacks off the top of my head other than Vuex plugins tend to be rare in my experience (and thats just my experience; maybe people are using them all the time and I just don't run into it). As far as importing a store being non-optimal; that seems to me to be a pretty outlandish claim and I would be curious to know the reasoning and where you read such a thing. – Bert Nov 07 '17 at 15:34
  • @Bert I'll try to find the source for that claim ;) One of our goals is more subscription-driven. The plugin offers me the ability to subscribe to store mutation changes. So there's one place where signalr events and store mutation changes can be handled. That sounds quite cool, but indeed it would be possible with your idea as well. I'll of course evaluate it. – jimmy Nov 07 '17 at 15:49
  • FWIW, `subscribe` is just [an instance method](https://vuex.vuejs.org/en/api.html) of the store. Anywhere you have the store object you can call `store.subscribe` (and as of the last month, store.subscribeAction`). – Bert Nov 07 '17 at 15:56
  • @jimmy Done, promised example added. –  Nov 07 '17 at 16:42

1 Answers1

7

There is no need for plugins, or constructions like Vue.prototype.$pusher = new Pusher('apiKey'). I hold the Pusher instance like any other value I need share between components. And I initialize the Pusher in Vue instance method create, like any other libraries I need to initialize first. Just for clarification, I hold trade data in component instances itself deliberately, as they are unique for each instance. Not all you need, you must place to store only.

var store = new Vuex.Store({
  state: {
    pusher: null
  },
  mutations: {
    saveInstance(state, instance) {
      state.pusher = instance
    }
  },
  actions: {
    initializePusher ({commit}, apiKey) {
      commit('saveInstance', new Pusher(apiKey))
    }
  }
})

Vue.component('live-price', {
  template: '#live-price',
  props: ['pair'],
  data () {
    return {
      price: 'wait for next trade...',
      channel: null
    }
  },
  created () {
    this.channel = this.$store.state.pusher.subscribe('live_trades_' + this.pair)
    this.channel.bind('trade', data => this.price = data.price)
  }
})

new Vue({
  el: '#app',
  store,
  created () {
    this.$store.dispatch('initializePusher', 'de504dc5763aeef9ff52')
  }
})
[v-cloak] { display: none }
<div id="app">
  <live-price pair="btceur">BITCOIN price in EUR:</live-price>
  <live-price pair="ltceur">LITECOIN price in EUR:</live-price>
  <live-price pair="etheur">ETHEREUM price in EUR:</live-price>
  <live-price pair="xrpeur">RIPPLE price in EUR:</live-price>
</div>

<template id="live-price">
  <p>
    <slot></slot>
    <span v-cloak>{{ price }}</span>
  </p>
</template>

<script src="https://unpkg.com/vue@2.5.3/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.min.js"></script>
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
  • Thanks a lot for your example! It's an interesting approach and we'll evaluate holding our instance inside the store. – jimmy Nov 08 '17 at 16:14
  • @jimmy You are welcome :) And for clarification, I am holding instance in Vuex, as it is also just an (data) object. And if all shared data belongs to Vuex, so... :) –  Nov 08 '17 at 16:20
  • is pusher third party library which we need to pay after a while? – sonertbnc Dec 15 '17 at 09:07