Skip to content
This repository has been archived by the owner on May 26, 2019. It is now read-only.

Commit

Permalink
Merge pull request #457 from HeroicEric/ghosts-be-gone
Browse files Browse the repository at this point in the history
Encourage decorator-style Ember.computed/Ember.observer
  • Loading branch information
michaelrkn committed Jul 23, 2015
2 parents c136293 + 518cb7e commit b18d4d7
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 80 deletions.
4 changes: 2 additions & 2 deletions source/models/defining-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export default DS.Model.extend({
firstName: DS.attr(),
lastName: DS.attr(),

fullName: function() {
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
})
});
```

Expand Down
4 changes: 2 additions & 2 deletions source/object-model/computed-properties-and-aggregate-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export default Ember.Controller.extend({
Ember.Object.create({ isDone: true })
],

remaining: function() {
remaining: Ember.computed('todos.@each.isDone', function() {
var todos = this.get('todos');
return todos.filterBy('isDone', false).get('length');
}.property('todos.@each.isDone')
})
});
```

Expand Down
31 changes: 11 additions & 20 deletions source/object-model/computed-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

In a nutshell, computed properties let you declare functions as properties. You create one by defining a computed property as a function, which Ember will automatically call when you ask for the property. You can then use it the same way you would any normal, static property.

It's super handy for taking one or more normal properties and transforming or manipulating their data to create a new value.
It's super handy for taking one or more normal properties and transforming or manipulating their data to create a new value.

### Computed properties in action

Expand All @@ -14,9 +14,9 @@ Person = Ember.Object.extend({
firstName: null,
lastName: null,

fullName: function() {
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
})
});

var ironMan = Person.create({
Expand All @@ -26,19 +26,10 @@ var ironMan = Person.create({

ironMan.get('fullName'); // "Tony Stark"
```
Notice that the `fullName` function calls `property`. This declares the function to be a computed property, and the arguments tell Ember that it depends on the `firstName` and `lastName` attributes.

Whenever you access the `fullName` property, this function gets called, and it returns the value of the function, which simply calls `firstName` + `lastName`.

#### Alternate invocation
This declares the function to be a computed property, and the arguments tell Ember that it depends on the `firstName` and `lastName` attributes.

At this point, you might be wondering how you are able to call the `.property` function on a function. This is possible because Ember extends the `function` prototype. More information about extending native prototypes is available in the [disabling prototype extensions guide](../../configuring-ember/disabling-prototype-extensions/). If you'd like to replicate the declaration from above without using these extensions you could do so with the following:

```javascript
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');
})
```
Whenever you access the `fullName` property, this function gets called, and it returns the value of the function, which simply calls `firstName` + `lastName`.

### Chaining computed properties

Expand All @@ -51,13 +42,13 @@ Person = Ember.Object.extend({
age: null,
country: null,

fullName: function() {
fullName: Ember.computed('firstName', 'lastName', function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName'),
}),

description: function() {
description: Ember.computed('fullName', 'age', 'country', function() {
return this.get('fullName') + '; Age: ' + this.get('age') + '; Country: ' + this.get('country');
}.property('fullName', 'age', 'country')
})
});

var captainAmerica = Person.create({
Expand All @@ -72,7 +63,7 @@ captainAmerica.get('description'); // "Steve Rogers; Age: 80; Country: USA"

### Dynamic updating

Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called. Let's use computed properties to dynamically update.
Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called. Let's use computed properties to dynamically update.

```javascript
captainAmerica.set('firstName', 'William');
Expand Down Expand Up @@ -112,4 +103,4 @@ captainAmerica.get('firstName'); // William
captainAmerica.get('lastName'); // Burnside
```

Ember will call the computed property for both setters and getters, so if you want to use a computed property as a setter, you'll need to check the number of arguments to determine whether it is being called as a getter or a setter. Note that if a value is returned from the setter, it will be cached as the propertys value.
Ember will call the computed property for both setters and getters, so if you want to use a computed property as a setter, you'll need to check the number of arguments to determine whether it is being called as a getter or a setter. Note that if a value is returned from the setter, it will be cached as the property's value.
60 changes: 22 additions & 38 deletions source/object-model/observers.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
Ember supports observing any property, including computed properties.
You can set up an observer on an object by using the `observes`
method on a function:
You can set up an observer on an object by using `Ember.observer`:

```javascript
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {

fullName: Ember.computed('firstName', 'lastName', function() {
var firstName = this.get('firstName');
var lastName = this.get('lastName');

return firstName + ' ' + lastName;
}.property('firstName', 'lastName'),
}),

fullNameChanged: function() {
fullNameChanged: Ember.observer('fullName', function() {
// deal with the change
}.observes('fullName').on('init')
})
});

