Understanding Ember Data & Models - Ember.js Tutorial part 7

Tue Dec 20 2016

Paul Shan

I’m using Ember 2.10.0 Here.

In the part 7 of our Ember.js complete tutotial series we’re going to discuss about the Model part of Ember. Making models is probably an easy task as it’s as simple as making objects for your application. But ember provides a data library called Ember data, which makes managing and cashing models super easy. The initial investments to understand and setup each model may be a little high with ember data; but it’s really worth it. From handling type, serializing and making appropriate api calls, all these are taken care of by ember-data.

A brief on Model

The first letter in MVC stands for Model. Model is the bunch of data which generally is displayed on the UI. We’ve already seen the model() hook in ember’s routehow to return model object. Let’s check it out again.

import Ember from 'ember';

let myModel = [{
    firstName: "Jack",
    lastName: "Bauer"
}, {
    firstName: "David",
    lastName: "Palmer"
}];

export default Ember.Route.extend({
    model() {
        return myModel;
    }
}); 

As you can see in the above example, from the model() hook of router we are just returning an object; and this object will be set as model for the controller and from the template of that route you will be able to access this model object as variable model.

The example above showed how to return a model object in synchronous way. However you can also return a promise instead and ember will wait till the time the model promise is resolved before calling anything else on that particular route. Below is an example how to do async model passing.

import Ember from 'ember';
import $ from 'jquery';

export default Ember.Route.extend({
    model() {
        return new Ember.RSVP.Promise(function(resolve, reject) {
            $.ajax({
                url:"/api/getMeModel/"
            }).then(response=>{
                resolve(response);
            });
        });
    }
});

Well, that’s it about simple models. If you get the above description you are well capable of making an ember application and using your model; be it a static one or fetched from an api. However ember-data makes handling of models more easy and manageable. Below is a brief description and how to use ember-data.

Ember-data Basics

Though you can run an Ember application with simple models but in that case you need to manually fetch data from server. While creating or updating any record you also need to write code for that. Serializing data at the time of fetching, cashing them for better performance all these things needs to be done by you. Even if your model series is relational (ORM), than it’s more hard to write code manually and filter out the required objects.

What Ember-data provides you - pros

  • Data persistence.
  • Object relation (ORM) handling.
  • Proper model class declaration with types.
  • Data serializing and deserializing.
  • Easy way to manage models.
  • Syncing client data with server; both fetching and updating.
  • A store object where you can get all your data; fetched from anywhere by any route or controller.

Cons of Ember-data

  • High learning curve.
  • No nested model support yet.

How to use Ember-data

So here is the prime part; how to implement ember-data in your ember app. To understand the usage, please read the entire article carefully.

I’m going to make a small blog application. And why I selected blog and nothing else is because a beautiful site typicode is proving awesome apis of blog posts, comments, users etc. Please go through the typicode page once to check the apis.
We’re going to use three rest apis of that site; which are as below.

Step 1: Create an ember app named BlogApp.

I hope you already know how to create or scaffold an ember application. So create an ember app and name it BlogApp. Or if you wanna test ember data in an already created app of yours; you can do that too.

Step 2: Create few routes

We’ve already covered how to create ember routes; so if you are not an expert, you may go through that once for better understanding of routes.
Now create two routes named posts and users.

$ ember g route posts
$ ember g route users

Step 3: Create two models

Using the commands below you can create two models named post and user which will hold the model definitions.

$ ember g model post
$ ember g model user

Two files will be generated inside the app/models/ folder. Now update those models as below.

app/models/post.js

import DS from 'ember-data';

const { Model, attr } = DS;

export default Model.extend({
    userId: attr("string"),
    title: attr("string"),
    body: attr("string")
});

app/models/user.js

import DS from 'ember-data';

const { Model, attr } = DS;

export default Model.extend({
    name: attr("string"),
    email: attr("string")
});

We haven’t taken every single field given in the apis, but only few of them.

Step 4: Create adapters

Adapters are the middlemen who communicates to the server apis and fetch the data or pushes the updated data from client side to server. So it’s the network buddy of our application. For each api url ideally there will be one adapter. So we’re going to create two adapters post and user.

$ ember g adapter post
$ ember g adapter user

app/adapters/post.js

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
    host: "https://jsonplaceholder.typicode.com",
    //namespace: "",
    pathForType(){
        return "posts";
    }
});

