We have looked at computed properties a couple of times now. Today, we are going to revisit them one final time. We are going to look at the array computed, @each, and filterBy and filter.

We are going to look at these with Ember Twiddle once again. Let's first look at how to watch an array with a computed property. If we have a list of folks:

// inside component/controller
listTeams: [
  Ember.Object.create({name: 'Cubs', best: true}),
  Ember.Object.create({name: 'Bears', best: false}),
  Ember.Object.create({name: 'Bulls', best: false}),
  Ember.Object.create({name: 'Blackhawks', best: true})
],

And we want to watch this for changes, let's set up an action to add items to this list:

{{input value=toAddToList}}
<button {{action 'addToList'}}>Add Team To List</button>
actions: {
  addToList(){
    let newObj = Ember.Object.create({name: this.get('toAddToList')});
    this.get('listTeams').addObject(newObj);
    this.set('toAddToList', '');
  }
}

Now back in our template, let's show the listTeams values and the values in a computed that we will change called computedListTeams.

<table>
  <tr>
    <th>
      listTeams:
    </th>
    <th>
      listTeamsComputed:
    </th>
  </tr>
  <tr>
    <td>
      <ul>
        {{#each listTeams as |team|}}
          <li>{{team.name}}</li>
        {{/each}}
      </ul>
    </td>
    <td>
      <ul>
        {{#each listTeamsComputed as |team|}}
          <li>{{team}}</li>
        {{/each}}
      </ul>
    </td>
  </tr>
</table>
<h3>

Now, let's create that computed property. You might write it something like this:

listTeamsComputed: Ember.computed('listTeams', function(){
  return this.get('listTeams').map(team => team.name);
}),

But as you can see as we add numbers here, that doesn't work. Why is that? It is because the computed property is watching the property listTeams, but it isn't watching for items being added to or removed from the array. To correct this, we just have to change our computed property just a touch.

listTeamsComputed: Ember.computed('listTeams.[]', function(){
  return this.get('listTeams').map(team => team.name);
})

Now we are watching for changes in the array rather than changes in the property. And as you can see it works, so if you need to create a computed property that watches the values of an array, you just add the dot and square brackets and it will work as expected.

Now, let's look at the @each for computed. If you have an array of objects and you want to watch one of the properties on the object you can use the computed @each. Let's look at that.

First, let's update our template to iterate over a new computed property and add let's add an action to toggle some property on back in our first each block.

<ul>
  {{#each listTeams as |team|}}
    <li>
      {{team.name}}: {{team.best}}
      <button {{action 'toggleOpt' team}}>toggleBest</button>
    </li>
  {{/each}}
</ul>
...
<ul>
  {{#each computedObjArr as |obj|}}
    <li>{{obj}}</li>
  {{/each}}
</ul>

Now, in the controller, implement our action, then we can add our computed property.

computedObjArr: Ember.computed('listTeams', function(){
  return this.get('listTeams').map(obj => obj.get('best'));
}),
actions:{
  toggleOpt(obj){
    obj.toggleProperty('best');
  }
}

First lets look at it just referencing the listTeams, obviously, this doesn't work. Now, if we add the array brackets, it still doesn't work. The brackets are just watching for adding and removing items from the array, we need to watch if the properties on the objects in the array change. In this case we need @each.

computedObjArr: Ember.computed('listTeams.@each.best', function(){
  return this.get('objArray').map(obj => obj.get('best'));
})

Now we can see that our computed is working. We can actually make this a little more concise use Ember.computed's mapBy method:

computedObjArr: Ember.computed.mapBy('listTeams', 'best')

We can use most of this code to look at filterBy. Let's just add a computed property that filters our objects by the best property:

computedObjArrBest: Ember.computed.filterBy('listTeams', 'best'),
computedObjArrNotBest: Ember.computed.filterBy('listTeams', 'best', false),

We can also use Ember.computed.filter() which takes an object as its first argument and a callback function as its second argument. The signature of the callback is item, index, array. So, to get the same result as the best computed above we could do:

computedObjArrBest: Ember.computed.filter('listTeams.@each.best', function(obj){
  return obj.get('best');
}),

That's it for today. I'll see you tomorrow when we get will have an exercise and look at some links.