Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

EmberJS Tutorial

DZone's Guide to

EmberJS Tutorial

In this article, we'll show you how to create a neat little web app that runs on the EmberJS framework and uses an API built on Node.js. Read on for more!

· Web Dev Zone
Free Resource

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

EmberJS was developed by Yehuda Katz. It was initially released in December 2011. EmberJS was also formerly known as SproutCore MVC framework. New applications now run on EmberJS 2 which was released in August 2015. EmberJS 2.0 introduced new APIs and removed deprecated ones from Ember 1. The goal of Ember 2 is to remove badly designed and unnecessarily complicated code from Ember 1. And apps that run on Ember 1.13 without any deprecation warnings should run without issues on Ember 2.0. Currently, many popular products use EmberJS to build their user interfaces. Such platforms include LinkedIn, Yahoo, Zendesk, Square, PlayStation Now, Apple Music, Heroku Dashboard, Twitch, Discourse, IndieHackers and more. There is a comprehensive list of projects using Emberjs on builtwithember.io. EmberJS documentation is very detailed, and there is a vibrant community of users.

Understanding Core Concepts in EmberJS

If you have experience with frameworks like VueJS, Angular, and React, then you'll understand how EmberJS works in a split second. Developers coming from the jQuery world might find it difficult to comprehend at first glance. But if you are familiar with frameworks like Laravel and Rails, then you'll discover a pattern that'll make fall in love with EmberJS.

I'll give a basic overview of these concepts to nourish your understanding of EmberJS. They are:

  • Routing
  • Templates
  • Models
  • Components
  • Controllers

Routing

Let's take a good look at how a typical EmberJS app works. Our fictitious app is a Student Management System and the URL is https://studember.ng. One of the URLs in this app is https://studember.ng/students. This route simply returns the details of all the students registered on this app. Now, check out what happens when the user loads the app for the first time.

Ember router maps the URL to a route handler. The route handler then renders a template and loads a model that is available to the template.

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('students');
});

export default Router;

router

import Ember from 'ember';

export default Ember.Route.extend({
    model() {
        return [
            'David Ajimobi',
            'Olorigbeske Ojuyobo',
            'Orieja Michael'
        ]
    }
});

Route Handler

You can easily create a route using the Ember CLI's generate command like so:

ember generate route route-name

This creates a route file at app/routes/route-name.js, a template for the route at app/templates/route-name.hbs, and a unit test file at tests/unit/routes/route-name-test.js. It also adds the route to the router.

Templates

Templates are used to organize the HTML layout of the application. By default, EmberJS uses Handlebars templates. Templates can display properties provided to them from a controller or a component. The screen rendered to the user is composed of handlebars templates. A typical example is this:

<h3> {{title}} </h3>

<ul>
    {{#each people as |person| }}
        <li>{{person}}</li>
    {{/each}}
</ul>

template.hbs

The template extension is .hbs.

Model

Models are objects that represent the underlying data that your application presents to the user. The structure and scope of your app will determine the types and number of models that will present in it.

A typical example is this:

Our student management app might have a Student model to represent a particular student. Models are also used to persist data. Typically, most models fetch and persist data to a store. The store could be a database on a server or simply a JSON file.

import DS from 'ember-data';

export default DS.Model.extend({
  first_name: DS.attr(),
  last_name: DS.attr(),
  city: DS.attr(),
  age: DS.attr(),
});

app/models/student.js

Ember comes with a data management library called Ember Data to help deal with persistent application data. The library requires you to define the structure of the data you wish to provide to your application by extending DS.Model. At first, using Ember Data may feel different than the way you're used to writing JavaScript applications. Many developers are familiar with using AJAX to fetch raw JSON data from an endpoint, which may appear easy at first. Over time, however, complexity leaks out into your application code, making it hard to maintain. Ember Data helps you manage your models in a simple way as your application grows.

Ember Data gives you a single store that is the central repository of models in your application. Components and routes can ask the store for models, and the store is responsible for knowing how to fetch them.

You can easily create a model using the Ember CLI's generate command like so:

ember generate model student

This creates a model at app/models/student.js file:

import DS from 'ember-data';

export default DS.Model.extend({
});

Components

Ember Components consist basically of two parts: a Handlebars template, and a JavaScript file that defines the component's behavior.

Components must have at least one dash in their name, e.g active-list. This helps Ember differentiate it from native HTML tags. Components control how the user interface behaves. Components are represented in the view with the curly brace rather than the angle tag like this:

{{active-list}}

Ember provides some methods that are triggered at various points from creating a component up until the component is destroyed. This is called the Component's Lifecycle. You can declare methods to hook into the component's lifecycle to control the behavior of components in your app.

On Initial Render, we have:

  • init
  • didReceiveAttrs
  • willRender
  • didInsertElement
  • didRender

On Re-render, we have:

  • didUpdateAttrs
  • didReceiveAttrs
  • willUpdate
  • willRender
  • didUpdate
  • didRender

On Component Destroy, we have:

  • willDestroyElement
  • willClearRender
  • didDestroyElement

A typical example of a component is this:

import Ember from 'ember';

export default Ember.Component.extend({
  init() {
    this._super(...arguments);
    this.errors = [];
  },

  didUpdateAttrs() {
    this._super(...arguments);
    this.set('errors', []);
  },

  actions: {
    required(event) {
      if (!event.target.value) {
        this.get('errors').pushObject({ message: `${event.target.name} is required`});
      }
    }
  }
});

You can easily create a component using the Ember CLI's generate command like so:

ember generate component student-list

This creates a component file at app/components/student-list.js, a template for the component at app/templates/components/student-list.hbs, and an integration test file at tests/integration/components/student-list-test.js.

Controllers

Ember Controllers are routable objects meant to decorate a model with display logic. They sit between the template and model to deal with logic and properties that do not belong to the view or the model.

When you have a property that needs to be in the template but doesn't exist in the model, you can place it the controller:

import Ember from 'ember';

export default Ember.Controller.extend({
  canDelete: true
});

This property can now be accessed in the template:

{{#if canDelete}}
// Go ahead and delete the student
{{/if}}

The controller can also be used to make model data more readable to the user. An example is returning the full name of the user:

import Ember from 'ember';

export default Ember.Controller.extend({
  getFullName() {
    return `${this.get('model.firstName')} - ${this.get('model.lastName')}`
  }
});

We can just call getFullName in the template:

<span>{{ getFullName }} is the senior prefect.</span>

You can easily create a controller using the Ember CLI's generate command like so:

ember generate controller students

This creates a controller file at app/controllers/students.js, and a unit test file at tests/unit/controllers/students-test.js.

Next, let's build an application with Emberjs 2.

Our App: Whistle Blower

Whistle Blower

The app we will build today is called Whistle Blower. A Whistle Blower is a person who exposes any kind of information or activity that is deemed illegal, unethical, or not correct within an organization that is either private or public. The Whistle Blower app does the following:

  • It gives information about whistle blowing activities in your region.
  • It's a small community of whistle blowers.
  • A guest user on the Whistle Blower app will only have access to basic information about the whistle blowing activities on the landing page.
  • An authenticated user will have access to whistle blowers and their profiles.
  • An authenticated user will have access to whistle blower meetups/gatherings.

Build The Backend

Let's build an API for our app. We'll quickly build the API with Node.js. The API is simple. This is what we need:

  • An endpoint to serve the latest whistle blowing activities around the world - /api/activities.
  • An endpoint to serve whistle blowers and their profiles - /api/whistleblowers.
  • An endpoint to serve whistle blower meetups - /api/meetups.
  • Securing the endpoint that serves whistle blowers profiles and meetups, so that it can only be accessed by registered users.

Go ahead and fetch the Node.js backend from GitHub.

Your server.js should look like this:

'use strict';

const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());

const authCheck = jwt({
  secret: jwks.expressJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: "https://{YOUR-AUTH0-DOMAIN}/.well-known/jwks.json"
    }),
    // This is the identifier we set when we created the API
    audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
    issuer: "{YOUR-AUTH0-DOMAIN}",
    algorithms: ['RS256']
});

app.get('/api/activities', (req, res) => {
  let whistleBlowerActivities = [
    // An array of whistleblowing activities
  ];

  res.json(whistleBlowerActivities);
})

app.get('/api/whistleblowers', (req,res) => {
  let whistleBlowers = [
    // An aray of whistle blowers
  ];

  res.json(whistleBlowers);
})

app.get('/api/meetups', (req,res) => {
  let meetups = [
    // An array of meetups
  ];

  res.json(meetups);
})

app.listen(3333);
console.log('Listening on localhost:3333');

Check out the full server.js file here.

Your package.json file should look like this:

{
  "name": "whistleblower",
  "version": "0.0.1",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "author": "Auth0",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.15.2",
    "cors": "^2.8.1",
    "express": "^4.14.0",
    "express-jwt": "^3.4.0",
    "jwks-rsa": "^1.1.1"
  }
}