app/adapters/user.js

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
    host: "https://jsonplaceholder.typicode.com",
    //namespace: "",
    pathForType(){
        return "users";
    }
});

The RESTAdapter is to communicate with rest apis. The property host is the host url of the website. pathForType() method return the last most part of the api url. The property namespace is all the middle part. For an example if an api url is http://abcd.com/something/more/blah/users, the host here will be http://abcd.com; namespace is something/more/blah and pathForType method will return users. Remember not to put the extra / symbol after any of these three property. Ember puts this by default.

Step 6: Get model in router

Now that you are all set with your model and adapter, you can now call the model inside your router. We’ve already created the routes in step 2 to comfort you in the begging. Now is the time to edit those two files to include models.

app/routes/posts.js

import Ember from 'ember';

export default Ember.Route.extend({
    model(){
        return this.store.findAll("post");
    }
});

app/routes/users.js

import Ember from 'ember';

export default Ember.Route.extend({
    model(){
        return this.store.findAll("user");
    }
});

As Ember is conventional, the code itself is describing it all. We are trying to get all the posts and users as model. findAll is to fetch all records of any model. It takes two parameters; first as modelName and second as options to do some more manipulations. You can check the details of findAll.

When you say findAll() it goes to the adapter and calls the given url as is. But again as Ember is all about convention, so it also expects the result in a particular format and that’s why you need the next step which is creating serializers.

Step 6: Setup serializers

If an adapter is named as post and from router or somewhere if someone calls store.findAll(); then the adapter hits the given host + / + namespace + / + pathForType() and the default serializer expects the response to be like below.

{
    posts: [
        {
        },
        {
        }
    ]
}

If your response is not exactly like this, then you can override the default serializer by creating one of your own. As in our case, both the response of /users and /postsare just simple array, we will use serializers to tweak the response and make it a proper one. Below are how to create the two serializers of us and what should we put inside them.

$ ember g serializer post
$ ember g serializer user

app/serializers/post.js

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
    normalizeResponse (store, primaryModelClass, payload, id, requestType){
        payload = {
            posts: payload
        };
        return this._super(store, primaryModelClass, payload, id, requestType);
    }
});

app/serializers/user.js

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
    normalizeResponse (store, primaryModelClass, payload, id, requestType){
        payload = {
            users: payload
        };
        return this._super(store, primaryModelClass, payload, id, requestType);
    }
});

So what we’ve done here is, we’ve made the response object, which is actually the payload object here as {posts: response}; i.e. {posts: payload}. After that just called the base adapter’s normalizeResponse with the new payload.

Step 7: Modify the templates.

Now everything is ready; your models, their adapters, the routers and serializers. All you need now is to display the data or model. So now change the templates of the route posts and users.

app/templates/posts

All posts here

{{#each model as |post index|}}

{{index}}. {{post.title}}

{{/each}} {{outlet}}

app/templates/users

All users here

{{#each model as |user index|}}

{{index}}. {{user.name}}

{{/each}} {{outlet}}

Step 8: Run the app

All setup done. Now run the ember app if it is not running already and hit the routes localhost:4200/posts/ and localhost:4200/users/ in your browser and you should be seeing the following.

ember-data-all-posts ember-data-user-list

Relational model with Ember-data

In the previous part we’ve learnt about simple models; where we’ve created adapters and serializers for them. However the models were not talking to each other; i.e. they were not relational. Now we will see how to create the ORM in ember-data.

In our case, each post has one author, who is an user from the user list. And an author may have multiple posts under his name. So we are going to build this relation between our two models user and post.

app/models/post.js

import DS from 'ember-data';

const { Model, attr, belongsTo } = DS;

export default Model.extend({
    userId: belongsTo("user"),
    title: attr("string"),
    body: attr("string")
});

app/models/user.js

import DS from 'ember-data';

const { Model, attr, hasMany } = DS;

export default Model.extend({
    name: attr("string"),
    email: attr("string"),
    posts: hasMany("post")
});

You can see in the two files above I’ve used two methods belongsTo and hasMany. These methods of DS are used to create relations between models. Here each blog post will have an author. The apis of jsonplaceholder.typicode.com gives a field named userId when you fetch from https://jsonplaceholder.typicode.com/posts. This userId is the unique id of the user who is the author of that particular post.

Now you can get the details of that user from https://jsonplaceholder.typicode.com/users/userId. After setting the one to one relation with the help belongsTo, you let ember handle the relationship. If you have already modified the models as mentioned above; now change the template posts.hbs as below and view in browser.

app/templates/posts.hbs

All posts here

{{#each model as |post index|}}

{{index}}. {{post.title}}

Author: {{post.userId.name}} {{/each}} {{outlet}}
ember-data-example-id

The authors names are also displayed and the network tab is saying that for each unique user it has called the api once; hence only 10 calls for users.

However the one to many relations do not make any automatic call and hence if we try to print user.posts.length in template users.hbs it will always give 0; unless someone else fetched data for that user.

Fetching individual records

In this section what we’re going to do selective model handling. The following things will be implemented.

  • /posts/id will display the particular post with the given id.
  • /posts/user/userId/ will display all the posts written by the author with given userId.

To do that, first of all let us create three different routes. posts/index, posts/single and posts/user.

$ ember g route posts/index
$ ember g route posts/single
$ ember g route posts/user

Now modify the app/router.js as below.

import Ember from 'ember';
import config from './config/environment';

const Router = Ember.Router.extend({
  location: config.locationType,
  rootURL: config.rootURL
});

Router.map(function() {
  this.route('posts', function() {
    this.route('single', {path: ":id"});
    this.route('user', {path: "user/:userId"});
  });
  this.route('users');
});

export default Router;

Another important thing is there to do. Copy the code of app/routes/posts.js to app/routes/posts/index.js and app/templates/posts.hbs to app/templates/posts/index.hbs. After copying you can delete the old ones.

app/routes/posts/single.js

import Ember from 'ember';

export default Ember.Route.extend({
    model(params){
        return this.store.findRecord("post", params.id);
    }
});

findRecord is to filter-out or fetch one single record. It takes three parameters; first as modelName, second as id and third as options to do some more manipulations. You can check the details of findRecord.

app/routes/posts/user.js

import Ember from 'ember';

export default Ember.Route.extend({
    model(params){
        return this.store.query("post", {
            userId: params.userId
        });
    }
});

app/templates/posts/single.hbs

{{model.title}}

{{model.body}}

Author: {{model.userId.name}}

app/templates/posts/user.hbs

{{#each model as |post index|}}

{{index}}. {{post.title}}

{{/each}}

Now if you visit the routes localhost:4200/posts/, localhost:4200/posts/1 and localhost:4200/posts/user/1 you will see all the blog posts, blog post with id=1 and all the blog posts by user with id 1 respectively.

So this is the basic of ember-data. If you need the project BlogPost we were discussing in the article, you can get it from github. You need to go through the ember-data apis to know each and every corner.

Next part

Hope now you are able to start working with ember-data library. However whether you need it in your application or going with simple model does the best for you; that completely depends on the use case. In the next part of this ember tutorial guide series we will talk about testing in Ember.js.

SHARE THIS ARTICLE

post-thumbnail
Today everyone knows the importance of a lightning-fast website and how the speed impacts the conversion rate of a business. Today, everyone wants the site to be a PWA so that the mobile users can have an app-like experience with the website because, for the majority of the merchants, the customers come through mobile devices.
Tue Apr 20 2021
post-thumbnail
Here we are going to see how you can manage backup and restore of Postgres database with docker.
Thu Sep 03 2020
post-thumbnail
Image sliders or carousels always have increased the UI attraction of websites and they are pretty useful for reflecting the major roles/products too. In case, I am having a website that sells tee-shirts,
Mon Apr 30 2018

About VoidCanvas

This blog was created out of hobby and talks mostly about technology, web development, JavaScript, NodeJS and related topics. Thank you for reading my blog.

Copyright 2022 - www.voidcanvas.com

Popular Articles

Authentication using Google's oAuth api with node.js

Thu Mar 10 2016

OAuth authentications are pretty popular now a days and another thing which is popular is JavaScript. This article shows how to plugin google’s oAuth api for authentication in your own node application.

CSS3 Loader Snippet Collection: (Part 2 - Squares)

Sat Mar 01 2014

This is a continuation of my CSS3 loader snippet collection series. I've provided spinning css3 animation loader in the part 1 of this series and here in part 2, I'm providing various square type loading