While you may never have to write your own Ember Object, you should know how they work since everything you'll be working with inherits from it.
Ember Models Ember Controllers Ember Services
Because of this, knowing how to work with Ember Objects is kind of a big deal. Let's get started.
Creating a new instance of a generic Ember Object is easy.
var obj = Ember.Object.create({name: "Bob"});
obj.get("name"); // "Bob"
obj.get("age"); // undefined
obj.set("age", 35);
obj.get("age"); // 35
Ember Objects expect you to interact with properties using the get
and set
methods. These methods allow Ember to update dependent properties, as we'll see later in the Computed Properties
section.
Ember Objects are like JavaScript objects, in that they are open. You can add any property to an object, no need to define setters.
What if we don't want a generic object? What if we want to design an object with functions and properties? Enter the extend
method.
var Person = Ember.Object.extend({
name: null,
greet: function(){
return `${this.get("name")} says "Hi!"`;
}
});
var bob = Person.create({name: "Bob", age: 35});
bob.greet(); // Bob says "Hi!"
Here we create a Person
Object. We can pass create
properties, even if they're not defined in the prototype. It's a good idea to set the expected properties to null to let users of your Object know what it expects. It's not required though.
In the tests you will see your classes imported like this:
export default Ember.Object.extend();
This is the same as saying:
var MyObject = Ember.Object.extend();
export default MyObject;
It's a shortcut.
Computed properties are a super power in Ember and the fact that they're in EVERY OBJECT should get you excited. A computed property is a property that gets updated as your object changes. You define a list of properties you depend on, and when one of those properties changes (meaning someone called set
on it), your computed property will also change. It's lazy so it won't regenerate the value every time, only when there's a change. Here's a simple example.
Let's say we want to have the total price of an item update based on the price ร the quantity. In regular javascript you might define a function called total that will multiply price and quantity. This calculation would run every time it's called. With computed properties we only run the computation when a dependent property changes. Here's the Ember code for that:
var Item = Ember.Object.extend({
price: 0,
quantity: 0,
total: Ember.computed("price", "quantity", function(){
return this.get("price") * this.get("quantity");
})
})
var ham = Item.create({price: 3, quantity: 1});
ham.get("total"); // 3
ham.set("quantity", 5)
ham.get("total"); //15
Be sure to look at the created ones Ember makes for you here.
Ember lets you monitor changes to the properties in a collection too. Let's setup a shopping cart with items. The shopping cart will hold a series of items.
var ShoppingCart = Ember.Object.extend({
items: [],
total: Ember.computed("[email protected]", function(){
return this.get("items").reduce(function(memo, item){
return memo + item.get('total');
}, 0);
})
});
var Item = Ember.Object.extend({
price: 0,
quantity: 0,
total: Ember.computed("price", "quantity", function(){
return this.get("price") * this.get("quantity");
})
});
var ham = Item.create({price: 3, quantity: 1});
var cheese = Item.create({price: 5, quantity: 1});
var bread = Item.create({price: 2, quantity: 1});
var cart = ShoppingCart.create();
cart.get("total"); // 0
cart.get('items').pushObjects([ham, cheese, bread]);
cart.get("total"); // 10
This function will run only when the total
property of an item is changed. We used that weird [email protected]
property to say just that. Notify me when any of the item's total changes.
What if we wanted to update when an item is added or removed? Say we wanted a total number of items in the cart:
var ShoppingCart = Ember.Object.extend({
items: [],
total: Ember.computed("[email protected]", function(){
return this.get("items").reduce(function(memo, item){
return memo + item.get('total');
}, 0);
}),
numberOfItems: Ember.computed("items.[]", function(){
return this.get('items.length');
})
});
var Item = Ember.Object.extend({
price: 0,
quantity: 0,
total: Ember.computed("price", "quantity", function(){
return this.get("price") * this.get("quantity");
})
});
var ham = Item.create({price: 3, quantity: 1});
var cheese = Item.create({price: 5, quantity: 1});
var bread = Item.create({price: 2, quantity: 1});
var cart = ShoppingCart.create();
cart.get("numberOfItems"); // 0
cart.get('items').pushObjects([ham, cheese, bread]);
cart.get("numberOfItems"); // 3
Here we did 2 things. One is the use of items.[]
. This means update the computed property if the number of elements changes. You won't be notified of changes to properties. The other thing we did is return this.get('items.length')
. Ember lets you chain messages inside of get
s and sets
s.
You can use a short hand for this though, by using one of Ember's built in computed properties. Basically what we're doing is creating an alias for items.length
that updates. Instead of the function we have for numberOfItems
, we can have this instead:
var ShoppingCart = Ember.Object.extend({
items: [],
total: Ember.computed("[email protected]", function(){
return this.get("items").reduce(function(memo, item){
return memo + item.get('total');
}, 0);
}),
numberOfItems: Ember.computed.alias("items.length")
});
var Item = Ember.Object.extend({
price: 0,
quantity: 0,
total: Ember.computed("price", "quantity", function(){
return this.get("price") * this.get("quantity");
})
});
var ham = Item.create({price: 3, quantity: 1});
var cheese = Item.create({price: 5, quantity: 1});
var bread = Item.create({price: 2, quantity: 1});
var cart = ShoppingCart.create();
cart.get("numberOfItems"); // 0
cart.get('items').pushObjects([ham, cheese, bread]);
cart.get("numberOfItems"); // 3
So nice!