As a test project, I’m building a commerce application with ember and ember-data. Things have been going well and I’m having fun learning, but I’m stumped on how to correctly model (and use) a cart using ember-data.
There are a few good resources and how-to’s out there that have helped, like the ember-cart source, the yoember product app, and a emberscreencast series. These all basically use the same pattern: cart items are stored in an array on the cart service (which is injected).
I initially went that route, but have come to the realization that the cart data needs to be persisted (or at least regularly validated) against the back-end, for these reasons. Since ember-data acts as both a store and a back-end persistence method, it seems pretty well suited. My idea was, instead of keeping the cart items as a property on the service, create a cart model and store the data there. I went down that path and kind of have that working…
The Ember-Data Setup
To start, my cart model is extremely simple. Just a model with a single property that is an array:
export default DS.Model.extend({
cartitems: attr('')
});
With a cart, obviously you need to have computed properties that “watch” the cart and provide running totals of quantities and item prices. With some help from typerturdenpants (love it) on the slack channel, I got a computed property to work on my ember-data cart model using peekRecord
so the API doesn’t get pummeled. Then from that property I can build the others. That looks like:
cartObj: null, // set on init with: set(this, 'cartObj', this.get('store').peekRecord('cart', id));
lineItems: computed('cartObj.cartitems.[]', function() {
let cartItems = get(this, 'cartObj.cartitems');
console.log("LINEITEMS COMPUTED", cartItems);
return cartItems;
}),
itemPrices: computed.mapBy('lineItems', 'itemPrice'), // array of all cart item prices
total: computed.sum('itemPrices'), // cart total
itemQuantities: computed.mapBy('lineItems', 'quantity'), // array of all cart item quantities
totalQuantities: computed.sum('itemQuantities'), // total quantity of all cart items
The Problem
I can add items…but it’s wonky. I put a console.log in the computed property and I can see it getting hit 3X every time an item is added (image). There are also 3 network requests (image). I can kind of understand why: checking store, adding locally, getting OPTIONS, GETing cart from API, POSTing cart back to API. But is this the right way to go? It seems really clunky. You can actually see this process happening in the UI (video clip).
I’m also not seeing the data live-update on my /basket route that I have open in a different tab, even though the cart service is injected into it. Which makes me think this is just all wrong.
Here’s what my ‘add’ method looks like in the cart service:
add(item) {
this.get('store').findRecord('cart', get(this, 'cartId')).then(cart => {
let existingLineItem = get(this, 'lineItems').findBy('skuid', item.skuid);
if (existingLineItem) { // if item is already in cart, just add more
set(existingLineItem, 'quantity', parseInt(existingLineItem.quantity)+parseInt(item.quantity))
} else {
cart.get('cartitems').addObject(item); // item is not in cart, add it
}
cart.save();
});
return true;
}
So, I dunno if I am even approaching this right. I am wondering if I should ditch ember-data for the cart and just use service properties like all the cart examples I found do, and just use ember-network at certain points to validate the cart data against the back end.
But it really just seems like ember-data should work for this. What am I missing here?