Change detection not triggering for template event handler
See original GitHub issue🐞 bug report
Description
I am working on an Angular gallery library (repository) and I have a change detection problem with the navigation arrows. The first navigation arrow looks like this in the template:
<div *ngIf="showPrevArrow" class="prev arrow" (click)="prev()">
<doe-chevron-icon
*ngIf="!arrowTemplate; else arrowTemplate"
></doe-chevron-icon>
</div>
Important part is the click event handler and ngIf on the div. Now everytime it is clicked, the prev
method should be called AND change detection should run. This works most of the time:
But in a certain scenario (see reproduction steps), the change detection is not triggered after click which then looks like this:
When I wrap contents of prev
method in ngZone.run(() => {...}
, the change detection always fires. That’s good, but still, I shouldn’t be supposed to do that in such a rudimentary case.
🔬 Minimal Reproduction
- go to https://stackblitz.com/edit/ngx-doe-gallery-x1mbws
- in the first gallery, swipe to the 2nd picture
- now click prev arrow to go back to 1st picture
- have a look at the image counter, it indicates the second picture is still selected
🌍 Your Environment
Angular Version:
Angular CLI: 8.3.20
Node: 12.16.3
OS: linux x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Package Version
@angular-devkit/architect 0.803.20
@angular-devkit/build-angular 0.803.25
@angular-devkit/build-ng-packagr 0.803.25
@angular-devkit/build-optimizer 0.803.25
@angular-devkit/build-webpack 0.803.25
@angular-devkit/core 8.3.20
@angular-devkit/schematics 8.3.20
@angular/cdk 8.2.3
@angular/cli 8.3.20
@angular/material 8.2.3
@ngtools/webpack 8.3.25
@schematics/angular 8.3.20
@schematics/update 0.803.20
ng-packagr 5.7.1
rxjs 6.4.0
typescript 3.5.3
webpack 4.39.2
Anything else relevant? The issue is not platform or browser specific. I tried with angular 9 with the same results.
Issue Analytics
- State:
- Created 3 years ago
- Comments:12 (7 by maintainers)
Top GitHub Comments
Here is my understanding: In
ngOnInit
you escapengZone
and than callthis.cd.detectChanges()
outsidengZone
Sincethis.cd.detectChanges()
causes*ngIf
to instantiate a template that template’s click handler will be registered outside of thengZone
as a result clicking on the registered event handler will execute handler function outside of thengZone
hence your observation.Yes this works as expected (correct behavior) and Yes it is surprising.
So the question is what can we do to make it less surprising.
One mental model which we could adopt is to say that all event handlers registered from the
(event)
syntax always run withNgZone
currently installed no matter how thethis.cd.detectChanges()
was actually called. This would actually align it nicely with #38236. There would be few perf advantages to this so I think it would be worth a try.EventManager
so thataddEventListener
is always done withelement.__zone_symbol__addEventListener
. This will effectively make sure that all registration will happen outside of the Zone, which should improve performance.function wrapListener
so that it will always execute thelistenerFn
in theNgZone
provided.The result is that no matter how the template was created, we can be sure that he template behaves consistently (We also save on the registration as we don’t have to pay for the zone.js registration overhead)
I don’t know if such change to the mental model would pass
google3
but the only way to know is to implement and try.NOTE: One place where I could see an issue is that in
DebugNode
we usezone.js
to retrieve callbacks for testing, which we may have to solve in a different way.I tried ng 9 again and the problem persists. I also copied the SO Q over. About narrowing the problem down, that won’t be easy as this repro is about a library which is an integral whole, but I’ll see what I can do.