Today we are going to be talking about observers. Observers in Ember allow you to setup a function to trigger based on changes in any dependent keys. You might think, "that sounds a lot like computed properties". Well, you aren't entirely wrong. They are similar in that they take a series of key properties and fire when those keys change. However, unlike computed properties, observers don't care about a return value. If you create a computed property called 'fullName' that takes as dependent keys a firstName and lastName fullName is never actually computed unless it is called, it's lazy. Observers on the other hand are not lazy, they will always fire when changes are made. Ember core team member Stefan Penner gave a terrific talk about observers which I highly recommend and I will link to it in the notes. He talks about a lot of the problems with Observers and generally states that they should never be used. I tend to agree with Mr. Penner. However, they are a core part of the Ember API and if you work in existing code bases you will encounter them. Just when you do encounter them try and refactor them out.

Ok, so let's get to it. The easiest way to get started is to navigate to Ember twiddle. I have prepped a twiddle which I will also link to in the notes. So far, all this twiddle does is when the index route is initialized we create a person record with firstName and lastName properties. Then on the index and about routes' models we return this record. On index we have inputs that modify the record and on about we just display it.

Ok, so the point is to look at observers. To do this we are going to setup a counter and an observer on the about controller.

About Controller:

import Ember from 'ember';

export default Ember.Controller.extend({
  observerCounter: 0,
  nameObserver: Ember.observer('model.firstName', 'model.lastName', function(){
    this.incrementProperty('observerCounter');
    console.log('observer counter: ', this.get('observerCounter'));
  })
});

Now, let's navigate to about. And we see right off the bat that our observer has fired twice. You'll notice that we are not calling our observer in the template like we do with computed properties. The return value of an observer doesn't matter, you can't call the from templates. They are just there to fire when a function when a watch property changes.

Now, let's navigate back to index and change the first and last names. And hey, our observer from the about route is still firing every time we change either of these properties. Remember that a controller is a singleton and the observer is watching model properties, so this observer is going to keep on firing every time one of it's properties change. Even when that observer has nothing to do which what is rendered on the page. Imagine a more complicated app with more observers, all of which most likely have side effects. This can quickly get costly in terms of both bugs and performance. The moral is, if you use observers extensively I hope you like tracking down super hard to locate bugs, cause that will be your primary job.

Now, let's go back to our about controller and add a computed property. This property will also key off of the model's first and last name properties. We will also add a counter for the computed property so we know how many times it is called.

//...
computedCounter: 0,
nameComputed: Ember.computed('model.firstName', 'model.lastName', function(){
  this.incrementProperty('computedCounter');
  console.log('computed counter: ', this.get('computedCounter'));
  return `${this.get('model.firstName')} ${this.get('model.lastName')}`;
})

So, now navigate to the about route again and our computed property doesn't fire at all. That is because computed properties are lazily evaluated while observers are eager. That means for our computed property to fire we have to call it. Let's do that, from the about template we will call this computed property.

<br>
<br>
{{nameComputed}}

Now, if we navigate to the about route, we will see that our observer is called twice and our computed is called once. But, more importantly, if we navigate back to index and start changing things the observer will still fire, but our computed no longer does. In just about every case where you might want to use an observer, you can probably refactor to use a computed property instead and your application will be much better.

That's it for today. Tomorrow we will look at an exercise and get some links about observers and the run loop.

Links