Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Last active April 21, 2023 17:14
Show Gist options
  • Star 230 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save samselikoff/1d7300ce59d216fdaf97 to your computer and use it in GitHub Desktop.
Save samselikoff/1d7300ce59d216fdaf97 to your computer and use it in GitHub Desktop.
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.

@samselikoff
Copy link
Author

@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
Copy link

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
Copy link

arenoir commented Jan 8, 2015

@samselikoff how do components work with a pod structure?

@knownasilya
Copy link

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

@ramybenaroya
Copy link

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?

@leejt489
Copy link

Very helpful!

@leejt489
Copy link

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

@samselikoff
Copy link
Author

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
Copy link

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.

@samselikoff
Copy link
Author

@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
Copy link

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?

@ultimatemonty
Copy link

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?

@holandes22
Copy link

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

@umpri5450
Copy link

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)

@jakedetels
Copy link

@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
Copy link

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!

@drewbaker
Copy link

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?

@blessanm86
Copy link

@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/

@blessanm86
Copy link

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

@EmergentBehavior
Copy link

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

@broerse
Copy link

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'),

@koriroys
Copy link

@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
Copy link

broerse commented Jul 16, 2015

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

@dangreenisrael
Copy link

@koriroys Saved my day, thanks!!!!

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