Note: Make sure you have nodemon installed globally.

Once you have cloned the project, run an npm install, then use postman to serve your routes like so:

API serving meetupsAPI serving meetups

API serving whistle blowersAPI serving whistle blowers

API serving whistle blowing activitiesAPI serving whistle blowing activities

The public whistle blowing activities endpoint should be http://localhost:3333/api/activities.

The private meet up endpoint should be http://localhost:3333/api/meetups.

The private whistle blower endpoint should be http://localhost:3333/api/whistleblowers.

Don't worry about the middleware in charge of securing our endpoint for now. We'll deal with that later. Now, let's build our frontend with EmberJS 2.

Build the Front-End With Emberjs 2

EmberJS has a very nice tool for scaffolding your apps. It's called the ember-cli. It's being maintained by the Ember team.

Go ahead and install the ember-cli tool globally like so:

npm install -g ember-cli

After installing globally, go ahead and scaffold a new EmberJS 2 app like so:

ember new whistleblower

Provisioning a new ember app

Move into the new directory, whistleblower and run ember serve to start up your app.

Default Page

Let's check out the structure of our newly scaffolded app.

Scaffolded App

whistleblower/
  app/ - All the controllers, components, routes and templates reside here
  config/ - All environment config files are here
  node_modules/ - All the packages required for the emberjs app resides here
  public/
  tests/ - All the tests file resides here
  vendor/
  .editorconfig
  .ember-cli
  .eslintrc.js
  .gitignore
  .travis.yml
  .watchmanconfig
  ember-cli-build.js
  package.json - File that contains the names of all the packages residing in node_modules folder
  README.md
  testem.js

Note: We are not writing any tests for this application. It's out of the scope of this tutorial.

We need to remove the default page content that Ember presents in our app. Open theapp/templates/application.hbs file and remove this:

