Access store from component

2019-01-16 18:23发布

问题:

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'

回答1:

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.



回答2:

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()
});


回答3:

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



回答4:

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



回答5:

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



回答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');
  },

});


回答7:

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}}