How to observe store changes from a component?


#1

Hi. I’m using 1.11.1 ember and 1.0.0-beta.16.1 ember-data and I’m trying to create a component (widget) that would pull some data from a store (because it’s a widget) and observe store changes to render latest values.

The problem is that neither observe(), nor property() is working.

Here is a sample:

import Ember from 'ember';

export default Ember.Controller.extend({
  paymentsCount: function() {
    console.log('paymentsCount called');
    var that = this;
    this.store.find('payment').then(function(payments) {
      console.log('payments loaded ');
      that.set('paymentsCount', payments.get('length'));
    });
  }.observes('payment.@each.length').property(),
});

I tried observes(‘payment’), observes(‘store.payment’) and etc but nothing worked. Ember doesn’t call paymentsCount when new payment is saved into store.


#2

Ok, let’s discuss it.

  1. As I see you talk about the component, but there is only a controller. In fact, it’s not an issue, just a note, that we talk exactly about the controller.
  2. You clearly need to use the route for this example. Because (1) it’s a route task - to pull data (2) this code will be called only once and only because property should be initialized. Just create the corresponding route with the model function, and obtain your data there.
  3. After that change you will be able to access the model in the controller as easy as this.model(). And your function will take a form of this: paymentsCount: function() { return this.model().length; }.property('model.length').
  4. In the property expression you wrote payment.@each.length. It means that you want to check the length of each payment (model). But in fact you want to check only the length of the whole model.
  5. And a quick unrelated question - who is supposed to change the model in your system?

#3

Sorry for misleading example, I tried component and controller, but with no luck.

Let me try to describe what I’m after for:

I need to create a home screen that will contain multiple widgets, each widget showing different information. For instace one widget shows information about accounts, another widget information about latest payments, third widget information about invoices and etc.The user will have an ability to dynamicly add or remove widgets.

I’m trying to create the first widget ‘payments’. I tried creating component first:

import Ember from 'ember';

export default Ember.Component.extend({
  store: Ember.inject.service(),
    
  paymentsCount: function() {
    console.log('paymentsCount called');
    var that = this;
    return this.get('store').find('payment').then(function(payments) {
      console.log('payments loaded ');
      that.set('paymentsCount', payments.get('length'));
    });
  }.observes('store.payment').property()
});

But property is called only once.

I tried creating controller also, but it didnt work either.

How can I follow route pattern, If I will have multiple controllers/components, each working with it’s own model, on a home screen, and I won’t know all of them in advance?

Regarding who is changing the models: it can be widgets themselfs, it can be other controllers on different screens and etc.


#4

Your code is not setup by ember convention so I’m afraid you may have to fight the platform if you do it this way.

Per convention, you would normally load the payments (or just the length property if that’s all you need) and set this on the controllers model from within the route, either using the routes model hook, or using setupController method.

Once in the controller, this model would be available in the view template, and so you’d pass the model (or it’s length property) to your payments widget component and therefore, each time you get a change in the payments array (or its length property) in the route, it would cascade down into your component and work exactly as you expect.

NOTE: that for your component’s template, the widget, if you were only passing the length property downstream, you could simply use {{paymentsLength}} or whatever you’ve called it, because it would already by dynamic. But, if you were passing the model array downstream, you’d need to calculate the length using a computed property, which would update each time the model array changes length, eg:

paymentsCount: function() {
    return this.get('payments').length;
  }.property('payments.@each')

(maybe you can even use .property('payments.length') instead of @each, i’m not sure if monitoring the payments.length property triggers re-execution of computed property.)

I think everything you need is in the docs though. You just have to get back to convention and load you model data in the route and pass it downstream.


#5

In fact as I think about it, in your components handlebars template I think you can even just use {{payments.length}} if you had passed the payments array downstream from the route.


#6

It is better if you use .property('payments.[]') instead of .property('payments.@each'), it is faster and it will observe the length of the array.