Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Future-proofing your Ember 1.x code

This post is also on my blog, since Gist doesn't support @ notifications.


Components are taking center stage in Ember 2.0. Here are some things you can do today to make the transition as smooth as possible:

  • Use Ember CLI

  • In general, replace views + controllers with components

  • Only use controllers at the top-level for receiving data from the route, and use Ember.Controller instead of Ember.ArrayController or Ember.ObjectController

  • Fetch data in your route, and set it as normal properties on your top-level controller. Export an Ember.Controller, otherwise a proxy will be generated. You can use Ember.RSVP.hash to simulate setting normal props on your controller.

    ```js   
    //controllers/index.js
    import Ember from 'ember';
    export default Ember.Controller;
    
    //routes/index.js
    model: function() {
      return Ember.RSVP.hash({
        users: this.store.find('user')
      });
    },
    
    setupController: function(controller, models) {
      controller.setProperties(models);
    }
    ```
    
    Now you can just refer to the keys in your route's template:
    
    ```hbs
    //templates/index.js
    <h1>Users</h1>
    {{user-list users=users}}
    ```
    This controller/template pair will eventually become a routable component.
    
  • In your templates, stay away from things like ItemControllers and calls to render(). Use components instead.

  • Don't use views

  • Write your app in the "data down, actions up" paradigm

    • Not currently enforced, but you can still structure your app this way
    • Stay away from two-way bindings and mutability
  • Don't use each or with in the context-switching form. That is, use

     {{#each users as |user|}} {{user.firstName}} {{/each}}
    

    instead of

     {{#each users}} {{firstName}} {{/each}}
    
  • Use pods

  • Use this.route instead of this.resource in Router.map. Couple this with pods and you'll quickly understand why you don't want to use resource anymore - your directory structure in pods will now mirror the view hierarchy of your UI.

Better apps

Follow these tips, and your apps will be ready for Ember 2.0. You'll also learn a lot about writing apps that are better structured and easier to understand!

Deprecations will be coming to help you move towards these newer practices. The goal is, if your app runs on the final version of 1.x with no deprecations, it should be able to run in 2.0.

ksol commented Nov 16, 2014

Hello,

I'm intrigued by the last point:

Use this.route instead of this.resource in Router.map.

Can you tell more about it?

Thanks !

Owner

samselikoff commented Nov 16, 2014

Sure - it used to be the case that you could only nest resources. Since 1.7 (I believe) you can now nest routes.

Also, resources reset the namespace, so with a map of

this.resource('posts', function() {
  this.resource('comments');
});

comments would get its own top-level namespace (e.g. CommentsController, etc.). Nested routes don't do this, so you would have PostsCommentsController - which sucks when you're dealing with globals. But with pods, this means your PostsComments controller will now live at pods/posts/comments/controller.js - so your file structure mirrors your UI hierarchy. Exactly what you want!

taras commented Nov 16, 2014

@samselikoff Do you know how you could use a component instead of itemController?

@taras In a world where your using Ember.Controller instead of Ember.ArrayController just iterating over the model and rendering a component is essentially the same as itemController. At least thats the way I understand it after hearing and talking to Robert Jackson at Boston Ember this week.

// routes/posts/index.js
import Ember from 'ember';

export default Ember.Rout.extend({
  model: function() {
    return this.store.find('post');
  });
});
{{! posts/index.hbs}}
{{#each post in model}}
  {{post-snippet post=post}}
{{/each}}

@samselikoff (carry over from Twitter conversation)

First, thanks for writing this up! I was putting something together for a local user group talk and I'm definitely happy to have all of these ideas in one place.

With regard to the possible resource deprecation: I don't understand the relationship between modules and the namespace resetting behaviors of resource. I'm guessing that it would be unusual for people to import controllers in their Ember apps as you've shown in your code example.

The 2 things I enjoy from namespace resetting are:

A simpler directory structure.

So with namespace resetting:

routes/
  posts.js
  comments.js
controllers/
  posts.js
  comments.js

Without

routes/
  posts.js
  posts/
    comments.js
controllers/
  posts.js
  posts/
    comments.js

Simpler {{link-to}} paths

So with namespace resetting:

{{link-to 'comments'}}
{{link-to 'posts'}}

Without

{{link-to 'posts.comments'}}
{{link-to 'posts'}}

I'm worried that losing the namespace resetting feature would:

Increase the Cost of UI Changes

The namespace resetting behavior can make some dramatic UI changes surprisingly easy. For instance going from an index page -> show page setup to a master-detail layout can be done by only changing the router and adding an outlet to the posts template. If we use routes over resources then that UI change would require changing {{link-to}} helpers and moving our routes and controllers to different directories.

Increase the Cost of Route Nesting

Yehuda posted his approach to authentication in September 2013:

https://twitter.com/wycats/status/376495062709854209

Removing the namespace resetting feature would make this pattern awkward to use.

I'm definitely for changing the resource and route API to make it simpler to understand, I just don't want to lose sight of the benefits that namespace resetting gives us today.

@samselikoff Do you think following will be one of the features and best practice in ember 2.0 ?

  • More intutive attribut binding. You would expect to be able type something like
    <a href="{{url}}">Click here</a>, instead of <a {{bind-attr href=url}}>Click here</a>.
Owner

samselikoff commented Nov 17, 2014

@mitchlloyd, if you use pods structure in your app you will avoid the problems you outline. I believe pods will be the recommended structure going forward.

@kavitshah8 that will definitely be a feature of 2.0, but it will probably land before then. Once it comes out you can certainly take advantage in your 1.x apps.

It would be useful to have an "Ember lint" to help us with these migration points when they're set in stone.

taras commented Nov 17, 2014

@raytiley ArrayController/itemController allowed us to have context specific state on itemControllers and aggregate of that state on ArrayController. With the constructs that are available in Ember today, we can use components for basic uses cases that itemControllers are being used for, but not all.

@samselikoff we can't PR this Gist, should we move it into a WIP guide on Ember.js website?

Owner

samselikoff commented Nov 17, 2014

Deprecation warnings will start coming into your apps to address these points. Still, might be useful to have somewhere in the guides.

Awesome guide!

Adding headings such as easy, medium, invasive would make it even better.

Switching to Ember-CLI and or avoiding Views should be fairly straight forward, where as removing most uses of controllers is more invasive. After all, 2.0 is still being discussed, docs aren't really in place for the transition and some changes will be harder until replacements are in place.

kgish commented Nov 17, 2014

Not sure what you mean by "data down, actions up" paradigm, please explain.

@taras

"ArrayController/itemController allowed us to have context specific state on itemControllers and aggregate of that state on ArrayController"

Wouldn't this be data up? Violating Data down, actions up?

taras commented Nov 17, 2014

@raytiley I suppose, I need to play around with it some more. I recently reimplemented checkboxes, I'm going to try refactoring it to eliminate the use of selected: mapBy('@this', 'isSelected', true) and see how that goes.

@kgish I think it means flow data Route -> Controller -> Component and handle actions Component -> Controller -> Route

I know the transition to components is the way forward, and I'm generally in favor of it, but if we're avoiding views, where are people putting their className overrides and properties for things that aren't specifically component-y like page-views? Not seeing any dashes in the resources and routes above. For a route of 'posts', Ember will automatically create a view called PostsView. Or are we supposed to not template directly to our routes?

Meaning, if I have a resource/route named 'articles' and I want to make changes to view-level properties, would I then have to take my 'articles' route and controller and point to a layoutName of x-article instead (or article-summary, article-full, etc.)?

I feel like there are some view-specific scenarios that need some explanation and extrapolation.

@taras You might want to look at http://discuss.emberjs.com/t/what-is-a-good-way-to-do-this-in-ember-2-0-no-itemcontroller-no-arraycontroller/6649/4 where I and a few others have tried some approaches to refactoring the kind of ArrayController/Item controller you're talking about.

@samselikoff I'm just learning about the pod structure so hopefully I'm not missing the boat here, but it looks like it doesn't address the points I brought up. Nested routes still need a nested directory structure and I don't think this structure affects the paths in {{link-to}} helpers.

aquamme commented Nov 20, 2014

I'll second the request @kgrish posed. Can someone elaborate on 'data down, actions up'? Is the concept as simple as @isocrate describes?

Owner

samselikoff commented Nov 20, 2014

Here's a good way to think about "data down actions up." Let's say we're making something like Trello, and our index route looks like this:

//routes/index.js
export default Ember.Route.extend({
  model: function() {
    return this.store.find('card');
  }
});

We want to display the cards in our template:

<!-- templates/index.hbs -->
<div class='board'>
  {{#each card in controller}}    <!-- soon, this will simply be `each card in cards`. No more proxies. -->
    {{card-summary card=card}}
  {{/each}}
</div>

We've made a CardSummaryComponent to wrap up the display logic for each card we're showing. Let's say this summary shows the title of the card, and has an "X" button in the top-right which lets our users delete the card. We can imagine writing something like this:

<!-- templates/components/card-summary.hbs -->
<div class='card'>
  <h2>{{card.title}}</h2>
  <span {{action 'removeCard'}} class='fa fa-remove'> X </span>
</div>

and the component code

//components/card-summary.js
export default Ember.Component.extend({
  actions: {
    removeCard: function() {
      return this.get('card').destroyRecord();
    }
  }
});

Now, the problem here is that the CardSummaryComponent is deleting the data, but it's really the IndexController that owns the data. Its the one that passed the card into the CardSummaryComponent, and now that component has gone and deleted some data right out from underneath its feet. This is sort of thing that becomes really difficult to trace in larger, more complex applications.

Note that in 2.0, we wouldn't have an IndexController but rather a routable component. We don't know exactly how the API will shake out, but somehow we'd probably have a template with an attrs.cards property from the server, which would be the array of cards. We'd probably then make a separate CardSummaryListComponent and do something like {{card-summary-list cards=cards}}.

In any case, back to the example. This clearly violates "data down", because there was a data change in a child component (CardSummary) which affected parent components (in this case the each block, in the alternative case the CardSummaryList component).

To revise this, first, pretend that you live in a world without two-way bindings. That is, when we render {{card-summary card=card}}, what's actually happening is the card from the parent scope is being "copied", and passed into component, which now has the data in its own isolated scope. Any change to card in the parent scope will re-push that data into the card-summary component, which is the "data down" part. But, if the card-summary component mutates its own card data, none of its parent components will be the wiser. That's one-way bindings.

But of course, we want those interactions in our child components to actually affect our real data. So, we use actions to send those messages back up our hierarchy. This is "actions up", and the idea is that our card-summary component is simply "notifying" its parent components that a certain action has taken place - "deleteCard", for example. It's up to the parent components to decide how they want to handle the actions their children are emitting.

In React, those parent components do this by explicitly passing their action handlers into their children. Something like this:

//components/card-summary-list.js

// Here's the JSX template for a <CardSummaryList>
cards.map(function(card) {
  return <CardSummary card={card} onCardDelete={this.handleCardDelete} />
});

Now, the CardSummary component can simply invoke its own onCardDelete function, which references the handleCardDelete method passed into it from its parent.

In Ember, we're used to doing something more along the lines of

<!-- templates/index.hbs -->
<div class='board'>
  {{#each card in controller}}
    {{card-summary card=card action='deleteCard'}}
  {{/each}}
</div>

and then writing the deleteCard handler within the parent. This is changing, for a few reasons (read the Improving Actions section of the 2.0 RFC for more info). In 2.0 we'll be doing things similar to how React does it, by passing actions directly in:

<!-- templates/index.hbs -->
<div class='board'>
  {{#each card in controller}}
    <card-summary card=card deleteCard={{action "valueChanged"}}>
  {{/each}}
</div>

The CardSummary component will have access to that action via attrs['deleteCard'], and can invoke it just like in React. This way, the child component is effectively delegating the event handler to the parent, without knowing anything about what that handler does.


So, that's essentially (my understanding) of "data down, actions up!" Hopefully it cleared some things up. Btw, if you haven't already, I highly recommend spending several hours with React. Once you see how they do it, the motivation for a lot of the 2.0 changes becomes pretty clear, and it will help you out as Ember's API continues to evolve.

aquamme commented Nov 21, 2014

@samselikoff , I figured it was something roughly along those lines but your post definitely helped my understanding. Thank you.

Thanks @samselikoff, great explanation.

kgish commented Nov 26, 2014

Wonder why I'm spending so much time struggling to understand the current situation when in the not so distant future so much is going to change and I have to relearn everything all over again :(

Owner

samselikoff commented Dec 1, 2014

@kgish I'd say keep learning Ember with the best resources you can find today. Things are becoming simplified, not replaced. Whatever you know about Ember today will continue to be useful in the future, not only for writing Ember 2.0 apps, but also for deepening your understanding of JS app architecture.

Baukereg commented Dec 1, 2014

Since components aren't routable yet, I have moved all code from the route's controller, view and template to a new component. Right now a route only does a data request, creates the right component in its template and passes the data. I guess that's the best way to move to a component centric setup.

Owner

samselikoff commented Dec 1, 2014

@Baukereg exactly. No longer should think about controller as data container + a route rendering a separate, independent template. Route just fetches + sets data on a top-level component, and then you start by writing that component's template, adding new components etc. and keep going.

For now, since a route can't hand data directly to a component, just set the data as attrs on the route's controller, and treat the route's template as the future top-level component's template. Then when routable components land, you'll be able to change controller.set to component.set and be good to go.

@samselikoff what about Controller's actions that were called from other controllers? Suppose you have a Controller which has an action to add a new element to its Model.

This action was called (send) from other controllers and now this Controller is going to be converted to a Component. Where should we move this action code? Route?

Owner

samselikoff commented Dec 2, 2014

@jsangilve I'd have to see the code to be sure, but yes that sounds like a route. There will be some more apis to help with this sort of thing.

If it's too hard to change things now, again don't worry about it. New apis and deprecation warnings will help you as new versions of 1.x are released.

mankind commented Dec 5, 2014

@samselikoff I was wondering if you have suggestions on how to convert a controller into a component if that controller uses the needs api to access another controller . For instance in my ember-cli app, I have this code:

     import Ember from 'ember';

     export default Ember.ArrayController.extend({
       needs: ['application'],
       application: Ember.computed.alias('controllers.application')
     });

How do you flip this into a component. I thought of the code below, but this component will be inserted in posts.hbs template, so passing the application controller in as shown below won't work, since I don't have access to the application controller in posts.hbs.

   {posts-display applicationController=application}}

An aha! moment occurred after posting here and reflecting on how to resolve this, it seems I can just inject it into the component with:

app/initilializers/post-display.js

  export function initialize(container, application) {
     application.inject('component:posts-display', 'application', 'service:applicationController');
  }

Are there any other approaches.

arenoir commented Jan 8, 2015

@samselikoff how do components work with a pod structure?

app/pods/user/profile/component.{js,hbs} ==> {{user-profile}}, I think that's how they work.

TL;DR - A working JS Bin example.
(ember discussion forum thread)
@samselikoff I think that forcing yourself writing components with one-way bounded properties is also a good way to prepare your code for 2.0
I came up with a Mixin which scans for properties names that ends with the suffix "OneWay" and creates/re-defines correspondent one-way bounded props (same name, omitting the suffix) for each prop. Thus, in your component code you can refer only to the auto generated properties and change only them.
In this way the uni directional data flow can occur:
DD → AU → DD → AU → .....
(* DD - Data Down, AU- Action Up)

// app/mixins/one-way-props.js

export default Ember.Mixin.create({
  oneWayPropRegExp: /^(.*)OneWay$/,
  init: function(){
    var oneWayRegexp = this.get('oneWayPropRegExp');
    this._super();
    Ember.keys(this).forEach(function(propName){
      var matches = oneWayRegexp.exec(propName),
          oneWayPropName = matches && matches[1];
      if (oneWayPropName){
        Ember.defineProperty(this, oneWayPropName, function(key, value) {
        var retValue;
        if (arguments.length > 1){
          retValue = value;
        } else {
          retValue = this.get(propName);
        }
        return retValue;
      }.property(propName));  
      }
    }, this);
  }
});

Usage:

{{!-- app/templates/application.hbs --}}

{{some-example aPropOneWay=someValue action="actOnCommit"}}
{{!-- app/templates/components/some-example.hbs --}}

{{input value=aProp}}
 <button {{action 'commit'}}>Commit</button>
// app/components/some-example.js
import OneWayPropsMixins from 'app/mixins/one-way-props';

export default Ember.Component.extend(OneWayPropsMixins, {
  actions: {
    commit: function(){
      this.sendAction('action', this.get('aProp'));
    }
  }
});

WDYT?

Very helpful!

@knownasilya @samselikoff can you confirm that component file structure? Does ember-cli support generating components in this way right now?

Owner

samselikoff commented Mar 25, 2015

I haven't used components the way @knownasilya laid out, but I do next them within pods.

/pods/todos/todo-list/template.hbs
/pods/todos/todo-list/component.hbs

would let me do {{todos/todo-list}}

varblob commented Mar 26, 2015

@samselikoff
I'm curious how pods + no resource in the router + models in the pod.

For example

this.route('potato', function(){
  this.route('tomato')
});

would have pod structure

/pods/potato/tomato

but if I create a tomato model it will go into

pods/tomato/model.js

Which means the model doesn't live with its tomato friends.

Owner

samselikoff commented Mar 27, 2015

@varblob My current thinking is, don't put models in pods. Pods should be classes directly related to your UI hierarchy (since the hierarchical structure of your directory matches the hierarchical structure of your UI) - essentially, routes, components, and templates (and controllers for now). Models are your data layer + are shared throughout the UI. So, I keep my models in /app/models.

tolgaek commented Apr 2, 2015

Dealing with the 'loading' issue right now. We have the set up outlined above. We have an index page that has 4 components that get assigned from 4 models we load through the use of Ember.RSVP. The issue is we have to wait to load all the 4 models. And using the 'loading' template, we can show a loading screen for the whole page. What I would like to do is show the loading pages independently for each component. So we would load the componens and show 'loading' within them.

One solution I can think of is to inject 'store' into the components and treat them like 'routable components' and assign them each a model (which gets passed down as a string -model name-) Then components can load their models using the model name sent down from the Router -> Template and use a component for the loading spinner display.

@samselikoff you have been guiding me through your past posts on my attempt to fully implement our current site with Ember 2.0 way. Can you shine some light into this discussion?

Placing everything on the attrs hash makes the Inspector unable see any models and - as far I can tell - there isn't an easy way to inspect components at this time. You can always echo the attrs hash to the console from the Controller view but it's not nearly as friendly.

I've moved to using model and attrs in my controllers. Model is obvious and enables me to still easily use the inspector and anything else is going on the attrs hash. Seem like a decent compromise to keep Inspector easy to use?

A great video here by Edward Faulkner about the planned features and the deprecations towards the Ember 2.0.0 release.

Not strictly related to this discussion, but describes very well when each feature will land, so I'm sharing here: https://youtu.be/wsydQzQF4Ww

Hi!

I'm a padawan who started learning ember quite a recently..

I would like to know why should we just use this.route instead of this.resource to prepare for Ember 2.0?? as far as I know, we should use 'resource' for nouns (photos,contacts etc) and 'route for verbs and adjectives ('modify', 'update' etc)

@umpri5450: According to @ebryn, this.resource will be deprecated in Ember 1.12, so we should only use this.route. Apparently, you can pass the resetNamespace: true property to the route options if you need to reset the route's namespace (as resources would do by default).

ekanna commented May 9, 2015

@samselikoff

My current thinking is, don't put models in pods. Pods should be classes directly related to your UI hierarchy (since the hierarchical structure of your directory matches the hierarchical structure of your UI) - essentially, routes, components, and templates (and controllers for now). Models are your data layer + are shared throughout the UI. So, I keep my models in /app/models.

Great advice. Thanks a lot. It really made my day!

I've refactored away from controllers, and I have to say Ember make s a lot more sense to me now!

But in this new "no Views" world, but still without an Application Component yet, how do you recommend we do this:

Once a route has been activated/transitioned too, resize a containing DIV to the window size. If we had an Application Component, you'd do this with didInsertElement, and currently we do it in the application View (ApplicationView).

Do we just wait for Application Component's with 2.0?

@drewbaker Why not put a component in the routes template and inside it's didInsertElement resize your containing div. ember-worm hole uses this approach where it insert its child to another destination.

But using an event bus would also seem to be a solution. http://www.thesoftwaresimpleton.com/blog/2015/04/27/event-bus/

@samselikoff can you elaborate on what you do to handle this point Stay away from two-way bindings and mutability

In your route, I think the Ember 2.0 way to fetch all records for a model is now findAll instead of find.

broerse commented Jul 15, 2015

I just changed from ArrayController to Controller and got it to work again but I now get .createWithMixins is deprecated, please use .create or .extend accordingly but I can't find how to do it. Can somebody point me to an example or take a look at my code:

  arrangedContent: function() {
    return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
      sortProperties: ['date'],
      sortAscending: false,
      content: this.get('model')
    });
  }.property('model'),

@broerse I think you can just do:

  arrangedContent: function() {
    return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
      sortProperties: ['date'],
      sortAscending: false,
      content: this.get('model')
    });
  }.property('model'),

broerse commented Jul 16, 2015

@koriroys This was just what solved it. Many thanks!

@koriroys Saved my day, thanks!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment