How to watch the elements of an array?

I have a computed property on an ObjectController, which depends on a computed property itself and watches the number of elements in the underlying array. The problem is that the computed property updates if the number of elements changes but does not recomputed if the contents of an element changes.

newSnakeMorph: function() {
  if (this.get('model.newSnakeGenes').get('length') > 0)
  {
    var genes = this.get('model.newSnakeGenes');
    var Result = "";
    for (var Index in genes)
    {
      if (isNumber(Index)) // Leaking properties bug         
      {
        Result += genes[Index].get('morph') + " ";
      }
    }
    return Result;
  }
  else
  {
    return "Normal";
  }
}.property('model.newSnakeGenes.[]'),

How to I alter the observer “,property(‘model.newSnakeGenes.’)” to observe changes within the elements stored in the array?

Well I thought of a very hackish way to do it and tested it and it works. Not sure it’s the best thing to do but it’s all I can figure out. Would love to find the Ember right way to do this.

In the code below I create a fake object and add and remove it from the array so the length will change (temporarily, at least) and cause the computed properties to recompute. I can do this because in the controller I know I have changed the contents of one of the items in the array (as shown below).

var genes = this.get('model.newSnakeGenes');
for (var index in genes)
{
  // Ember bug #5501
  if (isNumber(index))
  {
    if (genes[index].get('complex') == gene.get('complex'))
    {
      if (genes[index].get('allele2') == "Normal")
      {
        genes[index].set('allele2', gene.get('name'));
        // Hackish way to cause computed properties to recompute
        var Fake = App.Locus.create({id: 'fake'});
        genes.pushObject(Fake);
        genes.removeObject(Fake);
        return;
      }
      else
      {
        alert("This complex (" + gene.complex + ") already has the maximum number of genes.");
        return;
      }
    }
  }
}

Are you maybe looking for the @each key to use in your .property(), instead of the .[]? Ex: .property('model.newSnakeGenes.@each')

http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/

A lot of things in Ember don’t actually exist until they are observed, including computer properties- they only calculate when you’re actually calling them. If you have chains of these, they aren’t always calling the most current version. Try running Ember.run.sync(); after you set the value.

I am not entirely sure what the issue you’re having is, can you isolate it to a jsfiddle.

It’s likely what @kylecoberly mentioned. Computed properties are only triggered when bound to.

As I understand Ember Docs the array.[] notation tells Ember to watch for Array manipulation - so push & remove are observed, whereas array.@each tells Ember to not only watch for Array manipulation but for manipulation of the Array values too, which seems to be the solution to your problem. So, instead of

function () {}.property('someArray.[]')

you would have to use

function () {}.property('someArray.@each')

in order to achieve what you want.

@each is used to watch individual properties but not the entire object for changes.

Thank you all for your responses. Yes, @each is not sufficient to watch element count change and element detail change. But @each.something does watch both, as I am using:

}.property('model.newSnakeGenes.@each.allele2'),

and it works great. The only property of an existing “gene” that can change is the second allele so this watches that property and does also seem to get element change when new “genes” (an additional Locus object) are added to the array.

1 Like