Skip to content

Commit

Permalink
Add manual recalculation trigger
Browse files Browse the repository at this point in the history
* Updated README accordingly

* Bumped version to 1.2.3
  • Loading branch information
karolsluszniak committed Apr 7, 2015
1 parent 24fb52f commit bb98b48
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 20 deletions.
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# angular-computed

Computed property concept allows to produce more efficient and elegant scope properties. With **$computed** you get an easy way to define them using pattern similar to Dependency Injection, well known to all Angular developers. Here are some advantages:
Computed properties allow to produce efficient and elegant properties that are a result of some calculation dependent on other properties. With **$computed** you get an easy way to define them using pattern similar to Angular's Dependency Injection. Here are some advantages:

- property computation is executed just once after input change
- property computation is executed just once even if property is used in multipe places
- property computation is executed just once even if property is used in multiple places
- property computation can make use of promises (e.g. compute property via API call)
- views are simplified with `{{ var }}` instead of `{{ computeVar() }}`
- views are simplified with `{{ prop }}` instead of `{{ computeProp() }}`
- computed properties are visually separated in controller code
- definition syntax is consistent with core Angular concepts

Internally, **$computed** is very simple and mostly based on `$watch` ability of Angular Scope. It's just a tiny, clean pattern implementation.
Internally, **$computed** is very simple and mostly based on Angular's `$watch`. It's just a tiny, clean pattern implementation.

## Usage

Expand Down Expand Up @@ -37,7 +37,6 @@ app.controller('AppCtrl', ['$computed', function($computed) {
// Computed property
$computed(this, 'sum', ['a', 'b', function(a, b) {
// You can still use *this* inside computing function

return a + b + this.const;
}]);
}]);
Expand Down Expand Up @@ -65,7 +64,6 @@ app.controller('AppCtrl', ['$scope', function($scope) {
// Computed property
$scope.$computed('sum', ['a', 'b', function(a, b) {
// Here, *this* refers to your scope

return a + b + this.const;
}]);
}]);
Expand Down Expand Up @@ -99,15 +97,15 @@ $computed(this, 'sum', [
computeFunc]);
```

Guessing variable names from function signature, like Angular does in its DI, is not supported here. It would not work after minification and could create ugly bugs in your code.
Guessing dependencies from function signature, like Angular's DI, is not supported. It would not work after minification and could create ugly bugs in your code.

### Computing the property

Compute function gets called when any of its dependencies change. It takes all dependencies as arguments and should return new property value:

```js
$computed(scope, 'descriptionIsLong', ['description', function(description) {
return (description.length > 512);
return (description.length > 100);
}]);
```

Expand All @@ -123,7 +121,7 @@ $computed(scope, 'userName', ['userId', function(userId) {

**$computed** protects your async properties against race conditions. If input changes again before previous promise was resolved, the value returned by that previous promise will never get assigned to the property anymore.

Your `.then()` callbacks will still execute, but there's a way to avoid it - by using a special `$valid` function injected for your convenience by **$computed**:
Your `then()` callbacks will still execute, but there's a way to avoid it - by using a special `$valid` function injected for your convenience by **$computed**:

```js
$computed(scope, 'userName', ['userId', function(userId, $valid) {
Expand Down Expand Up @@ -167,7 +165,17 @@ this.a = this.a + 1;
this.b = this.b - 1;
```

The `sum` property will be recomputed due to input change but its result will not change and thus the `sumPow` property will not have to be recomputed.
The `sum` property will be recomputed due to input change but its result will not change and thus the `sumPow` property will not have to be recomputed. Neat, isn't it?

### Manual recalculation

You may wish to recompute previously defined property on demand, even if none of its dependencies have changed. You can do so like this:

```js
$computed(this, 'prop').touch();
```

You could even define a computed property without dependencies and have it computed automatically only once, on scope initialization, and recompute it on demand afterwards. This is not a primary application of **$computed**, but this behavior may prove useful at some point.

## Contributing

Expand Down
37 changes: 28 additions & 9 deletions angular-computed.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
(function() {'use strict';
angular.module('ngComputed', []).
angular.module('ngComputed', [])

service('$computed', ['$rootScope', '$q', function($rootScope, $q) {
return function angularCmputed(context, name, properties) {
.service('$computed', ['$rootScope', '$q', function($rootScope, $q) {
function touchKey(name) {
return '$$computed-touch-' + name;
}

function controller(context, name) {
return {
touch: function() {
return (context[touchKey(name)] = (context[touchKey(name)] || 0) + 1);
}
};
}

return function angularComputed(context, name, properties) {
/* computed(this) in controller injects service into the controller context */
if (typeof context === 'object' && name === undefined) {
context.$computed = angularCmputed;
return;
context.$computed = angularComputed;
return context;
}

/* computed(name, properties) uses this for context */
Expand All @@ -16,12 +28,17 @@
context = this;
}

/* computed([context], name) returns controller */
if (properties === undefined) {
return controller(context, name);
}

/* last element in properties array is a compute function */
var valueFunc = properties.splice(properties.length - 1)[0],
version = 0;

/* watch a group of specified properties and evaluate compute function */
$rootScope.$watchGroup(properties.map(function(item) {
$rootScope.$watchGroup(properties.concat([touchKey(name)]).map(function(item) {
if (typeof item === 'string') {
return function() { return context[item]; };
} else if (typeof item === 'function') {
Expand All @@ -30,7 +47,7 @@
}), function(newValues) {
var thisVer = ++version,
valid = function valid() { return thisVer === version; },
valueArgs = newValues.concat([valid]),
valueArgs = newValues.slice(0, newValues.length - 1).concat([valid]),
promise = $q.when(valueFunc.apply(context, valueArgs));

promise.then(function(response) {
Expand All @@ -39,11 +56,13 @@
}
});
});

return controller(context, name);
};
}]).
}])

/* provide scope.$computed(name, properties) on all scopes */
run(['$rootScope', '$computed', function($rootScope, $computed) {
.run(['$rootScope', '$computed', function($rootScope, $computed) {
$computed($rootScope);
}]);
})();
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "angular-computed",
"main": "angular-computed.js",
"version": "1.2.2",
"version": "1.2.3",
"homepage": "https://github.com/karolsluszniak/angular-computed",
"authors": [
"Karol Sluszniak <ksluszniak@gmail.com>"
Expand Down

0 comments on commit bb98b48

Please sign in to comment.