Party of Five: Ember

Ember is an unusual beast, in some ways more Rails than React. It’s highly productive and has a uniquely developer-friendly approach.

Ember is different from the other frameworks here, because actually know it pretty well. That makes Ember the baseline for me, the reference application. This won’t be a typical experience for most people, though. As a result I’m going to have to go through the process much as a new Ember user would.

With even a vague knowledge, Ember is an impressive performer for outputting code. I know that will surprise people who may have a perception that it’s big, slow, heavy, difficult, etc. But I respectfully disagree. In fact, the reference app we’re building here I am able to create from start to finish in under half an hour in Ember. Including all persistence requirements, dependencies, and even full Server-Side Rendering. I would challenge anyone to achieve as much in any framework.

Out On Its Own

More than any other framework, Ember has a number of concepts and approaches that make it diverge from the pack. I personally think they’re good features, but they’re the elements that make Ember feel unapproachable in a world dominated so strongly by the Angular component pattern.

Abstraction

One term that will come up a lot in both this and the React article. Because React and Ember sit at opposite ends of a spectrum of abstraction levels. I’ve written about abstraction before. Ember is the most frameworky of frameworks. It is the most feature rich, but also the most distanced from pure JS. How much abstraction is “best” doesn’t really have a correct answer. My preference is for higher level abstractions. Good abstractions are typically more comprehensible, with more succinct and readable code.

Routes

The first thing you need to know to understand Ember is that it’s all about Routes.

Ember is focused entirely around being a web platform, for all the talk about redux and state management tools the first line of application state is the URL. A lot of JS apps store a lot of state internally, and Ember does not.

An example is Angular’s excellent Tour of Heroes guide and tutorial. This uses a master/detail ui pattern to show a list of heroes that can be clicked on to select and edit them, etc. In an Ember app they would undoubtedly be a URL routing. That routing setup is the first or second chapter of an Ember book. I know from that time I wrote an Ember book.

The Route is a unique abstraction to Ember. I spoke in the Aurelia article about my general discomfort with the concept that the structural routable part of an application is an identical abstraction to some trivial UI element — in both cases a “component”.

Ember is a strictly MVC application structure, and its pattern is based on this core abstraction of a Route. A Route is a JavaScript class that is the handler for a given URL pattern. This is atypical for a JavaScript framework, but standard in pattern for a backend framework like Laravel or Django — showing Ember’s Rails-descended heritage.

The Route’s role is getting the underlying model data, and passing it to the controller and template, which sets up the context of the route. It’s kind of a bootstrapper for that context. The lifecycle hook of model() is what gives it that model data.

Rich Model and State Layer — Ember Data

Ember is a strictly MVC framework. Most other JS frameworks that implement the pattern have a relatively thin model layer, typically just http calls that return json as a POJO. Ember doesn’t. It abstracts these http calls and persistence concerns in adapters that then map the data to defined models. By comparison, it’s actually also very similar to Laravel’s Eloquent ORM. This means Ember Data can actually be used with remarkable ease:

model({id}) {

return this.get('store').find('todo', id);

}

Saving is even easier.

item.save(); // update get('store')

.createRecord('todo', item)

.save(); // create and then save

Ember Data handles whether to do GET, PUT, or POST requests, etc. That’s just assuming you’re using a RESTAdapter. You can use a Firebase adapter and .save() will persist to that. Or local storage with .save() . Or whatever. The Ember Data abstraction handles the details.

I should note that this is optional. It’s also common to just use something like axios and return native js objects in the app, or make the call with a http library and then “decorate” the POJOs as Ember Data objects to populate the app.

Ember data doesn’t just store data, but also state properties, it’s not necessarily tied to your API’s data model.

Addons

Ember’s dependency management is different to most. It uses NPM under the hood, but also wraps it up for convenience. Initially this seems non-standard and pointless, but it makes a huge difference to usability. As well as NPM the CLI also adds anything required to the Broccoli build pipeline. For example, installing something like Bootstrap is trivialised as ember install ember-bootstrap and then a restart of the dev server and Bootstrap is available. There is no need to import the CSS or add it to a build tool, that’s automated.

This is even more valuable in systems that extend the CLI with additional functionality. Some of the more impressive are Ember Mirage, which creates a complex HTTP mocking system for testing or RAD, Ember Deploy, which allows commands like ember deploy staging to do what it says on the tin, and Fastboot, a single command-line install to enable Server-Side Rendering with no code changes or config.

Templating

The last difference worthy of mention is Ember’s templating is based on Handlebars, rather than the Angular-style markup-based templates. This makes constructions like if very different, as they don’t require an element to do so.

