1

When I thought I finally grasped the concept of how promises work in Ember. This scenario put me back on the train of confusion.

User model has the following association:

profile: DS.belongsTo('polymorphable', { polymorphic: true, async: true })

The route has:

model: function(params) {
  return this.store.findRecord('user', 1);
}

In the template, I am rendering a component:

{{model.profile.firstName}}
{{foo-bar saveProfile=(action "save") profile=model.profile}}

{{model.profile.firstName}} renders fine.

The component template has:

<button {{action "saveProfile" profile}}>Save</button>

The component object has:

actions: {
  saveProfile(profile) {
    console.log(profile);
  }
}

When the button in the component template is clicked. console.log renders:

Class {isFulfilled: true, __nextSuper: undefined, __ember_meta__: Object, __ember1442167214792: "ember688"}

The template already resolved model.profile and it is passing the resolved value to the template. Why is profile (in the component) returning a promise?

Christian Fazzini
  • 19,613
  • 21
  • 110
  • 215

1 Answers1

1

I'm going to guess a bit since you didn't include your user class definition. Your profile property is an asynchronous relationship, and asynchronous relationships use a proxy promise.

Proxy promises allow you to use a getter on the proxy promise and it resolves the property as if it existed on the proxy itself. Meaning your template doesn't have the resolved profile, but is calling model.get('profile.firstName') and getting the resolved value (even if profile is fulfilled later).

model.profile being the proxy promise, when the promise has already been fulfilled you can use a getter on it and expect the value from the underlying record to be displayed.

Since the relationship is asynchronous, you should always assume it hasn't been fulfilled yet, and await the fulfillment of the promise before attempting to use the record.

actions: {
  saveProfile(profilePromise) {
    profilePromise.then(function(profileRecord){
      console.log(profileRecord);
    });
  }
}

Using cast

actions: {
  saveProfile(profileMaybePromise) {
    Ember.RSVP.Promise.cast(profileMaybePromise).then(function(profileRecord){
      console.log(profileRecord);
    });
  }
}
Kingpin2k
  • 47,277
  • 10
  • 78
  • 96
  • What if I am using the same component in a different route, but this time, I am passing the `profile` object (not the user) directly. How should `saveProfile` look like? In your example, `saveProfile` expects the argument passed in, to be a promise object – Christian Fazzini Sep 13 '15 at 18:59
  • Personally I don't think it should be the component's job to resolve the promise, though it's super easy to implement using rsvp cast. Simply cast it to a promise, and if it is a promise nothing changes, if it isn't, it wraps it in a promise. (tossed an example above) – Kingpin2k Sep 13 '15 at 19:18
  • If you wanted to resolve it before hand and attach it to the controller and pass it in properly, I'd take a look at my answer on this question, it should be able to lead you down the right path: http://stackoverflow.com/questions/20521967/emberjs-how-to-load-multiple-models-on-the-same-route/20523407#20523407 – Kingpin2k Sep 13 '15 at 19:23
  • I also agree that it feels dirty for the component to resolve the promise. I was thinking of using an `afterModel` hook to set `profile` and then in the template I can render the component via: `{{foo-bar saveProfile=(action "save") profile=profile}}`. What are your thoughts on this? Or is there a better approach? – Christian Fazzini Sep 13 '15 at 19:23
  • 1
    That's the approach I would use, that feels like the right code are doing the right things. – Kingpin2k Sep 13 '15 at 19:26
  • Perfect! I'll go with an `afterModel` hook and mark the answer as such, based on this solution – Christian Fazzini Sep 13 '15 at 19:28