49

i have a component and when user click on component it add some value to store,i try to use this way but i get an error :

OlapApp.MeasureListItemComponent = Ember.Component.extend({
  tagName: 'li',
  isDisabled: false,
  attributeBindings: ['isDisabled:disabled'],
  classBindings: ['isDisabled:MeasureListItemDisabled'],

  actions: {
    add: function(measure) {
      var store = this.get('store');
      store.push('OlapApp.AxisModel', {
            uniqueName: measure.uniqueName,
            name: measure.name,
            hierarchyUniqueName: measure.hierarchyUniqueName,
            type: 'row',
            isMeasure: true,
            orderId: 1
      });
    }
  }
});

and this is error:

Uncaught TypeError: Cannot call method 'push' of undefined  MeasureListItemComponent.js:18

is it posible to push record to store from component? why i cant access to store ? my model name is 'AxisModel' and application namespace is 'OlapApp'

MBehtemam
  • 7,865
  • 15
  • 66
  • 108

7 Answers7

44

Since Ember v1.10, the store can be injected to components using initializers, see: http://emberjs.com/blog/2015/02/07/ember-1-10-0-released.html#toc_injected-properties:

export default Ember.Component.extend({
    store: Ember.inject.service()
});
Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
  • @MBehtemam you may consider marking this one as confirmed solution – chrmod May 05 '15 at 09:58
  • Why is it called `storage` and not `store`? – Ronni Egeriis Persson Jul 08 '15 at 13:22
  • That's just a name. You can call it whatever you want within your component (so `store` would work too). – Jacob van Lingen Jul 08 '15 at 14:32
  • 1
    Documentation states "Use Ember.inject.service() to inject a service with the same name as the property it is injected as". So I think you would need to do "store: Ember.inject.service('storage')" @RonniEgeriis – J. Barca Oct 21 '15 at 17:33
  • 3
    @J.Barca, you are right. I think storage is an alias for store at the moment of release of Ember 1.10. Nowadays I would just use `store: Ember.inject.service()`. – Jacob van Lingen Oct 26 '15 at 08:35
  • @JacobvanLingen, I ended up using what you put because 'storage' didn't seem to work (I am using mirage for ember-data right now) – J. Barca Oct 26 '15 at 18:03
  • You can also inject in arbitrary named properties the store. Example: `storage: Ember.inject.service('store')` – morhook Jul 12 '16 at 21:18
43

In a component the store does not get injected automatically like in route's or controller's when your app starts. This is because components are thought to be more isolated.

What follows below is not considered a best practice. A component should use data passed into it and not know about it's environment. The best way to handle this case would be using sendAction to bubble up what you want to do, and handle the action with the store in the controller itself.

@sly7_7 suggestion is a good one, and if you have a lot of components from where you need access to the store then it might be a good way to do it.

Another approach to get to your store could be to get the store your component surrounding controller has reference to. In this case it doesn't matter which controller this is because every controller has already a reference to the store injected into it. So now to get to your store could be done by getting the component's targetObject which will be the controller surrounding the component and then get the store.

Example:

OlapApp.MeasureListItemComponent = Ember.Component.extend({
  ...
  actions: {
    add: function(measure) {
      var store = this.get('targetObject.store');
      ...
    }
  }
});

See here for a working example.

Hope it helps.

Update in response to your comment having nested components

If for example you child component is only nested one level then you could still refer to parent's targetObject using parentView:

App.ChildCompComponent = Ember.Component.extend({
  storeName: '',
  didInsertElement: function() {
    console.log(this.get('parentView.targetObject.store'));
    this.set('storeName', this.get('parentView.targetObject.store'));
  }
});

Updated example.

Community
  • 1
  • 1
intuitivepixel
  • 23,302
  • 3
  • 57
  • 51
  • Fine, I was not aware of the component's targetObject. I definitely must update my knowledge about component. I was pretty sure it was really isolated, with no access at all at the controller – sly7_7 Sep 04 '13 at 18:11
  • 1
    how about sendAction in component ? using sendAction and using event bubling to send action to Route or controller that have store ? – MBehtemam Sep 05 '13 at 04:31
  • 1
    i have nested component and it dosent work for me. when i log with `console.log(this.get('targetObject.store'))` it say undefined – MBehtemam Sep 05 '13 at 05:13
  • im trying to accessing this way to stroe from componet and i can but from view i cant. – MBehtemam Sep 21 '13 at 04:22
  • @MBehtemam it seems that `sendAction` is a much better approach to this issue, as components ideally should not fiddle with the `store` directly. I've edited this answer to account for that. – igorsantos07 May 27 '15 at 05:31
  • any chance you could create example using actions? – SuperUberDuper Jun 02 '15 at 08:08
  • But how to do this in the future when controllers are gone? – Lux Sep 04 '15 at 14:40
