Angular data binding won't work with async/await, yet it will with promises
angular, async-await, data-binding, javascript, typescript
Solution
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:
function nativeAsync(target, method, descriptor) {
const originalMethod = target[method];
descriptor.value = function () {
return Promise.resolve(originalMethod.apply(this, arguments));
}
}
Here is an example that uses `@nativeAsync` decorator on `async` methods to trigger change detection:
@nativeAsync
async getFoo() {
await new Promise(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`.
Problem
Data bindings don't get updated if their values are changed after an `await` statement. ``` handle() { this.message = 'Works' } async handle() { this.message = 'Works' } async handle() { await new Promise((resolve, reject) => { resolve() }) this.message = 'Works' } async handle() { await new Promise((resolve, reject) => { setTimeout(() => resolve(), 3000) }) this.message = 'Doesn\'t work' } handle() { new Promise((resolve, reject) => { setTimeout(() => resolve(), 3000) }) .then(() => this.message = 'Works') } ``` Why do the last two not behave the same? aren't they supposed to be the same thing? Ionic: 3.9.2 Angular: 5.0.3 TypeScript: 2.4.2 EDIT: I came across another problem with this which may be useful to some. Changing the values of a binding in the constructor behaves differently to ionViewDidLoad or ngOnInit! ``` constructor(private zone: NgZone) { // This will cause the same problems, bindings not updating this.handle() } constructor(private zone: NgZone) { // Unless you do this... this.zone.run(() => { this.handle() }) } ionViewDidLoad() { // But I think this is better/cleaner this.handle() } ```