A group by computed property and helper that supports nested properties, e.g. model with a
belongsTo relationship.
import Controller from '@ember/controller';
import { groupByPath } from 'ember-cli-group-by/macros';
export default Controller.extend({
  arrayGrouped: groupByPath('array', 'nested.property'),
});Requires Ember 2.10 or higher, see Issue #2.
ember install ember-cli-group-by
Computed property
import Controller from '@ember/controller';
import { A } from '@ember/array';
import { groupByPath } from 'ember-cli-group-by/macros';
export default Controller.extend({
  cartGrouped: groupByPath('cart', 'category'),
  
  init() {
    this._super(...arguments);
    this.set('cart', A([
      { name: 'Cinnamon ', category: 'Spice' },
      { name: 'Banana', category: 'Fruit' },
      { name: 'Apple', category: 'Fruit' },
      { name: 'Lettuce', category: 'Vegetable' },
      { name: 'Broccoli', category: 'Vegetable' },
    ]);
  },
});Handlebars helper
import Controller from '@ember/controller';
import { A } from '@ember/array';
export default Controller.extend({
  init() {
    this._super(...arguments);
    this.set('cart', A([
      { name: 'Cinnamon ', category: 'Spice' },
      { name: 'Banana', category: 'Fruit' },
      { name: 'Apple', category: 'Fruit' },
      { name: 'Lettuce', category: 'Vegetable' },
      { name: 'Broccoli', category: 'Vegetable' },
    ]);
  },
});The group name for an item can be overridden by implementing a computed property function or by passing a closure action to the helper.
Computed property
import Controller from '@ember/controller';
import { A } from '@ember/array';
import { isNone } from '@ember/utils';
import { groupByPath } from 'ember-cli-group-by/macros';
export default Controller.extend({
  cartGrouped: groupByPath('cart', 'category', function (value) {
    return isNone(value) ? 'Other' :  value;
  }),
  
  init() {
    this._super(...arguments);
    this.set('cart', A([
      { name: 'Cinnamon ', category: 'Spice' },
      { name: 'Banana', category: 'Fruit' },
      { name: 'Apple', category: 'Fruit' },
      { name: 'Lettuce', category: 'Vegetable' },
      { name: 'Broccoli', category: 'Vegetable' },
      { name: 'Salt', category: null },
      { name: 'Sugar' },
    ]);
  },
});Handlebars helper
import Controller from '@ember/controller';
import { A } from '@ember/array';
import { isNone } from '@ember/utils';
export default Controller.extend({
  init() {
    this._super(...arguments);
    this.set('cart', A([
      { name: 'Cinnamon ', category: 'Spice' },
      { name: 'Banana', category: 'Fruit' },
      { name: 'Apple', category: 'Fruit' },
      { name: 'Lettuce', category: 'Vegetable' },
      { name: 'Broccoli', category: 'Vegetable' },
      { name: 'Salt', category: null },
      { name: 'Sugar' },
    ]);
  },
  
  actions: {
    defaultCategory(value) {
      return isNone(value) ? 'Other' :  value;
    },
  },
});The group by property path can be a nested belongsTo relationship that is loaded asynchronously.
Check out the example at ember-twiddle.
// models/user.js
export default Model.extend({
  fullname: attr('string'),
  
  cart: hasMany('product', { inverse: 'shopper' }),
});
// models/product.js
export default Model.extend({
  name: attr('string'),
  
  category: belongsTo('category'),
  shopper: belongsTo('user', { inverse: 'cart' }),
});
// models/category.js
export default Model.extend({
  name: attr('string'),
});import Controller from '@ember/controller';
import { isNone } from '@ember/utils';
import { alias } from '@ember/object/computed';
import { groupByPath } from 'ember-cli-group-by/macros';
export default Controller.extend({
  user: alias('model'),
  cart: alias('user.cart'),
  cartGrouped: groupByPath('cart', 'category.name', function (value) {
    return isNone(value) ? 'Other' :  value;
  }),
});