14

Since Ember 2.1.0

export default Ember.Component.extend({
  store: Ember.inject.service('store'),
});

before Ember 2.1.0 - dependency injection way

App.MyComponent = Ember.Component.extend({

  store: Ember.computed(function() {
     return this.get('container').lookup('store:main');
  })

});

before Ember 2.1.0 - controller way

You can pass store as property from controller:

App.MyComponent = Ember.Component.extend({

  value: null,
  store: null,
  tagName: "input",

  didInsertElement: function () {

     if (!this.get('store')) {
        throw 'MyComponent requires store for autocomplete feature. Inject as store=store'
     }
  }

});

Store is available on each controller. So in parent view you can include component as follows:

{{view App.MyComponent
    store=store
    class="some-class"
    elementId="some-id"
    valueBinding="someValue"
}}

Passing properties to component is documented here

Jan Míšek
  • 1,647
  • 1
  • 16
  • 22
13

The current ember-cli way to do this appears to be with an initializer. Very similar to the @Sly7_7 answer.

To get a basic model use:

  ember g initializer component-store-injector

Then edit this to:

// app/initializers/component-store-injector.js

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

export default {
  name: 'component-store-injector',
  initialize: initialize
};

I believe this will add the store to all components.

Stolen from https://github.com/ember-cli/ember-cli-todos

shime
  • 8,746
  • 1
  • 30
  • 51
jomofrodo
  • 1,119
  • 11
  • 19
  • Direct link to the source for [app/initializers/component-store-injector.js](https://github.com/ember-cli/ember-cli-todos/blob/f636ca2ef1f952e8fe8e0bff6332f7196c4401ba/app/initializers/component-store-injector.js) – Eliot Sykes May 27 '15 at 17:52
  • 1
    In ember 1.13, store seems to be registered as service:store. – Maksim Burnin Oct 30 '15 at 07:16
7

I don't know if components are intended to be used such a way. But if you want, I think you can declare an initializer and inject the store into all components.

Ember.onLoad('OlaApp', function(OlaApp) {
  OlapApp.initializer({
    name: 'injectStoreIntoComponents',
    before: 'registerComponents',
    initialize: function(container, application){
      container.register('store:main', App.Store);
      container.injection('component', 'store', 'store:main');
    }
  })
});

Here is a contrived but working example: http://jsbin.com/AlIyUDo/6/edit

Panagiotis Panagi
  • 9,927
  • 7
  • 55
  • 103
sly7_7
  • 11,961
  • 3
  • 40
  • 54
  • 1
    Good one, just added my two cents in the case he only needs access to the store in *some* components, but anyway both solutions create dependencies – intuitivepixel Sep 04 '13 at 18:03
  • I think yours is better :) – sly7_7 Sep 04 '13 at 18:11
  • Is this solution working for you? I've tried it but the 'store' property doesn't show up in my components. – andrei1089 Sep 05 '13 at 14:37
  • Actually I did'nt test it. So either it's just completely wrong and I should remove, either something is wrong with the initializer. Perhaps it shoud be declare inside an Ember.onLoad() hook. Could you try it and tell me if it works better ? – sly7_7 Sep 05 '13 at 15:46
  • @andrei1089 I finally succeed. You need to register the store, and inject it, before the components are registered – sly7_7 Sep 05 '13 at 21:05
  • 2
    `registerComponents` was renamed to `registerComponentLookup` also `store:main` doesn't have to be registered and will be set after `App.ApplicationStore` is a store class. All these things throw errors now :) – K.. Oct 21 '14 at 23:02
  • @K.. can you edit it to provide the updated answer ? – vasilakisfil Oct 31 '14 at 15:48
  • Actually I don't think that should be considered as a good answer. Component are supposed to be isolated, injecting the store seems to break that. I think the preferred way is to bind an action on the component, and deal with in the controller. – sly7_7 Nov 01 '14 at 00:12
6

The store can be injected with help of dependency injection.

Example

import Ember from 'ember';

export default Ember.Component.extend({

  /**
   *
   */
  store: Ember.inject.service(),

  /**
   * Initialize the component.
   */
  init() {
    this.initialize();

    this._super();
  },

  /**
   * Initialize the properties and prerequisites.
   */
  initialize() {
    // Set the component properties
    this.todos().then((data) => {
      this.set('todoEntries', data);
    });
  },

  /**
   * Returns the todo entries.
   *
   * @returns {*|Promise|Promise.<T>}
   */
  todos() {
    const store = this.get('store');

    return store.findAll('todo');
  },

});
user634545
  • 9,099
  • 5
  • 29
  • 40
4

Another way which no one has yet mentioned is to simply pass controller.store to the component e.g.

{{my-awesome-component store=controller.store}}
TrevTheDev
  • 2,616
  • 2
  • 18
  • 36