Skip to content
This repository has been archived by the owner on May 3, 2018. It is now read-only.

Periodically update computed property #214

Closed
johannes-z opened this issue Jun 27, 2015 · 19 comments
Closed

Periodically update computed property #214

johannes-z opened this issue Jun 27, 2015 · 19 comments

Comments

@johannes-z
Copy link

I have a computed property, which formats the timestamp. time.fromNow() results in "a few seconds ago", "a minute ago", etc.
I want this computed property to update itself every minute (so that you actually have life feedback of the fromNow function. Ideas?

Computed Property:

timestamp: function () {
    var zone = moment().utcOffset();
    var time = moment.utc(this.created_at).utcOffset(zone);
    return time.format('HH:mm MMM Do') + ', ' + time.fromNow();
}
@Jinjiang
Copy link
Member

How about this way in the future?

computed: {
  timestamp: function (done) {
    this.created_at
    setInterval(function () { ... done(value)}, 1000)
  }
}

@nirazul
Copy link

nirazul commented Jun 27, 2015

A function return value is not observable, so directly observing time.fromNow() doesn't work out.

You can include a dummy data property in you computed attribute, that you start changing every minute (or more fine grained) in your ready hook. The computed gets updated because a depending variable has changed:

    ready: function() {
        var self = this;

        setInterval(function() {
            self.$data.ticker = Date.now(); 
        }, 1000);
    },

http://jsfiddle.net/8afnc44s/9/

@johannes-z
Copy link
Author

Thanks @nirazul . Is there an easy way to use this mechanism on a v-repeat component?

data: {
  messages: []
},
components: {
  message: {
    timestamp: function () {
      ...
    }
  }
}

@yyx990803
Copy link
Member

I suggest approaching this problem by thinking about what is the "data" that actually changes.

In this case, the created_at timestamp doesn't change, what is actually changing is "now". So, your vm should have a normal property that represents "now", and the computed should depend on that property. When you update the "now" property in a setInterval, it would trigger your computed property to update as well.

If you want to use it inside a repeat component, put the "now" on the parent and pass it down as a prop. Or, if your components are tightly coupled, you can directly access it as this.$parent.now.

@johannes-z
Copy link
Author

thanks @yyx990803. Your suggestion helped me a lot - never used props before. If it is actually tightly coupled, can I still pass the property? I tried it (see example below) and it didn't work.

new Vue({
    el: '#ts',
    data: {
        messages: [
            { created_at: Date.now() }    
        ],
        now: Date.now()
    },
    ready: function () {
      var self = this;
      setInterval(function () {
         console.log('updating ticker')
         self.$data.now = Date.now()
      }, 1000)
    }, 
    components: {
        message: {
         props: [
            'now'
          ],
          computed: {
            timestamp: function () {
              //var now = this.$parent.now // works - although I don't like having an unused variable.
              console.log(this.now) // undefined

              var zone = window.moment().utcOffset()
              var time = window.moment.utc(this.created_at).utcOffset(zone)
              var formatted = time.format('HH:mm MMM Do') + ', ' + time.fromNow()

              return formatted;
            }
          }   
        }
    }
});
<div id="ts">
    <message v-repeat="messages" inline-template>
        {{ timestamp }}
    </message>
</div>

edit: https://jsfiddle.net/xpe9r6wa/

@yyx990803
Copy link
Member

You need to pass the props in the template:

<message v-repeat="messages" now="{{now}}" inline-template>
    {{ timestamp }}
</message>

@johannes-z
Copy link
Author

It does pass the property down to the child now, but the child computed property isn't called when the parent is updated - what am I doing wrong?

https://jsfiddle.net/xpe9r6wa/1/

@yyx990803
Copy link
Member

Ahh, you found a bug!
I've fixed it, but for now you can make it work by using an alias for v-repeat: https://jsfiddle.net/yyx990803/xpe9r6wa/2/

@yyx990803
Copy link
Member

Btw, make sure to clear the interval in your component's destroyed hook.

@johannes-z
Copy link
Author

Awesome, thanks! I decided to integrate your commit into my local vue.js - using an alias vor the v-repeat would break my current structure (the provided example was a minimalistic one).

@johannes-z
Copy link
Author

If you don't access the variable, the computed prop is not called. Is this the expected behaviour? If so, there would be no benefit passing the prop to the component rather than accessing the parent directly (in this example at least).

https://jsfiddle.net/xpe9r6wa/3/

@yyx990803
Copy link
Member

That is the point of a "computed property": it updates when one of its dependency properties has changed. If you don't depend on anything, it never updates.

Passing in the prop makes it less "coupled" - that's why I said if your components are coupled by design then it's ok to just directly access $parent.time.

@johannes-z
Copy link
Author

So what is the best way (in this scenario) to updated the computed property. Assigning it to a variable seems to be the first choice (although I'd rather not have an unused variable lying around).

@yyx990803
Copy link
Member

Instead of moment.fromNow(), use something like moment.from(moment.utc(this.$parent.now))...

@cheebhodh
Copy link

Thank a lot master.

@shtse8
Copy link

shtse8 commented Oct 27, 2017

Is there any good way to avoid setting interval in each component for making reactive timer displayed?
Is it good to set it in a global store for doing global timer reactively?

@GregPeden
Copy link

GregPeden commented Nov 5, 2017

@shtse8 You could use a separate Vue instance as a global event bus and broadcast the time update as a global event, or use VueX's reactive properties and have your Vue components subscribe to the global "now" update with a VueX getter within a computed property.

@shtse8
Copy link

shtse8 commented Nov 5, 2017

@SirLamer I am using VueX as my global reactive properties now, but it's not much perform well once there are no component using the now, the interval is still running. Are there any way to make it much better like, global interval, once there are no component is using that value, then the interval is paused.

@kasvith
Copy link

kasvith commented Apr 6, 2018

I've used the following method described by @yyx990803

Instead of moment.fromNow() use moment.from(moment.utc(yourtime))

Code

<div id="app">
  <timer :created="timeCreated"></timer>
</div>
Vue.component('timer', {
  props: ['created'],
  template: '<span>{{ timeFromNow }}</span>',
  data () {
    return {
      timeFromNow: null
    }
  },
  created () {
    this.getTimeFromNow()
    setInterval(this.getTimeFromNow, 1000)
  },
  destroyed () {
    clearInterval(this.getTimeFromNow)
  },
  methods: {
    getTimeFromNow () {
      this.timeFromNow = moment(this.created).fromNow()
    }
  }
})

new Vue({
  el: "#app",
  data: {
  	timeCreated: Date.now()
  }
})

Working JSFiddle : https://jsfiddle.net/kasvith/eywraw8t/18473/

@johannes-z hope this helps you and others

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants