Ember.js

The good

The bad

The questionable

The good

ConventionS & Structure

RAILS CORE EXPERIENCE/MATURity

Tooling: Ember CLI, Addons & Extension

$ ember new leipzigjs
// Setup environments + config
// App structure
// NPM install
// Bower install

$ ember s
// Watch
// Livereload
// Compile
// Jshint

$ ember s --proxy http://localhost:3000
// All the above
// Proxy api calls

$ ember t
$ ember t -s
$ ember t -m "IndexController"

$ ember build -prod
// ES6 transpile
// Sass, Less, Stylus, CoffeeScript etc.
// Uglify
// Minify
// Concat
// Fingerprinting of asssets + CDN URLs
// Sourcemaps (disabled in prod by default, enabled in dev)

$ ember install:addon addon-name

$ ember g [controller|route|component|acceptance-test|http-mock|...]

$ ember addon

The good

ConventionS & Structure

RAILS CORE EXPERIENCE/MATURity

Tooling: Ember CLI, Addons & Extension

COMMUNITY/DOCS (Versioned)/ECOsystem

Helpful ERROR MESSAGES

Test integration

// Unit test
moduleFor('controller:index', 'IndexController');

test('#add', function() {
  var controller = this.subject();
  equal(controller.add(2, 3), 5, 'should add the two given numbers');
});


// Acceptance test
var App;

module('Acceptance: Index page', {
  setup: function() {
    App = startApp();
  },
  teardown: function() {
    Ember.run(App, 'destroy');
  }
});

test('displays form after click on toggle button', function() {
  expect(2);

  visit('/');

  andThen(function() {
    equal(find('form').length, 0, 'no form shown');
    click('.show-form');
    equal(find('form').length, 1, 'form is shown');
  });
});

The good

ConventionS & Structure

RAILS CORE EXPERIENCE/MATURity

Tooling: Ember CLI, Addons & Extension

COMMUNITY/DOCS (Versioned)/ECOsystem

Helpful ERROR MESSAGES

EVERYTHING YOU'LL EVER NEED (YAGNI?)

Test integration

Ember.copy()
Ember.isEmpty()
Ember.isBlank()
Ember.isPresent()
Ember.isNone()
Ember.isArray()
Ember.makeArray()
Ember.keys()
Ember.merge()
Ember.tryInvoke()
Ember.typeOf()
Ember.inspect()
Ember.run()

Array polyfills for map, forEach, filter, etc.
Array.contains()
Array.uniq()
Array.without()
Array.compact()
Array.invoke()
Array.addObject() / Array.removeObject()

String.fmt()
String.camelize()
String.loc()
String.underscore()
String.w()

RSVP.Promise()
RSVP.hash()
RSVP.all()

Ember.Mixin
Ember.CoreObject.extend()

The Bad

COMPONENTS ARE NOT INDEPENDENT

Data down actions up

App = Ember.Application.create();

App.Router.map(function() {
});

App.IndexController = Ember.Controller.extend({
  actions: {
    login: function(data) {
      console.log(data);
      alert('Logging in...');
    }
  }
});

App.MyLoginComponent = Ember.Component.extend({
  actions: {
    login: function() {
      this.sendAction('action', this.getProperties('username', 'password'));
    }
  }
})
<script type="text/x-handlebars">
  <h1>Login</h1>

  {{outlet}}
</script>

<script type="text/x-handlebars" id="index">
  {{my-login action='login'}}
</script>

<script type="text/x-handlebars" id="components/my-login">
  {{input value=username placeholder='Username'}}
  {{input value=password placeholder='Password'}}
  <button {{action 'login'}}>Login</button>
</script>
<script type="text/x-handlebars">
  <h1>Login</h1>

  {{outlet}}
</script>

<script type="text/x-handlebars" id="index">
  {{my-menu}}
</script>

<script type="text/x-handlebars" id="components/my-login">
  {{input value=username placeholder='Username'}}
  {{input value=password placeholder='Password'}}
  <button {{action 'login'}}>Login</button>
</script>

<script type="text/x-handlebars" id="components/my-menu">
  <h1>Menu</h1>
  {{my-login action='login'}}
</script>

The Bad

COMPONENTS ARE NOT INDEPENDENT

God damn state issues

App = Ember.Application.create();

App.Router.map(function() {
  this.route('other');
});

App.IndexController = Ember.Controller.extend({
  actions: {
    toggleForm: function() {
      this.toggleProperty('showForm');
    }
  }
});
<script type="text/x-handlebars">
  <h1>Login</h1>
  
  <h2>{{link-to 'Homepage' 'index'}}</h2>
  <h2>{{link-to 'Other page' 'other'}}</h2>

  {{outlet}}
</script>

<script type="text/x-handlebars" id="index">
  {{#if showForm}}
    {{my-login}}
  {{else}}
    <a {{action 'toggleForm'}}>Show form</a>
  {{/if}}
</script>

<script type="text/x-handlebars" id="other">
  <h3>Other</h3>
</script>

<script type="text/x-handlebars" id="components/my-login">
  {{input value=username placeholder='Username'}}
  {{input value=password placeholder='Password'}}
  <button {{action 'login'}}>Login</button>
</script>

The Bad

Performance (On Load)

God damn state issues

COMPONENTS ARE NOT INDEPENDENT

Heroku

Discourse

Vine

451kb / 1.7 mb

897kb / 1.88 mb

259kb / 1.1 mb

Travis CI

715kb / 1.0 mb

Digital ocean

402kb / 1.4 mb

The Bad

Performance (ON LOAD)

VENDOR LOCK-IN

God damn state issues

COMPONENTS ARE NOT INDEPENDENT

Ember.computed.FUCK* madness

"3 Ways to enter a page"-issue

Ember-data: Nested API Urls

The Questionable

SIZE (102kb gzipped) W/o Ember-Data (22 kb)

HANDLEBARS/HTMLBARS RESTRICTIONS

TWO-WAY-BINDING BY DEFAULT

NO POJOS but Mixins, classEs & Ember.set

EMBER-DATA

Framework/Complexity/LEarning curve

Conventions :)

When to use

big teams & OOP DUDES

Huge monolithic Single page Apps

DatA heavy apps/Ember-data conform apis

Design heavy apps

Fast prototyping

Enterprise

Apps with A lot of 3rd party libs/Plugins

The (Bright) future

Fastboot (Not ember specific)

NO 2-way-Binding, controllers or Proxies

COMPONENT ROUTING & <ANGLE-BRACKET>

HTMLBARS all the way & Glimmer!!111Elf

Engines

Ember-Cli-Deploy

Ember-Data Stable & JSON-API BY Default

Questions?

Ember.js - The good, the bad, the questionable

By Mario Behrendt

Ember.js - The good, the bad, the questionable

  • 3,443