In this article by Erik Hanchett, author of the book Ember.js cookbook, Ember.js is an open source JavaScript framework that will make you more productive. It uses common idioms and practices, making it simple to create amazing single-page applications. It also let's you create code in a modular way using the latest JavaScript features. Not only that, it also has a great set of APIs in order to get any task done. The Ember.js community is welcoming newcomers and is ready to help you when required.
(For more resources related to this topic, see here.)
Creating and extending classes is a major feature of the Ember object model. In this recipe, we'll take a look at how creating and extending objects works.
const Light = Ember.Object.extend({
isOn: false
});
This defines a new Light class with a isOn property. Light inherits the properties and behavior from the Ember object such as initializers, mixins, and computed properties.
Ember Twiddle Tip
At some point of time, you might need to test out small snippets of the Ember code. An easy way to do this is to use a website called Ember Twiddle. From that website, you can create an Ember application and run it in the browser as if you were using Ember CLI. You can even save and share it. It has similar tools like JSFiddle; however, only for Ember. Check it out at http://ember-twiddle.com.
constbulb = Light.create();
console.log(bulb.get('isOn'));
The preceding code will get the isOn property from the bulb instance.
bulb.set('isOn', true)
The isOn property will now be set to true instead of false.
The init method is invoked whenever a new instance is created. This is a great place to put in any code that you may need for the new instance.
In our example, we'll go ahead and add an alert message that displays the default setting for the isOn property:
const Light = Ember.Object.extend({
init(){
alert('The isON property is defaulted to ' +
this.get('isOn'));
},
isOn: false
});
As soon as the Light.create line of code is executed, the instance will be created and this message will pop up on the screen.
The isON property is defaulted to false.
Subclass
Be aware that you can create subclasses of your objects in Ember. You can override methods and access the parent class by using the _super() keyword method. This is done by creating a new object that uses the Ember extend method on the parent class.
Another important thing to realize is that if you're subclassing a framework class such as Ember.Component and you override the init method, you'll need to make sure that you call this._super(). If not, the component may not work properly.
At anytime, you can reopen a class and define new properties or methods in it. For this, use the reopen method.
In our previous example, we had an isON property. Let's reopen the same class and add a color property, as follows:
Light.reopen({
color: 'yellow'
});
Light.reopen({
wattage: 40
});
You can now access the static property:
Light.wattage
In the preceding examples, we have created an Ember object using extend. This tells Ember to create a new Ember class. The extend method uses inheritance in the Ember.js framework. The Light object inherits all the methods and bindings of the Ember object.
The create method also inherits from the Ember object class and returns a new instance of this class. The bulb object is the new instance of the Ember object that we created.
To use the previous examples, we can create our own module and have it imported to our project.
To do this, create a new MyObject.js file in the app folder, as follows:
// app/myObject.js
import Ember from 'ember';
export default function() {
const Light = Ember.Object.extend({
init(){
alert('The isON property is defaulted to ' + this.get('isOn'));
},
isOn: false
});
Light.reopen({
color: 'yellow'
});
Light.reopenClass({
wattage: 80
});
const bulb = Light.create();
console.log(bulb.get('color'));
console.log(Light.wattage);
}
This is the module that we can now import to any file of our Ember application.
In the app folder, edit the app.js file. You'll need to add the following line at the top of the file:
// app/app.js
import myObject from './myObject';
At the bottom, before the export, add the following line:
myObject();
This will execute the myObject function that we created in the myObject.js file. After running the Ember server, you'll see the isOn property defaulted to the false pop-up message.
In this recipe, we'll take a look at the computed properties and how they can be used to display data, even if that data changes as the application is running.
Let's create a new Ember.Object and add a computed property to it, as shown in the following:
const Light = Ember.Object.extend({
isOn: false,
color: 'yellow',
description: Ember.computed('isOn','color',function() {
return 'The ' + this.get('color') + ' light is set to '
+ this.get('isOn');
})
});
const bulb = Light.create();
bulb.get('description'); //The yellow light is set to false
The preceding example creates a computed property that depends on the isOn and color properties. When the description function is called, it returns a string describing the state of the light.
Computed properties will observe changes and dynamically update whenever they occur.
bulb.set('isOn', true);
bulb.get('description') //The yellow light is set to true
The description has been automatically updated and will now display that the yellow light is set to true.
Ember provides a nice feature that allows computed properties to be present in other computed properties. In the previous example, we created a description property that outputted some basic information about the Light object, as follows:
const Light = Ember.Object.extend({
isOn: false,
color: 'yellow',
age: null,
description: Ember.computed('isOn','color',function() {
return 'The ' + this.get('color') + ' light is set to ' + this.get('isOn');
}),
fullDescription: Ember.computed('description','age',function() {
return this.get('description') + ' and the age is ' + this.get('age')
}),
});
const bulb = Light.create({age:22});
bulb.get('fullDescription'); //The yellow light is set to false and the age is 22
In this example, during instantiation of the Light object, we set the age to 22. We can overwrite any property if required.
The Ember.computed.alias method allows us to create a property that is an alias for another property or object.
Any call to get or set will behave as if the changes were made to the original property, as shown in the following:
const Light = Ember.Object.extend({
isOn: false,
color: 'yellow',
age: null,
description: Ember.computed('isOn','color',function() {
return 'The ' + this.get('color') + ' light is set to ' + this.get('isOn');
}),
fullDescription: Ember.computed('description','age',function() {
return this.get('description') + ' and the age is ' + this.get('age')
}),
aliasDescription: Ember.computed.alias('fullDescription')
});
const bulb = Light.create({age: 22});
bulb.get('aliasDescription'); //The yellow light is set to false and the age is 22.
The aliasDescription will display the same text as fullDescription since it's just an alias of this object. If we made any changes later to any properties in the Light object, the alias would also observe these changes and be computed properly.
Computed properties are built on top of the observer pattern. Whenever an observation shows a state change, it recomputes the output. If no changes occur, then the result is cached.
In other words, the computed properties are functions that get updated whenever any of their dependent value changes. You can use it in the same way that you would use a static property. They are common and useful throughout Ember and it's codebase.
Keep in mind that a computed property will only update if it is in a template or function that is being used. If the function or template is not being called, then nothing will occur. This will help with performance.
Observers are fundamental to the Ember object model. In the next recipe, we'll take our light example and add in an observer and see how it operates.
const Light = Ember.Object.extend({
isOn: false,
color: 'yellow',
age: null,
description: Ember.computed('isOn','color',function() {
return 'The ' + this.get('color') + ' light is set to ' + this.get('isOn')
}),
fullDescription: Ember.computed('description','age',function() {
return this.get('description') + ' and the age is ' + this.get('age')
}),
desc: Ember.computed.alias('description'),
isOnChanged: Ember.observer('isOn',function() {
console.log('isOn value changed')
})
});
const bulb = Light.create({age: 22});
bulb.set('isOn',true); //console logs isOn value changed
The Ember.observer isOnChanged monitors the isOn property. If any changes occur to this property, isOnChanged is invoked.
Computed Properties vs Observers
At first glance, it might seem that observers are the same as computed properties. In fact, they are very different. Computed properties can use the get and set methods and can be used in templates. Observers, on the other hand, just monitor property changes. They can neither be used in templates nor be accessed like properties. They also don't return any values. With that said, be careful not to overuse observers. For many instances, a computed property is the most appropriate solution.
Light.reopen({
isAnythingChanged: Ember.observer('isOn','color',function() {
console.log('isOn or color value changed')
})
});
const bulb = Light.create({age: 22});
bulb.set('isOn',true); // console logs isOn or color value changed
bulb.set('color','blue'); // console logs isOn or color value changed
The isAnything observer is invoked whenever the isOn or color properties changes. The observer will fire twice as each property has changed.
It's very easy to get observers out of sync. For example, if a property that it observes changes, it will be invoked as expected. After being invoked, it might manipulate a property that hasn't been updated yet. This can cause synchronization issues as everything happens at the same time, as follows:
Light.reopen({
checkIsOn: Ember.observer('isOn', function() {
console.log(this.get('fullDescription'));
})
});
const bulb = Light.create({age: 22});
bulb.set('isOn', true);
When isOn is changed, it's not clear whether fullDescription, a computed property, has been updated yet or not. As observers work synchronously, it's difficult to tell what has been fired and changed. This can lead to unexpected behavior.
To counter this, it's best to use the Ember.run.once method. This method is a part of the Ember run loop, which is Ember's way of managing how the code is executed.
Light.reopen({
checkIsOn: Ember.observer('isOn','color', function() {
Ember.run.once(this,'checkChanged');
}),
checkChanged: Ember.observer('description',function() {
console.log(this.get('description'));
})
});
const bulb = Light.create({age: 22});
bulb.set('isOn', true);
bulb.set('color', 'blue');
The checkIsOn observer calls the checkChanged observer using Ember.run.once. This method is only run once per run loop. Normally, checkChanged would be fired twice; however, since it's be called using Ember.run.once, it only outputs once.
Ember observers are mixins from the Ember.Observable class. They work by monitoring property changes. When any change occurs, they are triggered. Keep in mind that these are not the same as computed properties and cannot be used in templates or with getters or setters.
In this article you learned classes and instances. You also learned computed properties and how they can be used to display data.
Further resources on this subject: