Skip to content

Commit

Permalink
Protect async properties against race conditions
Browse files Browse the repository at this point in the history
* Added  injector for async promise implementation convenience

* Updated README accordingly

* Bumped version to 1.2.2
  • Loading branch information
karolsluszniak committed Apr 2, 2015
1 parent ae9574c commit 24fb52f
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 13 deletions.
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Computed property concept allows to produce more efficient and elegant scope pro

- 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 can make use of promises (e.g. compute property via API call)
- views are simplified with `{{ var }}` instead of `{{ computeVar() }}`
- computed properties are visually separated in controller code
- definition syntax is consistent with core Angular concepts
Expand Down Expand Up @@ -90,11 +91,7 @@ Or, if you need more control, as functions:

```js
this.nested = { num: 31 };

var local = 49;
var computeFunc = function(nestedNum, localNum) {
return nestedNum + localNum;
};

$computed(this, 'sum', [
function() { return this.nested.num; },
Expand All @@ -119,7 +116,24 @@ You can also return a promise from your compute function. This, for instance, ma
```js
$computed(scope, 'userName', ['userId', function(userId) {
return $http.get('/users/' + userId).then(function(response) {
return response.data.user.firstName + ' ' + response.data.user.lastName;
return response.data.firstName + ' ' + response.data.lastName;
});
}]);
```

**$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**:

```js
$computed(scope, 'userName', ['userId', function(userId, $valid) {
return $http.get('/users/' + userId).then(function(response) {
if ($valid()) {
// imagine here a code you don't want to process for old promises
scope.userChangeCount += 1;
}

return response.data.firstName + ' ' + response.data.lastName;
});
}]);
```
Expand Down
12 changes: 9 additions & 3 deletions angular-computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
}

/* last element in properties array is a compute function */
var valueFunc = properties.splice(properties.length - 1)[0];
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) {
Expand All @@ -27,10 +28,15 @@
return angular.bind(context, item);
}
}), function(newValues) {
var promise = $q.when(valueFunc.apply(context, newValues));
var thisVer = ++version,
valid = function valid() { return thisVer === version; },
valueArgs = newValues.concat([valid]),
promise = $q.when(valueFunc.apply(context, valueArgs));

promise.then(function(response) {
context[name] = response;
if (valid()) {
context[name] = response;
}
});
});
};
Expand Down
7 changes: 2 additions & 5 deletions 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.1",
"version": "1.2.2",
"homepage": "https://github.com/karolsluszniak/angular-computed",
"authors": [
"Karol Sluszniak <ksluszniak@gmail.com>"
Expand All @@ -18,8 +18,5 @@
"$computed",
"$watch"
],
"license": "MIT",
"ignore": [
".jshintrc"
]
"license": "MIT"
}

0 comments on commit 24fb52f

Please sign in to comment.