Access store from component - ember.js

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'

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

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.

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

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

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

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

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

Related

Set component property to data from store

Seems it's a bad practice to retrieve data from your component, but this is kind of an experiment (hope the code explains itself)
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
items: [],
init () {
this._super(...arguments);
var store = this.get('store');
let items = store.findAll('dealtype');
}
});
While I can see my api is being hit (debug) and data returned (ember inspector) when looping over "items" in my component template, it's always empty.
Curious what I'm doing wrong (learning still)
You didn't set items in your init
Set that like :
...
let items = store.findAll('dealtype');
this.set('items', items);

PromiseProxy Service with Ember Data query

I'm trying to setup a PromiseProxy Service that returns an Ember Data model, but the result doesn't seem to set the content property.
My service looks like this:
import Ember from 'ember';
const { computed, inject, ObjectProxy, PromiseProxyMixin } = Ember;
export default ObjectProxy.extend(PromiseProxyMixin, {
isServiceFactory: true,
store: inject.service(),
promise: computed({
get() {
var store = this.get('store');
return store.findRecord('community', window.community.id);
}
})
});
I then inject this service into the following locations:
export function initialize(container, application) {
application.inject('controller', 'community', 'service:community');
application.inject('route', 'community', 'service:community');
application.inject('model', 'community', 'service:community');
application.inject('component', 'community', 'service:community');
}
export default {
name: 'community',
after: 'store',
initialize: initialize
};
And then I use it as a model in my application route as a sort of deferReadiness workaround, since my whole app depends on this one model
which is used throughout and expected to be there.
export default Ember.Route.extend({
model() {
return this.get('community');
}
});
The issue is that it goes on to other routes, and properties on the community object are not there, i.e. content isn't set. Also community.isPending is true. The CP does get hit and the data comes back (I tested with a then in the CP).
Here is a full gist example: https://gist.github.com/knownasilya/8c9f78d910ed50ec8d84
Edit
So I found a workaround:
promise: computed({
get() {
var store = this.get('store');
return store.findRecord('community', window.community.id)
.then(data => {
this.set('content', data);
return data;
})
}
})
Seems like it doesn't set the content because model is proxied already?
Ember Data already wraps its objects in an ObjectProxy, you could just set the object as your service.
Additionally, this syntax is deprecated in future versions syntax for initializers, since it's moved to instance initializers, but no big deal.
initialize: function (container, application) {
// the store will be available from the container,
// and the name of the store changes depending on which version you are using.
var store = container.lookup('service:store'),
community= store.find('community', id);
application.register("service:community", community, { instantiate: false });
application.inject("controller", "community", "service:community");
application.inject("route", "community", "service:community");
application.inject("component", "community", "service:community");
}
And then you can still return community from the model, beforeModel hook etc.

Ember: Accessing a data store in an Ember component

I have component that I want to provide data too. I am using Ember-CLI if that helps.
The component is a map that I am loading onto the page that I than want to place markers on. I used a component so I could use the didInsertElement method to get access to the element once it is ready.
export default Ember.Component.extend({
componentMap: '',
didInsertElement: function() {
navigator.geolocation.getCurrentPosition(position => {
//Initialize map...
this.populateMap();
});
},
populateMap: function() {
//Get store
var store = this.get('parentView.targetObject.store');
console.log(store);
//Search Store
var data = store.find('restaurant');
//Where is the data?!
data.map(item => {
console.log(item.get('name'));
});
}
});
I am having an issues getting the data from a store. I have seen a couple methods, here shows two different methods. First being the this.get('parentView.targetObject.store') or this.get('targetObject.store'). I have also tried the {{component store=store}} method, but that was not working for me either. This might have to do with a fundamental lack of understanding of data flow in an ember app.
I am using Ember CLI and I am wondering if it has anything to do with the context of this inside modules?
If I am way off base as to how I should do this, please let em know!
UPDATE: Adding route, controller and template for context.
Route
export default Ember.Route.extend({
model: function() {
return this.store.find('restaurant');
}
});
Controller
export default Ember.Controller.extend({
actions: {
add: function() {
var $addForm = $('.add-form');
$addForm.show();
}
}
});
Template (index.hbs, which is output in application.hbs {{outlet}})
{{main-map store=store}}
Thanks.
What is happening is as follows:
The model associated with your control is populated as an array of restaurants, not a single map or anything of that sort.
return this.store.find('restaurant'); returns an array of restaurants from the store which ultimately populates the model of your controller.
If you want access to the data contained within your model in your component, you should pass the model as an argument into your component.
So, you can pass the array of restaurants as follows (rename the property as appropriate):
{{main-map data=model}}
Or, if in theory you wanted to display a component for each restaurant:
{{#each restaurant in model}}
{{your-component name=restuarant.name}}
{{/each}}

Ember component cannot use access controller property via "needs"

I'm trying to change a controller's property from a component as follows(JSBIN example http://jsbin.com/gevuhu):
App.CategoryManagerController = Ember.Controller.extend({
selectedCategory: null,
});
App.BlogPostComponent = Ember.Component.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
actions:{
selectedCategory: function (){
this.set('selectedCategory',1);
}
}
});
but getting the error Property set failed: object in path "controllers.categoryManager" could not be found or was destroyed.
Is it that we cannot use "needs" in components ?
Ember Components are completely isolated from surrounding context including controllers (see here). That's the bad news. The good news is that if you pass selectedCategory into the component, it will become 2-way bound, so any change to it in the component will be known by your controller.
So, your controller could be something like:
App.ApplicationController = Ember.ObjectController.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
selectedCategoryChanged: function(){
alert("NEW CATEGORY: " + this.get('selectedCategory'));
}.observes('selectedCategory')
});
and then in your application template, you can say
{{ blog-post selectedCategory=selectedCategory }}
See a working example here
In later version like 2.2. We'll be writing this as:
App.ApplicationController = Ember.Controller.extend({
categoryManager: Ember.inject.controller("categoryManager")
});
and now, categoryManager will now have the controller named categoryManager.

Accessing component property in parent controller in Ember

I want to access a property, say selectedItem defined in a component from a parent controller. How to achieve this? I want to access this item so that I can open a modal defined as a partial with this. If anyone can suggest any better solution that is also welcome.
You could bind the property to a property of the controller. Something like this:
App.FooBarComponent = Ember.Component.extend({
selectedItem: null,
// set the property somewhere in your component
});
In your controller
App.SomeController = Ember.Controller.extend({
fooBarSelectedItem: /* empty (null) or a value */
});
In your template
{{foo-bar selectedItem=controller.fooBarSelectedItem}}
I have a "count-down" component, which renders a clock, and when the count-down ends, I need to disable some buttons on the view around the component. The solution is similar to #splattne's answer, but it's newer Ember 3.1 syntax and the shared value is not part of the model.
Component:
export default Component.extend({
'remaining_time': computed('timer_end', 'dummy', function() {
let now = new Date();
let remaining = this.get('timer_end') - now;
if (remaining < 0) {
scheduleOnce('afterRender', this, function(){
this.set('event_listener.expired', true);
});
this.set('should_run', false);
return "Countdown Closed";
}
...
}),
});
Template:
{{count-down timer_end=model.timer_end event_listener=countdown_status}}
Controller:
export default Controller.extend({
countdown_status: Object.create({'expired': false}),
controls_enabled: computed('countdown_status.expired', function() {
return !this.get('countdown_status.expired');
}),
...
});
Note the scheduleOnce('afterRender': it was necessary for https://github.com/emberjs/ember.js/issues/13948. You will only need it if your component changes the shared value before the whole view is rendered, which is unfortunately what mine did.