{!-- The following component displays Ember's default welcome message. --}}
{{welcome-page}}
{{!-- Feel free to remove this! --}}

Now, our app will show a blank screen. Sweet! Let's get started.

Style With Bootstrap

Go ahead and open the app/index.html file. Here, we will add the link to the bootstrap CSS and JS file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Whistleblower</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    {{content-for "head"}}

    <link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
    <link rel="stylesheet" href="{{rootURL}}assets/whistleblower.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">


    {{content-for "head-footer"}}
  </head>
  <body>
    {{content-for "body"}}

    <script src="{{rootURL}}assets/vendor.js"></script>
    <script src="{{rootURL}}assets/whistleblower.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js">

    {{content-for "body-footer"}}
  </body>
</html>

Ember already provides a directory and a stylesheet file for your custom CSS. So, open app/styles/app.css and add the CSS here.

That's all about our styling. Next, let's create our routes.

Create the Routes

We need users to be able to access a URL that: 

  • provides details about whistle blower meetups.
  • provides details about whistle blowers.

We also need a callback URL. I'll tell you why we need that later in the tutorial. Let's create these routes ASAP. The ember-cli provides generator commands that make this easy. So go ahead and run the following commands in your terminal:

ember generate route whistle-blowers
ember generate route meetups
ember generate route callback

The route files are generated together with their respective template files. The EmberJS route handler renders a template when a route is invoked.

Building the Nav Component

Let's build the Nav Component. This component will be shared amongst all the pages. Generate the component using the ember-cli:

ember generate component app-nav

This command generates a component and a template for the app nav. Open up app/templates/components/app-nav.hbs and add this to it:

<nav class="navbar navbar-default">
    <div class="navbar-header">
       Whistle Blower
    </div>

    <ul class="nav navbar-nav navbar-right">
      <li>
          <button class="btn btn-danger log">Log out</button>
          <button class="btn btn-info log">Log In</button>
      </li>
    </ul>
</nav>

Now, next, we need to create a utility file for authentication and fetching API data for our routes from the backend. Well, Ember allows us to create utilities, but this is better suited for services. An Ember service is a long lived Ember object that can be made available in different parts of your application. This is exactly what we need.

Creating Services

We need to create two services, the auth and API service. The former for everything related to user authentication and the latter for fetching API data from our server. Go ahead and create both services using the ember-cli:

ember generate service auth
ember generate service whistleblowerapi

app/services/auth.js and app/services/whistleblowerapi.js will be created. Now, open the auth service and add this to it:

app/services/auth.js

import Ember from 'ember';
import decode from 'npm:jwt-decode';
import auth0 from 'npm:auth0-js';
const ID_TOKEN_KEY = 'id_token';
const ACCESS_TOKEN_KEY = 'access_token';

const CLIENT_ID = '{AUTH0_CLIENT_ID}';
const CLIENT_DOMAIN = '{AUTH0_DOMAIN}';
const REDIRECT = '{CALLBACK_URL}';
const SCOPE = '{SCOPE}';
const AUDIENCE = '{API IDENTIFIER}';

export default Ember.Service.extend({

    auth: new auth0.WebAuth({
        clientID: CLIENT_ID,
        domain: CLIENT_DOMAIN
    }),

    login() {
        this.get('auth').authorize({
            responseType: 'token id_token',
            redirectUri: REDIRECT,
            audience: AUDIENCE,
            scope: SCOPE
        });
    },

    logout() {
        this.clearIdToken();
        this.clearAccessToken();
        window.location.href = "/";
    },

    getIdToken() {
        return localStorage.getItem(ID_TOKEN_KEY);
    },

    getAccessToken() {
        return localStorage.getItem(ACCESS_TOKEN_KEY);
    },

    clearIdToken() {
        localStorage.removeItem(ID_TOKEN_KEY);
    },

    clearAccessToken() {
        localStorage.removeItem(ACCESS_TOKEN_KEY);
    },

    // Helper function that will allow us to extract the access_token and id_token
    getParameterByName(name) {
        let match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash);
        return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
    },

    // Get and store access_token in local storage
    setAccessToken() {
        let accessToken = this.getParameterByName('access_token');
        localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
    },

    // Get and store id_token in local storage
    setIdToken() {
        let idToken = this.getParameterByName('id_token');
        localStorage.setItem(ID_TOKEN_KEY, idToken);
    },

    isLoggedIn() {
        const idToken = this.getIdToken();
        return !!idToken && !this.isTokenExpired(idToken);
    },

    getTokenExpirationDate(encodedToken) {
        const token = decode(encodedToken);
        if (!token.exp) { return null; }

        const date = new Date(0);
        date.setUTCSeconds(token.exp);

        return date;
    },

    isTokenExpired(token) {
        const expirationDate = this.getTokenExpirationDate(token);
        return expirationDate < new Date();
    }
});

Go ahead and install the auth0-js and jwt-decode packages from the terminal:

npm install auth0-js jwt-decode --save

Our auth service contains different functions for authenticating using Auth0 hosted lock, saving/extracting tokens, checking expiry date, and checking if a user is logged in or not.

Note: You can fetch a property in a service using the this.get('<name-of-property>') syntax.

Now, you might have noticed that we are importing them, using this syntax import module from npm:package. It turns out that CommonJS(Node) module doesn't play nice with ES6 import statements in Ember. It throws an error indicating that the module can't be found. As usual, we got a work around. To get our npm CommonJS version of our node modules to work with our ES6 import, all we have to do is:

ember install ember-browserify

And append npm to the module name like we did in the code snippet for the auth service above.

