How to load belongsTo/hasMany relationships in route with EmberJS

10,068

Solution 1

You are still asynchronously loading those records, so if you are fast enough you could still get undefined. It'd be better to return a promise from the afterModel hook, or just modify the model hook to do it all.

model: function() {
  return this.store.find('appointment').then(function(appointments){
    return Ember.RSVP.all(appointments.getEach('owner')).then(function(){
      return appointments;
    });
  });
}

or

model: function() {
  return this.store.find('appointment');
},
afterModel: function(model, transition){
  return Ember.RSVP.all(model.getEach('owner'));
}

Solution 2

Another way to go is:

export default Ember.Controller.extend({
  modelChanged: function(){
    this.set('loadingRelations',true);
    Ember.RSVP.all(this.get('model').getEach('owner')).then(()=>{
      this.set('loadingRelations',false);
    });      
  }.observes('model')
});

This way the transition finishes faster and the relations are loaded afterwards. The loading-state can be observed through loadingRelations.

When there are a lot of relations to load I think this gives a better UX.

Solution 3

You want to load all the assocations in the route, because you want to use Fastboot for search engines and better first time site opened experience.

Holding your assocation loading after primary models are loaded, might not be the best decision.

I am using a syntax to load all assocations in the route:

   let store = this.store;
   let pagePromise = store.findRecord('page', params.page_id);
   let pageItemsPromise = pagePromise.then(function(page) {
     return page.get('pageItems');
   });
  return this.hashPromises({
    page: pagePromise,
    pageItems: pageItemsPromise
  });

And for this.hashPromises I got a mixin:

    import Ember from 'ember';

    export default Ember.Mixin.create({
      hashPromises: function(hash) {
        let keys = Object.keys(hash);

        return Ember.RSVP.hashSettled(hash).then(function(vals) {
          let returnedHash = {};
          keys.forEach(function(key) {
            returnedHash[key] = vals[key].value;
          });
          return returnedHash;
        });
      }
    });
Share:
10,068
Karoline Brynildsen
Author by

Karoline Brynildsen

I'm a developer!

Updated on July 22, 2022

Comments

  • Karoline Brynildsen
    Karoline Brynildsen almost 2 years

    In my EmberJS application I am displaying a list of Appointments. In an action in the AppointmentController I need to get the appointments owner, but the owner always returns "undefined".

    My files:

    models/appointment.js

    import DS from 'ember-data';
    
    export default DS.Model.extend({
        appointmentStatus: DS.attr('number'),
        owner: DS.hasMany('person'),
        date: DS.attr('Date')
    });
    

    models/person.js

    import DS from 'ember-data';
    
    export default DS.Model.extend({
        name: DS.attr('string')
    });
    

    templates/appointmentlist.js

    {{#each appointment in controller}}
        <div>
            {{appointment.date}} <button type="button" {{action 'doIt'}}>Do something!</button>
        </div>
    {{/each }}
    

    controllers/appointmentlist.js

    export default Ember.ArrayController.extend({
        itemController: 'appointment'
    });
    

    controllers/appointment.js

    export default Ember.ObjectController.extend({
        actions:{
            doIt: function(){
                var appointment = this.get('model');
                var owner = appointment.get('owner'); //returns undefined
                //Do something with owner
            }
        }
    });
    

    Now, I know I can change the owner-property to owner: DS.hasMany('person', {async: true}), and then handle the promise returned from appointment.get('owner');, but that is not what I want. I have discovered that if I do this {{appointment.owner}} or this {{appointment.owner.name}} in the appointmentlist template, the owner record is fetched from the server. So I guess Ember does not load relationships unless they are used in the template.

    I think that the solution to my problem is to use the appointmentlists route to fetch the record in the belongsTo relationship. But I can't figure out how.

    Maybe something like this?

    routes/appointmentlist.js

    export default Ember.Route.extend({
        model: function() {
            return this.store.find('appointment');
        },
        afterModel: function(appointments){
            //what to do
        }
    });
    

    EDIT

    I did this:

    routes/appointmentlist.js

    export default Ember.Route.extend({
        model: function() {
            return this.store.find('appointment');
        },
        afterModel: function(appointments){
             $.each(appointments.content, function(i, appointment){
    
                    var owner= appointment.get('owner')   
             });
        }
    });
    

    and it works, but I do not like the solution...

  • Karoline Brynildsen
    Karoline Brynildsen almost 9 years
    It's been a while since I worked on this project, so I won't be able to test your solution in the nearest future. But this looks interesting and I feel the urge to look into this issue again.