Slow Ember Computed Property - ember.js

I've build a computed property inside my model to run a calculation on distance to a location using user location and point of interest location (in this case vineyards). The calculation seems to take a second and the template renders even when the milesAway attribute has not been set. Thus not rendering the pertinent information. Any ideas? Model code below...
import DS from 'ember-data';
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
import EmberObject, { computed, observer } from '#ember/object';
import { inject as service } from '#ember/service';
export default DS.Model.extend({
featuredImages: hasMany('contentful-asset'),
featured: attr('boolean'),
name: attr('string'),
slug: attr('string'),
rating: attr('number'),
location: attr('string'),
latitude: attr('string'),
longitude: attr('string'),
bodyOne: attr('string'),
milesAway: attr('string', {async: false}),
googleMapsApi: Ember.inject.service(),
googleLocation: computed(function() {
let userLocation = 'Hard Coded Test Address';
let endLocation = this.get('location');
let milesAway = '';
let googleMapsApi = this.get('googleMapsApi');
this.get('googleMapsApi.google').then((google) => {
var self = this;
let distanceMatrixService = new google.maps.DistanceMatrixService();
function calculateDistance() {
distanceMatrixService.getDistanceMatrix({
origins: [userLocation],
destinations: [endLocation],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, callback);
}
function callback(response, status) {
if (status != google.maps.DistanceMatrixStatus.OK) {
self.toggleProperty('errorState');
} else {
if (response.rows[0].elements[0].status === "ZERO_RESULTS") {
self.toggleProperty('errorState');
} else {
var distance = response.rows[0].elements[0].distance;
var distance_text = distance.text;
self.set('milesAway', distance_text);
}
}
}
calculateDistance();
});
})
});

A few things stand out...
Your computed property googleLocation is missing it's dependencies, which will prevent it being updated. At the very least, location should be listed.
Also, computed properties are meant to be 'pure functions', so that they have no side-effects whenever they are executed. So, instead of setting milesAway within the computed function, you should return a value from your computed function, which may as well be named milesAway. I can't comment too much on the distance calculation part as I've not done that yet myself.
You should remove this line from your code:
milesAway: attr('string', {async: false}),
and do something like the following structure:
milesAway: computed('location', function() {
let location = this.get('location');
// Your code to calculate the distance
return distance;
})
However, models are essentially intended for persistent data rather than transient application data. milesAway will vary per user (I see you have a hardcoded userLocation while you're working on this) for each vineyard that uses this model. So, you probably want to move this computed property out of your model and into your component or controller. So, in your component or controller, you'd have:
milesAway: computed('model.location', function() {
let location = this.get('model.location');
// Your code to calculate the distance
return distance;
})

Related

Calling Ember Model in Computed Property returns incorrect array

I'm trying to format data held in an Ember Model so that I can plug it into a Chart Component.
Route code:
import Route from '#ember/routing/route';
export default Route.extend({
model() {
let dateFrom = this.paramsFor('dashboard').dateFrom
let dateTo = this.paramsFor('dashboard').dateTo
let hash = {dateFrom, dateTo}
return Ember.RSVP.hash({
custakelist: this.get('store').query('custakelist', hash),
barchart: this.get('store').query('barchart', hash),
});
},
setupController(controller, models) {
this._super(controller, models);
controller.set('barchart', models.barchart);
controller.set('custakelist', models.custakelist);
},
Controller Code:
import Controller from '#ember/controller';
import groupBy from 'ember-group-by';
export default Controller.extend({
entriesByDate: groupBy('custakelist', 'take_list_date'),
entriesByAge: groupBy('custakelist', 'patient_age'),
wardData: Ember.computed.map('entriesByDate', function(group) {
return {
label: group.value,
count: group.items.length
};
}),
ageData: Ember.computed.map('entriesByAge', function(group) {
return {
label: group.value,
count: group.items.length
};
}),
clerkData: Ember.computed.map('barchart', function(barchart) {
return {
label: barchart.label,
count: barchart.count
};
}),
});
I know that the models are being loaded on the page correctly thanks to Ember Data. I also know that the 'custakelist' model is being used by other charts.
When I try to use the model 'barchart' and log the result to the console I can see that an array with the correct number of items is created but they don't contain any values, they just display as follows:
0: {label: Computed Property, count: Computed Property}
How can I make the data that is already loaded as per my model usable in this context?
I solved this by iterating on the model with a forEach and putting the result in a new array ready to plug into my chart:
clerkData: Ember.computed('barchart', function(test) {
let newArray = []
this.get('barchart').forEach(function(x) {
let newLabel = moment(x.data.label).format("MMM Do YY")
let newCount = x.data.count
let newData = {label:newLabel, count:newCount}
newArray.push(newData)
})
return newArray
}),

Ember return length of a model created today

I am trying to do this: I have a model called 'trip', and inside trip, an attribute called 'createdToday', which returns the date when a trip is created. What I want is to return a list of trips that were made today.
Here is my trip model:
import DS from 'ember-data';
export default DS.Model.extend({
driver: DS.belongsTo('driver', {
async: true,
inverse: 'trip'
}),
..... etc .......
createdAt: DS.attr('string', {
defaultValue() {
return new Date();
}
}),
isBookedToday: function(trip) {
var today = new Date().toDateString();
return (today === trip.get('createdAt').toDateString);
},
getTripsToday: Ember.computed('trip.#each.createdAt', function() {
var tripsToday = this.get('trip');
return tripsToday.filterBy('isBookedToday', true).get('length');
})
});
In my isBookedToday, I'm trying to see if an individual trip's created time is the same as todays time, and in getTripsToday, I am trying to loop through all the trips and filtering by isBookedToday.
And in my .hbs file, I'm saying: {{trips.getTripsToday}}, which won't render anything, so something's wrong.
I guess I am most confused at Ember's #each and exactly how it works.
Thanks for any feedback.
First you have to understand that your Trip Model instances represents a single Trip! Its absolutely not the right place to put a function that gives you a filtered list of trips!
Next isBookedToday is a normal function not a Computed Property. So you can't filterBy on it.
You may want to implement a isBookedToday on your trip, but you definitely have to filter your trips on the same place where you fetch them! Probably in a model() hook or a Computed Property on a component or a controller.
So you could do but don't need to do in your models/trip.js:
...
isBookedToday: Ember.computed('createdAt', {
get() {
let now = new Date();
let created = get(this, 'createdAt');
return now.getFullYear() === created.getFullYear() &&
now.getMonth() === created.getMonth() &&
now.getDate() === created.getDate();
}
})
...
And then in your model hook:
model() {
return this.store.findAll('trip').then(trips => trips.filterBy('isBookedToday'));
}
Or in a Computed Property in a controller or a component:
tripsToday: Ember.computed('trips.#each.isBookedToday', {
return get(this, 'trips').filterBy('isBookedToday');
})
Be careful. This will result in confusing things if you leave the page open overnight! when your date changes the Computed Properties will not recompute automatically!

ember data array UI not updating on pushObject

I have a list of product-tag that I fetch for my model.
Route:
model: function() {
return {
product_tags: this.store.find('product-tag', {merchant: merchId})
}
}
I have a component that adds tags to the model, however when after I create the record and push it into the model (as suggested on other posts) my UI still isn't updating.
addTag: function(name) {
tag = this.store.createRecord('product-tag', {
name: name
});
this.model.product_tags.toArray().addObject(tag);
tag.save();
}
//model merchant.js
export default DS.Model.extend({
user_id: DS.attr('number'),
product_tags: DS.hasMany('product-tag', {async: true})
});
//model product-tag.js
export default DS.Model.extend({
merchant: DS.belongsTo('merchant'),
name: DS.attr('string'),
});
What am I doing wrong? Thanks in advance.
You should make it array in the route, so u can use it always afterwards like u want. Your calling toArray() which makes a new Array instance, then your model is not hooked to the array u just made.
model: function() {
return {
product_tags: this.store.query('product-tag', {merchant: merchId}).then(function(pt) {
return pt.toArray();
});
}
}
var x = this.get('model.product_tags') === model's p_t // true
var y = this.get('model.product_tags').toArray() === model's p_t // false
Later on just do
addTag: function(name) {
this.get('store').createRecord('product-tag', {
name: name
}).save().then(function(saved){ 
this.get('model.product_tags').pushObject(saved);
}.bind(this);
}

Emberjs promiseArray inside route doesn't return properly

I have a controller for showing item.
Users can put the item in their wish list.
(Item has many users, User has many Items.)
So, when user enter the webpage, I want to show a AddToList or RemoveFromList button to the user based on isAddedToList property.
Below is the code.
User Model:
var User = DS.Model.extend({
username: DS.attr('string'),
email: DS.attr('string'),
avatar: DS.attr('string'),
items: DS.hasMany("item", { async: true })
});
export default User;
ItemModel:
var Item = DS.Model.extend({
name: DS.attr("string"),
priceInCent: DS.attr("number"),
salePriceInCent: DS.attr("number"),
brand: DS.belongsTo("brand"),
itemImages: DS.hasMany("itemImage", { async: true }),
users: DS.hasMany("user", { async: true }),
});
export default Item;
ItemRoute:
var ItemRoute = Ember.Route.extend({
model: function(params) {
var userId = this.get("session").get("userId");
return Ember.RSVP.hash({
item: this.store.find('item', params.item_id),
user: this.store.find('user', userId),
});
},
setupController: function(controller, model) {
controller.set('item', model.item);
controller.set('user', model.user);
}
});
export default ItemRoute;
ItemController:
var ItemController = Ember.Controller.extend({
needs: ["current-user", "application"],
currentUser: Ember.computed.alias("controllers.current-user"),
isAddedToList: function() {
var promiseUsers = this.get("item.users"), user = this.get("user");
return promiseUsers.contains(user);
}.property("item"),
actions: {
addToList: function() {
var item = this.get("item"), user = this.get("user");
item.get("users").pushObject(user);
item.set("addedUserIds", [user.get("id")]);
item.save();
},
removeFromList: function() {
var item = this.get("item"), user = this.get("user");
item.get("users").removeObject(user);
item.set("removedUserIds", [user.get("id")]);
item.save();
}
}
});
export default ItemController;
The problem is when I check the length of promiseUsers with
promiseUsers.get("length")
it always returns 0.
but when I try the same with Chrome console, it returns the length properly.
Do I miss something in the route? How to fix the problem?
The problem is you're using your code synchronously, despite it being an asynchronous property.
The first time you attempt to use an async relationship it will begin resolving the relationship, making a callback to the server is necessary. In your case you try to use the users right away, but they are going to be empty the first time, so you're contains will return false. Since you aren't watching the users' collection it will then update, but the computed property won't update since the computed property was just watching item. This is why when you try it from the console it works, because by the time you attempt to use it in the console it's finished resolving the async collection of users.
isAddedToList: function() {
var promiseUsers = this.get("item.users"), user = this.get("user");
return promiseUsers.contains(user);
}.property("user", 'item.users.[]')

Execute observer once model loaded

i am trying to have an observer execute when a model is loaded
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
}.observes('models.attachments.#each.type'),
the method is currently being called twice, if i change the observes statement to
observes('blablabla'),
it also gets called twice.
the method must only execute when the attachments property of the model updates
the model code :
App.Card = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
left: DS.attr('number'),
top: DS.attr('number'),
user: DS.belongsTo('user', { async: true }),
attachments: DS.hasMany('attachment',{async:true}),
tags:DS.hasMany('tag',{async:true})
});
Additionally you can observe the state of the model using the current state
App.ApplicationController = Em.ObjectController.extend({
timesFired: 0,
watcher: function(){
if(this.get('model.currentState.stateName') == 'root.loaded.saved'){
this.incrementProperty('timesFired');
}
}.observes('model.currentState.stateName')
});
http://jsbin.com/aYIkAcUk/9/edit
I think that your observer is called multiple times because for each item loaded in attachments relationship, the model.attachments.#each.type is triggered. You can use Ember.run.once to collapse all these calls in a single one:
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
},
sortAttachmentsOnce: function() {
Ember.run.once(this, this.sortAttachments);
}.observes('model.attachments.#each.type'),
I hope it helps