Open the whistleblower API service and add this to it:

app/services/whistleblowerapi.js

import Ember from 'ember';
import axios from 'npm:axios';

const ACCESS_TOKEN_KEY = 'access_token';
const BASE_URL = 'http://localhost:3333';

export default Ember.Service.extend({

    getMeetups() {
        const url = `${BASE_URL}/api/meetups`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },

    getWhistleBlowers() {
        const url = `${BASE_URL}/api/whistleblowers`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },

    getActivities() {
        const url = `${BASE_URL}/api/activities`;
        return axios.get(url).then(response => response.data);
    },

    getAccessToken() {
        return localStorage.getItem(ACCESS_TOKEN_KEY);
    }
});

Install the axios module via your terminal:

npm install axios --save

Here, we fetched the meetups, whistleblowers, and activities from the API. Ember already provides jQuery by default. So, an alternative is to use Ember.$.get(url) instead of axios. I personally love using axios, hence the reason I chose to use it here.

Ember Services are injectable. You can inject them into different parts of your application as the need arises.

Build the Routes

We created our routes earlier. Now, we need to pass data to the templates of these routes. Once a user hits a URL, they should be able to get data presented to them.

Ember provides a model method in routes that allows us to fetch data and pass it down to the route template. So, we'll add the model method into our routes, inject the API service and call the service methods to provide data to the model hook so that it can be passed down to the templates.

Open app/routes/meetups.js and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getMeetups();
    }
});

app/routes/meetups.js

Open app/routes/whistle-blowers.js and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getMeetups();
    }
});

app/routes/whistle-blowers.js

Now, we need to create a route for our index page, which is the landing page.

ember generate route index

Open app/routes/index and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getActivities();
    }
});

app/routes/index.js

Next, we need to display data in their respective templates.

Bring Templates to Life

Open app/templates/index.hbs and add this:

{{app-nav}}

<div class="container">
    <h3 class="text-center">Whistle Blowing Updates From Across The World</h3>
</div>

<br/>

{{#each model as |update|}}
    <div class="col-sm-6">
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title"> {{ update.title }} </h3>
        </div>
        <div class="panel-body">
          <p><span class="badge alert-danger"> Location: </span><strong> {{ update.location }} </strong></p>
        </div>
      </div>
    </div>
{{/each}}

We imported the app-nav component into this template to provide navigation menu for our app.

We also looped through the model data coming from the index route using the #each helper method.

Open app/templates/meetups.hbs and add this:

{{app-nav}}

<div class="container">
    <h3 class="text-center">Whistle Blower Meetups Across The World </h3>
</div>

<br/>

{{#each model as |meetup|}}
    <div class="col-sm-6">
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title"> {{ meetup.name }} </h3>
        </div>
        <div class="panel-body">
          <p><span class="badge alert-danger"> Location: </span><strong> {{ meetup.date }} </strong></p>
        </div>
      </div>
    </div>
{{/each}}

{{outlet}}

Open app/templates/whistle-blowers.hbs and add this:

{{app-nav}}

<div class="container">
    <h3 class="text-center">Whistle Blowers Across The World </h3>
</div>

<br/>

{{#each model as |whistleblower|}}
    <div class="col-lg-2 col-sm-4">
        <div class="card hovercard">
            <div class="cardheader">
            </div>
            <div class="avatar">
                <img alt="" src="{{whistleblower.avatar}}">
            </div>
            <div class="info">
                <div class="title">
                    <a target="_blank" href="http://scripteden.com/">{{ whistleblower.name }}</a>
                </div>
                <div class="desc">
                    <p><strong>{{whistleblower.level}}</strong></p></div>
                <div class="desc">
                    <p><span class="badge alert-danger"> Uncovered Spoils: </span> {{whistleblower.uncoveredSpoils}} </p></div>
            </div>
        </div>
    </div>
{{/each}}

{{outlet}}

Go to your browser and check out all the routes. They should be displaying the right data:

Index pageLanding page

Meetups routeMeetups Route

Whistleblowers routeWhistleblowers route

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:
web dev ,web application development ,emberjs

Published at DZone with permission of Prosper Otemuyiwa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}