Angular Data Binding Won't Work With Async/await, Yet It Will With Promises
Solution 1:
Angular relies on Zone.js for change detection, and Zone.js provides this by patching every API that can provide asynchronous behaviour.
The problem is in how native async
functions are implemented. As confirmed in this question, they don't just wrap around global Promise
but rely on internal mechanisms that may vary from one browser to another.
Zone.js patches Promise
but it's impossible to patch internal promise that is used by async
functions in current engine implementations (here is open issue for that).
Usually (async () => {})() instanceof Promise === true
. In case of Zone.js, this isn't true; async
function returns an instance of native Promise
, while Promise
global is zone-aware promise patched by Zone.js.
In order to make native async
functions work in Angular, change detection should be additionally triggered. This can be done by triggering it explicitly (as another answer already suggests) or by using any zone-aware API. A helper that wraps async
function result with zone-aware promise will do the trick:
functionnativeAsync(target, method, descriptor) {
const originalMethod = target[method];
descriptor.value = function () {
returnPromise.resolve(originalMethod.apply(this, arguments));
}
}
Here is an example that uses @nativeAsync
decorator on async
methods to trigger change detection:
@nativeAsyncasyncgetFoo() {
awaitnewPromise(resolve =>setTimeout(resolve, 100));
this.foo = 'foo';
}
Here is same example that doesn't use additional measures to trigger change detection and expectedly doesn't work as intended.
It makes sense to stick to native implementation in environment that doesn't require transpilation step. Since Angular application is supposed to be compiled any way, the problem can be solved by switching from ES2017
to ES2015
or ES2016
TypeScript target
.
Solution 2:
just like estus said, currently zone.js
don't support native async/await, so you can't compile typescript which target to ES2017
, and I am working on it, https://github.com/angular/zone.js/pull/795, I have made a working demo which can run in nodejs, but in browser(chrome), it will still take some time because chrome does't open javascript version of AsyncHooks and PromiseHooks for now.
Solution 3:
It's got to do with how change detection works in Angular. See: https://stackblitz.com/edit/angular-jajbza?file=app%2Fapp.component.ts
I'm betting Ionic uses OnPush strategy by default, or you've enabled it, as I did in the Blitz. It's all good, IMO it should be on by default anyway as it forces you to think about these things and write more performant code.
Can't say exatly why your view is updated in your last example when you call .then though. Maybe in that case CD manages to keep track of the Promise, but not with async functions. Async functions themselves return a Promise if you don't, so basically after transpilation, async handle() returns something like Promise.resolve(null), though the actual JS code probably looks messier than that.
Edit: Another way, perhaps cleaner than calling detectChanges manually would be to run anything that changes the view inside Angular's zone:
import { NgZone } from '@angular/core';
constructor (private zone:NgZone){}
// after await somePromise :this.zone.run(() => {
this.someProperty = 'Something changed';
});
Edit2: Interesting, zone.run() actually doesn't change anything. Tested that in the blitz. So manual CD is the only way. One more reason to avoid async/await and try to stick with Observables and NG's async pipe. :)
Solution 4:
Check your browser version and what it supports, async/await is native with ES2017. If your browser doesn't support that use ES2016 as your target.
I needed ES2016 for Electron.
Post a Comment for "Angular Data Binding Won't Work With Async/await, Yet It Will With Promises"