2

Im absolutely new in Vue framework and I need create reusable component with live BTC/LTC/XRP price

For live prices Im using Bitstamp websockets API. Here is example usage with jQuery - run this snippet, is really live.

var bitstamp = new Pusher('de504dc5763aeef9ff52')
var channel = bitstamp.subscribe('live_trades')
            
channel.bind('trade', function (lastTrade) {
  $('p').text(lastTrade.price)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>

<h3>BTC/USD price</h3>
<p>loading...</p>

As you can see, its really simple. But, I need to use Vue.js component. So I created this, and its also fully functional:

var bitstamp = new Pusher('de504dc5763aeef9ff52')

Vue.component('live-price', {
  template: '<div>{{price}}</div>',
  data: function () {
    return {
      price: 'loading...'
    }          
  },
  created: function () {
    this.update(this)
  },
  methods: {
    update: function (current) {
      var pair = current.$attrs.pair === 'btcusd'
        ? 'live_trades'
        : 'live_trades_' + current.$attrs.pair
      var channel = bitstamp.subscribe(pair)

      channel.bind('trade', function (lastTrade) {
        current.price = lastTrade.price
      })
    }
  }
})

new Vue({
  el: '.prices'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>

<section class="prices">
  <live-price pair="btcusd"></live-price>
  <live-price pair="ltcusd"></live-price>
  <live-price pair="xrpusd"></live-price>
</section>

But, there is big BUT. Am I using Vue right way? WHERE IS IDEAL PLACE to run Pusher? In "created" or "mounted" method? In "computed"? In "watch"? Or where? Am i doing it right? I really dont known, I started with Vue ... today :(

2 Answers 2

1

Looks pretty good for your first day using Vue! I would just make a few changes.

  • The component is reaching out and using a global, bitstamp. Generally with components, you want them to be independent, and not reaching out of themselves to get values. To that end, declare the socket as a property that can be passed in to the component.
  • Likewise, the pair is passed in as a property, but you do not declare it and instead, use current.$attrs.pair to get the pair. But that's not very declarative and makes it harder for anyone else to use the component. Moreover, by making it a property, you can reference it using this.pair.
  • When using something like a socket, you should always remember to clean up when you are done using it. In the code below, I added the unsubscribe method to do so. beforeDestroy is a typical lifecycle hook to handle these kinds of things.
  • Computed properties are useful for calculating values that are derived from your components data: the channel you are subscribing to is a computed property. You don't really need to do this, but its generally good practice.
  • A Vue can only bind to a single DOM element. You are using a class .prices which works in this case because there is only one element with that class, but could be misleading down the road.
  • Finally, created is an excellent place to initiate your subscription.

console.clear()
var bitstamp = new Pusher('de504dc5763aeef9ff52')

Vue.component('live-price', {
  props:["pair", "socket"],
  template: '<div>{{price}}</div>',
  data() {
    return {
      price: 'loading...',
      subscription: null
    }          
  },
  created() {
    this.subscribe()
  },
  beforeDestroy(){
    this.unsubscribe()
  },
  computed:{
    channel(){
      if (this.pair === 'btcusd')
        return 'live_trades'
      else
        return 'live_trades_' + this.pair
    }
  },
  methods: {
    onTrade(lastTrade){
      this.price = lastTrade.price
    },
    subscribe() {
      this.subscription = this.socket.subscribe(this.channel)
      this.subscription.bind('trade', this.onTrade)
    },
    unsubscribe(){
      this.subscription.unbind('trade', this.onTrade)
      this.socket.unsubscribe(this.channel)
    }
  }
})

new Vue({
  el: '#prices',
  data:{
    socket: bitstamp
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>

<section id="prices">
  <live-price pair="btcusd" :socket="bitstamp"></live-price>
  <live-price pair="ltcusd" :socket="bitstamp"></live-price>
  <live-price pair="xrpusd" :socket="bitstamp"></live-price>
</section>

Sign up to request clarification or add additional context in comments.

1 Comment

Rewrited - Is this ok?
0

Rewrited - is it ok now?

var config = {
  key: 'de504dc5763aeef9ff52'
}

var store = new Vuex.Store({
  state: {
    pusher: null
  },
  mutations: {
    initPusher (state, payload) {
      state.pusher = new Pusher(payload.key)
    }
  }
})

var livePrice = {
  template: '#live-price',
  props: ['pair'],
  data () {
    return {
      price: 'loading...',
      subscription: null
    }          
  },
  computed: {
    channel () {
      return this.pair === 'btcusd'
        ? 'live_trades'
        : 'live_trades_' + this.pair
    }
  },
  methods: {
    onTrade (lastTrade) {
      this.price = lastTrade.price
    },
    subscribe () {
      this.subscription = this.$store.state.pusher.subscribe(this.channel)
      this.subscription.bind('trade', this.onTrade)
    },
    unsubscribe () {
      this.subscription.unbind('trade', this.onTrade)
      this.$store.state.pusher.unsubscribe(this.channel)
    }
  },
  created () {
    this.subscribe()
  },
  beforeDestroy () {
    this.unsubscribe()
  }
}

new Vue({
  el: '#prices',
  store,
  components: {
    'live-price': livePrice
  },
  created () {
    store.commit({
      type: 'initPusher',
      key: config.key
    })
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>

<section id="prices">
  <live-price pair="btcusd"></live-price>
  <live-price pair="ltcusd"></live-price>
  <live-price pair="xrpusd"></live-price>
</section>

<template id="live-price">
  <div>
    {{price}}
  </div>
</template>

7 Comments

I think it looks good :) A purist might complain about hardcoded values like de504dc5763aeef9ff52, trade, and live_trades but I'm not a purist :) I like that you moved new Pusher(...) into the Vue declaration. I was considering that.
Unfortunately, im purist too :D Im looking only for really reausable solutions... So, is there possibility to move pusher key to attribute and then read it? Something like <section id="price" key="swescexr746sc"> and then read it in Vue instance? this.el.getAttribute, or what?
Done, rewrited. Is it good solution? Or is there something more native /elegant / efficient solution?
Personally I would have a config.js or something similar, import the value and set it from the imported value. But it depends on your setup.
Hm, this makes sense... Well, let there be config... :) Thanks.
|