{{#if isEditing}}

//form markup

{{else}}

//list item

{{/if}}

This also extends to components, components are embedded with the same double moustache syntax, a notable difference to every other framework.

{{awesome-component awesome=true}}

Getting Started

The workflow here is generally the same as it is with any other JS framework. ember new my-ember-app followed by cd my-ember-app and ember server .

That’s happily serving at http://localhost:4200 and gives a pretty cute hamster mascot screen. Entertainingly you can actually use the hamster emoji in place of “ember”.

🐹 server

Setting Up Routing

Routing has no setup in Ember. It’s already implemented, and there’s a router available by default.

One of the first things you need to know about Ember is that it’s serious about the principle of Convention Over Configuration. Ember’s draconian adherence to conventions has upsides, though. It makes for some pleasantly succinct code.

The code required to have a /about route in Angular is to add the following lines, here taken totally out of context for brevity:

Import AboutComponent from ‘./AboutComponent’;

// stuff

{ path: ‘about’, component: AboutComponent },

The above is pretty typical and minimal for a JS framework.

Ember just needs: this.route('about');

You don’t even need to add that, you can just let the generator handle it. Typing ember generate route about will add the above to the router, and create tests, a route file and template. It will also handle nested routes quite happily.

A quick look at an example route shows us something unexpected. There’s no reference to the controller or the template here at all. No mention of components, which means no need to import them. Just a route.

Note that this is actually a pretty big deal. My test app has three routes, meaning three imports in any framework but Ember.

But I’m currently working on a Laravel application with 223 routes that will soon be ported to an Ember app. Many of those routes are legacy garbage and will be shot in the face. Others will stay in the API, but even just taking GET routes there’s likely near 100 lines of components that would need importing. Angular is even worse, as it would need to import sub-components in its module file as well.

Moving back to our router, if you look you’ll see that as well as missing the component import we’re also not defining anything that the route connects to. There’s just the path and nothing else. No way to know the name of the resource used, no way to define a template for that resource.

This is where Convention Over Configuration comes in. The Route class for the /about route is in app/routes/about.js . Because that’s where it goes. The template for the /about route is in app/templates/about.hbs because is. It doesn’t have to be stated explicitly because it can be safely assumed.

This means that if you know Ember’s conventions you can make a bunch of assumptions up front and you know where everything is right from the start. But if you don’t know Ember’s conventions — and starting off you won’t — then may God have mercy on your soul. There’s nothing to tell you how things are strung together.

Where it does excel is at bulk production of application resources.

ember new tour-of-villains

cd tour-of-villains

ember g template index

ember g route villains

ember g route villains/show --path=:id

ember g route villains/edit --path=:id/edit

ember g model villains name:string power:string

ember install ember-bulma

ember install emberfire

ember install ember-cli-sass

ember install ember-cli-fastboot

ember server

This will generate pretty much an entire application. In this case it’s the Tour of Heroes Angular tutorial we mentioned earlier, only this time it’s for villains because villains are cooler. All the files are in place to create the whole app, including Sass support, the Bulma CSS framework for no real reason except that Bootstrap isn’t fancy enough for us, Firebase integration and even Server-Side Rendering. The router is set up with all of the routes implemented. It has a home, list of villains, and a display and an edit page for each villain. The only config required is for Firebase, and there’s zero build pipeline config. Obviously templates are required, and the actions to make things save. But it’s surprising how little of that is needed. In the villains route:

model(){

return this.get('store').findAll('villain');

},

actions: {

updateVillain(villain){

villain.save();

},

createVillain(villainData){

this.get('store').createRecord('villain', villainData).save();

}

}

This is an amazing amount of functionality for so little code.

Font-Awesome, Bootstrap and the Active LI issue

As mentioned earlier, Bootstrap is trivial to install. So is Font-Awesome. ember install ember-font-awesome gives you access to an fa-icon component that is one of the few UI components I used. Because you end up with {{fa-icon 'pencil'}} , which is actually usefully short. It also supports conditional icons, and events directly on the icon component. Something that proved impossible with things like Vue-Awesome.

The active link is a mixed bag. Searching for info on how to resolve this shows a lot of pretty hacky solutions around Ember’s built-in {{link-to}} helper. There’s an addon for this issue that I’ve used in the past but it seems to trigger some deprecation warnings. This was the easiest solution.

{{#link-to 'about' tagName='li'}}<a>About Us</a>{{/link-to}}

There is a better solution in using Ember Bootstrap components, which is what I’ve done if you look at the github code. I avoided them previously because they didn’t actually resolve this issue. But it would appear they do now, so it’s worth using.

Listing The Todo Items

Data Access

Ember’s idiomatic data access is Ember Data. It’s not strictly necessary to use it, using something like $.ajax or axios is common and easier to set up.

Setting up Ember Data is going to be the first hurdle people hit. It’s not always obvious what files you need and what they do. Ember Data gets especially complex when dealing with relationships. Though actual usage is excellent: eg {{book.author.name}} .

I may have overstated the challenge a little bit. Ember Data has two key files — the serializer, and the adapter. Both can be per-model or application wide. The serializer handles data formatting, and the adapter handles connection and access. In my case, all I needed to do was generate a serializer, generate an adapter, and then change the default JSONAPIAdapter to RESTAdapter and ditto the serializer. The adapter also needs to be told where the API’s entry point is. The entire connection setup for Ember is this.

export default DS.RESTAdapter.extend({

host: 'http://todo-api.test'

});

This process isn’t obvious, but when you know it, it’s a doddle, and it’s a hugely powerful pattern I greatly recommend.

With the right setup, Ember Data is mega helpful. In order to provide data to your actual route you need to return that data from the model(){} hook in the route class. Ember is the only framework that includes a central store with no further installation or setup. The store is automatically injected into Routes and Controllers and used directly. This is is used much like an ORM. Getting a list of todo items into the todo route is trivial, then.

model(){

return this.store.findAll('todo');

}

Templates

With that done, the process of looping is trivialised.

{{#each model as |item|}}

<li>{{item.title}}</li>

{{else}}

<li>No items found</li>

{{/each}}

I have to say I like the overall syntax, and it’s hard to beat that code for clarity and brevity. The else implementation is surprisingly hard to do in some other frameworks.

But I hate that |item| bit. The pipes are a Railsism that doesn’t help at all. It should work without them and it doesn’t and that annoys me. It used to work but now it doesn’t because “reasons”.

Philosophically, Ember templates are not intended to do much, particularly they’re not intended to be able to execute directives. That even goes to the degree that there’s no such thing as a an equals check. You can’t do something like {{if pet.type == 'cat'}} . One of the most popular Ember addons provides support for something like this. But it’s preferred to either use computed properties like isCat or create Handlebars helpers that better reflect the intent. This is a trivial example, but sometimes the goals are better served by a simple canEdit helper that wraps up a load of business logic and access checks than actually doing those in template.

There’s a particular area things become notably more difficult in Ember than any other framework, and that’s updating state directly on events.

Some examples.

// Aurelia

<button click.trigger="isEditing=true"> // Angular

<button (click)="isEditing=true"> // Vue

<button @click="isEditing=true"> // Ember

<button {{action (mut isEditing) true}}>

Ember’s very obviously an odd man out here. Its syntax for this is unquestionably more complex and harder to understand. I shotgun it every time. Does isEditing have quotes? Where do the brackets go again? The philosophical and architectural stance Ember takes is a valid one, but there are consequences.

List Item Components

There is a perception that Ember doesn’t use components. That’s not true. Components are a major part of Ember, and provide dedicated, isolated sections of UI.

However, Ember is much more prescriptive and opinionated about what components are for and how they work than other frameworks. For example, while either routes or controllers can access Ember Data quite happily, a component can’t.

Ember’s view is that a component is a custom markup element. It’s not the job of the table tag to know how to get its data, and it’s not the job of an input to know how to save.

Whether this is reasonable or not, it’s a significant difference from other frameworks, where components are a first order abstraction with access to the persistence layer.

Ember’s component embedding has some elegant features. For a start there is no defining or importing, no binding or building. Components are always global, and can be used at will in any template from the moment they’re generated. Components embed as a div by default but can be set either on embedding or in their JavaScript, and the same applies to css classes, making the following nice and clean.

<ul class="list-group">

{{#each model as |item|}}

{{todo-item item=item}}

{{/each}}

</ul>

This is a contrast to the wordier example of Aurelia, et al.

<ul class="list-group">

<li class="list-group-item" repeat.for="item of todos">

<todo-item item.bind="item"></todo-item>

</li>

</ul>

Updating and Creating

So far so good but it’s here Ember takes a steep dive downhill. When you use Ember with routes everything is very clear and staggeringly efficient. Any actions go in an actions: object very like the methods: used by Vue. Actions bubble down the application’s route heirarchy, so that the base or index route can handle the majority of interactions.

This intuitive pattern is broken if you use components. For reasons that are not clear at all, components do not bubble through the route heirarchy. They instead must go to a controller.

Controllers are a bit of a lost abstraction in Ember. If you read around you’ll see they’re “going away” or deprecated in modern Ember. This simply isn’t true, and component-heavy apps desperately need them. This isn’t something that’s obvious to people getting started. It burned me horribly, and I really struggled getting it working properly, not realising that the bubbling simply doesn’t work as I expected.

Action handling is also non-obvious. The best-practise approach is to pass in “closure actions”.

{{todo-item item=item onSave=(action 'updateItem')}}

It’s not obvious what this is doing, and there’s a lot of magic involved, as this action handler actually creates a closure, binding it to a function called “onSave”. It’s less obvious where function updateItem(){} actually needs to be, but it’s the controller. The term context was used before, that the route sets that up. It’s this context that we bind to, passing what’s current in the controller context to the component.

Usage of this is what really confused me for a long time. In the component you can simply call the function directly. The action isn’t super accessible. All properties of the object can be retrieved with this.get , and updated with this.set . In this case it grabs the onSave function closure and lets you execute it.

actions: {

save(item) {

this.set('isEditing', false);

this.get('onSave')(item);

}

}

This was probably the single hardest struggle I’ve had with Ember. It didn’t help that all of the examples I was looking at seemed to be things like save=(action 'save') which was then called in the save component method. It made things very confusing.

The end result is well worth it. It makes components whose behaviour as well as data can be passed in.

Advanced Learning

The perception of Ember is that it is hard, or that it has a steep learning curve. This isn’t really true in fact. It would be a better thing to say that Ember has a long learning curve, though actually quite shallow.

That said, there is a ton of particularly impressive things Ember can do at the high end.

If you look back at the list templates, you’ll see that in the Ember example, there’s a todo-item component, but no todo-list component. One isn’t needed, we can simply loop in the todo route’s template. But we can use a todo-list component and doing so gives us access to something unique. Not only can Ember components loop over themselves, they can also emit other components. This makes the following a reality.

{{#todo-list items=model as |item|}}

{{todo-item onSave=(action 'updateItem')}}

{{/todo-list}}

{{todo-item isNew=true onSave=(action 'createItem')}}

The above works by having components that “yield” other components, and can do so dynamically and selectively. Another impressive feature comes up with one way binding. Ember happily passes in objects, that’s what I’ve done, but many people prefer to pass in only primitives, eliminating the possibility of two way binding. Ember allows an elegant solution to this.

You can actually bind the item right back at the closure action as we did earlier.

{{todo-item

title=item.title

checked=item.checked

onSave=(action 'saveItem' item)

}}

The item passed into the action above will be curried all the way through the controller and available on its saveItem method automatically.

actions: {

saveItem(item, title, checked) {

item.checked = checked;

item.title = title;

item.save();

}

}

Conclusion

This is going to seem really off-putting to a lot of people. The fact is, I really like Ember. I just have enough experience with it to have run across the kind of edge cases that are almost certainly in any other framework too.

People often skip Ember. It’s really strange. It deals so cleanly with problems they’re having. They act like Ember is too hard, and or irrelevant.

There’s a pernicious myth or perception that Ember is difficult. I don’t agree. For example, it’s easier to add a component in Ember than to add a component in Vue. Hell, it’s easier to add Server Side Rendering in Ember than to add a component in Vue.

Ember solves hard problems easily. It might be reasonable to concede that it solves easy problems less easily, but that seems like an entirely reasonable compromise. I don’t think it does, but even if it did…

Concurrency? Solved. State management? Persistence abstraction? Backend mocking? Service workers? Deployment? Progressive apps? Animation? Authentication. Ember makes them easy. Gradually, the web is moving towards Webpack. And that’s good. Webpack gives sane build pipelines, build serving, ES6+ support, auto-reloading, fast rebuilding, and simplifies addition of new components and styles. Which means it’s caught up to Ember CLI circa 2014, which has literally had all of that since then and more. No other framework has an addon experience like Ember.

But still we have comments like this:

Ember was literally created by core Rails developers as a Rails-like JS framework. Naturally the responses simply doubled down…

Ember. Literally no other tools to install. Literally it just works. Ember has utterly avoided the tooling churn of the last few years. Its workflow has stayed the same for years. Things like its rendering engine got upgraded with no developer changes. In fact there was a release of 3.0, a major release literally yesterday, and it did not break any of my code or replace anything I knew about the framework. Ember has a disciplined, documented, and clear process for releases, and a public RFC process that keeps development within the framework in view. All of this is critical for genuine and serious professionals.

I really wish more people realised how productive and effective Ember actually is. Watching people complain about the same solved problems over and over is frustrating.

Github Link – Note: The github application here is more complex than it needed to be because I was playing with a few things and… well… to be honest I gold-plated the shit out of it. A simpler structure would just loop over the models in the todo route template and embed a list-item component in, which would have its own state, etc. I wanted to play with “contextual components” because I need to use them for an upcoming production project. Don’t think Ember is “complex” just because of this implementation. These contextual components are conceptually a bit like React higher order components, Vue slots, Angular content projection, etc. They’re absolutely not needed for this project.