How Ember Data Loads Async Relationships: Part 2

in ember-data, ember.js

If you haven’t already read Part 1, I recommend doing that now, as this continues right where we left off.

Part 1 explored how Ember Data responds to a few common scenarios. In Part 2, we will look at some less straightforward examples. Then, in a later post, we will examine limitations of Ember Data.

Links and Data with IDs

In Part 1, we talked about what happens when data is loaded via relationship links, when a relationships ids are already loaded, and what happens when neither are present.

So, what happens if both links and ids are present?

Post #4 data

Below we can see that the blog post contains both related links and ids in data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "id": 4,
  "type": "post",
  "attributes": {
    "title": "This is blog post #4",
    "body": "This post has mixed links and data",
  },
  "relationships": {
    "comments": {
      "links": {
        "related": { "href": "/posts/4/comments" },
      },
      "data": [
        { "id": 41, "type": "comment" },
      ],
    },
  },
}

In this case, Ember Data will prefer the data and call findRecord in the comment adapter. This way, any records in the relationship that have already been loaded won’t need to be loaded again. We will look at how reloading relationships works in the next couple of sections.

Anyway, accessing the comments relationship on post #4 will load the comment in data and not use the related link:

1
post.get('comments').mapBy('message') // => ["Comment 41 was loaded via findRecord in the comment adapter"]

Note that if the post data gets reloaded and only has a links section, it will correctly set hasLoaded to false so that the next attempt to load the relationship will use the link.

Reloading links

Speaking of subsequent loads of relationship data, let’s look at how Ember Data deals with reloading relationships. There are many possible scenarios, so let’s arbitrarily start where we just were: an updated link.

Let’s assume we have the previous post (post #4) loaded, and when we reload the blog post, we just get a link, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "id": 4,
  "type": "post",
  "attributes": {
    "title": "This is blog post #4",
    "body": "This blog post has been updated",
  },
  "relationships": {
    "comments": {
      "links": {
        "related": { "href": "/posts/4/comments" },
      },
    },
  },
}

What happens with this example when we try to load the comments relationship?

Because the link hasn’t changed, it will not cause hasLoaded to be set to false. So accessing the comments relationship after reloading the blog post will continue to use the already loaded data.

However, if the value of the related link changes, it will reload the relationship. For example, if reloading the post returns a link with a different url:

Updated post #4 data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "id": 4,
  "type": "post",
  "attributes": {
    "title": "This is blog post #4",
    "body": "This blog post has been updated and has a new related link",
  },
  "relationships": {
    "comments": {
      "links": {
        "related": { "href": "/posts/4/comments?1" },
      },
    },
  },
}

Then accessing the relationship will trigger reloading with the link:

1
2
post.get('comments').mapBy('message')
// => ["Comment 41 was loaded via findHasMany in the post adapter", "Comment 42 was loaded via findHasMany in the post adapter"]

Reloading data

Ok, let’s say we reload the post and now there’s more data. For this example, we’ll go back to post #2, which has three comments in the data section. When reloaded, two comments were added and one was deleted.

Updated post #2 data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "id": 2,
  "type": "post",
  "attributes": {
    "title": "This is blog post #2",
    "body": "This blog post's comments relationship has a data section, and has been updated with new comments",
  },
  "relationships": {
    "comments": {
      "data": [
        { "id": 21, "type": "comment" },
        { "id": 23, "type": "comment" },
        { "id": 24, "type": "comment" },
        { "id": 25, "type": "comment" },
      ],
    },
  },
}

Just like before, the comments are loaded through the comment adapter’s findRecord hook, but this time the only comments loaded are those that haven’t already been loaded.

We can verify this by adding a logging statement to the comment adapter. After reloading the post, we can see the correct comments.

Comment 21 was loaded via findRecord in the comment adapter Comment 23 was loaded via findRecord in the comment adapter Comment 24 was loaded via findRecord in the comment adapter Comment 25 was loaded via findRecord in the comment adapter

And in the developer console we see that the comment adapter’s findRecord hook was only called with the new records:

1
2
<ember-data-relationships-examples@adapter:comment::ember361> findRecord for comment 24
<ember-data-relationships-examples@adapter:comment::ember361> findRecord for comment 25

Summary

Depending on the contents of the relationships section, ember-data will call different hooks in your adapters to load relationship data. The following table summarizes which hooks will be called in each situation.

data.@each.id
not present present
link.related not present nothing adapter:comment findRecord
present adapter:post findHasMany adapter:comment findRecord

Up Next

In Part 3, we’ll look at how to load relationships that do not have existing links or data.


Comments