I'm using Typescript 2.1(developer version) to transpile async/await to ES5.
I've noticed that after I change any property which is bound to view in my async function the view isn't updated with current value, so each time I have to call $scope.$apply() at the end of function.
Example async code:
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
//$scope.$apply(); <-- would like to omit this
}
And new text
value isn't shown in view after this.
Is there any workaround so I don't have to manually call $scope.$apply() every time?
The answers here are correct in that AngularJS does not know about the method so you need to 'tell' Angular about any values that have been updated.
Personally I'd use $q
for asynchronous behaviour instead of using await
as its "The Angular way".
You can wrap non Angular methods with $q quite easily i.e. [Note this is how I wrap all Google Maps functions as they all follow this pattern of passing in a callback to be notified of completion]
function doAThing()
{
var defer = $q.defer();
// Note that this method takes a `parameter` and a callback function
someMethod(parameter, (someValue) => {
$q.resolve(someValue)
});
return defer.promise;
}
You can then use it like so
this.doAThing().then(someValue => {
this.memberValue = someValue;
});
However if you do wish to continue with await
there is a better way than using $apply
, in this case, and that it to use $digest
. Like so
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
$scope.$digest(); <-- This is now much faster :)
}
$scope.$digest
is better in this case because $scope.$apply
will perform dirty checking (Angulars method for change detection) for all bound values on all scopes, this can be expensive performance wise - especially if you have many bindings. $scope.$digest
will, however, only perform checking on bound values within the current $scope
making it much more performant.
This can be conveniently done with angular-async-await
extension:
class SomeController {
constructor($async) {
this.testAsync = $async(this.testAsync.bind(this));
}
async testAsync() { ... }
}
As it can be seen, all it does is wrapping promise-returning function with a wrapper that calls $rootScope.$apply()
afterwards.
There is no reliable way to trigger digest automatically on async
function, doing this would result in hacking both the framework and Promise
implementation. There is no way to do this for native async
function (TypeScript es2017
target), because it relies on internal promise implementation and not Promise
global. More importantly, this way would be unacceptable because this is not a behaviour that is expected by default. A developer should have full control over it and assign this behaviour explicitly.
Given that testAsync
is being called multiple times, and the only place where it is called is testsAsync
, automatic digest in testAsync
end would result in digest spam. While a proper way would be to trigger a digest once, after testsAsync
.
In this case $async
would be applied only to testsAsync
and not to testAsync
itself:
class SomeController {
constructor($async) {
this.testsAsync = $async(this.testsAsync.bind(this));
}
private async testAsync() { ... }
async testsAsync() {
await Promise.all([this.testAsync(1), this.testAsync(2), ...]);
...
}
}
I have examined the code of angular-async-await and It seems like they are using $rootScope.$apply()
to digest the expression after the async promise is resolved.
This is not a good method. You can use AngularJS original $q
and with a little trick, you can achieve the best performance.
First, create a function ( e.g., factory, method)
// inject $q ...
const resolver=(asyncFunc)=>{
const deferred = $q.defer();
asyncFunc()
.then(deferred.resolve)
.catch(deferred.reject);
return deferred.promise;
}
Now, you can use it in your for instance services.
getUserInfo=()=>{
return resolver(async()=>{
const userInfo=await fetch(...);
const userAddress= await fetch (...);
return {userInfo,userAddress};
});
};
This is as efficient as using AngularJS $q
and with minimal code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With