@hussfelt

Head of Mobile at @LinasMatkasse. Technical startup advisor with a passion for doing the right thing.

© 2014. Henrik All rights reserved.

Understanding EmberJS and JSONApi 2.0

Summary

After working too many hours on a solution that was just wrong I finally figured out how to work with the serializers in Ember 1.1.13+.

The below should work for Ember 2.0 as well.

Solution

What I do below is let Ember do the magic of converting the payload from the old format to the new, I just use the new hooks defined in the blog-post EMBER DATA V1.13 RELEASED.

This should help you if you want to:

  • Change the payload and do some magic before hitting the store/model
  • Generally modify data returned from all endpoints in your API

Understanding and converting to new JSONApi 2.0 in EmberJS

Summary

The documentation on how to work with the new JSONApi 2.0 standard required by Ember-data 2.0 is still a bit rusty, and converting all your API's to conform with this new standard might not be an option...

Where I am currently working we have a backend team and a frontend team and asking them to change all the formats whilst beeing in a major rewrite simply isn't cutting it...

Our only option is to make sure that the old format returned from our internal API's is converted to JSONApi 2.0 format in our own EmberJS appllication.

Enter Serializers!

New custom Serializer

WRONG IMPLEMENTATION! Don't use the below implementation.

Use this solution instead!

The solution is to use the new hooks described in the blog-post covering Ember Data 1.13.

Below is a rough implementation where the data is converted from the old EmberData format to return the new JSONApi 2.0 format. The serializer-mixin also has a custom extra-hook in case you'd want to adjust the formatted json even more - in case you have side-loaded data for instance - this function is called normalizeArrayResponseExtra.

WARNING: It's still work in progress so make sure you test it properly before using in a production environment!

The mixin you need, including an example implementation using the normalizeArrayResponseExtra method.

How to stop using deferReadiness and advanceReadiness in Ember

Summary

Several times I have read comments from @tomdale trying to explain to people why one should avoud deferReadiness of the application inside initializers of an application. I did use it until just a couple of days ago when I finally understood how to implement at better way of reaching a set application state before showing our application to the customer.

Problem

When building a single-page application and it’s loaded for the users you most probably want to do stuff before actually serving the application to the user. This could be things like:

  • Setting up a websocket
  • Authenticating the user
  • Preloading a set of data

Your first bet would be to ask yourself the question “How do I stop loading the application until I do X?” and after that you find the deferReadiness and advanceReadiness. It does not take that long until you actually implement an initializer with the above functionality in something like this:

/**
Session Service Initializer
@class initializers.sessionService
@extends Ember.Initializer
*/


export function initialize(container, application) {
    // Wait until all of the following promises are resolved
    application.deferReadiness();

    // Get session-service
    var session = container.lookup('service:session');

    // Find the current customer, use promise to reflect to other controllers
    if (session.hasSession()) {

        // Get the store
        var store = container.lookup('store:main');

        // Fetch the user
        store.find('Customer', session.get('userId')).then(function(customer) {
            // Set customer to application controller
            session.set('user', customer);

            // preload the application
            session.preloadApplication();

            // Continue the Application boot process, allowing other Initializers to run
            application.advanceReadiness();
        }, function() {
            // Kill sesion object
            session.set('user', '');
            session.set('authToken', '');

            // Continue the Application boot process, allowing other Initializers to run
            application.advanceReadiness();
        });
    } else {
        // Continue the Application boot process, allowing other Initializers to run
        application.advanceReadiness();
    }
}

Yaay! Success! Problem is if you ever forget to advanceReadiness, if you do it more than once or if your application breaks somewhere in between - which will lead to very unexpected behaviors. @tomdale wrote “asynchronous instance initializers are a very bad idea (because they don't participate in the router promise chain) “ which makes a valid point.

So how do you solve this issue?

Use the beforeModel() hook in the route, with a returned promise on all the things you want to preload!

First I moved the setups I wanted into my service, returning a promise.

import Ember from 'ember';

export default Ember.Service.extend({

    /**
     * This is run from the route after the applciation has loaded
     * @return {[type]} [description]
     */
    setup: function() {
        return new Ember.RSVP.Promise(function(resolve, reject) {
            // Do something that either resolves or reject
            if (true) {
                resolve();
            } else {
                reject();
            }
        });
    }
});


import Ember from 'ember';

export default Ember.Service.extend({

    /**
     * This is run from the route after the applciation has loaded
     * @return {[type]} [description]
     */
    setup: function() {
        return new Ember.RSVP.Promise(function(resolve, reject) {
            // Do something that either resolves or reject
            if (true) {
                resolve();
            } else {
                reject();
            }
        });
    }
});

Then I simply made sure that these where triggered in my application beforeModel.

import Ember from 'ember';

export default Ember.Route.extend({
    /**
     * Before anything proceeds in the application, resolve these promises
     * @return {[type]} [description]
     */
    beforeModel: function() {
        return Ember.RSVP.all([this.get('sessionService').setup(), this.get('cordovaService').setup()]);
    },

This way your application will always load and your application-template will render letting your users see something else but a blank broken page, and still preload the things you need done before your model is triggered.

Sometimes you feel down

Sometimes you feel down, because you do. Sometimes you feel down because you let yourself down. Sometimes you feel down because you let others down.

The first one is easy, you just tell yourself to get a grip. The second one, you realize that you need to change. The last one is hard, first you need to change and then you need to let others know you have changed.

This is why I am aiming for never letting anyone down. Sometimes I fail.