angular: Async event subscriber not updating UI after async call
I have (essentially) the following code in a component of my app:
public ngOnInit() {
this.thing.changed.subscribe(() => this.update());
}
public async update() {
try {
this.a = 1;
this.b = await this.dataService.getAsync();
this.c = 2;
}
catch (error) {
// blah
}
}
The TypeScript is being compiled to ES6 then run through Babel for ES5 (as TypeScript can’t compile to ES5 with async/await yet).
The UI is displaying variables a
, b
, and c
. When the changed
event is raised by the thing
service, variable a
is correctly displayed as 1
, but b
and c
do not update in the UI. Console logging shows they are updated in the component. If I then trigger an update manually (by clicking some unrelated button that updates the UI), b
and c
are rendered correctly.
I’ve put a breakpoint after the async call, and Zone.run
is in the call stack.
I can try and create a minimal reproduction, but my first question is whether this behaviour is expected (and async event subscribers are simply not supported), or if this looks like a genuine bug.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 12
- Comments: 26 (2 by maintainers)
Hi!
The issue is that the async call result is outside ngZone thus not triggering the UI update. You can do something like:
Resolved! The culprit was Babel… I’m not sure what precisely, but I reverted all my Babel related npm modules back to a known good version and everything is functioning normally again. Apologies that the title of this issue turned out to be nothing to do with the problem.
I reproduced the issue on the AngularClass Angular2 Webpack Starter project so I can upload that minimal demonstration of the issue if it interests anyone. I simply modified the project to update the UI using setTimeout as above, then modified the Webpack config to use Babel as part of the transpilation process… and suddenly setTimeout stops updating the page.
Thanks for the help @jpsfs and @zoechi.
So by last quarter of 2018 we still have the same issue with async calls and zone.js.
@jamesthurley Working with the configuration provided, I came across another issue that seems to be a hit or miss with the babel-loader I can’t quite narrow down. babel generates bad code (seems to happen when I refactor my code a lot). It generates “import _Object$defineProperty from ‘babel-runtime/core-js/object/define-property’;” that is still es6. I’m guessing this is still not quite stable.
Looking around I saw that others have hit the same issue and have had a workaround provided to isolate the plugins. I’ve changed my .babelrc configure to incorporate the workaround. Let me know if you hit this error yourself.
https://github.com/babel/babel-loader/issues/195#issuecomment-238804184
.babelrc
I had same problem with angular 6 and 7. There was a
*ngFor
in my html file and when i update the array, nothing happened to ui until click on one of components in that page. so this worked for me:*ngFor
on an array, use anObservable
andlet a of _observableVar | async
in html fileprivate _ngZone: NgZone
incomponent
constructorthis._ngZone.run(() => {});
after your async call.neither of my methods have
async/await
syntaxI have the same problem with the version of angular 6.1.10. It does not update the form data. It is only updated if I click on some field of the form which should not happen. This happens to me when I make a guard in a component that validates the user’s authentication status and I subscribe to a firebase route, but if I remove the guard it works correctly for me. I have been looking at all the logic and so far I have not been able to solve the problem
@jamesthurley I have a nice workaround update. I found a way to keep zone.js inside the npm/webpack package management. Add a separate loader to your webpack.config.js file and raw load in the javascript workaround.
webpack.config.js
./ClientApp/fixes/zone-babel-async.js
./ClientApp/boot-client.ts
@wizarrc, amazing work! This problem re-appeared for me when I updated to Angular 2 Beta 8, even with the fixed Babel version, so since then I’ve had both Babel and Angular fixed to old versions.
My plan was to wait until TypeScript 2.0 (now bumped to 2.1) before attempting to update everything, sans Babel, and it sounds like I would probably have been disappointed with the result! I’ll try and give your fix a try in the near future. I really appreciate you digging into this… I’m surprised it hasn’t effected more people.
@jamesthurley I ran into the same issue with babel. I figured this beast out finally! I will explain what is happening and what I was able to hack together to correct the issue.
I want to start out saying all the pieces I was using.
Windows 10 1607 Build 14393.51 AspNet Core 1.0 final nodejs 6.3.1 npm 3.10.3
package.json
.babelrc
webpack.config
tsconfig.json
./wwwroot/index.cshtml
./ClientApp/boot-client.ts
I cut out the unrelated parts of the file.
My understanding of zones.js is that it monkey patches all the various asynchronous calls in order to have angular 2 get notified and perform a change detection. So it turns out that if you import/require zones.js inside the boot-client.ts file while compiling async/await down to es5 using babel with the async plugin, in this case stage-0, it will wrap the async call in a promise. The problem is that babel injects code to patch the native Promise API before the zone.js has a chance to. A workaround is to patch the Promise using zone.js before the babel runtime has a chance to do so, then create a global variable of the patched zone-aware Promise API. Once inside the webpack bootstrap file, in this case, boot-client.ts, patch the babel runtime to use the zone-aware promise. I provide an example patch to solve this issue allowing babel to use a zone-aware promise. In the example I copy zone.js outside of the npm package and into the wwwroot/dist folder. Unfortunately, I can no longer update zone.js using npm. Ideally I would like to keep zone.js inside webpack but outside of babel, but I’m not really sure how to achieve this.
Workaournd:
index.cshtml
./ClientApp/boot-client.ts
Hope this hack helps anyone trying to use Angular2/TypeScript targeting es6 wanting async/await with babel and webpack.
@jpsfs the behavior looks like the update runs outside the zone, the question is IMHO - why is this running outside the zone?