Observers are fundamental to the Ember object model. In the next recipe, we'll take our light example, add an observer, and see how it operates.
isOnChanged
. This will only trigger when the isOn
property changes: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
Ember.observer
isOnChanged
monitors the isOn
property. If any changes occur to this property, isOnChanged
is invoked.
Computed properties versus 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 get
and set
methods and can be used in templates. Observers, on the other hand, just monitor property changes and cannot be used in templates or be accessed like properties. They don't return any values as well. With this said, be careful not to overuse observers. In many instances, a computed property is a more 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 change. The observer will fire twice as each property has changed.
It's very easy to get observers out of sync. If, for example, 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.
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 if 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.
Ember.run.once
method. This method is a part of the Ember run
loop, which is Ember's way of managing how code gets executed. Reopen the Light
object and you will see the following: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 gets run only once per run
loop. Normally, checkChanged
would get fired twice; however, as it's being called using Ember.run.once
, it outputs only once.