Ember can React

This is a follow-up post to the talk that Mattia and I did at Toronto Ember on December 4th, 2014 entitled Ember can React. In that talk we explained how Ember could be used in the same events up, bindings down way that React is so often utilized. In this post I’ll discuss the task of getting Ember to play nicely in an existing DOM, and walk through an approach for doing so.

React is HOT, so hot, I just couldn’t help myself but give it a whirl. I’m using it on LCBO API to spruce up the homepage with a live API explorer:

Okay, okay, maybe it wasn’t just the hype that sold me on React. The HTML portion of LCBO API is generated by Middleman, so I needed a way to add JavaScript functionality to an existing page — it seemed like a nicely contained use-case to give React a try.

Ember can do this since forever ago, and no, not just one Ember application per-page, it’s possible instantiate multiple Ember applications on one page, doing it is just not very ergonomic or obvious. Here’s an example of one possible way:

<!-- Ember needs a place for template data, if we were using a build tool
like Ember-CLI the templates wouldn't have to live in the DOM. React
developers will likely write JSX, and have it compiled to JavaScript as
part of their build process. -->

<script type="text/x-handlebars" data-template-name="components/my-widget">
  <p>This is my widget, it go: {{itGo}}</p>
</script>

<script type="text/x-handlebars" data-template-name="application">
  {{my-widget}}
</script>

<p>My Widget 1:</p>
<div data-component="my-widget" data-it-go="Wooo-Wooo"></div>

<p>My Widget 2:</p>
<div data-component="my-widget" data-it-go="Choo-Choo"></div>
// This is ONE way to do this, it's not THE way, or THE BEST way:
function injectEmberApp(container) {
  var App = Ember.Application.create({
    rootElement: container
  });

  App.Router.reopen({
    location: 'none'
  });

  App.MyWidgetComponent = Ember.Component.extend({
    itGo: $(container).data('it-go')
  });
}

$('[data-component="my-widget"]').each(function(i, div) {
  injectEmberApp(div);
});

JSBin

In contrast, the React implementation to achieve the same result:

<p>My Widget 1:</p>
<div data-component="my-widget" data-it-go="Wooo-Wooo"></div>

<p>My Widget 2:</p>
<div data-component="my-widget" data-it-go="Choo-Choo"></div>
var MyWidget = React.createClass({
  render: function() {
    return (
      <p>This is my widget, it go: {this.props.itGo}</p>
    );
  }
});

$('[data-component="my-widget"]').each(function(i, div) {
  var itGo = $(div).data('it-go');
  React.render(<MyWidget itGo={itGo} />, div);
});

JSBin

The above examples demonstrate a scenario where you’d like to inject multiple instances of a component onto the same page. This is an advanced scenario for adding Ember (or even React) to an existing page. In this example, components are able to appear more than once on a page, we select all placeholder nodes with jQuery, and then instantiate a component (React) or application (Ember) in that node. You can see how it’s fairly trivial to manually attach React components or Ember applications to existing DOM nodes.

I think it’s pretty clear that the React implementation is a bit tighter, React.render makes it very obvious how you place a component into the DOM. It really does feel like React was forged from these fires, and it has a leg up because of that. Ember solves this problem as a technical side-effect, not because it was a primary use-case in its development (although to be clear it’s certainly no accident).

During our talk, Ember can React, Mattia and I discussed how once you get past the above boilerplate, doing things React-style in Ember is not only possible, but quite natural. We also realized how often we take for granted the niceties and robustness of Ember, just check out the talk to see that statement in action.

All of this said, it’s not hard to imagine a similar API in Ember via an addon or even as part of core that would make doing this sort of thing just as clear:

<div data-component="my-widget" data-attr-it-go="nice"></div>
var MyWidget = Ember.Component.extend();

$('[data-component="my-widget"]').each(function(i, div) {
  Ember.render(MyWidget.extend({ itGo: $(div).data('it-go') }), div);
});

You get the idea, and we can take it even further, perhaps Ember.render knew to pass every attribute declared on el into MyWidget as attributes:

<my-widget it-go="nice"></my-widget>
$('my-widget').each(function(i, el) {
  Ember.render(MyWidget, el);
});

And, and, maybe this instantiation code was just part of Ember, or some community supported addon and it “just worked” — it’s more than possible, even today. We can take it even further, what if when you booted Ember with a specific flag, it sniffed the existing DOM for tags that matched components registered in the Ember application and treated those nodes as normal components. Ember 2.0 proposes a lot of great changes that would make doing something like that very possible — I’m getting dizzy!

Now What?

Go forth and React, go forth and Ember, try a new tool, retry an old tool. It will do nothing but make you a better developer.