© The Author(s), under exclusive license to APress Media, LLC , part of Springer Nature 2021
D. TangPro Ember Datahttps://doi.org/10.1007/978-1-4842-6561-1_8

8. Working with Nested Data and Embedded Records

David Tang1  
(1)
Playa Vista, CA, USA
 

In this chapter, we’ll look at two ways of handling nested data in API payloads. The first approach involves defining a model attribute without a transform. The second approach makes uses of embedded records. Let's dive in!

Declaring Attributes Without Transforms

Let’s say we have the following JSON for a contact resource with address being a nested object:
{
  "id": 1,
  "name": "Richard Hendrix",
  "address": {
    "street": "123 Main St.",
    "zip": "90003"
  }
}
In Chapter 1 – Ember Data Overview, we learned about the four different transforms: string, number, boolean, and date. There is no object transform. In order to have an address attribute on the model that contains an object, we can declare the attribute without a transform:
app/models/contact.js
import Model, { attr } from '@ember-data/model';
export default class ContactModel extends Model {
  @attr('string')  name;
  @attr address;
}

When we don’t specify a transform, Ember Data will just pass through the value and set it on the model.

To change a specific property on address, use dot notation with model.set() :
model.set('address.street', '1234 New St.');
Now let’s say we have the following JSON for a contact resource :
{
  "id": 1,
  "name": "Richard Hendricks",
  "history": [
    { "url": "http://piedpiper.com", "time": "2015-10-01T20:12:53Z" },
    { "url": "http://hooli.com", "time": "2014-10-01T20:12:53Z" },
    { "url": "http://endframe.com", "time": "2013-10-01T20:12:53Z" }
  ]
}
There is a history property containing an array of URLs. We won’t specify a transform on the model so the history data will be passed through and set on the record:
app/models/contact.js
import Model, { attr } from '@ember-data/model';
export default class ContactModel extends Model {
  @attr('string') name;
  @attr history;
}
Let’s see how we can work with the history attribute. You might think we could modify a history item and expect the UI to update:
model.history[0].url = 'http://amazon.com';
However, this won’t work. If we need to modify a specific history item, we will need to use set, for example:
import { set } from '@ember/object';
let googleItem = model.history[0];
set(googleItem, 'url', 'http://amazon.com');
Using set() will change the property and notify Ember to rerender. Alternatively, we can create a new array reference and reassign the history attribute:
let modifiedHistory = [...model.history, 'http://amazon.com'];
model.set('history', modifiedHistory);

Embedded Records

Nested objects with an id can also be treated as records using the mixin EmbeddedRecordsMixin. Let’s assume the JSON now looks like this:
{
  "id": 1,
  "name": "Richard Hendricks",
  "skills": [
    { "id": 1, "name": "Compression" },
    { "id": 2, "name": "Java" },
    { "id": 3, "name": "Algorithms" }
  ]
}
We can turn each object under skills into records with a hasMany relationship established between contact and skill:
app/models/contact.js
import Model, { attr, hasMany } from '@ember-data/model';
export default class ContactModel extends Model {
  @attr('string') name;
  @hasMany('skill') skill;
}
To have Ember Data establish the hasMany relationship, we can use the EmbeddedRecordsMixin in our serializer:
app/serializers/contact.js
import JSONSerializer from '@ember-data/serializer/json';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default class ContactSerializer extends JSONSerializer.extend(
  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. Note that EmbeddedRecordsMixin does not work with the JSONAPISerializer.

EmbeddedRecordsMixin also works with nested data inside of nested data! For example, let’s say each skill now has an embedded category model :
{
  "id": 1,
  "name": "Richard Hendricks",
  "skills": [
    {
      "id": 1,
      "name": "Compression",
      "category": {
        "id": 3,
        "name": "Technology"
      }
    },
    {
      "id": 2,
      "name": "Algorithms",
      "category": {
        "id": 6,
        "name": "Technology"
      }
    }
  ]
}
Similar to the preceding code, create a category model and specify the relationship:
app/models/skill.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default class SkillModel extends Model {
  @attr('string') name;
  @belongsTo('category', { async: false }) category;
}
app/models/category.js
import Model, { attr } from '@ember-data/model';
export default class CategoryModel extends Model {
  @attr('string') name;
}
Next, create a skill serializer that uses EmbeddedRecordsMixin :
app/serializers/skill.js
import RESTSerializer, {
  EmbeddedRecordsMixin
} from '@ember-data/serializer/rest';
export default class SkillSerializer extends RESTSerializer.extend(
  EmbeddedRecordsMixin
) {
  attrs = {
    category: { embedded: 'always' }
  };
}

Nested models can recursively use the EmbeddedRecordsMixin to handle records nested in records.

Summary

In this chapter, we looked at a few different ways of handling nested data and embedded records. If we need nested data to be turned into a record, use the EmbeddedRecordsMixin. Otherwise, declare the attribute without a transform.

Up until now, we’ve looked at how to work with successful API responses. In the next chapter, we will look at how to handle error responses.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.108.168