Rendering Ember components from Rails

At Kollegorna we have been working with Ember a lot. Usually we have Rails as a backend serving APIs and treat Ember as a separate project, in its own repo. When deploying Ember, we usually deploy Ember in a separate Heroku instance as well although leting Rails do the deployment of the Ember app through Redis is also possible.

However, what other options do we have with Ember? Can we have Ember only in a part of our app? Or even is it possible to let Rails render only specific Ember components ?

As we will see both are possible and easy using ember-cli-rails and ember-islands.

Option 1: Ember being the sole frontend of your Rails app

This option is when you want to go all-Ember in your front-end. To achieve that you need to install ember-cli-rails and follow the installation in the README. Basically you need to do the following:

  • Create an Ember app inside the Rails app: ember new frontend --skip-git
  • Make Ember app use pods by adding in config/environment.js: podModulePrefix: 'frontend/pods'. Actually this is optional but pods offer greater flexibility.
  • Add a hello world inside frontend/app/pods/application/template.hbs. We use the pods structure so parent dirs need to be created if not there.

Now if you go inside the frontend directory and start the Ember app you should see the hello world text (run node_modles/ember-cli/bin/ember s or just ember s if you have it globally installed). If this doesn't work it means you have something wrong on the Ember side.

Note that you can now start working on Ember like you would in any other Ember project. If Rails API endpoints need to run while working on Ember, instead of having 2 separate processes (bundle exec rails s and node_modles/ember-cli/bin/ember s) and specify the server API url/host/namespace in the Ember app, you can pack your Ember app in your Rails app (hot reloading is supported!):

  • Run: rails generate ember:init
  • Install Ember addon: ember install ember-cli-rails-addon (inside the frontend/ dir)
  • Mount Ember app to a route in Rails: mount_ember_app :frontend, to: "/"
  • Install Ember dependencies in Rails: bundle exec rake ember:install

Tada! Now you should be able to run just the Rails server (bundle exec rails s) and see your Ember app live.

Option 2: Use Ember app(s) in some part(s) of your Rails app

If you already have your Rails views but you want a part of it (say that you develop a /dashboard) to be in Ember then you can follow the same steps as in option 1, only that we need to tell Rails to mount Ember in a different location:

  • mount_ember_app :frontend, to: "/dashboard"

However there are 2 things that you need to configure in the Ember side: * set rootURL: '/dashboard' in the Ember config file * set locationType: 'none' also in the Ember config file

I like the approach of ember-cli-rails as it allows the front-end team to work independently from the backend team. Front-end team can design and implement the app routes, the flow, the HTML/CSS/JS code independently from the backend team and vice versa. When it makes sense both work using the Rails server which using ember-cli-rails packs and serves both codebases in a single one. Note that you can mount as many Ember apps as you want in the same way.

Option 3: Rendering Ember components from the Rails views

ember-islands takes it a step further by allowing you to render specific components only. For instance we might don't want to render the whole dashboard page in Ember but instead only a couple components that are very dynamic in nature (and using jQuery would result in spaghetti code). To achieve that we need to do a couple more things.

First, we can now remove the Ember mounted app from routes since we will render server-side code which will start Ember manually. We need to tell Rails to include all the Ember JS files in views:

  • Add mount_ember_assets :frontend, to: "/" inside routes. Note that we now mount only assets.
  • Add <%= include_ember_script_tags :frontend, prepend: "/" %> and <%= include_ember_stylesheet_tags :frontend, prepend: "/" %> in the head section of application layout (or anywhere else you want to have Ember components)

Then we can continue:

  • In Ember config set locationType to none and set rootUrl to root again ('/')
  • Install ember-islands in frontend ember app: ember install ember-islands
  • Add {{ember-islands}} in application template. This handles the manual Ember initialization.
  • Install new Ember dependencies in Rails: bundle exec rake ember:install (you should run this everytime you change an Ember dependency)

Go and create the template of a single component that we will use, named user-profile under frontend/app/pods/components/user-profile/template.hbs. The component is supposed to get name and id of the user and display them. Here is an example template:

<h3> User Profile </h3>

{{name}} ({{id}})

Now in any view we can render it:

<h1> Dashboard </h1>
<div
  data-component='user-profile'
  data-attrs='{"name": "Sally User", "id": "4"}'>
</div>

Fire up the Rails server and check your /dashboard url. You should see the HTML generated by the Ember component. If you render such components quite oftenly, you can use a Rails helper for that:

  def ember_component(tag, name, attrs = {})
    content_tag(tag, '', data: {component: name, attrs: attrs.to_json})
  end

So our view becomes more beautiful:

<h1> Dashboard </h1>
<%= ember_component(:div, 'user-profile', {name: 'Sally User', id: 4}) %>

We have to watch out what you pass in Ember through the data-attrs and authorize only the allowed attributes (like we would in a regular API). Personally, on models (or any Ruby object) I use an .as_ember_json method which I filter a lot before passing the model to a component.

I have created an example app (forked from the Rails tutorial app) in which I added a new route for users index, only this time I render the users using Ember components.

Can you spot the difference?

Note that ember-islands is backend-agnostic and you can use it with any backend framework (Phoenix, Spring Boot, Laravel, etc).

Have fun!

Loading comments…
All rights reserved