3

Ember docs say to define a store like this

MyApp.Store = DS.Store.extend();

If you are looking up records in components, this doc says you can inject the store into the component like this

// inject the store into all components
App.inject('component', 'store', 'store:main');

However, I am using the local storage adapter which I define like this

App.ApplicationAdapter = DS.LSAdapter.extend({ namespace: 'my-namespace' });

Therefore, I don't know how to inject this into the component (where I need to look up a record) following the above instructions.

Following the instructions of this SO answer, I tried to inject the store into a component by passing it in like store=store and/or store=controller.store

<li> {{my-component id=item.customid data=item.stats notes=item.notes store=store}} </li>

or

<li> {{my-component id=item.customid data=item.stats notes=item.notes store=controller.store}} </li>

The goal was then to be able to do this in an action in the componeent

      var todo = this.get('store');
      console.log(todo, "the new store");
      todo.set('notes', bufferedTitle);
      console.log(todo, "todo with notes set");
      todo.save();

However, todo.save(); always triggers

Uncaught TypeError: undefined is not a function

Notice that I logged the store? this is what it shows

Class {_backburner: Backburner, typeMaps: Object, recordArrayManager: Class, _pendingSave: Array[0], _pendingFetch: ember$data$lib$system$map$$Map…}

If i inspect it(by opening the tree, which isn't shown here), it does indeed show that notes were set via todo.set('notes', bufferedTitle); however, it doesn't have any of the other attributes of my model that I defined for the index route, and this object doesn't have a 'save' method. Therefore, it doesn't seem to be the actual store, but rather just some backburner object.

I got the same results trying this SO answer where it says to get the store of the targetObject

var todo = this.get('targetObject.store');

Note, I also tried this, i.e. setting the store to be the store of the item.

 <li> {{my-component id=item.customid data=item.stats notes=item.notes store=item.store}} </li>

It should be noted that if I set the store in the component, I can print the store on the page by doing {{store}} which gives me

<DS.Store:ember480>

but I can't do var todo = this.get('store'); in the action that handles the click even in the application code.

Question, using the localStorage adapter, how am I able to look up a record in a component (with the aim of then being able to alter the record and then save it again)

Note, if it's important, I define a model for the (index) route like this

App.Index = DS.Model.extend({

        title: DS.attr('string'),

version (unfortunately I don't know what version of Ember data or the adapter I'm using)

Ember Inspector
1.7.0
Ember
1.9.1
Ember Data
<%= versionStamp %>
Handlebars
2.0.0
jQuery
1.10.2

Update in response to request for more info

The code that sets up the problem is very simple. here's the router (with a bad name for the resource :) App.Router.map(function(){ this.resource('index', { path: '/'}); }

Here's the route that gets the record to use in the Index route

App.IndexRoute = Ember.Route.extend({
      model: function{
      var resource = this.store.find('index');
       return resource;
       }
     });

I have an Index Controller which does nothing in particular for the component (unless I should be defining methods on the Controller that get triggered by component events)

In the html, I do this with handlebars to pass data to the component

   {{#each item in items}}
 <li> {{my-component id=item.customid data=item.stats notes=item.notes store=store}}                   
   {{/each}}

Then, in components/my-component, I have a label that when clicked is supposed to trigger an action that will let me edit one of the attributes on the model

<label> {{action "editTodo" on="doubleClick">{{notes}}</label>

that click triggers this code in App.MyComponent, which triggers the error that prompted this question

 var todo = this.get('store')
 todo.set('notes', bufferedTitle);
 todo.save()
Community
  • 1
  • 1
BrainLikeADullPencil
  • 11,313
  • 24
  • 78
  • 134

4 Answers4

14

IMHO injecting store into components is not the best idea... By design, components should be isolated and shouldn't have any knowledge about the store.

In the doc you've given, it's written: In general, looking up models directly in a component is an anti-pattern, and you should prefer to pass in any model you need in the template that included the component.

However, if you really need it for some reason, then why not just to pass the variable store to the component?

{{my-component store=store}}

Then, you can pass the store from your controller only in the components where you really need that.

Injecting the store in all your components will most likely lead you to the bad design (although it seems tempting at first).

andrusieczko
  • 2,824
  • 12
  • 23
3

Here's an updated answer for Ember 2:

Ember Data's store is now a Service, and we can easily inject it into all Components via an Initializer, e.g. app/initializers/inject-store-into-components:

export function initialize(application) {
  application.inject('component', 'store', 'service:store');
}

export default {
  name: 'inject-store-into-components',
  initialize,
}

Then, in your Components, you can access the store with this.get('store'). The obviates the need to directly pass the store as an argument to Components, which requires a lot of boilerplate in your templates.

Max Wallace
  • 3,609
  • 31
  • 42
1

Whilst the accepted answer is sensible for simple applications it is perfectly acceptable to inject a store into a component if that component doesn't have a relationship with the url, like side bar content or a configurable widget on a dashboard.

In this situation you can use an initializer to inject the store into your component.

However, initializers can be a pain to mimic in testing. I have high hopes that the excellent Ember.inject API that is testing friendly will extend beyond services and accommodate stores. (Or that stores will simply become services).

  • I've worked on a quite big application and we have never ever injected `store` in any of our components. I guess that if you needed some communication with router, the component should fire an action and then, this should be caught by the controller that would have access to `store`. – andrusieczko Mar 26 '17 at 13:36
  • The problem with "firing actions" from components is that it decreases the isolation and DRYness of using components.. If you use a component that performs an action in several places, you have to define an identical action handler on parents in multiple locations. Why not just let the component dictate effects? At the end of the day, controllers are really no different than components except for query params, and dictating that "only these 2 things (controllers/routes) can do something but not that 1 other thing (components)" goes against the **zero-one-infinity** design idea. – kevlarr Sep 26 '17 at 18:22
0

According to this docThe preferred way to inject a store into a component is by setting a store variable to the record, for example

{{#each item in arrangedContent}}
<li> {{my-component store=item}} </li>
{{/each}}

Then in application code, you can do

var store = this.get('store');
store.set('todo', bufferedTitle);
BrainLikeADullPencil
  • 11,313
  • 24
  • 78
  • 134