Go Top
advertise here!!!

Library || READ MORE || DOWNLOAD PDF || QUESTION ANSWERS || HOME

  • EMBERJS INTRODUCTION ↓

    Fastread Emberjs Tutorial

    What is Ember JS

    Ember is a front-end Javascript framework. You can use it to write complex, front-end heavy web apps. It gives you an organized structure, conventions, and built-in ways to do some of the hard things.

    Like Angular, Backbone and Knockout, Ember arrived recently to help developers build great front-end applications while maintaining a clean code base.

    Why Choose Ember

    I can’t speak for every framework out there, but I can tell you what’s great about Ember.

    Logical Code Organization. If you come from a Rails background you will appreciate Ember’s naming conventions. For example a Users Controller looks for a Users View and a Users Template.

    Easy Persistence. Once you have your back-end communicating with Ember, saving a record as easy as calling user.save(). Want to delete this user? Call user.destroyRecord().

    Auto-Updating Templates. Create a property and display it with {{ myProperty }} in your Handlebars template. Any updates will appear instantly. Handlebars might not be beautiful, but your templates can look pretty darn good if you use Emblem, a templating language for Handlebars that’s like Slim.

    Helpful Object APIs. Ember implements its own set of objects, each of which comes with a really friendly API. For example Ember has an Array object with methods like contains, filterBy, and sortBy. These come in handy all the time.

    I think Ember is absolutely worth learning if you find yourself needing to build complex front-end applications. Ember may seem big, but it’s really not that complicated. Once you learn how the various Ember objects interact and get a handle on the basics of the API you’ll be coding glorious front-end apps in no time.

    Ember is Fun

    My takeaway from working in Ember is that it’s really fun. It opens up new potential for developers to write the crazy front-end apps they’ve always dreamed of while still maintaining a clean, readable codebase. Also, the Ember core team does a great job of consistently releasing bug fixes and improvements, while keeping a stable codebase.

    At the end of the day, I reckon that if you’re going to choose a front-end framework to learn, you can’t go wrong if you pick Ember.

    Tutorial Requirements

    This tutorial is designed for developers with basic knowledge of Javascript and Ruby on Rails. You should have a Rails development environment setup on your computer if you want to follow along.

    There are both Javascript and CoffeeScript Versions

    I originally wrote this tutorial in CoffeeScript because I use CoffeeScript when I write Ember. I think it results in cleaner code. If you’re going to do full-time development with Ember then I feel like you are better off with CoffeeScript.

    However, I know that there are many people who don’t know or like CoffeeScript, so I went ahead and made a Javascript version too. Just use the toggle in the upper right of the page to switch languages.

    As a heads up for CoffeeScript learners, JS2Coffee.org can convert any CoffeeScript code to Javascript and vice versa.

  • EMBERJS INSTALLATION ↓

    It's easy to configure Ember.js, by including the JavaScript library files into the <script> tag in the HTML file; this can be done in two ways as follows:

    • You can download the latest version of Ember.js JavaScript library files from its official website.

    • You can include the latest version of CDN's from the official website.

    Downloading JavaScript libraries

    When you click on this official website link http://aitechtonic.com/builds/#/release, you will get to see a screen as below with download button. Clicking on this button downloads ember.debug.js library file.

    Ember.js Setup

    As can be seen from the image, there are some more options as discussed below:

    • production: When you click on this link, you would get the ember.prod.js library file.

    • min: After clicking on this, you would get the ember.min.js library file.

    • debug: When you click on this link, you would get the ember.prod.js library file.

    • changelog: When you click on this link, you will get the changelog of the Ember.js.

    • live tests: When you click on this link, you will be redirected to the ember-tests-index.html page for live test of the library files.

    CDN from an official website

    A CDN or Content Delivery Network is a network of servers designed to serve files to users. If you use a CDN link in your web page, it moves the responsibility of hosting files from your own servers to a series of external ones. This also offers an advantage that if the visitor to your webpage has already downloaded a copy of Ember.js from the same CDN's, it won't have to be re-downloaded.

    You need put these js files to make use of the Ember.js using CDN's:

    • jQuery
    • Handlebars
    • Ember.js
    <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.10.0/ember.min.js"></script>
    <script src="http://builds.aitechtonic.com/tags/v1.10.0-beta.3/ember-template-compiler.js"></script>
    <script src="http://builds.aitechtonic.com/release/ember.debug.js"></script>
    <script src="http://builds.aitechtonic.com/beta/ember-data.js"></script>
    

    The following are the different packages for successful build:

    • ember.debug.js: This is the full development build. It includes nearly all of the packages.

    • jquery-2.1.3.min.js: It removes unnecessary characters to make file size smaller.

    • ember.prod.js: The production files remove any debug statements and/or assertions. It includes all of the packages except:

      • handlebars.min.js

      • ember.debug.js

    • handlebars.min.js: It is used for to build the semantic templates effectively.

    • ember-template-compiler.js: The template compiler can be used server-side for precompilation.

    • ember-data.js: It is used for to deal with the Ember Data.

    In all chapters of this tutorial programs, we have referred latest CDN's of Ember.js JavaScript libraries.

    Example

    Let's create one simple example using Ember.js:

    <!DOCTYPE html>
    <html>
       <head>
          <title>Ember.js Application example</title>
          <!-- CDN's -->
          <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
          <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.10.0/ember.min.js"></script>
          <script src="http://builds.aitechtonic.com/tags/v1.10.0-beta.3/ember-template-compiler.js"></script>
          <script src="http://builds.aitechtonic.com/release/ember.debug.js"></script>
          <script src="http://builds.aitechtonic.com/beta/ember-data.js"></script>
       </head>
       <body>
          <!-- Your JavaScript -->
          <script type="text/x-handlebars">
             <!-- this is default application template -->
              <h1>{{App.name}}</h1>
              {{outlet}}
          </script>
    
          <script type="text/javascript">
             //App is the object of the Ember.Application
             //create() is the constructor of the Ember.Application
             App = Ember.Application.create();
    
             //App.name is the variable that holds the string values
             App.name= "Hello... Welcome to TutorialsPoint";
          </script>
       </body>
    </html>
    

    The comments in the code are self explanatory. Some more details of the code are as follows:

    The App.name is a variable which holds the String value that to be displayed on the web browser by using the Handlebars.

    Output

    Let's carry out the following steps to see how above code works:

    • Save above code in hello_aitechtonic.htm file

    • Open this HTML file in a browser.


    Dependencies

    Node.js and npm

    Ember CLI is built with JavaScript, and expects the Node.js runtime. It also requires dependencies fetched via npm. npm is packaged with Node.js, so if your computer has Node.js installed you are ready to go.

    Ember requires Node.js 0.12 or higher and npm 2.7 or higher. If you're not sure whether you have Node.js, run this on your command line:

    node --version

    The output should give a version number. If it gives a "command not found" error instead:

    Once you've got Node.js installed, re-run node --version to verify your computer's setup.

    Git

    Ember requires Git to manage many of its dependencies. Git comes with Mac OS X and most Linux distributions. Windows users can download and run this Git installer.

    Watchman (optional)

    On Mac and Linux, you can improve file watching performance by installing Watchman.

    Installation

    Install Ember using npm:

    npm install -g ember-cli

    While you're at it we recommend you also install PhantomJS to run tests from the command line (without the need for a browser to be open):

    npm install -g phantomjs2

    If you run into any problems with this step, consult the PhantomJS download instructions.

    To verify that your installation was successful, run:

    ember -v

    If the result is something like version: 1.13.0, you're ready to go.



    Your First Ember App


    Now that you have Ember CLI installed, you can create your first Ember app by running:

    1
    
    ember new my-new-app
    

    This will create a new my-new-app directory with your new Ember app inside.

    Once the process finishes, launch the Ember development server:

    cd my-new-app
    ember server
    

    Navigate to http://localhost:4200 to see your app in action.

    The Ember CLI development server provides features like rebuilding your app every time a file changes, and a mock server for setting up fake data when testing your app.

    In a production environment, you'll want to build an optimized version of your website assets that can be copied to a server. To build your app for production, run:

    ember build --environment=production

    When this finishes the dist/ directory will contain a version of your application suitable for production use. Copy the contents of this folder to your production server. Alternatively you can, explore the ember-cli-deploy addon for more advanced functionality.



    Core Concepts


    To get started with Ember.js, there are a few core concepts you should understand.

    Templates

    Templates, written in the Handlebars language, describe the user interface of your application. In addition to plain HTML, templates can contain expressions, like {{title}} or {{author}}, which take information from a component or controller and put it into HTML. They can also contain helpers, such as {{#if isAdmin}}30 people have viewed your blog today.{{/if}}. Finally, they can contain components such as a template listing blog posts rendering a component for each post.

    Components

    Components are the primary way user interfaces are organized in Ember. They consist of two parts: a template, and a source file written in JavaScript that defines the component's behavior. For example, a blog application might have a component for displaying a list of blog posts called all-posts, and another component for displaying an individual post called view-post. If users can upvote a post, the view-post component might define a behavior like when the user clicks the upvote button, increase the vote property's value by 1.

    Controllers

    Controllers are very much like components, so much so that in future versions of Ember, controllers will be replaced entirely with components. At the moment, components cannot be routed to (see below), but when this changes, it will be recommended to replace all controllers with components.

    Models

    Models represent persistent state. For example, a blog application would want to save the content of a blog post when a user publishes it, and so the blog post would have a model defining it, perhaps called the Post model. A model typically persists information to a server, although models can be configured to save to anywhere else, such as the browser's Local Storage.

    Routes

    Routes load a controller and a template. They can also load one or more models to provide data to the controller that can then be displayed by the template. For example, an all-posts route might load all the blog posts from the Post model, load the all-posts controller, and render the all-posts template. Similarly, a view-post route might load the model for the blog post to be shown, load the view-post controller, and render the view-post template.

    The Router

    The router maps a URL to a route. For example, when a user visits the /posts URL, the router might load the all-posts route. The router can also load nested routes. For example, if our blogging app had a list of blog posts on the left of the screen and then showed the current blog post on the right, we'd say that the view-post route was nested inside the all-posts route.

    Perhaps the most important thing to remember about Ember is that the URL drives the state of the application. The URL determines what route to load, which in turn determines what model, controller, and template to load.

  • EMBERJS OBJECT MODEL ↓

    • Objects in Ember ↓
    • Classes and Instances ↓
    • Reopening Classes and Instances ↓
    • Computed Properties ↓
    • Computed Properties and Aggregate Data ↓
    • Observers ↓
    • Bindings ↓
    • Enumerables ↓

    Objects in Ember

    You'll notice standard JavaScript class patterns and the new ES2015 classes aren't widely used in Ember. Plain objects can still be found, and sometimes they're referred to as "hashes".

    JavaScript objects don't support the observation of property value changes. Consequently, if an object is going to participate in Ember's binding system you may see an Ember.Object instead of a plain object.

    Ember.Object also provides a class system, supporting features like mixins and constructor methods. Some features in Ember's object model are not present in JavaScript classes or common patterns, but all are aligned as much as possible with the language and proposed additions.

    Ember also extends the JavaScript Array prototype with its Ember.Enumerable interface to provide change observation for arrays.

    Finally, Ember extends the String prototype with a few formatting and localization methods.

    Classes and Instances

    Defining Classes

    To define a new Ember class, call the extend() method on Ember.Object:

    Person = Ember.Object.extend({
      say(thing) {
        alert(thing);
      }
    });
    

    This defines a new Person class with a say() method.

    You can also create a subclass from any existing class by calling its extend() method. For example, you might want to create a subclass of Ember's built-in Ember.Component class:

    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['isUrgent'],
      isUrgent: true
    });
    

    Overriding Parent Class Methods

    When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special _super() method:

    Person = Ember.Object.extend({
      say(thing) {
        var name = this.get('name');
        alert(`${name} says: ${thing}`);
      }
    });
    
    Soldier = Person.extend({
      say(thing) {
        // this will call the method in the parent class (Person#say), appending
        // the string ', sir!' to the variable `thing` passed in
        this._super(`${thing}, sir!`);
      }
    });
    
    var yehuda = Soldier.create({
      name: 'Yehuda Katz'
    });
    
    yehuda.say('Yes'); // alerts "Aitechtonic says: Yes, sir!"
    

    In certain cases, you will want to pass arguments to _super() before or after overriding.

    This allows the original method to continue operating as it normally would.

    One common example is when overriding the normalizeResponse() hook in one of Ember-Data's serializers.

    A handy shortcut for this is to use a "spread operator", like ...arguments:

    normalizeResponse(store, primaryModelClass, payload, id, requestType)  {
      // Customize my JSON payload for Ember-Data
      return this._super(...arguments);
    }
    

    The above example returns the original arguments (after your customizations) back to the parent class, so it can continue with its normal operations.

    Creating Instances

    Once you have defined a class, you can create new instances of that class by calling its create() method. Any methods, properties and computed properties you defined on the class will be available to instances:

    var person = Person.create();
    person.say('Hello'); // alerts " says: Hello"
    

    When creating an instance, you can initialize the values of its properties by passing an optional hash to the create() method:

    Person = Ember.Object.extend({
      helloWorld() {
        alert(`Hi, my name is ${this.get('name')}`);
      }
    });
    
    var tom = Person.create({
      name: 'Tom Dale'
    });
    
    tom.helloWorld(); // alerts "Hi, my name is Tom Dale"

    For performance reasons, note that you cannot redefine an instance's computed properties or methods when calling create(), nor can you define new ones. You should only set simple properties when calling create(). If you need to define or redefine methods or computed properties, create a new subclass and instantiate that.

    By convention, properties or variables that hold classes are PascalCased, while instances are not. So, for example, the variable Person would point to a class, while person would point to an instance (usually of the Person class). You should stick to these naming conventions in your Ember applications.

    Initializing Instances

    When a new instance is created, its init() method is invoked automatically. This is the ideal place to implement setup required on new instances:

    Person = Ember.Object.extend({
      init() {
        var name = this.get('name');
        alert(`${name}, reporting for duty!`);
      }
    });
    
    Person.create({
      name: 'Stefan Penner'
    });
    
    // alerts "Stefan Penner, reporting for duty!"
    

    If you are subclassing a framework class, like Ember.Component, and you override the init() method, make sure you call this._super(...arguments)! If you don't, a parent class may not have an opportunity to do important setup work, and you'll see strange behavior in your application.

    Arrays and objects defined directly on any Ember.Object are shared across all instances of that object.

    Person = Ember.Object.extend({
      shoppingList: ['eggs', 'cheese']
    });
    
    Person.create({
      name: 'Stefan Penner',
      addItem() {
        this.get('shoppingList').pushObject('bacon');
      }
    });
    
    Person.create({
      name: 'Robert Jackson',
      addItem() {
        this.get('shoppingList').pushObject('sausage');
      }
    });
    
    // Stefan and Robert both trigger their addItem.
    // They both end up with: ['eggs', 'cheese', 'bacon', 'sausage'
    

    To avoid this behavior, it is encouraged to initialize those arrays and object properties during init(). Doing so ensures each instance will be unique.

    Person = Ember.Object.extend({
      init() {
        this.set('shoppingList', ['eggs', 'cheese']);
      }
    });
    
    Person.create({
      name: 'Stefan Penner',
      addItem() {
        this.get('shoppingList').pushObject('bacon');
      }
    });
    
    Person.create({
      name: 'Robert Jackson',
      addItem() {
        this.get('shoppingList').pushObject('sausage');
      }
    });
    
    // Stefan ['eggs', 'cheese', 'bacon']
    // Robert ['eggs', 'cheese', 'sausage']
    

    Accessing Object Properties

    When accessing the properties of an object, use the get() and set() accessor methods:

    var person = Person.create();
    
    var name = person.get('name');
    person.set('name', 'Tobias Fünke');
    

    Make sure to use these accessor methods; otherwise, computed properties won't recalculate, observers won't fire, and templates won't update.

    Reopening Classes and Instances

    You don't need to define a class all at once. You can reopen a class and define new properties using the reopen() method.

    Person.reopen({
      isPerson: true
    });
    
    Person.create().get('isPerson') // true
    

    When using reopen(), you can also override existing methods and call this._super.

    Person.reopen({
      // override `say` to add an ! at the end
      say(thing) {
        this._super(thing + '!');
      }
    });
    

    reopen() is used to add instance methods and properties that are shared across all instances of a class. It does not add methods and properties to a particular instance of a class as in vanilla JavaScript (without using prototype).

    But when you need to add static methods or static properties to the class itself you can use reopenClass().

    // add static property to class
    Person.reopenClass({
      isPerson: false
    });
    // override property of Person instance
    Person.reopen({
      isPerson: true
    });
    
    Person.isPerson; // false - because it is static property created by `reopenClass`
    Person.create().get('isPerson'); // true
    

    Computed Properties

    What are Computed Properties?

    In a nutshell, computed properties let you declare functions as properties. You create one by defining a computed property as a function, which Ember will automatically call when you ask for the property. You can then use it the same way you would any normal, static property.

    It's super handy for taking one or more normal properties and transforming or manipulating their data to create a new value.

    Computed properties in action

    We'll start with a simple example:

    Person = Ember.Object.extend({
      // these will be supplied by `create`
      firstName: null,
      lastName: null,
    
      fullName: Ember.computed('firstName', 'lastName', function() {
        return `${this.get('firstName')} ${this.get('lastName')}`;
      })
    });
    
    var ironMan = Person.create({
      firstName: 'Tony',
      lastName:  'Stark'
    });
    
    ironMan.get('fullName'); // "Tony Stark"
    

    This declares the function to be a computed property, and the arguments tell Ember that it depends on the firstName and lastName attributes.

    Whenever you access the fullName property, this function gets called, and it returns the value of the function, which simply calls firstName + lastName.

    Chaining computed properties

    You can use computed properties as values to create new computed properties. Let's add a description computed property to the previous example, and use the existing fullName property and add in some other properties:

    Person = Ember.Object.extend({
      firstName: null,
      lastName: null,
      age: null,
      country: null,
    
      fullName: Ember.computed('firstName', 'lastName', function() {
        return `${this.get('firstName')} ${this.get('lastName')}`;
      }),
    
      description: Ember.computed('fullName', 'age', 'country', function() {
        return `${this.get('fullName')}; Age: ${this.get('age')}; Country: ${this.get('country')}`;
      })
    });
    
    var captainAmerica = Person.create({
      firstName: 'Steve',
      lastName: 'Rogers',
      age: 80,
      country: 'USA'
    });
    
    captainAmerica.get('description'); // "Steve Rogers; Age: 80; Country: USA"
    

    Dynamic updating

    Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called. Let's use computed properties to dynamically update.

    captainAmerica.set('firstName', 'William');
    
    captainAmerica.get('description'); // "William Rogers; Age: 80; Country: USA"
    

    So this change to firstName was observed by fullName computed property, which was itself observed by the description property.

    Setting any dependent property will propagate changes through any computed properties that depend on them, all the way down the chain of computed properties you've created.

    Setting Computed Properties

    You can also define what Ember should do when setting a computed property. If you try to set a computed property, it will be invoked with the key (property name), and the value you want to set it to.

    Person = Ember.Object.extend({
      firstName: null,
      lastName: null,
    
      fullName: Ember.computed('firstName', 'lastName', {
        get(key) {
          return `${this.get('firstName')} ${this.get('lastName')}`;
        },
        set(key, value) {
          var [firstName, lastName] = value.split(/\s+/);
          this.set('firstName', firstName);
          this.set('lastName',  lastName);
          return value;
        }
      })
    });
    
    
    var captainAmerica = Person.create();
    captainAmerica.set('fullName', 'William Burnside');
    captainAmerica.get('firstName'); // William
    captainAmerica.get('lastName'); // Burnside
    

    Computed Properties and Aggregate Data

    Sometimes you have a computed property whose value depends on the properties of items in an array. For example, you may have an array of todo items, and want to calculate how many remain incomplete based on their isDone property.

    To facilitate this, Ember provides the @each key illustrated below:

    app/components/todos.js
    export default Ember.Component.extend({
      todos: [
        Ember.Object.create({ isDone: true }),
        Ember.Object.create({ isDone: false }),
        Ember.Object.create({ isDone: true })
      ],
    
      remaining: Ember.computed('todos.@each.isDone', function() {
        var todos = this.get('todos');
        return todos.filterBy('isDone', false).get('length');
      })
    });
    

    Here, the dependent key todos.@each.isDone instructs Ember.js to update bindings and fire observers when any of the following events occurs:

    1. The isDone property of any of the objects in the todos array changes.
    2. An item is added to the todos array.
    3. An item is removed from the todos array.
    4. The todos property of the component is changed to a different array.

    In the example above, the remaining count is 1:

    import TodosComponent from 'app/components/todos';
    
    let todosComponent = TodosComponent.create();
    todosComponent.get('remaining');
    // 1
    

    If we change the todo's isDone property, the remaining property is updated automatically:

    let todos = todosComponent.get('todos');
    let todo = todos.objectAt(1);
    todo.set('isDone', true);
    
    todosComponent.get('remaining');
    // 0
    
    todo = Ember.Object.create({ isDone: false });
    todos.pushObject(todo);
    
    todosComponent.get('remaining');
    // 1
    

    Note that @each only works one level deep. You cannot use nested forms like todos.@each.owner.name or todos.@each.owner.@each.name.

    Sometimes you don't care if properties of individual array items change. In this case use the [] key instead of @each. Computed properties dependent on an array using the [] key will only update if items are added to or removed from the array, or if the array property is set to a different array. For example:

    app/components/todos.js
    export default Ember.Component.extend({
      todos: [
        Ember.Object.create({ isDone: true }),
        Ember.Object.create({ isDone: false }),
        Ember.Object.create({ isDone: true })
      ],
    
      selectedTodo: null,
      indexOfSelectedTodo: Ember.computed('selectedTodo', 'todos.[]', function() {
        return this.get('todos').indexOf(this.get('selectedTodo'));
      })
    });
    

    Here, indexOfSelectedTodo depends on todos.[], so it will update if we add an item to todos, but won't update if the value of isDone on a todo changes.

    Several of the Ember.computed macros utilize the [] key to implement common use-cases. For instance, to create a computed property that mapped properties from an array, you could use Ember.computed.map or build the computed property yourself:

    const Hamster = Ember.Object.extend({
      excitingChores: Ember.computed('chores.[]', function() {
        return this.get('chores').map(function(chore, index) {
          return `CHORE ${index}: ${chore.toUpperCase()}!`;
        });
      })
    });
    
    const hamster = Hamster.create({
      chores: ['clean', 'write more unit tests']
    });
    
    hamster.get('excitingChores'); // ['CHORE 1: CLEAN!', 'CHORE 2: WRITE MORE UNIT TESTS!']
    hamster.get('chores').pushObject('review code');
    hamster.get('excitingChores'); // ['CHORE 1: CLEAN!', 'CHORE 2: WRITE MORE UNIT TESTS!', '
    

    By comparison, using the computed macro abstracts some of this away:

    const Hamster = Ember.Object.extend({
      excitingChores: Ember.computed.map('chores', function(chore, index) {
        return `CHORE ${index}: ${chore.toUpperCase()}!`;
      })
    });
    

    The computed macros expect you to use an array, so there is no need to use the [] key in these cases. However, building your own custom computed property requires you to tell Ember.js that it is watching for array changes, which is where the [] key comes in handy.

    Observers

    Ember supports observing any property, including computed properties.

    Observers should contain behavior that reacts to changes in another property. Observers are especially useful when you need to perform some behavior after a binding has finished synchronizing.

    Observers are often over-used by new Ember developers. Observers are used heavily within the Ember framework itself, but for most problems Ember app developers face, computed properties are the appropriate solution.

    You can set up an observer on an object by using Ember.observer:

    Person = Ember.Object.extend({
      // these will be supplied by `create`
      firstName: null,
      lastName: null,
    
      fullName: Ember.computed('firstName', 'lastName', function() {
        return `${this.get('firstName')} ${this.get('lastName')}`;
      }),
    
      fullNameChanged: Ember.observer('fullName', function() {
        // deal with the change
        console.log(`fullName changed to: ${this.get('fullName')}`);
      })
    });
    
    var person = Person.create({
      firstName: 'Yehuda',
      lastName: 'Katz'
    });
    
    // observer won't fire until `fullName` is consumed first
    person.get('fullName'); // "Yehuda Katz"
    person.set('firstName', 'Brohuda'); // fullName changed to: Brohuda Katz
    

    Because the fullName computed property depends on firstName, updating firstName will fire observers on fullName as well.

    Observers and asynchrony

    Observers in Ember are currently synchronous. This means that they will fire as soon as one of the properties they observe changes. Because of this, it is easy to introduce bugs where properties are not yet synchronized:

    Person.reopen({
      lastNameChanged: Ember.observer('lastName', function() {
        // The observer depends on lastName and so does fullName. Because observers
        // are synchronous, when this function is called the value of fullName is
        // not updated yet so this will log the old value of fullName
        console.log(this.get('fullName'));
      })
    });
    

    This synchronous behavior can also lead to observers being fired multiple times when observing multiple properties:

    Person.reopen({
      partOfNameChanged: Ember.observer('firstName', 'lastName', function() {
        // Because both firstName and lastName were set, this observer will fire twice.
      })
    });
    
    person.set('firstName', 'John');
    person.set('lastName', 'Smith');
    

    To get around these problems, you should make use of Ember.run.once(). This will ensure that any processing you need to do only happens once, and happens in the next run loop once all bindings are synchronized:

    Person.reopen({
      partOfNameChanged: Ember.observer('firstName', 'lastName', function() {
        Ember.run.once(this, 'processFullName');
      }),
    
      processFullName: Ember.observer('fullName', function() {
        // This will only fire once if you set two properties at the same time, and
        // will also happen in the next run loop once all properties are synchronized
        console.log(this.get('fullName'));
      })
    });
    
    person.set('firstName', 'John');
    person.set('lastName', 'Smith');
    
    Observers and object initialization

    Observers never fire until after the initialization of an object is complete.

    If you need an observer to fire as part of the initialization process, you cannot rely on the side effect of set. Instead, specify that the observer should also run after init by using Ember.on():

    Person = Ember.Object.extend({
      init() {
        this.set('salutation', 'Mr/Ms');
      },
    
      salutationDidChange: Ember.on('init', Ember.observer('salutation', function() {
        // some side effect of salutation changing
      }))
    });
    
    Unconsumed Computed Properties Do Not Trigger Observers

    If you never get() a computed property, its observers will not fire even if its dependent keys change. You can think of the value changing from one unknown value to another.

    This doesn't usually affect application code because computed properties are almost always observed at the same time as they are fetched. For example, you get the value of a computed property, put it in DOM (or draw it with D3), and then observe it so you can update the DOM once the property changes.

    If you need to observe a computed property but aren't currently retrieving it, get it in your init() method.

    Outside of class definitions

    You can also add observers to an object outside of a class definition using addObserver():

    person.addObserver('fullName', function() {
      // deal with the change
    });
    

    Bindings

    Unlike most other frameworks that include some sort of binding implementation, bindings in Ember.js can be used with any object. That said, bindings are most often used within the Ember framework itself, and for most problems Ember app developers face, computed properties are the appropriate solution.

    The easiest way to create a two-way binding is to use a computed.alias(), that specifies the path to another object.

    wife = Ember.Object.create({
      householdIncome: 80000
    });
    
    Husband = Ember.Object.extend({
      householdIncome: Ember.computed.alias('wife.householdIncome')
    });
    
    husband = Husband.create({
      wife: wife
    });
    
    husband.get('householdIncome'); // 80000
    
    // Someone gets raise.
    wife.set('householdIncome', 90000);
    husband.get('householdIncome'); // 90000
    

    Note that bindings don't update immediately. Ember waits until all of your application code has finished running before synchronizing changes, so you can change a bound property as many times as you'd like without worrying about the overhead of syncing bindings when values are transient.

    One-Way Bindings

    A one-way binding only propagates changes in one direction, using computed.oneWay(). Often, one-way bindings are a performance optimization and you can safely use a two-way binding (which are de facto one-way bindings if you only ever change one side). Sometimes one-way bindings are useful to achieve specific behaviour such as a default that is the same as another property but can be overridden (e.g. a shipping address that starts the same as a billing address but can later be changed)

    user = Ember.Object.create({
      fullName: 'Kara Gates'
    });
    
    UserComponent = Ember.Component.extend({
      userName: Ember.computed.oneWay('user.fullName')
    });
    
    userComponent = UserComponent.create({
      user: user
    });
    
    // Changing the name of the user object changes
    // the value on the view.
    user.set('fullName', 'Krang Gates');
    // userComponent.userName will become "Krang Gates"
    
    // ...but changes to the view don't make it back to
    // the object.
    userComponent.set('userName', 'Truckasaurus Gates');
    user.get('fullName'); // "Krang Gates"
    

    Enumerables

    In Ember.js, an enumerable is any object that contains a number of child objects, and which allows you to work with those children using the Ember.Enumerable API. The most common enumerable in the majority of apps is the native JavaScript array, which Ember.js extends to conform to the enumerable interface.

    By providing a standardized interface for dealing with enumerables, Ember.js allows you to completely change the way your underlying data is stored without having to modify the other parts of your application that access it.

    The enumerable API follows ECMAScript specifications as much as possible. This minimizes incompatibility with other libraries, and allows Ember.js to use the native browser implementations in arrays where available.

    Use of Observable Methods and Properties

    In order for Ember to observe when you make a change to an enumerable, you need to use special methods that Ember.Enumerable provides. For example, if you add an element to an array using the standard JavaScript method push(), Ember will not be able to observe the change, but if you use the enumerable method pushObject(), the change will propagate throughout your application.

    Here is a list of standard JavaScript array methods and their observable enumerable equivalents:

    Standard MethodObservable Equivalent
    poppopObject
    pushpushObject
    reversereverseObjects
    shiftshiftObject
    unshiftunshiftObject

    Additionally, to retrieve the first and last objects in an array in an observable fashion, you should use myArray.get('firstObject') and myArray.get('lastObject'), respectively.

    API Overview

    In the rest of this guide, we'll explore some of the most common enumerable conveniences. For the full list, please see the Ember.Enumerable API reference documentation.

    Iterating Over an Enumerable

    To enumerate all the values of an enumerable object, use the forEach() method:

    var food = ['Poi', 'Ono', 'Adobo Chicken'];
    
    food.forEach(function(item, index) {
      console.log(`Menu Item ${index+1}: ${item}`);
    });
    
    // Menu Item 1: Poi
    // Menu Item 2: Ono
    // Menu Item 3: Adobo Chicken
    

    First and Last Objects

    All enumerables expose firstObject and lastObject properties that you can bind to.

    var animals = ['rooster', 'pig'];
    
    animals.get('lastObject');
    //=> "pig"
    
    animals.pushObject('peacock');
    
    animals.get('lastObject');
    //=> "peacock"
    

    Map

    You can easily transform each item in an enumerable using the map() method, which creates a new array with results of calling a function on each item in the enumerable.

    var words = ['goodbye', 'cruel', 'world'];
    
    var emphaticWords = words.map(function(item) {
      return item + '!';
    });
    // ["goodbye!", "cruel!", "world!"]
    

    If your enumerable is composed of objects, there is a mapBy() method that will extract the named property from each of those objects in turn and return a new array:

    var hawaii = Ember.Object.create({
      capital: 'Honolulu'
    });
    
    var california = Ember.Object.create({
      capital: 'Sacramento'
    });
    
    var states = [hawaii, california];
    
    states.mapBy('capital');
    //=> ["Honolulu", "Sacramento"]
    

    Filtering

    Another common task to perform on an enumerable is to take the enumerable as input, and return an Array after filtering it based on some criteria.

    For arbitrary filtering, use the filter() method. The filter method expects the callback to return true if Ember should include it in the final Array, and false or undefined if Ember should not.

    var arr = [1,2,3,4,5];
    
    arr.filter(function(item, index, self) {
      return item < 4;
    })
    
    // returns [1,2,3]
    

    When working with a collection of Ember objects, you will often want to filter a set of objects based upon the value of some property. The filterBy() method provides a shortcut.

    Todo = Ember.Object.extend({
      title: null,
      isDone: false
    });
    
    todos = [
      Todo.create({ title: 'Write code', isDone: true }),
      Todo.create({ title: 'Go to sleep' })
    ];
    
    todos.filterBy('isDone', true);
    
    // returns an Array containing only items with `isDone == true`
    

    If you only want to return the first matched value, rather than an Array containing all of the matched values, you can use find() and findBy(), which work like filter() and filterBy(), but return only one item.

    Aggregate Information (Every or Any)

    To find out whether every item in an enumerable matches some condition, you can use the every() method:

    Person = Ember.Object.extend({
      name: null,
      isHappy: false
    });
    
    var people = [
      Person.create({ name: 'Yehuda', isHappy: true }),
      Person.create({ name: 'Majd', isHappy: false })
    ];
    
    people.every(function(person, index, self) {
      return person.get('isHappy');
    });
    
    // returns false
    

    To find out whether at least one item in an enumerable matches some condition, you can use the any() method:

    people.any(function(person, index, self) {
      return person.get('isHappy');
    });
    
    // returns true
    

    Like the filtering methods, the every() and any() methods have analogous isEvery() and isAny() methods.

    people.isEvery('isHappy', true) // false
    people.isAny('isHappy', true)  // true
    
  • EMBERJS ROUITING ↓

    • Route Introduction ↓
    • Defining Your Routes ↓
    • Specifying a Route's Model ↓
    • Rendering a Template ↓
    • Redirecting ↓
    • Preventing and Retrying Transitions ↓
    • Loading / Error Substates ↓
    • Query Parameters ↓
    • Asynchronous Routing ↓

    Route Introduction

    Imagine we are writing a web app for managing a blog. At any given time, we should be able to answer questions like What post are they looking at? and Are they editing it? In Ember.js, the answer to these questions is determined by the URL.

    The URL can be set in a few ways:

    • The user loads the app for the first time.
    • The user changes the URL manually, such as by clicking the back button or by editing the address bar.
    • The user clicks a link within the app.
    • Some other event in the app causes the URL to change.

    Regardless of how the URL becomes set, the Ember router then maps the current URL to one or more route handlers. A route handler can do several things:

    • It can render a template.
    • It can load a model that is then available to the template.
    • It can redirect to a new route, such as if the user isn't allowed to visit that part of the app.
    • It can handle actions that involve changing a model or transitioning to a new route.

    Defining Your Routes

    When your application starts, the router matches the current URL to the routes that you've defined. The routes, in turn, are responsible for displaying templates, loading data, and otherwise setting up application state.

    Basic Routes

    The map() method of your Ember application's router can be invoked to define URL mappings. When calling map(), you should pass a function that will be invoked with the value this set to an object which you can use to create routes.

    app/router.js
    Router.map(function() {
      this.route('about', { path: '/about' });
      this.route('favorites', { path: '/favs' });
    });
    

    Now, when the user visits /about, Ember.js will render the about template. Visiting /favs will render the favorites template.

    You can leave off the path if it is the same as the route name. In this case, the following is equivalent to the above example:

    app/router.js
    Router.map(function() {
      this.route('about');
      this.route('favorites', { path: '/favs' });
    });
    

    Inside your templates, you can use {{link-to}} to navigate between routes, using the name that you provided to the route method.

    {{#link-to "index"}}{{/link-to}}
    
    
    

    The {{link-to}} helper will also add an active class to the link that points to the currently active route.

    Nested Routes

    Often you'll want to have a template that displays inside another template. For example, in a blogging application, instead of going from a list of blog posts to creating a new post, you might want to have the post creation page display next to the list.

    In these cases, you can use nested routes to display one template inside of another.

    You can define nested routes by passing a callback to this.route:

    app/router.js
    Router.map(function() {
      this.route('posts', function() {
        this.route('new');
      });
    });
    

    And then add the {{outlet}} helper to your template where you want the nested template to display:

    templates/posts.hbs
    <h1>Posts</h1>
    <!-- Display posts and other content -->
    {{outlet}}
    

    This router creates a route for /posts and for /posts/new. When a user visits /posts, they'll simply see the posts.hbs template. (Below, index routes explains an important addition to this.) When the user visits posts/new, they'll see the posts/new.hbs template rendered into the {{outlet}} of the posts template.

    A nested route's names includes the names of its ancestors. If you want to transition to a route (either via transitionTo or {{#link-to}}), make sure to use the full route name (posts.new, not new).

    The application route

    The application is entered when your app first boots up. Like other routes, it will load a template with the same name (application in this case) by default. You should put your header, footer, and any other decorative content here. All other routes will render their templates into the application.hbs templates's {{outlet}}.

    This route is part of every application, so you don't need to specify it in your app/router.js.

    Index Routes

    At every level of nesting (including the top level), Ember.js automatically provides a route for the / path named index.

    For example, if you write a simple router like this:

    app/router.js
    Router.map(function(){
      this.route('favorites');
    });
    

    It is the equivalent of:

    app/router.js
    Router.map(function(){
      this.route('index', { path: '/' });
      this.route('favorites');
    });
    

    The index template will be rendered into the {{outlet}} in the application template. If the user navigates to /favorites, Ember.js will replace the index template with the favorites template.

    A nested router like this:

    app/router.js
    Router.map(function() {
      this.route('posts', function() {
        this.route('favorites');
      });
    });
    

    Is the equivalent of:

    app/router.js
    Router.map(function(){
      this.route('index', { path: '/' });
      this.route('posts', function() {
        this.route('index', { path: '/' });
        this.route('favorites');
      });
    });
    

    If the user navigates to /posts, the current route will be posts.index, and the posts/index template will be rendered into the {{outlet}} in the posts template.

    If the user then navigates to /posts/favorites, Ember.js will replace the {{outlet}} in the posts template with the posts/favorites template.

    Dynamic Segments

    One of the responsibilities of a route is to load a model.

    For example, if we have the route this.route('posts');, our route might load all of the blog posts for the app.

    Because /posts represents a fixed model, we don't need any additional information to know what to retrieve. However, if we want a route to represent a single post, we would not want to have to hardcode every possible post into the router.

    Enter dynamic segments.

    A dynamic segment is a portion of a URL that starts with a : and is followed by an identifier.

    app/router.js
    Router.map(function() {
      this.route('posts');
      this.route('post', { path: '/post/:post_id' });
    });
    

    If the user navigates to /post/5, the route will then have the post_id of 5 to use to load the correct post. See Specifying a Route's Model for more about how to load a model.

    Wildcard / globbing routes

    You can define wildcard routes that will match multiple URL segments. This could be used, for example, if you'd like a catch-all route which is useful when the user enters an incorrect URL not managed by your app.

    app/router.js
    Router.map(function() {
      this.route('page-not-found', { path: '/*wildcard' });
    });
    

    Resetting Nested Route Namespace

    When nesting routes, it may be beneficial for a child route to not inherit its ancestors name. This allows you to reference and reuse a given route in multiple route trees as well as keep the class name short.

    You can reset the current "namespace" with the aptly named resetNamespace: true option.

    app/router.js
    Router.map(function() {
      this.route('post', { path: '/post/:post_id' }, function() {
        this.route('edit');
        this.route('comments', { resetNamespace: true }, function() {
          this.route('new');
        });
      });
    });
    

    Just like before, the comments template will be rendered in the post template's {{outlet}}, and all templates under comments (comments/index and comments/new) will be rendered in the comments outlet.

    However, the /post/:id/comments path will load the comments.hbs template, rather than the post/comments.hbs template.

    Specifying a Route's Model

    Often, you'll want a template to display data from a model. Loading the appropriate model is one job of a route.

    For example, take this router:

    app/router.js
    Router.map(function() {
      this.route('favorite-posts');
    });
    

    To load a model for the favoritePosts route, you would use the model() hook in the posts route handler:

    app/routes/favorite-posts.js
    export default Ember.Route.extend({
      model() {
        return this.store.query('post', { favorite: true });
      }
    });
    

    Typically, the model hook should return an Ember Data record, but it can also return any promise object (Ember Data records are promises), or a plain JavaScript object or array. Ember will wait until the data finishes loading (until the promise is resolved) before rendering the template.

    The return value from the model hook is then available in your template and controller with the model property:

    app/templates/favorite-posts.hbs
    <h1>Favorite Posts</h1>
    {{#each model as |post|}}
      <p>{{post.body}}</p>
    {{/each}}
    

    Dynamic Models

    Some routes always display the same model. For example, the /photos route will always display the same list of photos available in the application. If your user leaves this route and comes back later, the model does not change.

    However, you will often have a route whose model will change depending on user interaction. For example, imagine a photo viewer app. The /photos route will render the photos template with the list of photos as the model, which never changes. But when the user clicks on a particular photo, we want to display that model with the photo template. If the user goes back and clicks on a different photo, we want to display the photo template again, this time with a different model.

    In cases like this, it's important that we include some information in the URL about not only which template to display, but also which model.

    In Ember, this is accomplished by defining routes with dynamic segments.

    Once you have defined a route with a dynamic segment, Ember will extract the value of the dynamic segment from the URL for you and pass them as a hash to the model hook as the first argument:

    app/router.js
    Router.map(function() {
      this.route('photo', { path: '/photos/:photo_id' });
    });
    
    app/routes/photo.js
    export default Ember.Route.extend({
      model(params) {
        return this.store.findRecord('photo', params.photo_id);
      }
    });
    

    In the model hook for routes with dynamic segments, it's your job to turn the ID (something like 47 or post-slug) into a model that can be rendered by the route's template. In the above example, we use the photo's ID (params.photo_id) as an argument to Ember Data's findRecord method.

    Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.

    Multiple Models

    Multiple models can be returned through an Ember.RSVP.hash. The Ember.RSVP.hash takes parameters that return promises, and when all parameter promises resolve, then the Ember.RSVP.hash promise resolves. For example:

    app/routes/songs.js
    export default Ember.Route.extend({
      model() {
        return Ember.RSVP.hash({
          songs: this.store.findAll('song'),
          albums: this.store.findAll('album')
        });
      }
    });
    

    In the songs template, we can specify both models and use the {{#each}} helper to display each record in the song model and album model:

    app/templates/songs.hbs
    <h1>Playlist</h1>
    
    <ul>
      {{#each model.songs as |song|}}
        <li>{{song.name}} by {{song.artist}}</li>
      {{/each}}
    </ul>
    
    <h1>Albums</h1>
    
    <ul>
      {{#each model.albums as |album|}}
        <li>{{album.title}} by {{album.artist}}</li>
      {{/each}}
    </ul>
    

    Rendering a Template

    One job of a route handler is rendering the appropriate template to the screen.

    By default, a route handler will render the template with the same name as the route. Take this router:

    app/router.js
    Router.map(function() {
      this.route('posts', function() {
        this.route('new');
      });
    });
    

    Here, the posts route will render the posts.hbs template, and the posts.new route will render posts/new.hbs.

    Each template will be rendered into the {{outlet}} of its parent route's template. For example, the posts.new route will render its template into the posts.hbs's {{outlet}}, and the posts route will render its template into the application.hbs's {{outlet}}.

    If you want to render a template other than the default one, implement the renderTemplate() hook:

    app/routes/posts.js
    export default Ember.Route.extend({
      renderTemplate() {
        this.render('favoritePosts');
      }
    });
    

    Redirecting

    Calling transitionTo() from a route or transitionToRoute() from a controller will stop any transition currently in progress and start a new one, functioning as a redirect. transitionTo() behaves exactly like the link-to helper.

    If the new route has dynamic segments, you need to pass either a model or an identifier for each segment. Passing a model will skip that segment's model() hook (since the model is already loaded).

    Transitioning Before the Model is Known

    If you want to redirect from one route to another, you can do the transition in the beforeModel() hook of your route handler.

    app/router.js
    Router.map(function() {
      this.route('posts');
    });
    
    app/routes/index.js
    export default Ember.Route.extend({
      beforeModel() {
        this.transitionTo('posts');
      }
    });
    

    If you need to examine some application state to figure out where to redirect, you might use a service.

    Transitioning After the Model is Known

    If you need information about the current model in order to decide about redirection, you can use the afterModel() hook. It receives the resolved model as the first parameter and the transition as the second one. For example:

    app/router.js
    Router.map(function() {
      this.route('posts');
      this.route('post', { path: '/post/:post_id' });
    });
    
    app/routes/posts.js
    export default Ember.Route.extend({
      afterModel(model, transition) {
        if (model.get('length') === 1) {
          this.transitionTo('post', model.get('firstObject'));
        }
      }
    });
    

    When transitioning to the posts route if it turns out that there is only one post, the current transition will be aborted in favor of redirecting to the PostRoute with the single post object being its model.

    Child Routes

    Let's change the router above to use a nested route, like this:

    app/router.js
    Router.map(function() {
      this.route('posts', function() {
        this.route('post', { path: ':post_id' });
      });
    });
    

    If we redirect to posts.post in the afterModel hook, afterModel essentially invalidates the current attempt to enter this route. So the posts route's beforeModel, model, and afterModel hooks will fire again within the new, redirected transition. This is inefficient, since they just fired before the redirect.

    Instead, we can use the redirect() method, which will leave the original transition validated, and not cause the parent route's hooks to fire again:

    app/routes/posts.js
    export default Ember.Route.extend({
      redirect(model, transition) {
        if (model.get('length') === 1) {
          this.transitionTo('posts.post', model.get('firstObject'));
        }
      }
    });
    

    Preventing and Retrying Transitions

    During a route transition, the Ember Router passes a transition object to the various hooks on the routes involved in the transition. Any hook that has access to this transition object has the ability to immediately abort the transition by calling transition.abort(), and if the transition object is stored, it can be re-attempted at a later time by calling transition.retry().

    Preventing Transitions via willTransition

    When a transition is attempted, whether via {{link-to}}, transitionTo, or a URL change, a willTransition action is fired on the currently active routes. This gives each active route, starting with the leaf-most route, the opportunity to decide whether or not the transition should occur.

    Imagine your app is in a route that's displaying a complex form for the user to fill out and the user accidentally navigates backwards. Unless the transition is prevented, the user might lose all of the progress they made on the form, which can make for a pretty frustrating user experience.

    Here's one way this situation could be handled:

    app/routes/form.js
    export default Ember.Route.extend({
      actions: {
        willTransition(transition) {
          if (this.controller.get('userHasEnteredData') &&
              !confirm('Are you sure you want to abandon progress?')) {
            transition.abort();
          } else {
            // Bubble the `willTransition` action so that
            // parent routes can decide whether or not to abort.
            return true;
          }
        }
      }
    });
    

    When the user clicks on a {{link-to}} helper, or when the app initiates a transition by using transitionTo, the transition will be aborted and the URL will remain unchanged. However, if the browser back button is used to navigate away from route:form, or if the user manually changes the URL, the new URL will be navigated to before the willTransition action is called. This will result in the browser displaying the new URL, even if willTransition calls transition.abort().

    Aborting Transitions Within model, beforeModel, afterModel

    The model, beforeModel, and afterModel hooks described in Asynchronous Routing each get called with a transition object. This makes it possible for destination routes to abort attempted transitions.

    app/routes/disco.js
    export default Ember.Route.extend({
      beforeModel(transition) {
        if (new Date() > new Date('January 1, 1980')) {
          alert('Sorry, you need a time machine to enter this route.');
          transition.abort();
        }
      }
    });
    

    Storing and Retrying a Transition

    Aborted transitions can be retried at a later time. A common use case for this is having an authenticated route redirect the user to a login page, and then redirecting them back to the authenticated route once they've logged in.


    app/routes/some-authenticated.js
    export default Ember.Route.extend({
      beforeModel(transition) {
        if (!this.controllerFor('auth').get('userIsLoggedIn')) {
          var loginController = this.controllerFor('login');
          loginController.set('previousTransition', transition);
          this.transitionTo('login');
        }
      }
    });
    

    app/controllers/login.js
    export default Ember.Controller.extend({
      actions: {
        login() {
          // Log the user in, then reattempt previous transition if it exists.
          var previousTransition = this.get('previousTransition');
          if (previousTransition) {
            this.set('previousTransition', null);
            previousTransition.retry();
          } else {
            // Default back to homepage
            this.transitionToRoute('index');
          }
        }
      }
    });
    

    Loading / Error Substates

    The Ember Router allows you to provide feedback that a route is loading, as well as when an error occurs in loading a route.

    loading substates

    During the beforeModel, model, and afterModel hooks, data may take some time to load. Technically, the router pauses the transition until the promises returned from each hook fulfill.

    Consider the following:


    app/router.js
    Router.map(function() {
      this.route('slow-model');
    });
    

    app/routes/slow-model.js
    export default Ember.Route.extend({
      model() {
        return this.store.findAll('slowModel');
      }
    });
    

    If you navigate to slow-model, in the model hook, the query may take a long time to complete. During this time, your UI isn't really giving you any feedback as to what's happening. If you're entering this route after a full page refresh, your UI will be entirely blank, as you have not actually finished fully entering any route and haven't yet displayed any templates. If you're navigating to slow-model from another route, you'll continue to see the templates from the previous route until the model finish loading, and then, boom, suddenly all the templates for slow-model load.

    So, how can we provide some visual feedback during the transition?

    Simply define a template called loading (and optionally a corresponding route) that Ember will transition to. The intermediate transition into the loading substate happens immediately (synchronously), the URL won't be updated, and, unlike other transitions, the currently active transition won't be aborted.

    Once the main transition into slow-model completes, the loading route will be exited and the transition to slow-model will continue.

    For nested routes, like:


    app/router.js
    Router.map(function() {
      this.route('foo', function() {
        this.route('bar', function() {
          this.route('slow-model');
        });
      });
    });
    

    Ember will alternate trying to find a routeName-loading or loading template in the hierarchy starting with foo.bar.slow-model-loading:

    1. slow-model-loading
    2. foo.bar.loading or foo.bar-loading
    3. foo.loading or foo-loading
    4. loading or application-loading

    It's important to note that for slow-model itself Ember will not try to find a slow-model.loading template but for the rest of the hierarchy either syntax is acceptable. This can be useful for creating a custom loading screen for a leaf route like slow-model.

    The loading event

    If the various beforeModel/model/afterModel hooks don't immediately resolve, a loading event will be fired on that route.


    app/routes/foo-slow-model.js
    export default Ember.Route.extend({
      model() {
        return this.store.findAll('slowModel');
      },
      actions: {
        loading(transition, originRoute) {
          let controller = this.controllerFor('foo');
          controller.set('currentlyLoading', true);
        }
      }
    });
    

    If the loading handler is not defined at the specific route, the event will continue to bubble above a transition's parent route, providing the application route the opportunity to manage it.

    When using the loading handler, we can make use of the transition promise to know when the loading event is over:


    app/routes/foo-slow-model.js
    export default Ember.Route.extend({
      ...
      actions: {
        loading(transition, originRoute) {
          let controller = this.controllerFor('foo');
          controller.set('currentlyLoading', true);
          transition.promise.finally(function() {
              controller.set('currentlyLoading', false);
          });
        }
      }
    });
    

    error substates

    Ember provides an analogous approach to loading substates in the case of errors encountered during a transition.

    Similar to how the default loading event handlers are implemented, the default error handlers will look for an appropriate error substate to enter, if one can be found.


    app/router.js
    Router.map(function() {
      this.route('articles', function() {
        this.route('overview');
      });
    });
    

    As with the loading substate, on a thrown error or rejected promise returned from the articles.overview route's model hook (or beforeModel or afterModel) Ember will look for an error template or route in the following order:

    1. articles.overview-error
    2. articles.error or articles-error
    3. error or application-error

    If one of the above is found, the router will immediately transition into that substate (without updating the URL). The "reason" for the error (i.e. the exception thrown or the promise reject value) will be passed to that error state as its model.

    If no viable error substates can be found, an error message will be logged.

    The error event

    If the articles.overview route's model hook returns a promise that rejects (for instance the server returned an error, the user isn't logged in, etc.), an error event will fire from that route and bubble upward. This error event can be handled and used to display an error message, redirect to a login page, etc.


    app/routes/articles-overview.js
    export default Ember.Route.extend({
      model(params) {
        return this.store.findAll('nonexistentModel');
      },
      actions: {
        error(error, transition) {
          if (error && error.status === 400) {
            return this.transitionTo('modelNotFound');
          }
        }
      }
    });
    

    Analogous to the loading event, you could manage the error event at the application level to avoid writing the same code for multiple routes.

    Query Parameters

    Query parameters are optional key-value pairs that appear to the right of the ? in a URL. For example, the following URL has two query params, sort and page, with respective values ASC and 2:


    http://example.com/articles?sort=ASC&page=2
    

    Query params allow for additional application state to be serialized into the URL that can't otherwise fit into the path of the URL (i.e. everything to the left of the ?). Common use cases for query params include representing the current page number in a paginated collection, filter criteria, or sorting criteria.

    Specifying Query Parameters

    Query params are declared on route-driven controllers. For example, to configure query params that are active within the articles route, they must be declared on controller:articles.

    To add a category query parameter that will filter out all the articles that haven't been categorized as popular we'd specify 'category' as one of controller:article's queryParams:


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: ['category'],
      category: null
    });
    

    This sets up a binding between the category query param in the URL, and the category property on controller:articles. In other words, once the articles route has been entered, any changes to the category query param in the URL will update the category property on controller:articles, and vice versa.

    Now we need to define a computed property of our category-filtered array that the articles template will render:


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: ['category'],
      category: null,
    
      filteredArticles: Ember.computed('category', 'model', function() {
        var category = this.get('category');
        var articles = this.get('model');
    
        if (category) {
          return articles.filterBy('category', category);
        } else {
          return articles;
        }
      })
    });
    

    With this code, we have established the following behaviors:

    1. If the user navigates to /articles, category will be null, so the articles won't be filtered.
    2. If the user navigates to /articles?category=recent, category will be set to "recent", so articles will be filtered.
    3. Once inside the articles route, any changes to the category property on controller:articles will cause the URL to update the query param. By default, a query param property change won't cause a full router transition (i.e. it won't call model hooks and setupController, etc.); it will only update the URL.

    The link-to helper supports specifying query params using the query-params subexpression helper.


    // Explicitly set target query params
    {{#link-to "posts" (query-params direction="asc")}}Sort{{/link-to}}
    
    // Binding is also supported
    {{#link-to "posts" (query-params direction=otherDirection)}}Sort{{/link-to}}
    

    In the above examples, direction is presumably a query param property on the controller:post, but it could also refer to a direction property on any of the controllers associated with the posts route hierarchy, matching the leaf-most controller with the supplied property name.

    The link-to helper takes into account query parameters when determining its "active" state, and will set the class appropriately. The active state is determined by calculating whether the query params end up the same after clicking a link. You don't have to supply all of the current, active query params for this to be true.

    transitionTo

    Route#transitionTo and Controller#transitionToRoute accept a final argument, which is an object with the key queryParams.


    app/routes/some-route.js
    this.transitionTo('post', object, { queryParams: { showDetails: true }});
    this.transitionTo('posts', { queryParams: { sort: 'title' }});
    
    // if you want to transition the query parameters without changing the route
    this.transitionTo({ queryParams: { direction: 'asc' }});
    

    You can also add query params to URL transitions:


    app/routes/some-route.js
    
    this.transitionTo('/posts/1?sort=date&showDetails=true');
    
    

    Opting into a full transition

    Arguments provided to transitionTo or link-to only correspond to a change in query param values, and not a change in the route hierarchy, it is not considered a full transition, which means that hooks like model and setupController won't fire by default, but rather only controller properties will be updated with new query param values, as will the URL.

    But some query param changes necessitate loading data from the server, in which case it is desirable to opt into a full-on transition. To opt into a full transition when a controller query param property changes, you can use the optional queryParams configuration hash on the Route associated with that controller, and set that query param's refreshModel config property to true:


    app/routes/articles.js
    export default Ember.Route.extend({
      queryParams: {
        category: {
          refreshModel: true
        }
      },
      model(params) {
        // This gets called upon entering 'articles' route
        // for the first time, and we opt into refiring it upon
        // query param changes by setting `refreshModel:true` above.
    
        // params has format of { category: "someValueOrJustNull" },
        // which we can forward to the server.
        return this.store.query('articles', params);
      }
    });
    

    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: ['category'],
      category: null
    });
    

    Update URL with replaceState instead

    By default, Ember will use pushState to update the URL in the address bar in response to a controller query param property change, but if you would like to use replaceState instead (which prevents an additional item from being added to your browser's history), you can specify this on the Route's queryParams config hash, e.g. (continued from the example above):


    app/routes/articles.js
    export default Ember.Route.extend({
      queryParams: {
        category: {
          replace: true
        }
      }
    });
    

    Note that the name of this config property and its default value of false is similar to the link-to helper's, which also lets you opt into a replaceState transition via replace=true.

    Map a controller's property to a different query param key

    By default, specifying foo as a controller query param property will bind to a query param whose key is foo, e.g. ?foo=123. You can also map a controller property to a different query param key using the following configuration syntax:


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: {
        category: 'articles_category'
      },
      category: null
    });
    

    This will cause changes to the controller:articles's category property to update the articles_category query param, and vice versa.

    Note that query params that require additional customization can be provided along with strings in the queryParams array.


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: ['page', 'filter', {
        category: 'articles_category'
      }],
      category: null,
      page: 1,
      filter: 'recent'
    });
    

    Default values and deserialization

    In the following example, the controller query param property page is considered to have a default value of 1.


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: 'page',
      page: 1
    });
    

    This affects query param behavior in two ways:

    1. Query param values are cast to the same datatype as the default value, e.g. a URL change from /?page=3 to /?page=2 will set controller:articles's page property to the number 2, rather than the string "2". The same also applies to boolean default values.
    2. When a controller's query param property is currently set to its default value, this value won't be serialized into the URL. So in the above example, if page is 1, the URL might look like /articles, but once someone sets the controller's page value to 2, the URL will become /articles?page=2.

    Sticky Query Param Values

    By default, query param values in Ember are "sticky", in that if you make changes to a query param and then leave and re-enter the route, the new value of that query param will be preserved (rather than reset to its default). This is a particularly handy default for preserving sort/filter parameters as you navigate back and forth between routes.

    Furthermore, these sticky query param values are remembered/restored according to the model loaded into the route. So, given a team route with dynamic segment /:team_name and controller query param "filter", if you navigate to /badgers and filter by "rookies", then navigate to /bears and filter by "best", and then navigate to /potatoes and filter by "lamest", then given the following nav bar links,


    {{#link-to "team" "badgers"}}Badgers{{/link-to}}
    {{#link-to "team" "bears"}}Bears{{/link-to}}
    {{#link-to "team" "potatoes"}}Potatoes{{/link-to}}
    

    the generated links would be


    <a href="/badgers?filter=rookies">Badgers</a>
    <a href="/bears?filter=best">Bears</a>
    <a href="/potatoes?filter=lamest">Potatoes</a>
    

    This illustrates that once you change a query param, it is stored and tied to the model loaded into the route.

    If you wish to reset a query param, you have two options:

    1. explicitly pass in the default value for that query param into link-to or transitionTo
    2. use the Route.resetController hook to set query param values back to their defaults before exiting the route or changing the route's model

    In the following example, the controller's page query param is reset to 1, while still scoped to the pre-transition ArticlesRoute model. The result of this is that all links pointing back into the exited route will use the newly reset value 1 as the value for the page query param.


    app/routes/articles.js
    export default Ember.Route.extend({
      resetController(controller, isExiting, transition) {
        if (isExiting) {
          // isExiting would be false if only the route's model was changing
          controller.set('page', 1);
        }
      }
    });
    

    In some cases, you might not want the sticky query param value to be scoped to the route's model but would rather reuse a query param's value even as a route's model changes. This can be accomplished by setting the scope option to "controller" within the controller's queryParams config hash:


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: [{
        showMagnifyingGlass: {
          scope: 'controller'
        }
      }]
    });
    

    The following demonstrates how you can override both the scope and the query param URL key of a single controller query param property:


    app/controllers/articles.js
    export default Ember.Controller.extend({
      queryParams: ['page', 'filter',
        {
          showMagnifyingGlass: {
            scope: 'controller',
            as: 'glass'
          }
        }
      ]
    });
    

    Asynchronous Routing

    This section covers some more advanced features of the router and its capability for handling complex async logic within your app.

    A Word on Promises...

    Ember's approach to handling asynchronous logic in the router makes heavy use of the concept of Promises. In short, promises are objects that represent an eventual value. A promise can either fulfill (successfully resolve the value) or reject (fail to resolve the value). The way to retrieve this eventual value, or handle the cases when the promise rejects, is via the promise's then() method, which accepts two optional callbacks, one for fulfillment and one for rejection. If the promise fulfills, the fulfillment handler gets called with the fulfilled value as its sole argument, and if the promise rejects, the rejection handler gets called with a reason for the rejection as its sole argument. For example:


    var promise = fetchTheAnswer();
    
    promise.then(fulfill, reject);
    
    function fulfill(answer) {
      console.log('The answer is ' + answer);
    }
    
    function reject(reason) {
      console.log(`Couldn't get the answer! Reason: ${reason}`);
    }
    

    Much of the power of promises comes from the fact that they can be chained together to perform sequential asynchronous operations:


    // Note: jQuery AJAX methods return promises
    var usernamesPromise = Ember.$.getJSON('/usernames.json');
    
    usernamesPromise.then(fetchPhotosOfUsers)
                    .then(applyInstagramFilters)
                    .then(uploadTrendyPhotoAlbum)
                    .then(displaySuccessMessage, handleErrors);
    

    In the above example, if any of the methods fetchPhotosOfUsers, applyInstagramFilters, or uploadTrendyPhotoAlbum returns a promise that rejects, handleErrors will be called with the reason for the failure. In this manner, promises approximate an asynchronous form of try-catch statements that prevent the rightward flow of nested callback after nested callback and facilitate a saner approach to managing complex asynchronous logic in your applications.

    This guide doesn't intend to fully delve into all the different ways promises can be used, but if you'd like a more thorough introduction, take a look at the readme for RSVP, the promise library that Ember uses.

    The Router Pauses for Promises

    When transitioning between routes, the Ember router collects all of the models (via the model hook) that will be passed to the route's controllers at the end of the transition. If the model hook (or the related beforeModel or afterModel hooks) return normal (non-promise) objects or arrays, the transition will complete immediately. But if the model hook (or the related beforeModel or afterModel hooks) returns a promise (or if a promise was provided as an argument to transitionTo), the transition will pause until that promise fulfills or rejects.

    The router considers any object with a then() method defined on it to be a promise.

    If the promise fulfills, the transition will pick up where it left off and begin resolving the next (child) route's model, pausing if it too is a promise, and so on, until all destination route models have been resolved. The values passed to the setupController() hook for each route will be the fulfilled values from the promises.

    A basic example:


    app/routes/tardy.js
    export default Ember.Route.extend({
      model() {
        return new Ember.RSVP.Promise(function(resolve) {
          Ember.run.later(function() {
            resolve({ msg: 'Hold Your Horses' });
          }, 3000);
        });
      },
    
      setupController(controller, model) {
        console.log(model.msg); // "Hold Your Horses"
      }
    });
    

    When transitioning into route:tardy, the model() hook will be called and return a promise that won't resolve until 3 seconds later, during which time the router will be paused in mid-transition. When the promise eventually fulfills, the router will continue transitioning and eventually call route:tardy's setupController() hook with the resolved object.

    This pause-on-promise behavior is extremely valuable for when you need to guarantee that a route's data has fully loaded before displaying a new template.

    When Promises Reject...

    We've covered the case when a model promise fulfills, but what if it rejects?

    By default, if a model promise rejects during a transition, the transition is aborted, no new destination route templates are rendered, and an error is logged to the console.

    You can configure this error-handling logic via the error handler on the route's actions hash. When a promise rejects, an error event will be fired on that route and bubble up to route:application's default error handler unless it is handled by a custom error handler along the way, e.g.:


    app/routes/good-for-nothing.js
    export default Ember.Route.extend({
      model() {
        return Ember.RSVP.reject("FAIL");
      },
    
      actions: {
        error(reason) {
          alert(reason); // "FAIL"
    
          // Can transition to another route here, e.g.
          // this.transitionTo('index');
    
          // Uncomment the line below to bubble this error event:
          // return true;
        }
      }
    });
    

    In the above example, the error event would stop right at route:good-for-nothing's error handler and not continue to bubble. To make the event continue bubbling up to route:application, you can return true from the error handler.

    Recovering from Rejection

    Rejected model promises halt transitions, but because promises are chainable, you can catch promise rejects within the model hook itself and convert them into fulfills that won't halt the transition.


    app/routes/funky.js
    export default Ember.Route.extend({
      model() {
        return iHopeThisWorks().then(null, function() {
          // Promise rejected, fulfill with some default value to
          // use as the route's model and continue on with the transition
          return { msg: 'Recovered from rejected promise' };
        });
      }
    });
    
  • EMBERJS TEMPLATES ↓

    • Handlebars Basics ↓
    • Conditionals ↓
    • Displaying a List of Items ↓
    • Displaying the Keys in an Object ↓
    • Binding Element Attributes ↓
    • Links ↓
    • Actions ↓
    • Input Helpers ↓
    • Development Helpers ↓
    • Writing Helpers ↓

    Handlebars Basics

    Ember.js uses the Handlebars templating library to power your app's user interface. Handlebars templates contain static HTML and dynamic content inside Handlebars expressions, which are invoked with double curly braces: {{}}.

    Dynamic content inside a Handlebars expression is rendered with data-binding. This means if you update a property, your usage of that property in a template will be automatically updated to the latest value.

    Displaying Properties

    Templates are backed with a context. A context is an object from which Handlebars expressions read their properties. In Ember this is often a component. For templates rendered by a route (like application.hbs), the context is a controller.

    For example, this application.hbs template will render a first and last name:


    app/templates/application.hbs
    Hello, <strong>{{firstName}} {{lastName}}</strong>!
    

    The firstName and lastName properties are read from the context (the application controller in this case), and rendered inside the <strong> HTML tag.

    To provide a firstName and lastName to the above template, properties must be added to the application controller. If you are following along with an Ember CLI application, you may need to create this file:


    app/controllers/application.js
    import Ember from 'ember';
    
    export default Ember.Controller.extend({
      firstName: 'Trek',
      lastName: 'Glowacki'
    });
    

    The above template and controller render as the following HTML:


    Hello, <strong>Trek Glowacki</strong>!
    

    Remember that {{firstName}} and {{lastName}} are bound data. That means if the value of one of those properties changes, the DOM will be updated automatically.

    As an application grows in size, it will have many templates backed by controllers and components.

    Helpers

    Helpers bring a minimum of logic into Ember templating. Ember ships with several built-in helpers, which are explained in the given in this section writing-helpers, and also allows you to write your own helpers

    Conditionals

    Statements like if and unless are implemented as built-in helpers. Helpers can be invoked three ways, each of which is illustrated below with conditionals.

    The first style of invocation is inline invocation. This looks similar to displaying a property, but helpers accept arguments. For example:


    <div>
      {{if isFast "zoooom" "putt-putt-putt"}}
    </div>
    

    {{if}} in this case returns "zoooom" when isFast is true and "putt-putt-putt" when isFast is false. Helpers invoked as inline expressions render a single value, the same way that properties are a single value.

    Inline helpers don't need to be used inside HTML tags. They can also be used inside attribute values:


    
    <div class="is-car {{if isFast "zoooom" "putt-putt-putt"}}">
    </div>
    

    Nested invocation is another way to use a helper. Like inline helpers, nested helpers generate and return a single value. For example, this template only renders "zoooom" if both isFast and isFueled are true:


    <div>
      {{if isFast (if isFueled "zoooom")}}
    </div>
    

    The nested helper is called first returning "zoooom" only if isFueled is true. Then the inline expression is called, rendering the nested helper's value ("zoooom") only if isFast is true.

    The third form of helper usage is block invocation. Use block helpers to render only part of a template. Block invocation of a helper can be recognized by the # before the helper name, and the closing {{/ double curly brace at the end of the invocation.

    For example, this template conditionally shows properties on person only if that it is present:


    {{#if person}}
      Welcome back, {{person.firstName}} {{person.lastName}}!
    {{/if}}
    

    {{if}} checks for truthiness, which means all values except false, undefined, null, '', 0 or [] (i.e., any JavaScript falsy value or an empty array).

    If a value passed to {{#if}} evaluates to falsy, the {{else}} block of that invocation is rendered:


    {{#if person}}
      Welcome back, {{person.firstName}} {{person.lastName}}!
    {{else}}
      Please log in.
    {{/if}}
    

    {{else}} can chain helper invocation, the most common usecase for this being {{else if}}:


    {{#if isAtWork}}
      Ship that code!
    {{else if isReading}}
      You can finish War and Peace eventually...
    {{/if}}
    

    The inverse of {{if}} is {{unless}}, which can be used in the same three styles of invocation. For example, this template only shows an amount due when the user has not paid:


    {{#unless hasPaid}}
      You owe: ${{total}}
    {{/unless}}
    

    Displaying a List of Items

    To iterate over a list of items, use the {{#each}} helper. The first argument to this helper is the array to be iterated, and the value being iterated is yielded as a block param. Block params are only available inside the block of their helper.

    For example, this template iterates an array named people that contains objects. Each item in the array is provided as the block param person.


    <ul>
      {{#each people as |person|}}
        <li>Hello, {{person.name}}!</li>
      {{/each}}
    </ul>
    

    Block params, like function arguments in JavaScript, are positional. person is what each item is named in the above template, but human would work just as well.

    The template inside of the {{#each}} block will be repeated once for each item in the array, with the each item set to the person block param.

    Given an input array like:


    
    [ {name: 'Yehuda'},
      {name: 'Tom'   },
      {name: 'Trek'  } ]
    
    

    The above template will render HTML like this:


    <ul>
      <li>Hello, Yehuda!</li>
      <li>Hello, Tom!</li>
      <li>Hello, Trek!</li>
    </ul>
    
    

    Like other helpers, the {{#each}} helper is bound. If a new item is added to or removed from the iterated array, the DOM will be updated without having to write any additional code. That said, Ember requires that you use special methods to update bound arrays.

    Accessing an item's index

    During iteration, the index of each item in an the array is provided as a second block param. Block params are space-separated, without commas. For example:


    <ul>
      {{#each people as |person index|}}
        <li>Hello, {{person.name}}! You're number {{index}} in line</li>
      {{/each}}
    </ul>
    

    Empty Lists

    The {{#each}} helper can have a corresponding {{else}}. The contents of this block will render if the array passed to {{#each}} is empty:


    
    {{#each people as |person|}}
      Hello, {{person.name}}!
    {{else}}
      Sorry, nobody is here.
    {{/each}}
    

    Displaying the Keys in an Object

    If you need to display all of the keys or values of a JavaScript object in your template, you can use the {{#each-in}} helper:


    /app/components/store-categories.js
    export default Ember.Component.extend({
      willRender() {
        // Set the "categories" property to a JavaScript object
        // with the category name as the key and the value a list
        // of products.
        this.set('categories', {
          'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
          'Ryes': ['WhistlePig', 'High West']
        });
      }
    });
    

    /app/templates/components/store-categories.hbs
    <ul>
      {{#each-in categories as |category products|}}
        <li>{{category}}
          <ol>
            {{#each products key="@item" as |product|}}
              <li>{{product}}</li>
            {{/each}}
          </ol>
        </li>
      {{/each-in}}
    </ul>
    
    

    The template inside of the {{#each-in}} block is repeated once for each key in the passed object. The first block parameter (category in the above example) is the key for this iteration, while the second block parameter (product) is the actual value of that key.

    The above example will print a list like this:


    <ul>
      <li>Bourbons
        <ol>
          <li>Bulleit</li>
          <li>Four Roses</li>
          <li>Woodford Reserve</li>
        </ol>
      </li>
      <li>Ryes
        <ol>
          <li>WhistlePig</li>
          <li>High West</li>
        </ol>
      </li>
    </ul>
    

    Re-rendering

    The {{#each-in}} helper does not observe property changes to the object passed into it. In the above example, if you were to add a key to the component's categories property after the component had rendered, the template would not automatically update.


    /app/components/store-categories.js
    export default Ember.Component.extend({
      willRender() {
        this.set('categories', {
          'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
          'Ryes': ['WhistlePig', 'High West']
        });
      },
    
      actions: {
        addCategory(category) {
          // This won't work!
          let categories = this.get('categories');
          categories[category] = [];
        }
      }
    });
    

    In order to cause a component to re-render after you have added, removed or changed a property from an object, you need to either set() the property on the component again, or manually trigger a re-render of the component via rerender():


    /app/components/store-categories.js
    export default Ember.Component.extend({
      willRender() {
        this.set('categories', {
          'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
          'Ryes': ['WhistlePig', 'High West']
        });
      },
    
      actions: {
        addCategory(category) {
          let categories = this.get('categories');
          categories[category] = [];
    
          // A manual re-render causes the DOM to be updated
          this.rerender();
        }
      }
    });
    

    Ordering

    An object's keys will be listed in the same order as the array returned from calling Object.keys on that object. If you want a different sort order, you should use Object.keys to get an array, sort that array with the built-in JavaScript tools, and use the {{#each}} helper instead.

    Empty Lists

    The {{#each-in}} helper can have a matching {{else}}. The contents of this block will render if the object is empty, null, or undefined:


    {{#each-in people as |name person|}}
      Hello, {{name}}! You are {{person.age}} years old.
    {{else}}
      Sorry, nobody is here.
    {{/each-in}}
    

    Binding Element Attributes

    In addition to normal text, you may also want to have your templates contain HTML elements whose attributes are bound to the controller.

    For example, imagine your controller has a property that contains a URL to an image:


    <div id="logo">
      <img src={{logoUrl}} alt="Logo">
    </div>
    
    

    This generates the following HTML:


    <div id="logo">
      <img src="http://www.example.com/images/logo.png" alt="Logo">
    </div>
    

    If you use data binding with a Boolean value, it will add or remove the specified attribute. For example, given this template:


    
    <input type="checkbox" disabled={{isAdministrator}}>
    

    If isAdministrator is true, Handlebars will produce the following HTML element:


    <input type="checkbox" disabled>
    

    If isAdministrator is false, Handlebars will produce the following:


    <input type="checkbox">
    

    Adding Data Attributes

    By default, view helpers do not accept data attributes. For example


    {{#link-to "photos" data-toggle="dropdown"}}Photos{{/link-to}}
    
    {{input type="text" data-toggle="tooltip" data-placement="bottom" title="Name"}}
    
    

    renders the following HTML:


    <a id="ember239" class="ember-view" href="#/photos">Photos</a>
    
    <input id="ember257" class="ember-view ember-text-field" type="text"
           title="Name">
    
    

    To enable support for data attributes an attribute binding must be added to the component, e.g. Ember.LinkComponent or Ember.TextField for the specific attribute:


    
    Ember.LinkComponent.reopen({
      attributeBindings: ['data-toggle']
    });
    
    Ember.TextField.reopen({
      attributeBindings: ['data-toggle', 'data-placement']
    });
    

    Now the same handlebars code above renders the following HTML:


    <a id="ember240" class="ember-view" href="#/photos" data-toggle="dropdown">Photos</a>
    
    <input id="ember259" class="ember-view ember-text-field"
           type="text" data-toggle="tooltip" data-placement="bottom" title="Name">
    
    

    Links

    You create a link to a route using the {{link-to}} helper.


    app/router.js
    Router.map(function() {
      this.route('photos', function(){
        this.route('edit', { path: '/:photo_id' });
      });
    });
    

    app/templates/photos.hbs
    <ul>
      {{#each photos as |photo|}}
        <li>{{#link-to "photos.edit" photo}}{{photo.title}}{{/link-to}}</li>
      {{/each}}
    </ul>
    

    If the model for the photos template is a list of three photos, the rendered HTML would look something like this:


    app/templates/photos.hbs
    <ul>
      <li><a href="/photos/1">Happy Kittens</a></li>
      <li><a href="/photos/2">Puppy Running</a></li>
      <li><a href="/photos/3">Mountain Landscape</a></li>
    </ul>
    

    The {{link-to}} helper takes one or two arguments:

    • The name of a route. In this example, it would be index, photos, or photos.edit.
    • At most one model for each dynamic segment. By default, Ember.js will replace each segment with the value of the corresponding object's id property. In the example above, the second argument is each photo object, and the id property is used to fill in the dynamic segment with either 1, 2, or 3. If there is no model to pass to the helper, you can provide an explicit value instead:

    {{#link-to "photos.edit" 1}}
      First Photo Ever
    {{/link-to}}
    
    

    When the rendered link matches the current route, and the same object instance is passed into the helper, then the link is given class="active". For example, if you were at the URL /photos/2, the first example above would render as:


    app/templates/photos.hbs
    <ul>
      <li><a href="/photos/1">Happy Kittens</a></li>
      <li><a href="/photos/2" class="active">Puppy Running</a></li>
      <li><a href="/photos/3">Mountain Landscape</a></li>
    </ul>
    

    Example for Multiple Segments

    If the route is nested, you can supply a model or an identifier for each dynamic segment.


    Router.map(function() {
      this.route('photos', function(){
        this.route('photo', { path: '/:photo_id' }, function(){
          this.route('comments');
          this.route('comment', { path: '/comments/:comment_id' });
        });
      });
    });
    

    app/router.js
    <div class="photo">
      {{body}}
    </div>
    
    <p>{{#link-to "photos.photo.comment" primaryComment}}Main Comment{{/link-to}}</p>
    
    

    If you specify only one model, it will represent the innermost dynamic segment :comment_id. The :photo_id segment will use the current photo.

    Alternatively, you could pass both a photo's ID and a comment to the helper:


    app/templates/photo/index.hbs
    <p>
      {{#link-to 'photo.comment' 5 primaryComment}}
        Main Comment for the Next Photo
      {{/link-to}}
    </p>
    
    

    In the above example, the model hook for PhotoRoute will run with params.photo_id = 5. The model hook for CommentRoute won't run since you supplied a model object for the comment segment. The comment's id will populate the url according to CommentRoute's serialize hook.

    In addition to being used as a block expression, the link-to helper can also be used in inline form by specifying the link text as the first argument to the helper:


    A link in {{#link-to "index"}}Block Expression Form{{/link-to}},
    and a link in {{link-to "Inline Form" "index"}}.
    
    

    The output of the above would be:


    A link in <a href="/">Block Expression Form</a>,
    and a link in <a href="/">Inline Form</a>.
    
    

    When generating a link you might want to set additional attributes for it. You can do this with additional arguments to the link-to helper:


    <p>
      {{link-to "Edit this photo" "photo.edit" photo class="btn btn-primary"}}
    </p>
    
    

    Many of the common HTML properties you would want to use like class, and rel will work. When adding class names, Ember will also apply the standard ember-view and possibly active class names.

    Replacing history entries

    The default behavior for link-to is to add entries to the browser's history when transitioning between the routes. However, to replace the current entry in the browser's history you can use the replace=true option:


    <p>
      {{#link-to "photo.comment" 5 primaryComment replace=true}}
        Main Comment for the Next Photo
      {{/link-to}}
    </p>
    

    Actions

    Your app will often need a way to let users interact with controls that change application state. For example, imagine that you have a template that shows a blog title, and supports expanding the post to show the body.

    If you add the {{action}} helper to an HTML element, when a user clicks the element, the named event will be sent to the template's corresponding component or controller.


    app/templates/components/single-post.hbs
    <h3 {{action "toggleBody"}}>{{title}}</h3>
    {{#if isShowingBody}}
      <p>{{{body}}}</p>
    {{/if}}
    

    In the component or controller, you can then define what the action does within the actions hook:


    app/components/single-post.js
    export default Ember.Component.extend({
      actions: {
        toggleBody() {
          this.toggleProperty('isShowingBody');
        }
      }
    });
    

    Action Parameters

    You can optionally pass arguments to the action handler. Any values passed to the {{action}} helper after the action name will be passed to the handler as arguments.

    For example, if the post argument was passed:


    <p><button {{action "select" post}}>✓</button> {{post.title}}</p>
    
    
    

    The select action handler would be called with a single argument containing the post model:


    app/components/single-post.js
    export default Ember.Component.extend({
      actions: {
        select(post) {
          console.log(post.get('title'));
        }
      }
    });
    

    Specifying the Type of Event

    By default, the {{action}} helper listens for click events and triggers the action when the user clicks on the element.

    You can specify an alternative event by using the on option.


    <p>
      <button {{action "select" post on="mouseUp"}}>✓</button>
      {{post.title}}
    </p>
    

    You should use the camelCased event names, so two-word names like keypress become keyPress.

    Allowing Modifier Keys

    By default the {{action}} helper will ignore click events with pressed modifier keys. You can supply an allowedKeys option to specify which keys should not be ignored.


    <button {{action "anActionName" allowedKeys="alt"}}>
      click me
    </button>
    

    This way the {{action}} will fire when clicking with the alt key pressed down.

    Allowing Default Browser Action

    By default, the {{action}} helper prevents the default browser action of the DOM event. If you want to allow the browser action, you can stop Ember from preventing it.

    For example, if you have a normal link tag and want the link to bring the user to another page in addition to triggering an ember action when clicked, you can use preventDefault=false:


    <a href="newPage.htm" {{action "logClick" preventDefault=false}}>Go</a>
    
    

    Without preventDefault=false, if the user clicked on the link, Ember.js will trigger the action, but the user will remain on the current page.

    With preventDefault=false, if the user clicked on the link, Ember.js will trigger the action and the user will be directed to the new page.

    Modifying the action's first parameter

    If a value option for the {{action}} helper is specified, its value will be considered a property path that will be read off of the first parameter of the action. This comes very handy with event listeners and enables to work with one-way bindings.


    <label>What's your favorite band?</label>
    <input type="text" value={{favoriteBand}} onblur={{action "bandDidChange"}}/>
    
    

    Let's assume we have an action handler that prints its first parameter:


    actions: {
      bandDidChange(newValue) {
        console.log(newValue);
      }
    }
    

    By default, the action handler receives the first parameter of the event listener, the event object the browser passes to the handler, so bandDidChange prints Event {}.

    Using the value option modifies that behavior by extracting that property from the event object:


    <label>What's your favorite band?</label>
    <input type="text" value={{favoriteBand}} 
    onblur={{action "bandDidChange" value="target.value"}}/>
    
    

    The newValue parameter thus becomes the target.value property of the event object, which is the value of the input field the user typed. (e.g 'Foo Fighters')

    Attaching Actions to Non-Clickable Elements

    Note that actions may be attached to any element of the DOM, but not all respond to the click event. For example, if an action is attached to an a link without an href attribute, or to a div, some browsers won't execute the associated function. If it's really needed to define actions over such elements, a CSS workaround exists to make them clickable, cursor: pointer. For example:


    
    [data-ember-action] {
      cursor: pointer;
    }
    

    Input Helpers

    The {{input}} and {{textarea}} helpers in Ember.js are the easiest way to create common form controls. The {{input}} helper wraps the built-in Ember.TextField and Ember.Checkbox views, while {{textarea}} wraps Ember.TextArea. Using these helpers, you can create these views with declarations almost identical to how you'd create a traditional <input> or <textarea> element.

    Text fields


    {{input value="http://www.facebook.com"}}
    

    Will become:


    <input type="text" value="http://www.facebook.com"/>
    

    You can pass the following standard <input> attributes within the input helper:

    `readonly``required``autofocus`
    `value``placeholder``disabled`
    `size``tabindex``maxlength`
    `name``min``max`
    `pattern``accept``autocomplete`
    `autosave``formaction``formenctype`
    `formmethod``formnovalidate``formtarget`
    `height``inputmode``multiple`
    `step``width``form`
    `selectionDirection``spellcheck` 

    If these attributes are set to a quoted string, their values will be set directly on the element, as in the previous example. However, when left unquoted, these values will be bound to a property on the template's current rendering context. For example:


    {{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
    

    Will bind the disabled attribute to the value of entryNotAllowed in the current context.

    Actions

    To dispatch an action on specific events, such as enter or key-press, use the following


    {{input value=firstName key-press="updateFirstName"}}
    

    Event Names must be dasherized.

    Checkboxes

    You can also use the {{input}} helper to create a checkbox by setting its type:


    {{input type="checkbox" name="isAdmin" checked=isAdmin}}
    

    Checkboxes support the following properties:

    • checked
    • disabled
    • tabindex
    • indeterminate
    • name
    • autofocus
    • form

    Which can be bound or set as described in the previous section.

    Text Areas


    {{textarea value=name cols="80" rows="6"}}
    

    Will bind the value of the text area to name on the current context.

    {{textarea}} supports binding and/or setting the following properties:

    • value
    • name
    • rows
    • cols
    • placeholder
    • disabled
    • maxlength
    • tabindex
    • selectionEnd
    • selectionStart
    • selectionDirection
    • wrap
    • readonly
    • autofocus
    • form
    • spellcheck
    • required

    Binding dynamic attribute

    You might need to bind a property dynamically to an input if you're building a flexible form, for example. To achieve this you need to use the [{{get}}][http://fastread.aitechtonic.com/api/classes/Ember.Templates.helpers.html#method_get] and [{{mut}}][http://fastread.aitechtonic.com/api/classes/Ember.Templates.helpers.html#method_mut] in conjunction like shown in the following example:


    {{input value=(mut (get person field))}}
    

    The {{get}} helper allows you to dynamically specify which property to bind, while the {{mut}} helper allows the binding to be updated from the input. See the respective helper documentation for more detail.

    Development Helpers

    Handlebars and Ember come with a few helpers that can make developing your templates a bit easier. These helpers make it simple to output variables into your browser's console, or activate the debugger from your templates.

    Logging

    The {{log}} helper makes it easy to output variables or expressions in the current rendering context into your browser's console:


    {{log 'Name is:' name}}
    

    The {{log}} helper also accepts primitive types such as strings or numbers.

    Adding a breakpoint

    The {{debugger}} helper provides a handlebars equivalent to JavaScript's debugger keyword. It will halt execution inside the debugger helper and give you the ability to inspect the current rendering context:


    {{debugger}}
    

    When using the debugger helper you will have access to a get function. This function retrieves values available in the context of the template. For example, if you're wondering why a value {{foo}} isn't rendering as expected within a template, you could place a {{debugger}} statement and, when the debugger; breakpoint is hit, you can attempt to retrieve this value:


    
    > get('foo')
    
    

    get is also aware of keywords. So in this situation:


    {{#each items as |item|}}
      {{debugger}}
    {{/each}}
    

    You'll be able to get values from the current item:


    > get('item.name')
    

    You can also access the context of the view to make sure it is the object that you expect:


    > context
    

    Writing Helpers

    Helpers allow you to add additional functionality to your templates beyond what is included out-of-the-box in Ember. Helpers are most useful for transforming raw values from models and components into a format more appropriate for your users.

    For example, imagine we have an Invoice model that contains a totalDue attribute, which represents the total amount due for that invoice. Because we do not want our company to go out of business due to strange JavaScript rounding errors, we store this value in cents instead of a floating point dollar value.

    However, if we display dollar values to our users as "100¢" instead of "$1.00", they may be very confused. We can write a helper to format these values into the appropriate human-readable form.

    Let's create a format-currency helper that takes an integer count of cents and turns it into formatted dollars.

    To use the format-currency helper, you call it using curly braces in your template:


    Your total is {{format-currency model.totalDue}}.
    

    Let's now implement the helper. Helpers are functions that take one or more inputs and return a single output that should be put into the HTML.

    To add a new helper, create a file with the name of the helper you want (e.g. format-currency.js) in your application's helpers directory. You can also have Ember generate the file for you from the command line:


    ember generate helper format-currency
    

    That file should export a function wrapped with Ember.Helper.helper():


    app/helpers/format-currency.js
    export default Ember.Helper.helper(function(params) {
      let value = params[0],
          dollars = Math.floor(value / 100),
          cents = value % 100,
          sign = '$';
    
      if (cents.toString().length === 1) { cents = '0' + cents; }
      return `${sign}${dollars}.${cents}`;
    });
    

    In this example, the function receives a dollar amount in cents as the first parameter (params[0]). We then use regular JavaScript to turn the count of cents into a formatted string, like "$5.00".

    Whenever you use your helper in a template, Ember will call this function and insert whatever you return from the helper into the DOM.

    So, if we want to display a purchase total we can pass the value into the template in cents:


    Your total is {{format-currency 250}}.
    

    And Ember makes use of our new helper function to replace the content inside the {{ }} with the formatted amount.


    Your total is $2.50.
    

    Whenever the arguments you've passed to a helper change, whether they come from a model or a component, Ember will automatically call your helper again with the new values and keep the page up-to-date.

    Helper Names

    Unlike components, helpers do not require a dash (-) character in their name.

    Helper Arguments

    You can pass one or more arguments to be used inside the function. In the above example, we passed the amount in cents as the first and only argument.

    To pass multiple arguments to a helper, add them as a space-separated list after the helper name:


    {{my-helper "hello" "world"}}
    

    An array of these arguments is passed to the helper function:


    app/helpers/my-helper.js
    export default Ember.Helper.helper(function(params) {
      let arg1 = params[0];
      let arg2 = params[1];
    
      console.log(arg1); // => "hello"
      console.log(arg2); // => "world"
    });
    

    You can use JavaScript's destructuring assignment shorthand to clean up the code. This example is equivalent to the above example (note the function signature):


    app/helpers/my-helper.js
    export default Ember.Helper.helper(function([arg1, arg2]) {
      console.log(arg1); // => "hello"
      console.log(arg2); // => "world"
    });
    

    Named Arguments

    Normal arguments are useful for passing data to be transformed into helper functions. However, because the order in which you pass arguments matters, it is usually best not to have helpers take more than one or two of them.

    That said, sometimes you may want to make behavior of helpers configurable by the developers that call them from their templates. For example, let's abandon our Americentric ways and update our format-currency helper to take an optional configuration for which currency symbol to display.

    Helpers allow you to pass named arguments as a JavaScript object that contains the name of the argument along with an associated value. The order in which named arguments are supplied does not affect functionality.

    In this example, we can pass a sign argument to our format-currency helper:


    {{format-currency 350 sign="£"}}
    

    We'd like our helper to print pounds sterling rather than US dollars:


    £3.50
    

    The object containing named arguments is passed as the second argument to the helper function. Here is our example from above, updated to support the optional sign option:


    app/helpers/format-currency.js
    export default Ember.Helper.helper(function(params, namedArgs) {
      let value = params[0],
          dollars = Math.floor(value / 100),
          cents = value % 100,
          sign = namedArgs.sign === undefined ? '$' : namedArgs.sign;
    
      if (cents.toString().length === 1) { cents = '0' + cents; }
      return `${sign}${dollars}.${cents}`;
    });
    

    You can pass as many named arguments as you'd like. They get added to the namedArgs argument passed to the function:


    {{my-helper option1="hello" option2="world" option3="goodbye cruel world"}}
    

    app/helpers/my-helper.js
    export default Ember.Helper.helper(function(params, namedArgs) {
      console.log(namedArgs.option1); // => "hello"
      console.log(namedArgs.option2); // => "world"
      console.log(namedArgs.option3); // => "goodbye cruel world"
    });
    

    You can use JavaScript's destructuring assignment shorthand in this case as well to clean up the above code:


    app/helpers/my-helper.js
    export default Ember.Helper.helper(function(params, { option1, option2, option3 }) {
      console.log(option1); // => "hello"
      console.log(option2); // => "world"
      console.log(option3); // => "goodbye cruel world"
    });
    

    In sum, arguments are good for passing values:


    {{format-date currentDate}}
    

    Hashes are useful for configuring the behavior of a helper:


    {{print-current-date format="YYYY MM DD"}}
    

    You can have as many of both as you want, so long as the parameters come first:


    {{format-date-and-time date time format="YYYY MM DD h:mm" locale="en"}}
    

    The above example contains two arguments:

    • date
    • time

    And two named arguments:

    • format="YYY MM DD h:mm"
    • locale="en"

    Class-based Helpers

    By default, helpers are stateless. They are passed inputs (parameters and a hash), they perform an operation on those inputs, and return a single output. They have no side-effects and don't save any information that is used on subsequent runs of the function.

    In some situations, however, you may need to write a helper that interacts with the rest of your application. You can create class-based helpers that have access to services in your application, and can optionally save state as well, although this is usually unnecessary and error-prone.

    To create a class-based helper, rather than exporting a simple function, you should export a subclass of Ember.Helper. Helper classes must contain a compute method that behaves the same as the function passed to Ember.Helper.helper. In order to access a service, you must first inject it into the class-based helper. Once added, you can call the service's methods or access its properties from within the compute() method.

    To exemplify, let's make a helper utilizing an authentication service that welcomes users by their name if they're logged in:


    app/helpers/is-authenticated.js
    export default Ember.Helper.extend({
      authentication: Ember.inject.service(),
      compute() {
        let authentication = this.get('authentication');
    
        if (authentication.get('isAuthenticated')) {
          return 'Welcome back, ' + authentication.get('username');
        } else {
          return 'Not logged in';
        }
      }
    });
    

    In fact, we could also refactor the above stateless helper into a class-based helper by making the function into a compute method on the class:


    app/helpers/format-currency.js
    export default Ember.Helper.extend({
      compute(params, hash) {
        let value = params[0],
            dollars = Math.floor(value / 100),
            cents = value % 100,
            sign = hash.sign === undefined ? '$' : hash.sign;
    
        if (cents.toString().length === 1) { cents = '0' + cents; }
        return `${sign}${dollars}.${cents}`;
      }
    });
    

    This is exactly equivalent to the example above. You can think of the function version as a shorthand for the longer class form if it does not require any state.

    Escaping HTML Content

    To protect your application from cross-site scripting attacks (XSS), Ember automatically escapes any value you return from a helper so that the browser will not interpret it as HTML.

    For example, here's a make-bold helper that returns a string containing HTML:


    app/helpers/make-bold.js
    export default Ember.Helper.helper(function(params) {
      return `<b>${params[0]}</b>`;
    });
    

    You can invoke it like this:


    {{make-bold "Hello world"}}
    

    Ember will escape the HTML tags, like this:


    &lt;b&gt;Hello world&lt;/b&gt;
    

    This shows the literal string <b>Hello world</b> to the user, rather than the text in bold as you probably intended. We can tell Ember not to escape the return value (that is, that it is safe) by using the htmlSafe string utility:


    app/helpers/make-bold.js
    export default Ember.Helper.helper(function(params) {
      return Ember.String.htmlSafe(`<b>${params[0]}</b>`);
    });
    

    If you return a SafeString (a string that has been wrapped in a call to htmlSafe), Ember knows that you have vouched on its behalf that it contains no malicious HTML.

    However, note that in the above code we may have inadvertently introduced an XSS vulnerability into our application! By blindly marking the string as safe, a malicious user could get their own HTML into our app, allowing them to do things like access sensitive customer data.

    For example, imagine that we have a chat app and use our make-bold helper to welcome the new users into the channel:


    Welcome back! {{make-bold model.firstName}} has joined the channel.
    

    Now a malicious user simply needs to set their firstName to a string containing HTML (like a <script> tag that sends private customer data to their server, for example) and every user in that chat room has been compromised.

    In general, you should prefer using components if you are wrapping content in HTML. However, if you really want to include a mix of HTML and values from models in what you return from the helper, make sure you escape anything that may have come from an untrusted user with the escapeExpression utility:


    app/helpers/make-bold.js
    export default Ember.Helper.helper(function(params) {
      let value = Ember.Handlebars.Utils.escapeExpression(params[0]);
      return Ember.String.htmlSafe(`<b>${value}</b>>`);
    });
    

    Now the value passed into the helper has its HTML escaped, but the trusted <b> tags that we want to wrap the value in are not escaped. A malicious user setting their firstName to something containing HTML would see this:


    Welcome back! &lt;script
    type="javascript"&gt;alert('pwned!');&lt;/script&gt; has joined the channel.
    
  • EMBERJS COMPONENTS ↓

    • Defining a Component ↓
    • The Component Lifecycle ↓
    • Passing Properties to a Component ↓
    • Wrapping Content in a Component ↓
    • Customizing a Component's Element ↓
    • Handling Events ↓
    • Triggering Changes with Actions ↓

    Defining a Component

    To define a component, run:


    ember generate component my-component-name
    

    Components must have at least one dash in their name. So blog-post is an acceptable name, and so is audio-player-controls, but post is not. This prevents clashes with current or future HTML element names, aligns Ember components with the W3C Custom Elements spec, and ensures Ember detects the components automatically.

    A sample component template could look like this:


    app/templates/components/blog-post.hbs
    <article class="blog-post">
      <h1>{{title}}</h1>
      <p>{{yield}}</p>
      <p>Edit title: {{input type="text" value=title}}</p>
    </article>
    

    Given the above template, you can now use the {{blog-post}} component:


    app/templates/index.hbs
    {{#each model as |post|}}
      {{#blog-post title=post.title}}
        {{post.body}}
      {{/blog-post}}
    {{/each}}
    

    Its model is populated in model hook in the route handler:


    app/routes/index.js
    export default Ember.Route.extend({
      model() {
        return this.store.findAll('post');
      }
    });
    

    Each component, under the hood, is backed by an element. By default Ember will use a <div> element to contain your component's template. To learn how to change the element Ember uses for your component, see Customizing a Component's Element.

    Defining a Component Subclass

    Often times, your components will just encapsulate certain snippets of Handlebars templates that you find yourself using over and over. In those cases, you do not need to write any JavaScript at all. Define the Handlebars template as described above and use the component that is created.

    If you need to customize the behavior of the component you'll need to define a subclass of Ember.Component. For example, you would need a custom subclass if you wanted to change a component's element, respond to actions from the component's template, or manually make changes to the component's element using JavaScript.

    Ember knows which subclass powers a component based on its filename. For example, if you have a component called blog-post, you would create a file at app/components/blog-post.js. If your component was called audio-player-controls, the file name would be at app/components/audio-player-controls.js.

    Dynamically rendering a component

    The {{component}} helper can be used to defer the selection of a component to run time. The {{my-component}} syntax always renders the same component, while using the {{component}} helper allows choosing a component to render on the fly. This is useful in cases where you want to interact with different external libraries depending on the data. Using the {{component}} helper would allow you to keep different logic well-separated.

    The first parameter of the helper is the name of a component to render, as a string. So {{component 'blog-post'}} is the same as using {{blog-post}}.

    The real value of {{component}} comes from being able to dynamically pick the component being rendered. Below is an example of using the helper as a means of choosing different components for displaying different kinds of posts:


    app/templates/components/foo-component.hbs
    <h3>Hello from foo!</h3>
    <p>{{post.body}}</p>
    

    app/templates/components/bar-component.hbs
    <h3>Hello from bar!</h3>
    <div>{{post.author}}</div>
    

    app/routes/index.js
    export default Ember.Route.extend({
      model() {
        return this.store.findAll('post');
      }
    });
    

    app/templates/index.hbs
    {{#each model as |post|}}
      {{!-- either foo-component or bar-component --}}
      {{component post.componentName post=post}}
    {{/each}}
    

    When the parameter passed to {{component}} evaluates to null or undefined, the helper renders nothing. When the parameter changes, the currently rendered component is destroyed and the new component is created and brought in.

    Picking different components to render in response to the data allows you to have different template and behavior for each case. The {{component}} helper is a powerful tool for improving code modularity.

    The Component Lifecycle

    Part of what makes a component such a useful tool is that it is closely tied to your app's templates and the DOM itself.

    Components are your primary means for direct DOM manipulation, listening and responding to browser events, and attaching 3rd party JS libraries into your Ember app.

    To get the most use out of a component, it is important to understand its "lifecycle" methods. The following hooks are a few of the most useful and commonly used in current apps.

    Attaching to the Component Element

    Suppose you want to integrate your favorite date picker library into an Ember project. Typically, 3rd party JS/Jquery libraries require a DOM element to bind itself to. So, where is the best place to initialize and attach the library?

    When a component successfully renders its backing html element into the DOM, it will trigger its didInsertElement() hook.

    It is at this point in the component lifecycle, when this.$() will become available to target with jQuery.

    this.$() will, by default, return the component's main element, but it is also valid to target child elements within the component's template as well: this.$('.some-css-selector').

    So let's initialize our date picker by overriding the didInsertElement() method with _super().

    Date picker libraries usually attach to an <input> so we will use jQuery to find an appropriate input within our component's template.


    didInsertElement() {
      this._super(...arguments);
      this.$('input.date').myDatepickerLib();
    }
    

    didInsertElement() is also a place to attach event listeners. This is particularly useful for custom events or other browser events which do not have a built-in event handler.

    Perhaps you have some custom CSS animations trigger when the component is rendered and you want to handle some cleanup when it ends?


    didInsertElement() {
      this._super(...arguments);
      this.$().on('animationend', () => {
        $(this).removeClass('.sliding-anim');
      });
    }
    

    There are a few things to note about the didInsertElement() hook:

    • It is only triggered once when the component element is first rendered.
    • In the case wherein a parent component is rendering child components, child components trigger their respective didInsertElement() first, and then bubble up to the parent.
    • Setting properties in didInsertElement() triggers a re-render, and for performance reasons, is not allowed.
    • While didInsertElement() is technically an event that can be listened for using on(), it is encouraged to override the default method itself, particularly when order of execution is important.

    Detaching and Tearing Down Component Elements

    When a component detects that it is time to remove itself from the DOM, willDestroyElement() will trigger, allowing for any teardown logic to be performed.

    This can be triggered by number of conditions, for instance, a conditional htmlbars block closing around your component: {{#if falseBool}}{{my-component}}{{/if}}, or a parent template being torn down in response to a route transition.

    Let's use that hook to cleanup our date picker and event listener from above:


    willDestroyElement() {
      this.$().off('animationend');
      this.$('input.date').myDatepickerLib().destroy();
    }
    

    There is no default implementation for willDestroyElement() so _super is not necessary.

    Passing Properties to a Component

    Components are isolated from their surroundings, so any data that the component needs has to be passed in.

    For example, imagine you have a blog-post component that is used to display a blog post:


    app/templates/components/blog-post.hbs
    <article class="blog-post">
      <h1>{{title}}</h1>
      <p>{{body}}</p>
    </article>
    

    Now imagine we have the following template and route:


    app/routes/index.js
    export default Ember.Route.extend({
      model() {
        return this.store.findAll('post');
      }
    });
    

    If we tried to use the component like this:


    app/templates/index.hbs
    {{#each model as |post|}}
      {{blog-post}}
    {{/each}}
    

    The following HTML would be rendered:


    <article class="blog-post">
      <h1></h1>
      <p></p>
    </article>
    

    In order to make a property available to a component, you must pass it in like this:


    app/templates/index.hbs
    {{#each model as |post|}}
      {{blog-post title=post.title body=post.body}}
    {{/each}}
    

    It is important to note that these properties stay in sync (technically known as being "bound"). That is, if the value of componentProperty changes in the component, outerProperty will be updated to reflect that change. The reverse is true as well.

    Positional Params

    In addition to passing parameters in by name, you can pass them in by position. In other words, you can invoke the above component example like this:


    app/templates/index.hbs
    {{#each model as |post|}}
      {{blog-post post.title post.body}}
    {{/each}}
    

    To set the component up to receive parameters this way, you need set the positionalParams attribute in your component class.


    app/components/blog-post.js
    const BlogPostComponent = Ember.Component.extend({});
    
    BlogPostComponent.reopenClass({
      positionalParams: ['title', 'body']
    });
    
    export default BlogPostComponent;
    

    Then you can use the attributes in the component exactly as if they had been passed in like {{blog-post title=post.title body=post.body}}.

    Notice that the positionalParams property is added to the class as a static variable via reopenClass. Positional params are always declared on the component class and cannot be changed while an application runs.

    Alternatively, you can accept an arbitrary number of parameters by setting positionalParams to a string, e.g. positionalParams: 'params'. This will allow you to access those params as an array like so:


    app/components/blog-post.js
    const BlogPostComponent = Ember.Component.extend({
      title: Ember.computed('params.[]', function(){
        return this.get('params')[0];
      }),
      body: Ember.computed('params.[]', function(){
        return this.get('params')[1];
      })
    });
    
    BlogPostComponent.reopenClass({
      positionalParams: 'params'
    });
    
    export default BlogPostComponent;
    

    Wrapping Content in a Component

    Sometimes, you may want to define a component that wraps content provided by other templates.

    For example, imagine we are building a blog-post component that we can use in our application to display a blog post:


    app/templates/components/blog-post.hbs
    <h1>{{title}}</h1>
    <div class="body">{{body}}</div>
    

    Now, we can use the {{blog-post}} component and pass it properties in another template:


    {{blog-post title=title body=body}}
    

    (See Passing Properties to a Component for more.)

    In this case, the content we wanted to display came from the model. But what if we want the developer using our component to be able to provide custom HTML content?

    In addition to the simple form you've learned so far, components also support being used in block form. In block form, components can be passed a Handlebars template that is rendered inside the component's template wherever the {{yield}} expression appears.

    To use the block form, add a # character to the beginning of the component name, then make sure to add a closing tag. (See the Handlebars documentation on block expressions for more.)

    In that case, we can use the {{blog-post}} component in block form and tell Ember where the block content should be rendered using the {{yield}} helper. To update the example above, we'll first change the component's template:


    app/templates/components/blog-post.hbs
    
    <h1>{{title}}</h1>
    <div class="body">{{yield}}</div>
    

    You can see that we've replaced {{body}} with {{yield}}. This tells Ember that this content will be provided when the component is used.

    Next, we'll update the template using the component to use the block form:


    app/templates/index.hbs
    {{#blog-post title=title}}
      <p class="author">by {{author}}</p>
      {{body}}
    {{/blog-post}}
    

    It's important to note that the template scope inside the component block is the same as outside. If a property is available in the template outside the component, it is also available inside the component block.

    Customizing a Component's Element

    By default, each component is backed by a <div> element. If you were to look at a rendered component in your developer tools, you would see a DOM representation that looked something like:


    <div id="ember180" class="ember-view">
      <h1>My Component</h1>
    </div>
    

    You can customize what type of element Ember generates for your component, including its attributes and class names, by creating a subclass of Ember.Component in your JavaScript.

    Customizing the Element

    To use a tag other than div, subclass Ember.Component and assign it a tagName property. This property can be any valid HTML5 tag name as a string.


    app/components/navigation-bar.js
    export default Ember.Component.extend({
      tagName: 'nav'
    });
    

    app/templates/components/navigation-bar.hbs
    <ul>
      <li>{{#link-to "home"}}Home{{/link-to}}</li>
      <li>{{#link-to "about"}}About{{/link-to}}</li>
    </ul>
    
    

    Customizing Class Names

    You can also specify which class names are applied to the component's element by setting its classNames property to an array of strings:


    app/components/navigation-bar.js
    export default Ember.Component.extend({
      classNames: ['primary']
    });
    

    If you want class names to be determined by properties of the component, you can use class name bindings. If you bind to a Boolean property, the class name will be added or removed depending on the value:


    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['isUrgent'],
      isUrgent: true
    });
    

    This component would render the following:


    <div class="ember-view is-urgent"></div>
    
    

    If isUrgent is changed to false, then the is-urgent class name will be removed.

    By default, the name of the Boolean property is dasherized. You can customize the class name applied by delimiting it with a colon:


    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['isUrgent:urgent'],
      isUrgent: true
    });
    

    This would render this HTML:


    <div class="ember-view urgent">
    

    Besides the custom class name for the value being true, you can also specify a class name which is used when the value is false:


    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['isEnabled:enabled:disabled'],
      isEnabled: false
    });
    

    This would render this HTML:


    <div class="ember-view disabled">
    

    You can also specify a class which should only be added when the property is false by declaring classNameBindings like this:


    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['isEnabled::disabled'],
      isEnabled: false
    });
    

    This would render this HTML:


    <div class="ember-view disabled">
    
    

    If the isEnabled property is set to true, no class name is added:


    
    <div class="ember-view">
    

    If the bound property's value is a string, that value will be added as a class name without modification:


    app/components/todo-item.js
    export default Ember.Component.extend({
      classNameBindings: ['priority'],
      priority: 'highestPriority'
    });
    

    This would render this HTML:


    
    <div class="ember-view highestPriority">
    

    Customizing Attributes

    You can bind attributes to the DOM element that represents a component by using attributeBindings:


    app/components/link-item.js
    export default Ember.Component.extend({
      tagName: 'a',
      attributeBindings: ['href'],
      href: "http://fastread.aitechtonic.com"
    });
    

    You can also bind these attributes to differently named properties:


    pp/components/link-item.js
    export default Ember.Component.extend({
      tagName: 'a',
      attributeBindings: ['customHref:href'],
      customHref: 'http://fastread.aitechtonic.com'
    });
    

    Handling Events

    You can respond to user events on your component like double-clicking, hovering, and key presses through event handlers. Simply implement the name of the event you want to respond to as a method on your component.

    For example, imagine we have a template like this:


    {{#double-clickable}}
      This is a double clickable area!
    {{/double-clickable}}
    

    Let's implement double-clickable such that when it is clicked, an alert is displayed:


    app/components/double-clickable.js
    export default Ember.Component.extend({
      doubleClick() {
        alert("DoubleClickableComponent was clicked!");
      }
    });
    

    Browser events may bubble up the DOM which potentially target parent component(s) in succession. To enable bubbling return true; from the event handler method in your component.


    app/components/double-clickable.js
    export default Ember.Component.extend({
      doubleClick() {
        Ember.Logger.info("DoubleClickableComponent was clicked!");
        return true;
      }
    });
    

    See the list of event names at the end of this page. Any event can be defined as an event handler in your component.

    Sending Actions

    In some cases your component needs to define event handlers, perhaps to support various draggable behaviors. For example, a component may need to send an id when it receives a drop event:


    
    	
    
    {{drop-target action="didDrop"}}
    
    

    You can define the component's event handlers to manage the drop event. And if you need to, you may also stop events from bubbling, by using return false;.


    app/components/drop-target.js
    export default Ember.Component.extend({
      attributeBindings: ['draggable'],
      draggable: 'true',
    
      dragOver() {
        return false;
      },
    
      drop(event) {
        let id = event.dataTransfer.getData('text/data');
        this.sendAction('action', id);
      }
    });
    

    Event Names

    The event handling examples described above respond to one set of events. The names of the built-in events are listed below. Custom events can be registered by using Ember.Application.customEvents.

    Touch events:

    • touchStart
    • touchMove
    • touchEnd
    • touchCancel

    Keyboard events

    • keyDown
    • keyUp
    • keyPress

    Mouse events

    • mouseDown
    • mouseUp
    • contextMenu
    • click
    • doubleClick
    • mouseMove
    • focusIn
    • focusOut
    • mouseEnter
    • mouseLeave

    Form events:

    • submit
    • change
    • focusIn
    • focusOut
    • input

    HTML5 drag and drop events:

    • dragStart
    • drag
    • dragEnter
    • dragLeave
    • dragOver
    • dragEnd
    • drop

    Triggering Changes with Actions

    You can think of a component as a black box of UI functionality. So far, you've learned how parent components can pass attributes in to a child component, and how that component can use those attributes from both JavaScript and its template.

    But what about the opposite direction? How does data flow back out of the component to the parent? In Ember, components use actions to communicate events and changes.

    Let's look at a simple example of how a component can use an action to communicate with its parent.

    Imagine we're building an application where users can have accounts. We need to build the UI for users to delete their account. Because we don't want users to accidentally delete their accounts, we'll build a button that requires the user to confirm in order to trigger some action.

    Once we create this "button with confirmation" component, we want to be able to reuse it all over our application.

    Creating the Component

    Let's call our component button-with-confirmation. We can create it by typing:


    ember generate component button-with-confirmation
    

    We'll plan to use the component in a template something like this:


    app/templates/components/user-profile.hbs
    {{button-with-confirmation text="Click OK to delete your account."}}
    

    We'll also want to use the component elsewhere, perhaps like this:


    app/templates/components/send-message.hbs
    {{button-with-confirmation text="Click OK to send your message."}}
    

    Designing the Action

    When implementing an action on a component, you need to break it down into two steps:

    1. In the parent component, decide how you want to react to the action. Here, we want to have the action delete the user's account in one place, and send a message in another place.
    2. In the component, determine when something has happened, and when to tell the outside world. Here, we want to trigger the outside action (deleting the account or sending the message) after the user clicks the button and then confirms.

    Let's take it step by step.

    Implementing the Action

    In the parent component, let's first define what we want to happen when the user clicks the button and then confirms. In this case, we'll find the user's account and delete it.

    In Ember, each component can have a property called actions, where you put functions that can be invoked by the user interacting with the component itself, or by child components.

    Let's look at the parent component's JavaScript file. In this example, imagine we have a parent component called user-profile that shows the user's profile to them.

    We'll implement an action on the parent component called userDidDeleteAccount() that, when called, gets a hypothetical login service and calls the service's deleteUser() method.


    app/components/user-profile.js
    export default Ember.Component.extend({
      login: Ember.inject.service(),
    
      actions: {
        userDidDeleteAccount() {
          this.get('login').deleteUser();
        }
      }
    });
    

    Now we've implemented our action, but we have not told Ember when we want this action to be triggered, which is the next step.

    Designing the Child Component

    Next, let's implement the logic to confirm that the user wants to take the action from the component:


    app/components/button-with-confirmation.js
    export default Ember.Component.extend({
      tagName: 'button',
      click() {
        if (confirm(this.get('text'))) {
          // trigger action on parent component
        }
      }
    });
    

    Passing the Action to the Component

    Now we need to make it so that the onConfirm() event in the button-with-confirmation() component triggers the userDidDeleteAccount() action in the user-profile component. One important thing to know about actions is that they're functions you can call, like any other method on your component. So they can be passed from one component to another like this:


    app/components/user-profile.hbs
    {{button-with-confirmation text="Click here to delete your account." onConfirm=(action 'userDidDeleteAccount')}}
    
    

    This snippet says "take the userDidDeleteAccount action from the parent and make it available on the child component as onConfirm."

    We can do a similar thing for our send-message component:


    app/templates/components/send-message.hbs
    {{button-with-confirmation text="Click to send your message." onConfirm=(action 'sendMessage')}}
    
    

    Now, we can use onConfirm in the child component to invoke the action on the parent:


    app/components/button-with-confirmation.js
    export default Ember.Component.extend({
      tagName: 'button',
      click() {
        if (confirm(this.get('text'))) {
          this.get('onConfirm')();
        }
      }
    });
    

    this.get('onConfirm') will return the function passed from the parent as the value of onConfirm, and the following () will invoke the function.

    Like normal attributes, actions can be a property on the component; the only difference is that the property is set to a function that knows how to trigger behavior.

    That makes it easy to remember how to add an action to a component. It's like passing an attribute, but you use the action helper to pass a function instead.

    Actions in components allow you to decouple an event happening from how it's handled, leading to modular, more reusable components.

  • EMBERJS MODELS ↓

    • Introduction ↓
    • Defining Models ↓
    • Finding Records ↓
    • Creating, Updating and Deleting ↓
    • Pushing Records into the Store ↓
    • Working with Relationships ↓
    • Handling Metadata ↓
    • Customizing Adapters ↓
    • Customizing Serializers ↓

    Models are objects that represent the underlying data that your application presents to the user. Different apps will have very different models, depending on what problems they're trying to solve.

    For example, a photo sharing application might have a Photo model to represent a particular photo, and a PhotoAlbum that represents a group of photos. In contrast, an online shopping app would probably have different models, like ShoppingCart, Invoice, or LineItem.

    Models tend to be persistent. That means the user does not expect model data to be lost when they close their browser window. To make sure no data is lost, if the user makes changes to a model, you need to store the model data somewhere that it will not be lost.

    Typically, most models are loaded from and saved to a server that uses a database to store data. Usually you will send JSON representations of models back and forth to an HTTP server that you have written. However, Ember makes it easy to use other durable storage, such as saving to the user's hard disk with IndexedDB, or hosted storage solutions that let you avoid writing and hosting your own servers.

    Once you've loaded your models from storage, components know how to translate model data into a UI that your user can interact with. For more information about how components get model data, see the Specifying a Route's Model guide.

    Ember Data, included by default when you create a new application, is a library that integrates tightly with Ember to make it easy to retrieve models from your server as JSON, save updates back to the server, and create new models in the browser.

    Thanks to its use of the adapter pattern, Ember Data can be configured to work with many different kinds of backends. There is an entire ecosystem of adapters that allow your Ember app to talk to different types of servers without you writing any networking code.

    If you need to integrate your Ember.js app with a server that does not have an adapter available (for example, you hand-rolled an API server that does not adhere to any JSON specification), Ember Data is designed to be configurable to work with whatever data your server returns.

    Ember Data is also designed to work with streaming servers, like those powered by WebSockets. You can open a socket to your server and push changes into Ember Data whenever they occur, giving your app a real-time user interface that is always up-to-date.

    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.

    With Ember Data, managing models as your application grows becomes both simple and easy.

    Once you have an understanding of Ember Data, you will have a much better way to manage the complexity of data loading in your application. This will allow your code to evolve without becoming a mess.

    The Store and a Single Source of Truth

    One common way of building web applications is to tightly couple user interface elements to data fetching. For example, imagine you are writing the admin section of a blogging app, which has a feature that lists the drafts for the currently logged in user.

    You might be tempted to make the component responsible for fetching that data and storing it:


    app/components/list-of-drafts.js
    export default Ember.Component.extend({
      willRender() {
        $.getJSON('/drafts').then(data => {
          this.set('drafts', data);
        });
      }
    });
    

    You could then show the list of drafts in your component's template like this:


    app/templates/components/list-of-drafts.hbs
    <ul>
      {{#each drafts key="id" as |draft|}}
        <li>{{draft.title}}</li>
      {{/each}}
    </ul>
    

    This works great for the list-of-drafts component. However, your app is likely made up of many different components. On another page you may want a component to display the number of drafts. You may be tempted to copy and paste your existing willRender code into the new component.


    app/components/drafts-button.js
    export default Ember.Component.extend({
      willRender() {
        $.getJSON('/drafts').then(data => {
          this.set('drafts', data);
        });
      }
    });
    

    app/templates/components/drafts-button.hbs
    {{#link-to 'drafts' tagName="button"}}
    Drafts ({{drafts.length}})
    {{/link-to}}
    

    Unfortunately, the app will now make two separate requests for the same information. Not only is the redundant data fetching costly in terms of wasted bandwidth and affecting the perceived speed of your app, it's easy for the two values to get out-of-sync. You yourself have probably used a web application where the list of items gets out of sync with the counter in a toolbar, leading to a frustrating and inconsistent experience.

    There is also a tight coupling between your application's UI and the network code. If the url or the format of the JSON payload changes, it is likely to break all of your UI components in ways that are hard to track down.

    The SOLID principles of good design tell us that objects should have a single responsibility. The responsibility of a component should be presenting model data to the user, not fetching the model.

    Good Ember apps take a different approach. 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.

    It also means that the store can detect that two different components are asking for the same model, allowing your app to only fetch the data from the server once. You can think of the store as a read-through cache for your app's models. Both your components and routes have access to this shared store; when they need to display or modify a model, they first ask the store for it.

    Convention Over Configuration with JSON API

    You can significantly reduce the amount of code you need to write and maintain by relying on Ember's conventions. Since these conventions will be shared among developers on your team, following them leads to code that is easier to maintain and understand.

    Rather than create an arbitrary set of conventions, Ember Data is designed to work out of the box with JSON API. JSON API is a formal specification for building conventional, robust, and performant APIs that allow clients and servers to communicate model data.

    JSON API standardizes how JavaScript applications talk to servers, so you decrease the coupling between your frontend and backend, and have more freedom to change pieces of your stack.

    As an analogy, JSON API is to JavaScript apps and API servers what SQL is to server-side frameworks and databases. Popular frameworks like Ruby on Rails, Laravel, Django, Spring and more work out of the box with many different databases, like MySQL, PostgreSQL, SQL Server, and more.

    Frameworks (or apps built on those frameworks) don't need to write lots of custom code to add support for a new database; as long as that database supports SQL, adding support for it is relatively easy.

    So too with JSON API. By using JSON API to interop between your Ember app and your server, you can entirely change your backend stack without breaking your frontend. And as you add apps for other platforms, such as iOS and Android, you will be able to leverage JSON API libraries for those platforms to easily consume the same API your Ember app uses.

    Models

    In Ember Data, each model is represented by a subclass of Model that defines the attributes, relationships, and behavior of the data that you present to the user.

    Models define the type of data that will be provided by your server. For example, a Person model might have a firstName attribute that is a string, and a birthday attribute that is a date:


    app/models/person.js
    export default DS.Model.extend({
      firstName: DS.attr('string'),
      birthday:  DS.attr('date')
    });
    

    A model also describes its relationships with other objects. For example, an order may have many line-items, and a line-item may belong to a particular order.


    app/models/order.js
    export default DS.Model.extend({
      lineItems: DS.hasMany('line-item')
    });
    

    app/models/line-item.js
    export default DS.Model.extend({
      order: DS.belongsTo('order')
    });
    

    Models don't have any data themselves, they define the attributes, relationships and behavior of specific instances, which are called records.

    Records

    A record is an instance of a model that contains data loaded from a server. Your application can also create new records and save them back to the server.

    A record is uniquely identified by its model type and ID.

    For example, if you were writing a contact management app, you might have a Person model. An individual record in your app might have a type of person and an ID of 1 or steve-buscemi.


    this.store.findRecord('person', 1); // => { id: 1, name: 'steve-buscemi' }
    

    An ID is usually assigned to a record by the server when you save it for the first time, but you can also generate IDs client-side.

    Adapter

    An adapter is an object that translates requests from Ember (such as "find the user with an ID of 123") into requests to a server.

    For example, if your application asks for a Person with an ID of 123, how should Ember load it? Over HTTP or a WebSocket? If it's HTTP, is the URL /person/1 or /resources/people/1?

    The adapter is responsible for answering all of these questions. Whenever your app asks the store for a record that it doesn't have cached, it will ask the adapter for it. If you change a record and save it, the store will hand the record to the adapter to send the appropriate data to your server and confirm that the save was successful.

    Adapters let you completely change how your API is implemented without impacting your Ember application code.

    Caching

    The store will automatically cache records for you. If a record had already been loaded, asking for it a second time will always return the same object instance. This minimizes the number of round-trips to the server, and allows your application to render its UI to the user as fast as possible.

    For example, the first time your application asks the store for a person record with an ID of 1, it will fetch that information from your server.

    However, the next time your app asks for a person with ID 1, the store will notice that it had already retrieved and cached that information from the server. Instead of sending another request for the same information, it will give your application the same record it had provided it the first time. This feature—always returning the same record object, no matter how many times you look it up—is sometimes called an identity map.

    Using an identity map is important because it ensures that changes you make in one part of your UI are propagated to other parts of the UI. It also means that you don't have to manually keep records in sync—you can ask for a record by ID and not have to worry about whether other parts of your application have already asked for and loaded it.

    One downside to returning a cached record is you may find the state of the data has changed since it was first loaded into the store's identity map. In order to prevent this stale data from being a problem for long, Ember Data will automatically make a request in the background each time a cached record is returned from the store. When the new data comes in, the record is updated, and if there have been changes to the record since the initial render, the template is re-rendered with the new information.

    Architecture Overview

    The first time your application asks the store for a record, the store sees that it doesn't have a local copy and requests it from your adapter. Your adapter will go and retrieve the record from your persistence layer; typically, this will be a JSON representation of the record served from an HTTP server.

    Diagram showing process for finding an unloaded record

    As illustrated in the diagram above, the adapter cannot always return the requested record immediately. In this case, the adapter must make an asynchronous request to the server, and only when that request finishes loading can the record be created with its backing data.

    Because of this asynchronicity, the store immediately returns a promise from the find() method. Similarly, any requests that the store makes to the adapter also return promises.

    Once the request to the server returns with a JSON payload for the requested record, the adapter resolves the promise it returned to the store with the JSON.

    The store then takes that JSON, initializes the record with the JSON data, and resolves the promise returned to your application with the newly-loaded record.

    Diagram showing process for finding an unloaded record after the payload has returned from the server

    Let's look at what happens if you request a record that the store already has in its cache.

    Diagram showing process for finding an unloaded record after the payload has returned from the server

    In this case, because the store already knew about the record, it returns a promise that it resolves with the record immediately. It does not need to ask the adapter (and, therefore, the server) for a copy since it already has it saved locally.


    Models, records, adapters and the store are the core concepts you should understand to get the most out of Ember Data. The following sections go into more depth about each of these concepts, and how to use them together.

    A model is a class that defines the properties and behavior of the data that you present to the user. Anything that the user expects to see if they leave your app and come back later (or if they refresh the page) should be represented by a model.

    When you want a new model for your application you need to create a new file under the models folder and extend from DS.Model. This is more conveniently done by using one of Ember CLI's generator commands. For instance, let's create a person model:


    ember generate model person
    
    

    This will generate the following file:


    app/models/person.js
    export default DS.Model.extend({
    });
    
    

    After you have defined a model class, you can start finding and working with records of that type.

    Defining Attributes

    The person model we generated earlier didn't have any attributes. Let's add first and last name, as well as the birthday, using DS.attr:


    app/models/person.js
    export default DS.Model.extend({
      firstName: DS.attr(),
      lastName: DS.attr(),
      birthday: DS.attr()
    });
    
    

    Attributes are used when turning the JSON payload returned from your server into a record, and when serializing a record to save back to the server after it has been modified.

    You can use attributes like any other property, including as part of a computed property. Frequently, you will want to define computed properties that combine or transform primitive attributes.


    app/models/person.js
    export default DS.Model.extend({
      firstName: DS.attr(),
      lastName: DS.attr(),
    
      fullName: Ember.computed('firstName', 'lastName', function() {
        return `${this.get('firstName')} ${this.get('lastName')}`;
      })
    });
    
    

    For more about adding computed properties to your classes, see Computed Properties.

    Transforms

    You may find the type of an attribute returned by the server does not match the type you would like to use in your JavaScript code. Ember Data allows you to define simple serialization and deserialization methods for attribute types called transforms. You can specify that you would like a transform to run for an attribute by providing the transform name as the first argument to the DS.attr method.

    For example if you would like to transform an ISO 8601 string to a JavaScript date object you would define your attribute like this:


    app/models/person.js
    export default DS.Model.extend({
      birthday: DS.attr('date')
    });
    
    

    Ember Data supports attribute types of string, number, boolean, and date. Which coerce the value to the JavaScript type that matches its name.

    Transforms are not required. If you do not specify a transform name Ember Data will do no additional processing of the value.

    Options

    DS.attr can also take a hash of options as a second parameter. At the moment the only option available is defaultValue, which can use a value or a function to set the default value of the attribute if one is not supplied.

    In the following example we define that verified has a default value of false and createdAt defaults to the current date at the time of the model's creation:


    app/models/user.js
    export default DS.Model.extend({
      username: DS.attr('string'),
      email: DS.attr('string'),
      verified: DS.attr('boolean', { defaultValue: false }),
      createdAt: DS.attr('date', {
        defaultValue() { return new Date(); }
      })
    });
    
    

    Defining Relationships

    Ember Data includes several built-in relationship types to help you define how your models relate to each other.

    One-to-One

    To declare a one-to-one relationship between two models, use DS.belongsTo:


    app/models/user.js
    export default DS.Model.extend({
      profile: DS.belongsTo('profile')
    });
    
    

    app/models/profile.js
    export default DS.Model.extend({
      user: DS.belongsTo('user')
    
    

    One-to-Many

    To declare a one-to-many relationship between two models, use DS.belongsTo in combination with DS.hasMany, like this:


    app/models/post.js
    export default DS.Model.extend({
      comments: DS.hasMany('comment')
    });
    
    

    app/models/comment.js
    export default DS.Model.extend({
      post: DS.belongsTo('post')
    });
    
    

    Many-to-Many

    To declare a many-to-many relationship between two models, use DS.hasMany:


    app/models/post.js
    export default DS.Model.extend({
      tags: DS.hasMany('tag')
    });
    
    

    app/models/tag.js
    export default DS.Model.extend({
      posts: DS.hasMany('post')
    });
    
    

    Explicit Inverses

    Ember Data will do its best to discover which relationships map to one another. In the one-to-many code above, for example, Ember Data can figure out that changing the comments relationship should update the post relationship on the inverse because post is the only relationship to that model.

    However, sometimes you may have multiple belongsTo/hasManys for the same type. You can specify which property on the related model is the inverse using DS.belongsTo or DS.hasMany's inverse option. Relationships without an inverse can be indicated as such by including { inverse: null }.


    app/models/comment.js
    export default DS.Model.extend({
      onePost: DS.belongsTo('post', { inverse: null }),
      twoPost: DS.belongsTo('post'),
      redPost: DS.belongsTo('post'),
      bluePost: DS.belongsTo('post')
    });
    
    

    app/models/post.js
    
    export default DS.Model.extend({
      comments: DS.hasMany('comment', {
        inverse: 'redPost'
      })
    });
    

    Reflexive Relations

    When you want to define a reflexive relation (a model that has a relationship to itself), you must explicitly define the inverse relationship. If there is no inverse relationship then you can set the inverse to null.

    Here's an example of a one-to-many reflexive relationship:


    app/models/folder.js
    export default DS.Model.extend({
      name: DS.attr('string'),
      bestFriend: DS.belongsTo('user', { inverse: 'bestFriend' }),
    });
    
    

    Here's an example of a one-to-one reflexive relationship:


    
    
    

    You can also define a reflexive relationship that doesn't have an inverse:


    app/models/folder.js
    export default DS.Model.extend({
      parent: DS.belongsTo('folder', { inverse: null })
    });
    
    

    Readonly Nested Data

    Some models may have properties that are deeply nested objects of readonly data. The naïve solution would be to define models for each nested object and use hasMany and belongsTo to recreate the nested relationship. However, since readonly data will never need to be updated and saved this often results in the creation of a great deal of code for very little benefit. An alternate approach is to define these relationships using an attribute with no transform (DS.attr()). This makes it easy to access readonly values in computed properties and templates without the overhead of defining extraneous models.

    The Ember Data store provides an interface for retrieving records of a single type.

    Retrieving a Single Record

    Use store.findRecord() to retrieve a record by its type and ID. This will return a promise that fulfills with the requested record:


    
    var post = this.store.findRecord('post', 1); // => GET /posts/1
    

    Use store.peekRecord() to retrieve a record by its type and ID, without making a network request. This will return the record only if it is already present in the store:


    var post = this.store.peekRecord('post', 1); // => no network request
    

    Retrieving Multiple Records

    Use store.findAll() to retrieve all of the records for a given type:


    
    var posts = this.store.findAll('post'); // => GET /posts
    

    Use store.peekAll() to retrieve all of the records for a given type that are already loaded into the store, without making a network request:


    var posts = this.store.peekAll('post'); // => no network request
    
    

    store.findAll() returns a DS.PromiseArray that fulfills to a DS.RecordArray and store.peekAll directly returns a DS.RecordArray.

    It's important to note that DS.RecordArray is not a JavaScript array. It is an object that implements Ember.Enumerable. This is important because, for example, if you want to retrieve records by index, the [] notation will not work--you'll have to use objectAt(index) instead.

    Querying for Multiple Records

    Ember Data provides the ability to query for records that meet certain criteria. Calling store.query() will make a GET request with the passed object serialized as query params. This method returns a DS.PromiseArray in the same way as find.

    For example, we could search for all person models who have the name of Peter:


    // GET to /persons?filter[name]=Peter
    this.store.query('person', { filter: { name: 'Peter' } }).then(function(peters) {
      // Do something with `peters`
    });
    
    

    Querying for A Single Record

    If you know your query will return only one result Ember Data provides a convenience method that will return a promise that resolves with a single record. Calling store.queryRecord() will make a GET request with the passed object serialized as query params.

    For example, if we know that an email uniquely identifies a person, we could search for a person model that has an email address of tomster@example.com:


    // GET to /persons?filter[email]=tomster@example.com
    this.store.queryRecord('person', { filter: { email: 'tomster@example.com' } }).then(function(tomster) {
      // do something with `tomster`
    });
    
    

    Creating Records

    You can create records by calling the createRecord() method on the store.


    store.createRecord('post', {
      title: 'Rails is Omakase',
      body: 'Lorem ipsum'
    });
    
    

    The store object is available in controllers and routes using this.store.

    Although createRecord is fairly straightforward, the only thing to watch out for is that you cannot assign a promise as a relationship, currently.

    For example, if you want to set the author property of a post, this would not work if the user with id isn't already loaded into the store:


    var store = this.store;
    
    store.createRecord('post', {
      title: 'Rails is Omakase',
      body: 'Lorem ipsum',
      author: store.findRecord('user', 1)
    });
    
    

    However, you can easily set the relationship after the promise has fulfilled:


    var store = this.store;
    
    var post = store.createRecord('post', {
      title: 'Rails is Omakase',
      body: 'Lorem ipsum'
    });
    
    store.findRecord('user', 1).then(function(user) {
      post.set('author', user);
    });
    
    

    Updating Records

    Making changes to Ember Data records is as simple as setting the attribute you want to change:


    this.store.findRecord('person', 1).then(function(tyrion) {
      // ...after the record has loaded
      tyrion.set('firstName', "Yollo");
    });
    
    

    All of the Ember.js conveniences are available for modifying attributes. For example, you can use Ember.Object's incrementProperty helper:


    person.incrementProperty('age'); // Happy birthday!
    
    

    Persisting Records

    Records in Ember Data are persisted on a per-instance basis. Call save() on any instance of DS.Model and it will make a network request.

    Ember Data takes care of tracking the state of each record for you. This allows Ember Data to treat newly created records differently from existing records when saving.

    By default, Ember Data will POST newly created records to their type url.


    var post = store.createRecord('post', {
      title: 'Rails is Omakase',
      body: 'Lorem ipsum'
    });
    
    post.save(); // => POST to '/posts'
    
    

    Records that already exist on the backend are updated using the HTTP PATCH verb.


    store.findRecord('post', 1).then(function(post) {
      post.get('title'); // => "Rails is Omakase"
    
      post.set('title', 'A new post');
    
      post.save(); // => PATCH to '/posts/1'
    });
    
    

    You can tell if a record has outstanding changes that have not yet been saved by checking its hasDirtyAttributes property. You can also see what parts of the record were changed and what the original value was using the changedAttributes() method. changedAttributes returns an object, whose keys are the changed properties and values are an array of values [oldValue, newValue].


    person.get('isAdmin');            //=> false
    person.get('hasDirtyAttributes'); //=> false
    person.set('isAdmin', true);
    person.get('hasDirtyAttributes'); //=> true
    person.changedAttributes();       //=> { isAdmin: [false, true] }
    
    

    At this point, you can either persist your changes via save() or you can roll back your changes. Calling rollbackAttributes() for a saved record reverts all the changedAttributes to their original value. If the record isNew it will be removed from the store.


    person.get('hasDirtyAttributes'); //=> true
    person.changedAttributes();       //=> { isAdmin: [false, true] }
    
    person.rollbackAttributes();
    
    person.get('hasDirtyAttributes'); //=> false
    person.get('isAdmin');            //=> false
    person.changedAttributes();       //=> {}
    
    

    Handling Validation Errors

    If the backend server returns validation errors after trying to save, they will be available on the errors property of your model. Here's how you might display the errors from saving a blog post in your template:


    {{#each post.errors.title as |error|}}
      <div class="error">{{error.message}}</div>
    {{/each}}
    {{#each post.errors.body as |error|}}
      <div class="error">{{error.message}}</div>
    {{/each}}
    
    
    
    
    

    Promises

    save() returns a promise, which makes easy to asynchronously handle success and failure scenarios. Here's a common pattern:


    var post = store.createRecord('post', {
      title: 'Rails is Omakase',
      body: 'Lorem ipsum'
    });
    
    var self = this;
    
    function transitionToPost(post) {
      self.transitionToRoute('posts.show', post);
    }
    
    function failure(reason) {
      // handle the error
    }
    
    post.save().then(transitionToPost).catch(failure);
    
    // => POST to '/posts'
    // => transitioning to posts.show route
    
    

    Deleting Records

    Deleting records is as straightforward as creating records. Call deleteRecord() on any instance of DS.Model. This flags the record as isDeleted. The deletion can then be persisted using save(). Alternatively, you can use the destroyRecord method to delete and persist at the same time.


    store.findRecord('post', 1).then(function(post) {
      post.deleteRecord();
      post.get('isDeleted'); // => true
      post.save(); // => DELETE to /posts/1
    });
    
    // OR
    store.findRecord('post', 2).then(function(post) {
      post.destroyRecord(); // => DELETE to /posts/2
    });
    
    

    Deleted records will still show up in RecordArrays returned by store.peekAll() and hasMany relationships until they have been successfully saved.

    One way to think about the store is as a cache of all of the records that have been loaded by your application. If a route or a controller in your app asks for a record, the store can return it immediately if it is in the cache. Otherwise, the store must ask the adapter to load it, which usually means a trip over the network to retrieve it from the server.

    Instead of waiting for the app to request a record, however, you can push records into the store's cache ahead of time.

    This is useful if you have a good sense of what records the user will need next. When they click on a link, instead of waiting for a network request to finish, Ember.js can render the new template immediately. It feels instantaneous.

    Another use case for pushing in records is if your application has a streaming connection to a backend. If a record is created or modified, you want to update the UI immediately.

    Pushing Records

    To push a record into the store, call the store's push() method.

    For example, imagine we want to preload some data into the store when the application boots for the first time.

    We can use the route:application to do so. The route:application is the top-most route in the route hierarchy, and its model hook gets called once when the app starts up.


    app/models/album.js
    export default DS.Model.extend({
      title: DS.attr(),
      artist: DS.attr(),
      songCount: DS.attr()
    });
    

    app/routes/application.js
    export default Ember.Route.extend({
      model() {
        this.store.push({
          data: [{
            id: 1,
            type: 'album',
            attributes: {
              title: 'Fewer Moving Parts',
              artist: 'David Bazan',
              songCount: 10
            },
            relationships: {}
          }, {
            id: 2,
            type: 'album',
            attributes: {
              title: 'Calgary b/w I Can\'t Make You Love Me/Nick Of Time',
              artist: 'Bon Iver',
              songCount: 2
            },
            relationships: {}
          }]
        });
      }
    });
    

    The store's push() method is a low level API which accepts a JSON API document with a few important differences from the JSON API document that the JSONAPISerializer accepts. The type name in the JSON API document must match the type name of the model exactly (In the example above the type is album because the model is defined in app/models/album.js). Attributes and relationship names must match the casing of the properties defined on the Model class.

    If you would like to the data to be normalized by the model's default serializer before pushing it into the store, you can use the store.pushPayload() method.


    app/serializers/album.js
    export default DS.RestSerializer.extend({
      normalize(typeHash, hash) {
        hash['songCount'] = hash['song_count']
        delete hash['song_count']
        return this._super(typeHash, hash);
      }
    
    })
    

    app/routes/application.js
    export default Ember.Route.extend({
      model() {
        this.store.pushPayload({
          albums: [
            {
              id: 1,
              title: 'Fever Moving Parts',
              artist: 'David Bazan',
              song_count: 10
            },
            {
              id: 2,
              title: 'Calgary b/w I Can\'t Make You Love Me/Nick Of Time',
              artist: 'Bon Iver',
              song_count: 2
            }
          ]
        });
      }
    });
    

    The push() method is also important when working with complex endpoints. You may find your application has an endpoint that performs some business logic then creates several records. This likely does not map cleanly to Ember Data's existing save() API which is structured around persisting a single record. Instead you should make your own custom AJAX request and push the resulting model data into the store so it can be accessed by other parts of your application.


    app/routes/confirm-payment.js
    export default Ember.Route.extend({
      actions: {
        confirm: function(data) {
          $.ajax({
            data: data,
            method: 'POST',
            url: 'process-payment'
          }).then((digitalInventory) => {
            this.store.pushPayload(digitalInventory);
            this.transitionTo('thank-you');
          });
        }
      }
    });
    

    Let's assume that we have a post and a comment model, which are related to each other as follows:


    app/models/post.js
    export default DS.Model.extend({
      comments: DS.hasMany('comment')
    });
    

    app/models/comment.js
    export default DS.Model.extend({
      post: DS.belongsTo('post')
    });
    

    When a user comments on a post, we need to create a relationship between the two records. We can simply set the belongsTo relationship in our new comment:


    let post = this.store.peekRecord('post', 1);
    let comment = this.store.createRecord('comment', {
      post: post
    });
    comment.save();
    

    This will create a new comment record and save it to the server. Ember Data will also update the post to include our newly created comment in its comments relationship.

    We could have also linked the two records together by updating the post's hasMany relationship:


    JS
    let post = this.store.peekRecord('post', 1);
    let comment = this.store.createRecord('comment', {
    });
    post.get('comments').pushObject(comment);
    comment.save();
    

    In this case the new comment's belongsTo relationship will be set to the parent post.

    Updating Existing Records

    Sometimes we want to set relationships on already existing records. We can simply set a belongsTo relationship:


    JS
    let post = this.store.peekRecord('post', 1);
    let comment = this.store.peekRecord('comment', 1);
    comment.set('post', post);
    comment.save();
    

    Alternatively, we could update the hasMany relationship by pushing a record into the relationship:


    JS
    let post = this.store.peekRecord('post', 1);
    let comment = this.store.peekRecord('comment', 1);
    post.get('comments').pushObject(comment);
    post.save();
    

    Removing Relationships

    To remove a belongsTo relationship, we can set it to null, which will also remove it from the hasMany side:


    JS
    let comment = this.store.peekRecord('comment', 1);
    comment.set('post', null);
    comment.save();
    

    It is also possible to remove a record from a hasMany relationship:


    JS
    let post = this.store.peekRecord('post', 1);
    let comment = this.store.peekRecord('comment', 1);
    post.get('comments').removeObject(comment);
    post.save();
    

    As in the earlier examples, the comment's belongsTo relationship will also be cleared by Ember Data.

    Relationships as Promises

    While working with relationships it is important to remember that they return promises.

    For example, if we were to work on a post's asynchronous comments, we would have to wait until the promise has fulfilled:


    JS
    let post = this.store.peekRecord('post', 1);
    
    post.get('comments').then((comments) => {
      // now we can work with the comments
    });
    

    The same applies to belongsTo relationships:


    JS
    let comment = this.store.peekRecord('comment', 1);
    
    comment.get('post').then((post) => {
      // the post is available here
    });
    

    Handlebars templates will automatically be updated to reflect a resolved promise. We can display a list of comments in a post like so:


    HBS
    <ul>
      {{#each post.comments as |comment|}}
        <li>{{comment.id}}</li>
      {{/each}}
    </ul>
    

    Ember Data will query the server for the appropriate records and re-render the template once the data is received.

    Along with the records returned from your store, you'll likely need to handle some kind of metadata. Metadata is data that goes along with a specific model or type instead of a record.

    Pagination is a common example of using metadata. Imagine a blog with far more posts than you can display at once. You might query it like so:


    JS
    let result = this.store.query('post', {
      limit: 10,
      offset: 0
    });
    

    To get different pages of data, you'd simply change your offset in increments of 10. So far, so good. But how do you know how many pages of data you have? Your server would need to return the total number of records as a piece of metadata.

    Each serializer will expect the metadata to be returned differently. For example, Ember Data's JSON deserializer looks for a meta key:


    JS
    {
      "post": {
        "id": 1,
        "title": "Progressive Enhancement is Dead",
        "comments": ["1", "2"],
        "links": {
          "user": "/people/tomdale"
        },
        // ...
      },
    
      "meta": {
        "total": 100
      }
    }
    

    Regardless of the serializer used, this metadata is extracted from the response. You can then read it with .get('meta').

    This can be done on the result of a store.query() call:


    JS
    store.query('post').then((result) => {
      let meta = result.get('meta');
    })
    

    On a belongsTo relationship:


    JS
    let post = store.peekRecord('post', 1);
    
    post.get('author').then((author) => {
      let meta = author.get('meta');
    });
    

    Or on a hasMany relationship:


    JS
    let post = store.peekRecord('post', 1);
    
    post.get('comments').then((comments) => {
      let meta = comments.get('meta');
    });
    

    After reading it, meta.total can be used to calculate how many pages of posts you'll have.

    To customize metadata extraction, check out the documentation for your serializer.

    In Ember Data, the Adapter determines how data is persisted to a backend data store, such as the URL format and headers for a REST API. (The format of the data itself is determined by the serializer.) Ember Data's default Adapter has some built-in assumptions of how a REST API should look. If your backend conventions differ from these assumptions Ember Data makes it easy to change its functionality by swapping out or extending the default Adapter.

    Some reasons for customizing an Adapter include using underscores_case in your urls, using a medium other than REST to communicate with your backend API or even using a local storage backend.

    Extending Adapters is a natural process in Ember Data. Ember takes the position that you should extend an adapter to add different functionality. This results in code that is more testable, easier to understand and reduces bloat for people who may want to subclass your adapter.

    If your backend has some consistent rules you can define an adapter:application. The adapter:application will get priority over the default Adapter, however it will still be superseded by model specific Adapters.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      // Application specific overrides go here
    });
    

    If you have one model that has exceptional rules for communicating with its backend than the others you can create a Model specific Adapter by running the command ember generate adapter adapter-name". For example, running ember generate adapter post will create the following file:


    app/adapters/post.js
    export default DS.JSONAPIAdapter.extend({
      namespace: 'api/v1'
    });
    

    By default Ember Data comes with several built-in adapters. Feel free to use these adapters as a starting point for creating your own custom adapter.

    • DS.Adapter is the basic adapter with no functionality. It is generally a good starting point if you want to create an adapter that is radically different from the other Ember adapters.

    • DS.JSONAPIAdapter The JSONAPIAdapter is the default adapter and follows JSON API conventions to communicate with an HTTP server by transmitting JSON via XHR.

    • DS.RESTAdapter The RESTAdapter allows your store to communicate with an HTTP server by transmitting JSON via XHR. Before Ember Data 2.0 this adapter was the default.

    Customizing the JSONAPIAdapter

    The DS.JSONAPIAdapter has a handful of hooks that are commonly used to extend it to work with non-standard backends.

    URL Conventions

    The JSONAPIAdapter is smart enough to determine the URLs it communicates with based on the name of the model. For example, if you ask for a Post by ID:


    JS
    store.find('post', 1).then(function(post) {
    });
    

    The JSON API adapter will automatically send a GET request to /posts/1.

    The actions you can take on a record map onto the following URLs in the JSON API adapter:

    ActionHTTP VerbURL
    FindGET/posts/123
    Find AllGET/posts
    UpdatePATCH/posts/123
    CreatePOST/posts
    DeleteDELETE/posts/123

    Pluralization Customization

    To facilitate pluralizing model names when generating route urls Ember Data comes bundled with Ember Inflector, a ActiveSupport::Inflector compatible library for inflecting words between plural and singular forms. Irregular or uncountable pluralizations can be specified via Ember.Inflector.inflector. A common way to do this is:


    app/app.js
    // sets up Ember.Inflector
    import './models/custom-inflector-rules';
    

    app/models/custom-inflector-rules.js
    var inflector = Ember.Inflector.inflector;
    
    inflector.irregular('formula', 'formulae');
    inflector.uncountable('advice');
    
    // Meet Ember Inspector's expectation of an export
    export default {};
    

    This will tell the JSON API adapter that requests for formula should go to /formulae/1 instead of /formulas/1.

    Endpoint Path Customization

    The namespace property can be used to prefix requests with a specific url namespace.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      namespace: 'api/1'
    });
    

    Requests for person would now target http://aitechtonic.com/api/1/people/1.

    Host Customization

    By default the adapter will target the current domain. If you would like to specify a new domain you can do so by setting the host property on the adapter.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      host: 'https://api.example.com'
    });
    

    Requests for person would now target https://api.example.com/people/1.

    Path Customization

    By default the JSONAPIAdapter will attempt to pluralize and dasherize the model name to generate the path name. If this convention does not conform to your backend you can override the pathForType method.

    For example, if you did not want to pluralize model names and needed underscore_case instead of camelCase you could override the pathForType method like this:


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      pathForType: function(type) {
        return Ember.String.underscore(type);
      }
    });
    

    Requests for person would now target /person/1. Requests for user-profile would now target /user_profile/1.

    Headers customization

    Some APIs require HTTP headers, e.g. to provide an API key. Arbitrary headers can be set as key/value pairs on the JSONAPIAdapter's headers object and Ember Data will send them along with each ajax request.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      headers: {
        'API_KEY': 'secret key',
        'ANOTHER_HEADER': 'Some header value'
      }
    });
    

    headers can also be used as a computed property to support dynamic headers. In the example below, the headers are generated with a computed property dependent on the session service.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      session: Ember.inject.service('session'),
      headers: Ember.computed('session.authToken', function() {
        return {
          'API_KEY': this.get('session.authToken'),
          'ANOTHER_HEADER': 'Some header value'
        };
      })
    });
    

    In some cases, your dynamic headers may require data from some object outside of Ember's observer system (for example document.cookie). You can use the volatile function to set the property into a non-cached mode causing the headers to be recomputed with every request.


    app/adapters/application.js
    export default DS.JSONAPIAdapter.extend({
      headers: Ember.computed(function() {
        return {
          'API_KEY': Ember.get(document.cookie.match(/apiKey\=([^;]*)/), '1'),
          'ANOTHER_HEADER': 'Some header value'
        };
      }).volatile()
    });
    

    Authoring Adapters

    The defaultSerializer property can be used to specify the serializer that will be used by this adapter. This is only used when a model specific serializer or serializer:application are not defined.

    In an application, it is often easier to specify an serializer:application. However, if you are the author of a community adapter it is important to remember to set this property to ensure Ember does the right thing in the case a user of your adapter does not specify an serializer:application.


    app/adapters/my-custom-adapter.js
    export default DS.JSONAPIAdapter.extend({
      defaultSerializer: '-default'
    });
    

    Community Adapters

    If none of the built-in Ember Data Adapters work for your backend, be sure to check out some of the community maintained Ember Data Adapters. Some good places to look for Ember Data Adapters include:

    In Ember Data, serializers format the data sent to and received from the backend store. By default, Ember Data serializes data using the JSON API format. If your backend uses a different format, Ember Data allows you to customize the serializer or use a different serializer entirely.

    Ember Data ships with 3 Serializers. The JSONAPISerializer is the default serializer and works with JSON API backends. The JSONSerializer is a simple serializer for working with single json object or arrays of records. The RESTSerializer is a more complex serializer that supports sideloading and was the default serializer before 2.0.

    JSONAPISerializer Conventions

    When requesting a record, the JSONAPISerializer expects your server to return a JSON representation of the record that conforms to the following conventions.

    JSON API Document

    The JSONAPI serializer expects the backend to return a JSON API Document that follows the JSON API specification and the conventions of the examples found on http://jsonapi.org/format. This means all type names should be pluralized and attribute and relationship names should be dash-cased. For example, if you request a record from /people/123, the response should looks like this:


    JS
    {
      "data": {
        "type": "people",
        "id": "123",
        "attributes": {
          "first-name": "Jeff",
          "last-name": "Atwood"
        }
      }
    }
    

    A response that contains multiple records may have an array in its data property.


    JS
    {
      "data": [{
        "type": "people",
        "id": "123",
        "attributes": {
          "first-name": "Jeff",
          "last-name": "Atwood"
        }
      }, {
        "type": "people",
        "id": "124",
        "attributes": {
          "first-name": "Yehuda",
          "last-name": "Katz"
        }
      }]
    }
    

    Sideloaded Data

    Data that is not a part of the primary request but includes linked relationships should be placed in an array under the included key. For example if you /people/1 and the backend also returned any comments associated with that relationship the response should look like this:


    JS
    {
      "data": {
        "type": "articles",
        "id": "1",
        "attributes": {
          "title": "JSON API paints my bikeshed!"
        },
        "links": {
          "self": "http://example.com/articles/1"
        },
        "relationships": {
          "comments": {
            "data": [
              { "type": "comments", "id": "5" },
              { "type": "comments", "id": "12" }
            ]
          }
        }
      }],
      "included": [{
        "type": "comments",
        "id": "5",
        "attributes": {
          "body": "First!"
        },
        "links": {
          "self": "http://example.com/comments/5"
        }
      }, {
        "type": "comments",
        "id": "12",
        "attributes": {
          "body": "I like XML better"
        },
        "links": {
          "self": "http://example.com/comments/12"
        }
      }]
    }
    

    Customizing Serializers

    Ember Data uses the JSONAPISerializer by default, but you can override this default by defining a custom serializer. There are two ways to define a custom serializer. First, you can define a custom serializer for you entire application by defining an "application" serializer.


    app/serializers/application.js
    import DS from 'ember-data';
    
    export default DS.JSONSerializer.extend({});
    

    You can also define serializer for a specific model. For example if you had a post model you could also define a post serializer:


    app/serializers/post.js
    import DS from 'ember-data';
    
    export default DS.JSONSerializer.extend({});
    

    To change the format of the data that is sent to the backend store, you can use the serialize() hook. Let's say that we have this JSON API response from Ember Data:


    {
      "data": {
        "attributes": {
          "id": "1",
          "name": "My Product",
          "amount": 100,
          "currency": "SEK"
        },
        "type": "product"
      }
    }
    

    But our server expects data in this format:


    {
      "data": {
        "attributes": {
          "id": "1",
          "name": "My Product",
          "cost": {
            "amount": 100,
            "currency": "SEK"
          }
        },
        "type": "product"
      }
    }
    

    Here's how you can change the data:


    app/serializers/application.js
    import DS from 'ember-data';
    
    export default DS.JSONSerializer.extend({
      serialize(snapshot, options) {
        var json = this._super(...arguments);
    
        json.data.attributes.cost = {
          amount: json.data.attributes.amount,
          currency: json.data.attributes.currency
        };
    
        delete json.data.attributes.amount;
        delete json.data.attributes.currency;
    
        return json;
      },
    });
    

    Similarly, if your backend store provides data in a format other than JSON API, you can use the normalizeResponse() hook. Using the same example as above, if the server provides data that looks like:


    {
      "data": {
        "attributes": {
          "id": "1",
          "name": "My Product",
          "cost": {
            "amount": 100,
            "currency": "SEK"
          }
        },
        "type": "product"
      }
    }
    

    And so we need to change it to look like:


    {
      "data": {
        "attributes": {
          "id": "1",
          "name": "My Product",
          "amount": 100,
          "currency": "SEK"
        },
        "type": "product"
      }
    }
    

    Here's how we could do it:


    app/serializers/application.js
    import DS from 'ember-data';
    
    export default DS.JSONSerializer.extend({
      normalizeResponse(store, primaryModelClass, payload, id, requestType) {
        payload.data.attributes.amount = payload.data.attributes.cost.amount;
        payload.data.attributes.currency = payload.data.attributes.cost.currency;
    
        delete payload.data.attributes.cost;
    
        return this._super(...arguments);
      },
    });
    

    To normalize only a single model, you can use the normalize() hook similarly.

    For more hooks to customize the serializer with, see the Ember Data serializer API documentation.

    IDs

    In order to keep track of unique records in the store Ember Data expects every record to have an id property in the payload. Ids should be unique for every unique record of a specific type. If your backend used a different key other then id you can use the serializer's primaryKey property to correctly transform the id property to id when serializing and deserializing data.


    app/serializers/application.js
    export default DS.JSONSerializer.extend({
      primaryKey: '_id'
    });
    

    Attribute Names

    In Ember Data the convention is to camelize attribute names on a model. For example:


    app/models/person.js
    export default DS.Model.extend({
      firstName: DS.attr('string'),
      lastName:  DS.attr('string'),
    
      isPersonOfTheYear: DS.attr('boolean')
    });
    

    However, the JSONAPISerializer expects attributes to be dasherized in the document payload returned by your server:


    JS
    {
      "data": {
        "id": "44",
        "type": "people",
        "attributes": {
          "first-name": "Barack",
          "last-name": "Obama",
          "is-person-of-the-year": true
        }
      }
    }
    

    If the attributes returned by your server use a different convention you can use the serializer's keyForAttribute() method to convert an attribute name in your model to a key in your JSON payload. For example, if your backend returned attributes that are under_scored instead of dash-cased you could override the keyForAttribute method like this.


    app/serializers/application.js
    export default DS.JSONAPISerializer.extend({
      keyForAttribute: function(attr) {
        return Ember.String.underscore(attr);
      }
    });
    

    Irregular keys can be mapped with a custom serializer. The attrs object can be used to declare a simple mapping between property names on DS.Model records and payload keys in the serialized JSON object representing the record. An object with the property key can also be used to designate the attribute's key on the response payload.

    If the JSON for person has a key of lastNameOfPerson, and the desired attribute name is simply lastName, then create a custom Serializer for the model and override the attrs property.


    app/models/person.js
    export default DS.Model.extend({
      lastName: DS.attr('string')
    });
    

    app/serializers/person.js
    export default DS.JSONAPISerializer.extend({
      attrs: {
        lastName: 'lastNameOfPerson',
      }
    });
    

    Relationships

    References to other records should be done by ID. For example, if you have a model with a hasMany relationship:


    app/models/post.js
    export default DS.Model.extend({
      comments: DS.hasMany('comment', { async: true })
    });
    

    The JSON should encode the relationship as an array of IDs and types:


    JS
    {
      "data": {
        "type": "posts",
        "id": "1",
        "relationships": {
          "comments": {
            "data": [
              { "type": "comments", "id": "5" },
              { "type": "comments", "id": "12" }
            ]
          }
        }
      }
    }
    

    Comments for a post can be loaded by post.get('comments'). The JSON API adapter will send 3 GET request to /comments/1/, /comments/2/ and /comments/3/.

    Any belongsTo relationships in the JSON representation should be the dasherized version of the property's name. For example, if you have a model:


    app/models/comment.js
    export default DS.Model.extend({
      originalPost: DS.belongsTo('post')
    });
    

    The JSON should encode the relationship as an ID to another record:


    JS
    {
      "data": {
        "type": "comment",
        "id": "1",
        "relationships": {
          "original-post": {
            "data": { "type": "post", "id": "5" },
          }
        }
      }
    }
    

    If needed these naming conventions can be overwritten by implementing the keyForRelationship() method.


    app/serializers/application.js
    export default DS.JSONAPISerializer.extend({
      keyForRelationship: function(key, relationship) {
        return key + 'Ids';
      }
    });
    

    Creating Custom Transformations

    In some circumstances, the built in attribute types of string, number, boolean, and date may be inadequate. For example, a server may return a non-standard date format.

    Ember Data can have new JSON transforms registered for use as attributes:


    app/transforms/coordinate-point.js
    export default DS.Transform.extend({
      serialize: function(value) {
        return [value.get('x'), value.get('y')];
      },
      deserialize: function(value) {
        return Ember.create({ x: value[0], y: value[1] });
      }
    });
    

    app/models/cursor.js
    export default DS.Model.extend({
      position: DS.attr('coordinate-point')
    });
    

    When coordinatePoint is received from the API, it is expected to be an array:


    JS
    {
      cursor: {
        position: [4,9]
      }
    }
    

    But once loaded on a model instance, it will behave as an object:


    JS
    var cursor = store.findRecord('cursor', 1);
    cursor.get('position.x'); //=> 4
    cursor.get('position.y'); //=> 9
    

    If position is modified and saved, it will pass through the serialize function in the transform and again be presented as an array in JSON.

    JSONSerializer

    Not all APIs follow the conventions that the JSONAPISerializer uses with a data namespace and sideloaded relationship records. Some legacy APIs may return a simple JSON payload that is just the resource request or an array of serialized records. The JSONSerializer is a serializer that ships with Ember Data that can be used along side the RESTAdapter to serialize these simpler APIs.

    To use it in your application you will need to define an adapter:application that extends the JSONSerializer.


    app/serializers/application.js
    export default DS.JSONSerializer.extend({
      // ...
    });
    

    For requests that are only expected to return 1 record (e.g. store.findRecord('post', 1)) the JSONSerializer expects the response to be a JSON object that looks similar to this:


    {
        "id": "1",
        "title": "Rails is omakase",
        "tag": "rails",
        "comments": ["1", "2"]
    }
    

    For requests that are only expected to return 0 or more records (e.g. store.findAll('post') or store.query('post', { filter: { status: 'draft' } })) the JSONSerializer expects the response to be a JSON array that looks similar to this:


    [{
      "id": "1",
      "title": "Rails is omakase",
      "tag": "rails",
      "comments": ["1", "2"]
    }, {
      "id": "2",
      "title": "I'm Running to Reform the W3C's Tag",
      "tag": "w3c",
      "comments": ["3"]
    }]
    

    The JSONAPISerializer is built on top of the JSONSerializer so they share many of the same hooks for customizing the behavior of the serialization process. Be sure to check out the API docs for a full list of methods and properties.

    EmbeddedRecordMixin

    Although Ember Data encourages you to sideload your relationships, sometimes when working with legacy APIs you may discover you need to deal with JSON that contains relationships embedded inside other records. The EmbeddedRecordsMixin is meant to help with this problem.

    To set up embedded records, include the mixin when extending a serializer then define and configure embedded relationships.

    For example if your post model contained an embedded author record that looks similar to this:


    {
        "id": "1",
        "title": "Rails is omakase",
        "tag": "rails",
        "authors": [
            {
                "id": "2",
                "name": "Steve"
            }
        ]
    }
    

    You would define your relationship like this:


    app/serializers/post.js
    export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
      attrs: {
        author: {
          serialize: 'records',
          deserialize: 'records'
        }
      }
    });
    

    If you find yourself needing to both serialize and deserialize the embedded relationship you can use the shorthand option of { embedded: 'always' }. The following example and the one above are equivalent.


    app/serializers/post.js
    export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
      attrs: {
        author: { embedded: 'always' }
      }
    });
    

    The serialize and deserialize keys support 3 options. - records is uses to signal that the entire record is expected - ids is uses to signal that only the id of the record is expected - false is uses to signal that the record is not expected

    For example you may find that you want to read an embedded record when extracting a JSON payload but only include the relationship's id when serializing the record. This is possible by using the serialize: 'ids' option. You can also opt out of serializing a relationship by setting serialize: false.


    app/serializers/post.js
    export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
      attrs: {
        author: {
          serialize: false,
          deserialize: 'records'
        },
        comments: {
          deserialize: 'records',
          serialize: 'ids'
        }
      }
    });
    

    EmbeddedRecordsMixin Defaults

    If you do not overwrite attrs for a specific relationship, the EmbeddedRecordsMixin will behave in the following way:

    BelongsTo: { serialize: 'id', deserialize: 'id' } HasMany: { serialize: false, deserialize: 'ids' }

    There is an option of not embedding JSON in the serialized payload by using serialize: 'ids'. If you do not want the relationship sent at all, you can use serialize: false.

    Authoring Serializers

    If you would like to create a custom serializer its recommend that you start with the JSONAPISerializer or JSONSerializer and extend one of those to match your needs. However, if your payload is extremely different from one of these serializers you can create your own by extending the DS.Serializer base class. There are 3 methods that must be implemented on a serializer.

    Its also important to know about the normalized JSON form that Ember Data expects as an argument to store.push().

    store.push accepts a JSON API document. However, unlike the JSONAPISerializer, store.push does not do any transformation of the record's type name or attributes. It is important to make sure that the type name matches the name of the file where it is defined exactly. Also attribute and relationship names in the JSON API document should match the name an casing of the attribute and relationship properties on the Model.

    For Example: given this post model.


    app/models/post.js
    export default DS.Model.extend({
      title: DS.attr('string'),
      tag: DS.attr('string'),
      comments: hasMany('comment', { async: false }),
      relatedPosts: hasMany('post')
    });
    

    store.push would accept an object that looked like this:


    {
      data: {
        id: "1",
        type: 'post',
        attributes: {
          title: "Rails is omakase",
          tag: "rails",
        },
        relationships: {
          comments: {
            data: [{ id: "1", type: 'comment' },
                   { id: "2", type: 'comment' }],
          },
          relatedPosts: {
            data: {
              related: "/api/v1/posts/1/related-posts/"
            }
          }
        }
    }
    

    Every serialized record must follow this format for it to be correctly converted into an Ember Data record.

    Properties that are defined on the model but are omitted in the normalized JSON API document object will not be updated. Properties that are included in the normalized JSON API document object but not defined on the Model will be ignored.

    Community Serializers

    If none of the built-in Ember Data Serializers work for your backend, be sure to check out some of the community maintained Ember Data Adapters and serializers. Some good places to look for Ember Data Serializers include:

Read More...