var person = Person.create({
Expand All @@ -31,7 +30,6 @@ person.set('firstName', 'Brohuda'); // observer will fire
Because the `fullName` computed property depends on `firstName`,
updating `firstName` will fire observers on `fullName` as well.


### Observers and asynchrony

Observers in Ember are currently synchronous. This means that they will fire
Expand All @@ -40,23 +38,23 @@ is easy to introduce bugs where properties are not yet synchronized:

```javascript
Person.reopen({
lastNameChanged: function() {
lastNameChanged: Ember.observer('lastName', function() {
// The observer depends on lastName and so does fullName. Because observers
// are synchronous, when this function is called the value of fullName is
// not updated yet so this will log the old value of fullName
console.log(this.get('fullName'));
}.observes('lastName')
})
});
```

This synchronous behaviour can also lead to observers being fired multiple
This synchronous behavior can also lead to observers being fired multiple
times when observing multiple properties:

```javascript
Person.reopen({
partOfNameChanged: function() {
partOfNameChanged: Ember.observer('firstName', 'lastName', function() {
// Because both firstName and lastName were set, this observer will fire twice.
}.observes('firstName', 'lastName')
})
});

person.set('firstName', 'John');
Expand All @@ -69,15 +67,15 @@ next run loop once all bindings are synchronized:

```javascript
Person.reopen({
partOfNameChanged: function() {
partOfNameChanged: Ember.observer('firstName', 'lastName', function() {
Ember.run.once(this, 'processFullName');
}.observes('firstName', 'lastName'),
}),

processFullName: function() {
processFullName: Ember.observer('fullName', function() {
// This will only fire once if you set two properties at the same time, and
// will also happen in the next run loop once all properties are synchronized
console.log(this.get('fullName'));
}.observes('fullName')
})
});

person.set('firstName', 'John');
Expand All @@ -89,18 +87,18 @@ person.set('lastName', 'Smith');
Observers never fire until after the initialization of an object is complete.

If you need an observer to fire as part of the initialization process, you
cannot rely on the side effect of set. Instead, specify that the observer
should also run after init by using `.on('init')`:
cannot rely on the side effect of `set`. Instead, specify that the observer
should also run after `init` by using `Ember.on()`:

```javascript
Person = Ember.Object.extend({
init: function() {
this.set('salutation', "Mr/Ms");
this.set('salutation', 'Mr/Ms');
},

salutationDidChange: function() {
salutationDidChange: Ember.on('init', Ember.observer('salutation', function() {
// some side effect of salutation changing
}.observes('salutation').on('init')
}))
});
```

Expand All @@ -116,26 +114,12 @@ the value of a computed property, put it in DOM (or draw it with D3), and then
observe it so you can update the DOM once the property changes.

If you need to observe a computed property but aren't currently retrieving it,
just get it in your init method.


### Without prototype extensions

You can define inline observers by using the `Ember.observer` method if you
are using Ember without prototype extensions:

```javascript
Person.reopen({
fullNameChanged: Ember.observer('fullName', function() {
// deal with the change
})
});
```
just get it in your `init` method.

### Outside of class definitions

You can also add observers to an object outside of a class definition
using addObserver:
using `addObserver`:

```javascript
person.addObserver('fullName', function() {
Expand Down
4 changes: 2 additions & 2 deletions source/routing/query-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default Ember.Controller.extend({
queryParams: ['category'],
category: null,

filteredArticles: function() {
filteredArticles: Ember.computed('category', 'model', function() {
var category = this.get('category');
var articles = this.get('model');

Expand All @@ -52,7 +52,7 @@ export default Ember.Controller.extend({
} else {
return articles;
}
}.property('category', 'model')
})
});
```

Expand Down
6 changes: 3 additions & 3 deletions source/templates/rendering-with-helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ Total Posts: {{postCount}}

```app/controllers/author.js
export default Ember.Controller.extend({
postCount: function() {
return this.get("model.posts.length");
}.property("model.posts.[]")
postCount: Ember.computed('model.posts.[]', function() {
return this.get('model.posts.length');
})
})
```

Expand Down
12 changes: 6 additions & 6 deletions source/testing/testing-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default Ember.Component.extend({
layout: layout,
classNames: ['pretty-color'],
attributeBindings: ['style'],
style: function() {
style: Ember.computed('name', function() {
return 'color: ' + this.get('name') + ';';
}.property('name')
})
});
```

Expand Down Expand Up @@ -214,9 +214,9 @@ export default Ember.Component.extend({
layout: layout,
tagName: 'img',
attributeBindings: ['width', 'height', 'src'],
src: function() {
src: Ember.computed('width', 'height', function() {
return 'http://placekitten.com/' + this.get('width') + '/' + this.get('height');
}.property('width', 'height')
})
});
```
Expand All @@ -232,9 +232,9 @@ export default Ember.Component.extend({
layout: layout,
tagName: 'img',
attributeBindings: ['width', 'height', 'src'],
src: function() {
src: Ember.computed('width', 'height', function() {
return 'http://placekitten.com/' + this.get('width') + '/' + this.get('height');
}.property('width', 'height')
})
});
```
Expand Down
13 changes: 6 additions & 7 deletions source/testing/unit-testing-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ based on a `foo` property.
export default Ember.Object.extend({
foo: 'bar',

computedFoo: function() {
computedFoo: Ember.computed('foo', function() {
return 'computed ' + this.get('foo');
}.property('foo')
})
});
```

Expand Down Expand Up @@ -60,8 +60,8 @@ export default Ember.Object.extend({
});
```

To test it, we create an instance of our class `SomeThing` as defined above,
call the `testMethod` method and assert that the internal state is correct as a
To test it, we create an instance of our class `SomeThing` as defined above,
call the `testMethod` method and assert that the internal state is correct as a
result of the method call.

```tests/unit/models/some-thing-test.js
Expand Down Expand Up @@ -114,9 +114,9 @@ Suppose we have an object that has a property and a method observing that proper
export default Ember.Object.extend({
foo: 'bar',
other: 'no',
doSomething: function(){
doSomething: Ember.observer('foo', function(){
this.set('other', 'yes');
}.observes('foo')
})
});
```

Expand All @@ -134,4 +134,3 @@ test('doSomething observer sets other prop', function() {
equal(someThing.get('other'), 'yes');
});
```

0 comments on commit b18d4d7

Please sign in to comment.