I'm very new to Ember.js, so still getting the fundamentals down. I have a model, let's call it dog, whose data is coming from an external API. The API only allows you to get big dogs and small dogs separately:
http://api.example.com/big/dogs
http://api.example.com/small/dogs
If I were to have big and small routes of my own—each of which would get the appropriate list of dogs—how could I customize my DogAdapter's buildURL method to put together the correct API call depending on the current route?
This is a very simplified example, please let me know if anything needs to be clarified.
I figured it out. As an example, my big route should return:
this.store.query( 'dog', { size: 'big' } )
and my DogAdapter should define something like:
buildUrl( model, id, snapshot, req, query ) {
return 'http://api.example.com/' + query.size + '/dogs'
}
Related
Is it possible to query for all items of a specific type in the apollo client cache? Our team is trying to adopt the Apollo cache to centralize state, and this question comes up. We have implemented almost all parts of this tutorial, but we are still unsure about the question.
Let's say that we this query, someCollectionQuery
someCollectionQuery {
edges: {
node: {
factory {
_id
factoryName
}
}
}
}
According to the documentation about readQuery, we can now get all factories in the cache by doing:
client.readQuery({
query: someCollectionQuery,
});
We can do some simple iterations on the data and extract only the factories.
But this requires us to use the someCollectionQuery. What if other queries in the app also returned factories? Would it somehow be possible to create a readQuery, that returned all factories in the cache - regardless of what specific query that caused the factories to be in the cache in the first place? Something like:
// How would you achieve something like this?
const allFactories = client.readByType('factory')
I need to generate a random string from two arrays with a bunch of names of people and devices. I have an Ember computed property that does it perfectly well. Except computed properties only happen once. So I end up with a list of like 5 rows with the SAME TEXT.
The reason I'm doing this is because I am getting a list of devices from a REST web service, but they don't have the ability to return the name of the device, only id and a bunch of other info. So I am supposed to use dummy names for now, and so I figure. no problem. I'll just generate a random name for each row. Well as I already said, I end up with this:
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
When it needs to be the name of a random person and random device (which I have in arrays and the code works perfectly, but only executes once, and returns the same value for each consecutive time it's called.
So then I read on here and in an Ember book I have to do this:
property1: function() {
bunch of code
}.property('property2');
So this is supposed to run every time property2 changes, except. Back to square one. How do I change property2? I can't do it from the hbs template, from the code within the {{#each}} .. Then I read somewhere to use a custom helper, but in my experience ANY helper ecapsulates the returned value in a div which breaks the layout and would result in
text
helper response
remaining text
when what I want is:
text helper response remaining text
I mean yeah I could just make an array of text and then pass the index but then when I add new data I have to manually add items to the array because it's not dynamically generated for every row of data.
With my method I have like a ton of names and device names chosen randomly so no matter how much data is returned it can populate the name field until they fix it on the back end to return names.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
Sometimes you want an actual helper that doesn't encapsulate the response in a div, so you can do stuff like The answer is {{answer-helper}}. and not have the output be this:
The answer is
5
.
Okay, first there are helpers if you just won't to output data. And the helper does not encapsulate anything in a div. And a helper is not a component. A component usually produces a div. But if you want a component that doesn't produce a div you can just set tagName to ''.
And thats what you should do: use two components:
device-list/template.hbs
{{#each devices as |device|}}
{{device-data device=device}}
{{/each}}
device-item/template.hbs
{{myprop}}
device-item/component.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
myprop: Ember.computed('device.foo', {
get() {
return this.get('device.foo') + 'bla';
}
}),
});
now you have the property myprop once for every item in the array.
Another option is to have an array as computed property:
devicesWithNames: Ember.computed('devices.#each.foo', {
get() {
return this.get('devices').map(device => ({
foo: device.foo,
name: device.foo + 'bla', // anything you want to do
}));
}
}),
Another way to solve your problem is an ArrayProxy.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
You can't, and thats good. This forces you to follow clean standards. For example you can't just call code on the component from the template. You can invoke helpers, or use data provided by the template. But this is a one-way data flow, and this helps a lot in understanding how a component works. If you need to do this use a computed property, and if you need this inside an {{#each}} loop write another component to be used inside the loop.
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
It seems you don't understand this. But this works as expected. A component that is called twice will compute all properties twice. However you need to understand that if you use an {{#each}} loop you are still in the same component, so where would you want to declare the property that should run for every instance in the array? Thats why you need another component for this. The other option is to have a computed property that does provide you with a new different array, with all the required data.
I thought I had this figured out using reduce(), but the twist is that I need to roll up multiple properties on each record, so every time I am returning an object, and the problem I'm having is that previousValue is an Ember Object, and I'm returning a plain object, so it works fine on the first loop, but the second time through, a is no longer an Ember object, so I get an error saying a.get is not a function. Sample code:
/*
filter the model to get only one food category, which is determined by the user selecting a choice that sets the property: theCategory
*/
var foodByCategory = get(this, 'model').filter(function(rec) {
return get(rec, 'category') === theCategory;
});
/*
Now, roll up all the food records to get a total
of all cost, salePrice, and weight
*/
summary = foodByCategory.reduce(function(a,b){
return {
cost: a.get('cost') + b.get('cost'),
salePrice: a.get('salePrice') + b.get('salePrice'),
weight: a.get('weight') + b.get('weight')
};
});
Am I going about this all wrong? Is there a better way to roll up multiple records from the model into one record, or do I just need to either flatten out the model records into plain objects first, or alternatively, return an Ember object in the reduce()?
Edit: doing return Ember.Object.create({...}) does work, but I still would like some opinion on whether this is the best way to achieve the goal, or if Ember provides functions that will do this, and if so, if they're any better than reduce.
Assuming this.get('model') returns an Ember.Enumerable, you can use filterBy instead of filter:
var foodByCategory = get(this, 'model').filterBy('category', theCategory);
As for your reduce, I don't know of any Ember built-ins that would improve it. The best I can think of is using multiple, separate mapBy and reduce calls:
summary = {
cost: foodByCategory.mapBy('cost').reduce(...),
salePrice: foodByCategory.mapBy('salePrice').reduce(...),
...
};
But that's probably less performant. I wouldn't worry too much about using Ember built-ins to do standard data manipulation... most Ember projects I know of still use a utility library (like Lodash) alongside Ember itself, which usually ends being more effective when writing this sort of data transformation.
I'm looking for an idiomatic way to write an "active record" like method to filter out inactive models
Using the latest ember-data I usually pull in all employee records like so
var employees = this.store.all('employee');
Keep in mind that I'm doing this "filter" 100% client side because I have everything in memory. I use all the employees for a few parts of the app and need the "all" like behavior in these situations. But as I'm also allowing "active/ inactive" status I'd like to filter them down client side for a few features.
I wanted a nice way to query this using a simple filter and I thought it would be active-record like to extend the model and add this but I wanted some guidance first (ie- should I be doing this when the store is not injected into the model directly, and if yes how should I go about injecting this?)
If I shouldn't do this, what is the best way to get all employees and filter down to get only the active ones? (ie- can I just invoke store.all and apply the filter or do I need to work with this data differently) ?
(here is a sample of the filter I'm doing manually now)
return content.filter(function(apt) {
return apt.get('employee').get('active') === true;
});
Ember Data's store has a filter method that has the same functionality as the all filter, aka live record array.
store.filter('employee', function(employee){
return employee.get('active');
});
Let's say I do the following request:
App.Phones.find({'country' : 'DE'});
My backend replies with some telephone numbers. Now I do:
App.Phones.find({'country' : 'ES'});
Now I get other telephone numbers. But:
App.Phones.all();
Has accumulated the "old" numbers and the new ones. Is it possible to clean the store between calls to find? How?
I have tried with App.Phones.clean();, without success (has no method 'clean')
EDIT
This is quite strange but: calling record.destroy(); (as suggested by intuitivepixel) on an object does not remove it from the store, it just marks it as destroyed=true. That means, the drop-down is still showing that option. Actually, walking the records (all()) shows that the records are still there after being destroyed. Maybe Ember will remove them from the store eventually, but that does not help me at all: since my select is bound to all(), I need them to be removed right now.
Even worse: since the object is there, but destroyed, the select shows it, but it does not allow to select it!
I can think of a very ugly hack where I create an Ember.A with filtered records (removing the destroyed records), like this:
Destroy all records (the old ones)
Request new records from the backend
When the records are received (.then), walk the records in the store (.all()), that is, the destroyed and the new ones.
Add the records in the array which are not destroyed
Bind the select to this filtered array.
This looks extremely ugly, and I am really surprised that Ember is not able to just fully and reliably clean the store for a certain record type.
I guess you could do the following to clean the in the store saved Phones records:
App.Phones.find({}); //this will invalidate your cache
But obviously this will make a new request retrieving all the phone numbers.
Depending on what you want to achieve, you could use find() in the application route, then either all() or a filter() in other routes to retrieve just DE, ES etc. In other words there is no such method available to do something like: App.Phones.clean().
Update
Another way (manually) I can think of to remove the records of one type from the cache could be to delete them one by one beetwen your find() operations, for example create a simple utility function which contains the below code, and call it beetwen your calls to find():
App.Phones.all().forEach(function(record) {
record.destroy();
});
Hope it helps.
So, this is the (unsatisfying, ugly, hacky, non-intuitive) code that I have come up with. It is doing the job, but I have a very bad feeling about this:
getNewPhones : function (countryCode, subtype, city) {
// Mark old records as destroyed
App.Availablephone.all().forEach(function(phone, index) {
phone.destroy();
console.log('Destroyed phone %o', phone);
});
// Not possible to set the availablePhones to .all(), because old records are still there
//App.Availablephone.find({country : countryCode, subtype : subtype, city : city});
//this.set('availablePhones', App.Availablephone.all());
// So, hack is in order:
// 1. request new data
// 2. filter all records to just get the newly received ones (!!!)
var _this = this;
App.Availablephone.find({country : countryCode, subtype : subtype, city : city}).then(function(recordArray) {
var availablePhones = Ember.A();
App.Availablephone.all().forEach(function(phone, index) {
if(!phone.isDestroyed) {
console.log('Adding phone=%o', phone);
availablePhones.push(phone);
}
});
_this.set('availablePhones', availablePhones);
});
},
Comments / critiques / improvements suggestions are very much welcome!