Tom Mathew Tom Mathew - 2 months ago 27
Ajax Question

Ember.JS create record in component and add to local Datastore, or best practice solution

Current situation: I am trying to find a way to create a record of a specific model from within the "didInsertElement" method of a component, and add said data to the data store.

Reason for current situation: I am attaching event listeners to a button in my component HBS file. One of the buttons will trigger two cascaded but light ajax calls. The response from the last of these ajax calls will determine if I should create records or not.

Problem I am having: When I try to access the store like this from the component:

var myStore = this.store


I am getting an undefined object. For those of you asking, I have already added the following line to my component:

store: Ember.inject.service()


And I already have Ember-Data installed.

Solutions: I have found from a lot of research that interaction with the store is best done through the main route. But how would I attach event listeners to a component's jquery widget from a route?

Are there any solutions that wouldn't require me to move all all the event listener code from the component to the route and thereby undo the modularity that ember is supposed to offer?

Answer

this.store property exists on elements on which it was injected by default (controllers and routes) and should probably not be used since it skips the getters, which are an essential part of Ember.

The proper way to access it in a component (assuming you injected it) is via:

this.get('store')

I have found from a lot of research that interaction with the store is best done through the main route.

You would probably want (at least in most cases), to access store data not in the main route but in the controller in which your component exists and then just pass the data into the component.

There are a few reasons for this:

  • Routes main usage is to obtain the data and control the routing mechanism of your app, where the user transitions to, at which points the transition happens and why, etc. Routes shouldn't control which data is shown and how, just how/when its obtained.
  • Controllers main usage is data massaging, preparation, conversion, etc. This includes things like filtering the data, sorting it, extracting a subset etc.
  • Controller data is directly available in the templates, Route data isn't.

The setupController hook in an Ember.Route actually does this by default.

This is the default operation done in the setupController() hook:

set(controller, 'model', model);

So you controller has access to the model by default and can then pass it on to the components.

This way you clearly separate the logic that extracts/obtains/handles the data from the logic that displays it.

I am attaching event listeners to a button in my component HBS file. One of the buttons will trigger two cascaded but light ajax calls. The response from the last of these ajax calls will determine if I should create records or not.

The natural way to do this in Ember, is to attach an action to your button, which will just propagate to the Controller

// inside components/example.js
// ...
actions: {
  myButtonAction() {
    // call the controller action
    this.get('controllerActionThatWasPassedIn')(/* pass any data if necessary */);
  }
}

and have the Controller do the actual store operations and then pass any results if needed back down to the Component.

// controllers/example.js
// ...
actions: {
  handleData(someArgs) {
    this.get('store)'.findSomething(someArgs).then((result) => {
      this.set('result', result); // set the result which will automatically be passed down into the component
    });
  }
}

// templates/example.hbs
{{my-component result=result
               controllerActionThatWasPassedIn=(action "handleData")
}}

In Ember, this concept is called Data down, Actions up.

Actions passed like this are called closure actions, and, in fact, you can also return from them so something like this would work as well:

// inside components/example.js
// ...
actions: {
  myButtonAction() {
    // call the controller action
    this.get('controllerActionThatWasPassedIn')(/* pass any data if necessary */)
      .then((result) => {
        // do stuff with result
      });
  }
}

How would I attach event listeners to a component's jquery widget from a route?

Note: Anti-pattern

There isn't really a natural way of doing this in Ember, mainly because Routes and Controllers get created before anything gets rendered. That's why you wouldn't want to do this in a Route and in a Controller instead.

However, if you perhaps don't need a Controller and want only to do some quick/small DOM operation in the Route (note that this is usually an anti-pattern), you can always use the Ember Run Loop hooks and schedule some operation to happen after a render:

export default Ember.Route.extend({
  init() {
    this._super(...arguments);

    Ember.run.scheduleOnce('afterRender', () => {
      $('.my-component').click(/* ... */)
    });
  }
});

Or you can do this directly in the renderTemplate() hook, after you render the content:

renderTemplate() {
  this.render();
  $('.my-component').click(/* ... */); // Probably shouldn't do this though
}
Comments