Animation and Predictable Data Loading in Ember
Tags: javascript, ember, showdev, tutorial
Published: 2019-04-03 18:44:03

Originally posted on [crunchingnumbers.live](https://crunchingnumbers.live)

At [EmberConf 2019](https://emberconf.com), I had the chance to meet and learn from many Ember developers around the globe. I'm excited about [Ember Octane](https://emberjs.com/editions/octane/), a new edition built with developer productivity and app performance in mind. It's in beta and readying for release. I think there's no better time to learn and use Ember.

This tutorial covers how to load complex data in a predictable manner and how to add animation to liven up your site. A hearty thanks goes to Sam Selikoff and Ryan Toronto, whose teaching at the conference I'm heavily basing mine on. They had taken time to build a polished demo app; I was inspired to follow their footsteps.

tl;dr. Use [Ember Animated](https://ember-animation.github.io/ember-animated/) and [Ember Data Storefront](https://embermap.github.io/ember-data-storefront/) today!


# 0. Finished App

You can see my demo app at [https://ember-animated.herokuapp.com/](https://ember-animated.herokuapp.com/). It is responsive and scales up to 4K screens.

Here is the scenario. You are looking to hire students (candidates) whose skills match your desired ones. From the Students tab, you can see all students at a glance and examine each in detail. From the Search tab, you can set the desired skills and find students who best meet them. You can navigate between tabs and pages in any order without encountering errors.

{% youtube Q3Wu-HroNy8 %}


# 1. Predictable Data Loading

In the demo app, we have 5 models: Student, Resume, Degree, Experience, and Skill. These models are related through **one-to-many** and **many-to-many relationships**.

I think relationships are what makes Ember Data difficult to learn and use. Let me show you 4 types of bugs that you can encounter when you have models with relationships. I will then show how Ember Data Storefront helps us solve these problems.


## a. {% raw %}{{link-to}}{% endraw %} bug

{% youtube flQPYH85338 %}

After looking at all students, we click on one student to see their details. Strangely, we can see their name, email, phone, and profile image (the **attributes**), but not their degrees, experiences, and skills (the relationships). We click on another student to see a similar behavior. Lastly, when we refresh the page, we can see all information, but only of that student and no one else.

We suspect the problem to lie with **route handlers**, because they are responsible for loading data. When we examine the files, however, we see nothing wrong. Just good old friends, `findAll` and `findRecord`, from Ember.

```javascript
File: /app/routes/students.js

import Route from '@ember/routing/route';

export default Route.extend({
    model() {
        return this.store.findAll('student');
    },
});
```

```javascript
File: /app/routes/students/student.js

import Route from '@ember/routing/route';

export default Route.extend({
    model(params) {
        return this.store.findRecord('student', params.id, {
            include: 'resumes,resumes.degrees,resumes.experiences,resumes.skills',
        });
    },
});
```

The culprit turns out to be a **template** that creates a link to each student. We encounter this template when we visit `students` and `students.student` routes. Can you spot the problem?

```handlebars
File: /app/components/students-grid/template.hbs

{{#let (component "students-grid/card") as |Card|}}
    {{#each (sort-by "lastName" "firstName" students) as |student|}}
        <li>
            {{#link-to "students.student" student}}
                <Card @student={{student}} />
            {{/link-to}}
        </li>
    {{/each}}
{{/let}}
```

In line 4, we passed the `student` **model** to the `{{link-to}}` **helper**. When we do so, Ember skips calling the `model` **hook** of the `students.student` route. *I already have the model, so why should I fetch it again?* Although Ember thinks it is making a smart decision, the user is actually missing out on crucial data.

The fix is simple. To ensure that Ember calls the `model` hook, we pass the model ID.

```handlebars
File: /app/components/students-grid/template.hbs

{{#let (component "students-grid/card") as |Card|}}
    {{#each (sort-by "lastName" "firstName" students) as |student|}}
        <li>
            {{#link-to "students.student" student.id}}
                <Card @student={{student}} />
            {{/link-to}}
        </li>
    {{/each}}
{{/let}}
```

I think the `{{link-to}}` syntax that results from passing the ID makes more sense. After all, if I were to create a URL for a student, i.e. `/students/:some_parameter`, I would want to indicate what identifies them.

Unfortunately, I believe the mistake of passing the model is easy to make for new and seasoned developers. I didn't know the difference until I attended Sam and Ryan's talk. Even Ember documentation suggests that passing the model is okay. (It later corrects code and warns us in [a different section](https://guides.emberjs.com/release/routing/specifying-a-routes-model/#toc_dynamic-models), buried under other texts.)

![As of version 3.8, Ember documentation suggests that it's okay to pass model to the link-to helper. It is not.](https://crunchingnumbersdotlive.files.wordpress.com/2019/04/ember_link_to.png)

<figcaption>Think carefully before passing a model to the link-to helper.</figcaption>

Sam and Ryan commented that they created their own link-to helper that can handle both model and ID gracefully. They also suggested that we lint against `{{link-to}}` so that passing a model results in a runtime error.

tl;dr. Always pass the model ID to `{{link-to}}`.


## b. findRecord bug

{% youtube 7a4UskY6Jus %}

After fixing the `{{link-to}}` bug, we can now see a student's degrees, experiences, and skills. However, these information pop on the screen after a delay. Refreshing the page lends to the same behavior. Can we prevent the template from "flashing"? Why does this happen in the first place?

The key is that the `students` route **nests** the `students.student` route (you can check this in `/app/router.js`). As a result, when we visit the student details page, whether through Students tab or directly by URL, Ember calls the `model` hook of `students`, the parent route handler, first. In other words, when Ember tries to load the details page, it already has the student's name, email, phone, and profile image. Why not show them immediately?

Let's take another look at `students.student`, the child route handler.

```javascript
File: /app/routes/students/student.js

import Route from '@ember/routing/route';

export default Route.extend({
    model(params) {
        return this.store.findRecord('student', params.id, {
            include: 'resumes,resumes.degrees,resumes.experiences,resumes.skills',
        });
    },
});
```

The `include` query parameter, specific to the JSON:API, allows us to **sideload** related data. Since resumes, degrees, experiences, and skills take an extra call, they get shown in the template at a later time.

One way to mitigate flashing is to pass the `reload` parameter of `true`. Ember will block rendering until it has reloaded the student data.

```javascript
File: /app/routes/students/student.js

import Route from '@ember/routing/route';

export default Route.extend({
    model(params) {
        return this.store.findRecord('student', params.id, {
            include: 'resumes,resumes.degrees,resumes.experiences,resumes.skills',
            reload: true,
        });
    },
});
```

However, by setting `reload` to `true`, we lose the benefit of **caching** that `findRecord` provides. Every time we visit the student's page, we are loading that student's data. (We know this because we see the loading spinner.) Perhaps we can set `reload` to `true` on an initial visit, then to `false` for subsequent visits, but this leads to more work and complex code.

Sam and Ryan (I too) advocate for writing a **declarative** `model` hook. Simply put, we should be able to express our ideas in code without worrying about implementation details. In addition, if the complex logic of loading data can all be done in the `model` hook, we wouldn't see side effects that may arise from spreading the logic to other hooks (e.g. `afterModel`).

Their solution, [Ember Data Storefront](https://embermap.github.io/ember-data-storefront/), does just this. It's easy to use too!

First, we change Ember Data's `findRecord` to Ember Data Storefront's `loadRecord`.

```javascript
File: /app/routes/students/student.js

import Route from '@ember/routing/route';

export default Route.extend({
    model(params) {
        return this.store.loadRecord('student', params.id, {
            include: 'resumes,resumes.degrees,resumes.experiences,resumes.skills',
        });
    },
});
```

Second, we update the student model to extend the `LoadableModel` **mixin**, so that we can use `loadRecord` in the route handler. We also set all `async` options to `false` so that we can avoid unexpected surprises. (See Notes to learn why.)

```javascript
File: /app/models/student.js

import DS from 'ember-data';
import LoadableModel from 'ember-data-storefront/mixins/loadable-model';

export default DS.Model.extend(LoadableModel, {
    resumes: DS.hasMany('resume', { async: false }),
});
```

That's it. No step 3.

Ember Data Storefront blocks rendering until all data are present. It is also query-aware. It will return the cached data if we made the query before or if the included models can be decomposed into past queried ones.

tl;dr. Use `loadRecord` instead of `findRecord`.


## c. findAll bug

{% youtube _2gLx7dyPqc %}

We can now view a student's details. Next, we want to specify our desired skills and find students who best meet them.

If we navigate directly from Home to Search, we will see all skills without a problem. However, if we visit a student details page, then visit Search, we see only the skills of that student. When we navigate to Home then back to Search, we see all skills once again. What's going on?

This time, let's have a look at the `search` route handler.

```javascript
File: /app/routes/search.js

import Route from '@ember/routing/route';

export default Route.extend({
    model() {
        return this.store.findAll('skill');
    },
});
```

Ember Data's `findAll`, like its singular counterpart `findRecord`, uses caching and **background reload** so that the user sees some content immediately while fresh data gets served. Unfortunately, this creates side effects when we have related models and can't predict all possible states that arise from user interaction.

Again, one solution is to pass `{ reload: true }` and forget about caching. The other is to use Ember Data Storefront.

```javascript
File: /app/routes/search.js

import Route from '@ember/routing/route';

export default Route.extend({
    model() {
        return this.store.loadRecords('skill');
    },
});
```

tl;dr. Use `loadRecords` instead of `findAll`.


## d. (n + 1) query bug

{% youtube paNj3rdXOrI %}

The last bug concerns making excessive AJAX requests due to relationships. Consider this simplified `student-details` template.

```handlebars
File: /app/components/student-details/template.hbs

<ul>
    {{#each resume.degrees as |degree|}}
        <li>{{degree.name}}</li>
    {{/each}}
</ul>
```

We already made 1 request to get the student. Without proper treatment, the template will make *n* additional requests, one for each degree. Hence, *(n + 1)*.

Thanks to JSON:API, we can make 1 request with all data necessary to render the page. In Ember, we pass the `include` query parameter.

```javascript
File: /app/routes/students/student.js

import Route from '@ember/routing/route';

export default Route.extend({
    model(params) {
        return this.store.loadRecord('student', params.id, {
            include: 'resumes,resumes.degrees,resumes.experiences,resumes.skills',
        });
    },
});
```

Then, in Rails (or your preferred backend), we allow **eager loading**.

```ruby
File: /api/app/controllers/students_controller.rb

class StudentsController < ApplicationController
    def show
        render json: StudentSerializer.new(
            @student,
            include: [:resumes, :'resumes.degrees', :'resumes.experiences', :'resumes.skills']
        )
        .serialized_json
    end
end
```

As a tidbit, Ember Data Storefront provides the `AssertMustPreload` component. It throws a runtime error for missing relationships.

```handlebars
File: /app/components/student-details/template.hbs

{{assert-must-preload
    student
    "resumes,resumes.degrees,resumes.experiences,resumes.skills"
}}

<ul>
    {{#each resume.degrees as |degree|}}
        <li>{{degree.name}}</li>
    {{/each}}
</ul>
```

![Ember Data Storefront provides a runtime error for missing data relationships.](https://crunchingnumbersdotlive.files.wordpress.com/2019/04/ember_data_storefront_error.jpg)

<figcaption>Ember Data Storefront can return a runtime error for missing relationships.</figcaption>

We can also use `AssertMustPreload` to prevent *(n + 1)* queries. If we had created all relationships with `{ async: false }`, a runtime error would imply that we forgot to use `include`. Without `include`, we would make *(n + 1)* queries if we loop over the related data. QED.

tl;dr. Use `include`.


# 2. Animation

Now we can load data in a predictable manner. But so far, our app is static, in the sense that it shows content without flair. Here, I want to convey to you that even small drops of animation can make our app feel dynamic and polished.

In addition to CSS transition (please see Notes for more information), we can use [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) and [Ember Animated](https://ember-animation.github.io/ember-animated/) to create animations in Ember.


## a. Web Animations API

A details page, whose content is heavy, can be overwhelming to look at. Let's spruce it up by fading in sections one at a time. We can use a **modifier**, introduced in Ember 3.8 and Octane, to solve this problem easily. A modifier modifies a DOM element and lets us stretch our imaginations.

I have a modifier called `fade-up`. First, let me show you how we use the modifier.

```handlebars
File: /app/components/student-details/template.hbs

<header {{fade-up id=id}}>
    <h1>{{fullName}}</h1>
</header>

<section {{fade-up id=id}}>
    <ProfileImage @imageUrl={{imageUrl}} @altText={{fullName}} />
</section>

<section {{fade-up id=id delay=50}}>
    <header>
        <h2>Email</h2>
    </header>

    <a href="mailto:{{email}}">
        {{email}}
    </a>
</section>
```

With these simple changes, we can fade in the name and profile image immediately, while fade in the email shortly after (50 ms). We can similarly modify the remaining sections with longer delays to create a staggered effect.

{% youtube m_h4EeWFGtk %}

Next, let's check how our modifier works.

```javascript
File: /app/modifiers/fade-up.js

import { Modifier } from 'ember-oo-modifiers';

const FadeUpModifier = Modifier.extend({
    didReceiveArguments(args, options) {
        this.element.animate(
            [
                { opacity: 0, transform: 'translateY(60px)' },
                { opacity: 1, transform: 'translateY(0px)' },
            ],
            {
                duration: options.duration || 2000,
                delay: options.delay || 0,
                easing: 'cubic-bezier(0.075, 0.82, 0.165, 1)',
                fill: 'backwards',
            },
        );
    },
});

export default Modifier.modifier(FadeUpModifier);
```

The modifier extends the one from [Ember OO Modifiers](https://github.com/sukima/ember-oo-modifiers) addon. Inside the modifier, we have a reference to the DOM element; it's just `this.element`. We call `.animate` from Web Animations API and pass two parameters—an array of **keyframes** and an options hash—to describe how we want to animate the element.

We use the `didReceiveArguments` hook so that the modifier will be called again when we switch between child routes (the model ID changes). The `options` parameter is an object that can take *any* form. Pass whatever you'd like. Here, I'm allowing ourselves to control the animation `duration` and `delay`.

Web Animations API isn't a perfect solution, however. The API is experimental and our browsers [don't fully support it](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate#Browser_compatibility). You may end up needing a [polyfill](https://github.com/web-animations/web-animations-js).

At the time of writing, the `fade-up` modifier also has a problem. (I'm not sure whether I misused Ember's modifier or Ember OO Modifiers had a bug.) If you visit a child route, say `/students/1`, then another child, `/students/2`, and come back to `/students/1` (i.e. cached data), you may end up seeing the animation happen twice.


## b. Ember Animated

Finally, let's learn how to create more complex animations.

Ember Animated comes with 2 helpers, `{{animated-each}}` and `{{animated-if}}`, among other things. The idea is, we simply replace Ember's `{{each}}` and `{{if}}` in our code with their `animated-` counterparts.


### i. animated-each

Consider this simplified `search` template. It shows the selected skills, sorted by their category and name.

```handlebars
File: /app/templates/search.hbs

<span>Selected:</span>

{{#each
    (sort-by "category" "name" selectedSkills)
    as |skill|
}}
    <SkillPill @skill={{skill}} />
{{/each}}
```

Then, we introduce `{{animated-each}}` as follows:


```handlebars
File: /app/templates/search.hbs

<span>Selected:</span>

<AnimatedContainer>
    {{#animated-each
        (sort-by "category" "name" selectedSkills)
        use=transition
        as |skill|
    }}
        <SkillPill @skill={{skill}} />
    {{/animated-each}}
</AnimatedContainer>
```

I think it's brilliant how `{{animated-each}}` just works with other addons. The `sort-by` helper comes from [Ember Composable Helpers](https://github.com/DockYard/ember-composable-helpers). I didn't have to do extra work to make the two play nice.

The `{{animated-container}}` reserves space for animation. Should there be content after the loop, it will gracefully step aside as the skills enter and exit the container.

{% youtube Rn2vHZIwhug %}

Let's take a look at `transition` that we used in the template. We can find its definition in the `search` **controller**.

```javascript
File: /app/controllers/search.js

import Controller from '@ember/controller';

export default Controller.extend({
    *transition({ insertedSprites, keptSprites, removedSprites }) {
        insertedSprites.forEach(fadeIn);

        keptSprites.forEach(sprite => {
            parallel(
                fadeIn(sprite),
                move(sprite, { easing: easeIn })
            );
        });

        removedSprites.forEach(sprite => {
            sprite.endTranslatedBy(60, 80);

            parallel(
                fadeOut(sprite),
                move(sprite, { easing: easeOut })
            );
        });
    },
});
```

In line 4, we see that `transition` is a **generator function**. Anything a regular function can do, generator functions can also. But generator functions can do even more. They allow us to **yield** (output) intermediate values and check the **context** (the current state) when we enter the function. You can imagine how these features would be useful for animation.

In Ember Animated, the context is an object that keeps track of 5 types of **sprites**. In the example above, we make use of three of them: `insertedSprites` (elements that are to be added to the DOM), `keptSprites` (those that are to stay), and `removedSprites` (those that will be removed). For each type (and quite possibly, for each sprite), we can define their movement.


### ii. animated-if


Next, let's animate the navigation.

{% youtube meIcvx2GjlY %}

Here's the simplified `navigation-drawer` template. We see that if a nav item's route matches the current one, then we add a highlight below the text.

```handlebars
File: /app/components/navigation-drawer.hbs

{{#each navItems as |navItem|}}
    {{#link-to navItem.route}}
        <span>{{navItem.label}}</span>

        {{#if (eq navItem.route currentParentRoute)}}
            <div class="highlighted" aria-hidden="true"></div>
        {{/if}}
    {{/link-to}}
{{/each}}
```

First, we replace `{{if}}` with `{{animated-if}}`. We pass the `group` property to treat the 3 `<div>` elements as if they were one.

```handlebars
File: /app/components/navigation-drawer.hbs

{{#each navItems as |navItem|}}
    {{#link-to navItem.route}}
        <span>{{navItem.label}}</span>

        {{#animated-if
            (eq navItem.route currentParentRoute)
            use=transition
            group="navItems"
        }}
            <div class="highlighted" aria-hidden="true"></div>
        {{/animated-if}}
    {{/link-to}}
{{/each}}
```

Second, we define the `transition`.

```javascript
File: /app/components/navigation-drawer/component.js

import Component from '@ember/component';

export default Component.extend({
    *transition({ receivedSprites }) {
        receivedSprites.forEach(sprite => {
            parallel(
                move(sprite),
                scale(sprite)
            );
        });
    },
});
```

This time, we use `receivedSprites` to express how we want to animate the highlight. The highlight simply moves to its final position and changes its size (its width, to match the text width).

`receivedSprites` and `sentSprites` are the other two types of sprites. They can be used to animate sprites that move between two different components.


# 3. Conclusion

Thanks to addons like [Ember Animated](https://ember-animation.github.io/ember-animated/) and [Ember Data Storefront](https://embermap.github.io/ember-data-storefront/), we can really build ambitious apps and let our imaginations come alive. I had a lot of fun (and lost a lot of sleep) creating my demo app, and hope that you will enjoy creating too.

As great as these addons are, they are also work in progress; they need your help in achieving stability and realizing their full potential. I'll close by reiterating Kenneth Larsen's message at EmberConf. If you have time, please give back to your community by helping with documentations, issues, or tutorials. Be awesome to each other.


# Notes

From Ember Discord, I realized that adding a data-driven CSS transition is neither obvious nor well-documented. Suppose we have a component called `bar-chart`. It draws bars and sets their heights dynamically.

```handlebars
File: /app/components/bar-chart/template.hbs

{{#each bars as |bar|}}
    <div
        class="bar"
        style={{if initialRender (concat "height: " bar.height "%;")}}
    >
    </div>
{{/each}}
```

```javascript
File: /app/components/bar-chart/component.js

import Component from '@ember/component';
import { later } from '@ember/runloop';

export default Component.extend({
    classNames: ['bar-chart'],

    didRender() {
        this._super(...arguments);

        later(() => {
            this.set('initialRender', true);

        }, 1);
    },
});
```

```scss
File: /app/styles/components/bar-chart.scss

.bar-chart {
    display: flex;

    .bar {
        border: 1px solid orange;
        width: 100px;
        height: 0;
        transition: height 2s ease;
    }
}
```

The key is that we can use Ember's `later` to control when the CSS transition should be applied (after the initial render).

For more information on animation and predictable data loading, I encourage you to visit the following links:

- [Ember Data Storefront at Ember.js NYC](https://www.youtube.com/watch?time_continue=1250&v=X-LjrRx2wMI), by Sam Selikoff and Ryan Toronto

- [EmberConf 2018 Living Animation](https://www.youtube.com/watch?v=4JofVQ3nGrw), by Edward Faulkner

- [EmberConf 2019 Real-World Animations](https://github.com/embermap/emberconf2019-real-world-animations), by Sam Selikoff and Ryan Toronto

- [EmberConf 2019 Robust Data Fetching](https://github.com/embermap/emberconf2019-robust-data-fetching), by Sam Selikoff and Ryan Toronto

- [N + 1 Queries or Memory Problems: Why Not Solve Both?](https://blog.heroku.com/solving-n-plus-one-queries), by Richard Schneeman

- [The Case Against Async Relationships](https://embermap.com/notes/83-the-case-against-async-relationships), by Ryan Toronto

- [What Are Modifiers?](https://blog.emberjs.com/2019/03/06/coming-soon-in-ember-octane-part-4.html), by Kristen Garrett

You can find the code in its entirety here:

[Download from GitHub](https://github.com/ijlee2/ember-animated-tutorial)