Working With Nested Data In Ember Data Models

Last reviewed on January 29, 2016

In today's post, I'd like to share a few ways of how you can work with nested data in Ember Data models.

1. Nested Objects

Let's say you have the following JSON for a user:

{
  "id": 5,
  "name": "David",
  "address": {
    "street": "123 Main St.",
    "zip": 90003
  }
}

You might not need address to be its own model. If that's the case, don't specify a transform when declaring the address attribute on the model:

app/models/user.js
export default DS.Model.extend({
  name: DS.attr('string'),
  address: DS.attr(),
});

By not using a transform for address (nothing is passed to DS.attr()), Ember Data will just pass through the data and set it on the model. To change a specific property on the address, you can do:

model.set('address.street', '1234 New St.');

2. Nested Arrays

Now let's say you have the following JSON for a user.

{
  "id": 5,
  "name": "David",
  "history": [
    { "url": "http://google.com", "time": "2015-10-01T20:12:53Z" },
    { "url": "http://apple.com", "time": "2014-10-01T20:12:53Z" },
    { "url": "http://yahoo.com", "time": "2013-10-01T20:12:53Z" }
  ]
}

This time it has a history property containing an array of items. The model won't use any transform so the history data will be passed through and set on the model.

export default DS.Model.extend({
  name: DS.attr('string'),
  history: DS.attr(),
});

Similar to before, maybe each history item doesn't need to be its own model. If that's the case, there are two ways to work with the data. You might think you could modify a history item like below and expect the UI to update, especially if you are coming from the Angular world:

model.get('history')[0].url = 'http://amazon.com';

However, this won't work. If you need to modify a specific history item, you will need to use Ember.set. For example:

let googleItem = model.get('history')[0];
Ember.set(googleItem, 'url', 'http://amazon.com');

Using Ember.set() will change the property and notify Ember to rerender.

Alternatively, you can change the entire array, like if you were using map() or filter() on Array.prototype, and reassign the history attribute.

model.set('history', modifiedHistory);

3. Embedded Records

The last approach to work with nested data is with embedded records using the mixin DS.EmbeddedRecordsMixin. Let's assume the JSON now looks like this:

{
  "id": 5,
  "name": "David",
  "skills": [
    { "id": 2, "name": "JavaScript" },
    { "id": 6, "name": "PHP" },
    { "id": 9, "name": "Ember" }
  ]
}

Now you want each object under skills to be its own model and there to be a hasMany relationship between user and skill.

app/models/user.js
export default DS.Model.extend({
  name: DS.attr('string'),
  skills: DS.hasMany('skill'),
});

To have Ember Data create the hasMany relationship, use the DS.EmbeddedRecordsMixin in your serializer.

app/serializers/user.js
export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
    skills: { embedded: 'always' },
  },
});

In the attrs property, set skills to { embedded: 'always' }. This also works for a belongsTo relationship. This example is using the JSONSerializer but the same technique can apply to an API based on the RESTSerializer. However, this does not work with the JSONAPISerializer at the time of this writing when I used Ember Data 2.3.3.

The EmbeddedRecordsMixin also works with nested data inside of nested data! For example, let's say each skill now has an embedded category model:

{
  "id": 5,
  "name": "David",
  "skills": [
    {
      "id": 2,
      "name": "Teaching",
      "category": {
        "id": 3,
        "name": "Education"
      }
    },
    {
      "id": 9,
      "name": "Ember",
      "category": {
        "id": 8,
        "name": "Technology"
      }
    }
  ]
}

Similar to above, create a category model and specify the relationship:

app/models/skill.js
export default DS.Model.extend({
  name: DS.attr('string'),
  category: DS.belongsTo('category', { async: false }),
});
app/models/category.js
export default DS.Model.extend({
  name: DS.attr('string'),
});

Then, create a skill serializer and use the EmbeddedRecordsMixin:

app/serializers/skill.js
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
    category: { embedded: 'always' },
  },
});

Pro Ember Data cover
Pro Ember Data available now!

Are you struggling with Ember Data?

In my new book Pro Ember Data, you will learn how to work with Ember Data efficiently, from APIs, adapters, and serializers to polymorphic relationships, using your existing JavaScript and Ember knowledge. This book will teach you how to adapt Ember Data to fit your custom API.

What You'll Learn

  • Review the differences between normalization and serialization
  • Understand how the built-in adapters and serializers in Ember Data
  • Understand how the built-in adapters and serializers in Ember Data work
  • Customize adapters and serializers to consume any API and write them from scratch
  • Handle API errors in Ember Data
  • Work with the Reddit API using Ember Data
  • Learn how to use polymorphic relationships
Check out Pro Ember Data on Amazon using my affiliate link
You can also find Pro Ember Data on Apress.

I've been enjoying @iamdtang's Pro Ember Data book. It's to the point and has lots of great examples. My favorite part was about error handling. This book is perfect after you've exhausted the official guides.

Ilya Radchenko

Product Developer at Applied Geographics

Ilya Radchenko

Pro Ember Data is such an approachable book @iamdtang! Good job. Loving the book so far! Thank you!

Lenora Porter

Software Engineer at Heroku

Lenora Porter

Great to see all those topics in which I struggled when I started working on Ember. Nice job, will check it out :)

AbulAsar S

Fullstack Developer

AbulAsar S

I haven't read David's new book, but his previous Ember Data book helped me a lot when I was starting out with Ember, so I am sure this book is a must if you're working with Ember Data!

Kenneth Larsen

Ember Learning Core Team member

Kenneth Larsen

I bought Ember Data in the Wild back in 2016 and found it really helpful. Looking forward to reading this.

@EmberLinks

Ember related news by Chris Masters

@EmberLinks