12
votes

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?

1
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.user6748331
There is no need for a plugin. Just dispatch an action when you receive an event in SignalR. stackoverflow.com/questions/44333164/vuex-websockets/44336198Bert
@WaldemarIce Thanks a lot! A short inspiration would be great :)jimmy
@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
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

1 Answers

7
votes

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/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.min.js"></script>
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>