The first issue

Identify a common data loading issue in Ember applications.

Summary

By default, Ember data will lazy load relationships. At run time, if an unloaded relationship is used in a template Ember data will go make a request to the backend to fetch that relationships.

Unfortunately, if this happens within an {{#each}} loop, we end up making many requests, one at a time, to the backend as we loop through the template.

This class of problem is called the N+1 problem, because when the template is done rendering we've made a total of N+1 queries, where N is the number of items in the {{#each}} loop.

Many backend API formats support the concept of side loading, which is the solution to preventing N+1 queries from happening in the template layer. JSON:API supports side loading by using the include parameter to load compound documents.

In order to use side loading with JSON:API and Ember data, we need to change our model hook to use the include parameter:

model() {
  return this.store.findAll('photo', { include: 'user' });
}

Transcript

In this series, we're going to talk about data loading in Ember applications.

Now data loading includes everything from API design, to relation modeling, to building UI loading screens. So, we're going to keep this series focused and discuss how to build UIs that accommodate your application's data loading needs.

What made me so interested in this topic is that traditionally in server render web application, all of the data is loaded up front, so there's not too many decisions developers need to make when it comes to data fetching. However, in client side applications we have the ability to load data at any point in the application's life cycle. With this we get the ability to build beautiful and seamless UIs, but there's many more decisions and tradeoffs that we need to be aware of.

So let's go jump into some code and uncover our first data loading issue.


For the remainder of this series we're going be working on this photo sharing application for skiers and snowboarders, but right now the application doesn’t work. So first, let’s take a quick detour and get it working together.

Right here in the blank part of the screen we want to display a feed of photos. Let's jump over to the code and open the index route. We'll fetch all photos in it's model hook.

model() {
  return this.store.findAll('photo');
}

Then in the index template we can display these photos. Now, we've already got some of the page's structure setup here.

The first thing we want to do in this template is make sure our route is loading the photos correctly. So, let's loop over each photo and display the photo's url.

{{#each model as |photo|}}
  {{photo.url}}
{{/each}}

Ok, this looks right. Let's put these photos in an image tag.

{{#each model as |photo|}}
  
{{/each}}

Ok, it's ugly, but it's working.

So, usually at this point before continuing, I find it best to make a photo-card component. I know I'm going to have a list of photos here, and these photos are going to have some meta data as well as some custom styling. So a component is the right approach here.

Let's create that photo-card component.

ember g component photo-card

And switch our each loop to render our new component.

{{#each model as |photo|}}
  {{photo-card phot=photo}}
{{/each}}

Now, to start building this component let's open the it's template and stylesheet. Just a quick note, we're using ember-component-css, which sets us up with a stylesheet per component.

Let's also go ahead resize our windows so we can see browser as we're developing.

Ok first, in the component's template let's add a div with a background image for our photo. We'll give this element a Photo-card__photo class so we can position and size the photo using CSS. We'll use a height of 300 pixels, center the photo, and make sure the it can cover it's container.

&__photo {
  height: 300px;
  background-position: center;
  background-size: cover;
}

Let's also add a border and spacing around the card component.

.Photo-card {
  border: 1px solid #eee;
  margin: 30px;

  // ...
}

Ok great, so far so good.

Now let's add some meta data about the photo. We'll show the user that took the photo and the date it was taken on. Let's put this information in a new class called Photo-card__header and then add some padding to it in the CSS file.

{{photo.user.name}}
{{photo.createdAt}}
&__header {
  padding: 15px;
}

The date is looking a little too long for a feed. We'll format it to display the month and day using the ember-moment addon, which we've already installed. Also, let's make the date's font color gray. We'll give it it's own class and then change the color it in CSS.

{{moment-format photo.createdAt "MMM D"}}
&__date {
  color: #666;
}

Finally, let's add the avatar of the user who took the photo. We'll put it up in the header next to the user's name and give it the Photo-card__avatar class.

Ok this is pretty ugly, but we can fix it. First, in CSS let's use a column based layout by switching the header's display to flexbox with centered items.

&__header {
  display: flex;
  align-items: center;
}

Next, let's group the user name and date into the same column.

{{photo.user.name}}
{{moment-format photo.createdAt "MMM D"}}

Now, the avatar image is too big. Back in CSS, let's set the avatar's height and width to 50 pixels and we'll make the image round using a border radius of 100%.

&__avatar {
  height: 50px;
  width: 50px;
  border-radius: 100%;
}

Also, let's add some spacing to the right of the photo.

&__avatar {
  margin-right: 15px;
}

Ok, so this is much better. And now we have a working application.

But we already have our first data-loading issue... did you notice it?

We'll full screen the browser and open the dev tools. If we refresh the page we can see the network request that loads all of our photos. However, after that request, we see seven more requests that fetch various users.

Since each photo card displays the user's name and avatar, Ember data will have to fetch each user, one at a time, as it loops through the list of photos.

This type of issue is so common in application development that it has a name, it's often referred to as the N+1 query problem. Since Ember data will lazily load relationships we end up with one query in the route to fetch all the photos, then N requests in the template as we loop through the photos and fetch user information.

That's why it's called an N+1 problem, if N is the number of items in our set, in our case photos, you end up making N+1 queries. It's more like the 1+N query problem, one to fetch the photos then N to fetch each user, but it's called N+1.

What makes this problem extra tricky is that its often goes unnoticed in development. Usually we're developing against a fast and local backend, with a small set of data, so a few extra requests, they barely add up. However, production environments usually have both more data and latency, so this problem sticks out like a sore thumb once we deploy our code.

So how do we prevent our application from making N+1 queries? We need a way to load all the data upfront, before we render anything, since rendering the photos is what is triggering the additional requests.

So, in other words, we need our route to not only fetch photos, but instead fetch photos and their users all in one request.

To do this, we need our model hook to send a single request that tells our server to respond with all the data needed for this page. There are many strategies for this, and what you choose depends on your server, but recently most Ember developers have rallied around the JSON:API format for data transfer. We’re using that in this app, so let me show you just how easy JSON:API makes it to solve this problem.

We'll use the include option and specify that we want users included in the response as well.

model() {
  return this.store.findAll('photo', { include: 'user' });
}

Now let's save and refresh the page, we can see in the network tab a single request is made that fetches all photos and their users.

That was really easy. Just one line in our route fixed the whole N+1 problem.

So, let's talk about why that was so easy. JSON:API, and it has a feature called compound documents. This lets Ember data specify that it wants related data included in the response.

To show this, let's go back to the network tab and look at the API request that loaded all photos and their users. Ember data added an include user query string onto the URL. This is what tells JSON:API that for every photo it gives us, it also needs to include that photo's user.

This concept is often referred to as side-loading, and it's not unique to JSON:API at all. In fact, it exists in many different API formats. If you're backend doesn't support side-loading, I recommend taking some time to figure out how to build this feature into your API. Once you have this, it makes solving the N+1 query problem as easy as we just saw.

In the next video, we'll take a how to speed up our rendering when the backend is slow to respond with data. If you've ever worked with a slower backend, then the next video is for you.

Questions?

Send us a tweet:

Or ask us in Inside EmberMap, our private Slack workspace for subscribers.