admin管理员组文章数量:1287524
To put my problem simply, I have an element in ponent's template. This element has an ngIf
condition and a (click)
handler. It is not rendered from the very beginning, because the ngIf condition evaluates to false
.
Now es the interesting part: A code running outside the angular zone changes that condition to true
, and after executing detectChanges
on the change detector ref manually, this element gets rendered and the click handler ofc bees active.
It all seems ok so far, but the problem is that when the (click)
callback is run upon user's click, change detection is not triggered for the ponent.
Here is the reproduction
Steps to reproduce it there:
- Click at the beige area
- Button appears, click it too
- Nothing happens, although message should have appeared below
Description:
The beige area has a click event handler registered via addEventListener, and this event listener's callback is running outside the angular zone. Inside it a ponent's
showButton
property is set fromfalse
totrue
and I trigger change detection there manually by callingdetectChanges()
, otherwise the change in theshowButton
property wouldn't be registered. The code looks like this:this.zone.runOutsideAngular(() => { const el = this.eventTarget.nativeElement as HTMLElement; el.addEventListener('click', e => { this.showButton = true; this.cd.detectChanges(); }) })
Now button appears, which thanks to
*ngIf="showButton"
wasn't rendered initially, and it has a click even handler declared in the template. This handler again changes ponent's property, this timeshowMessage
totrue
.<button *ngIf="showButton" (click)="onButtonClick()">Click me!</button> onButtonClick() { this.showMessage = true; }
When I click it, the handler obviously runs and changes ponent's
showMessage
totrue
, but it doesn't trigger change detection and message below doesn't appear. To make the example work, just set showButton to true from the very beginning, and the scenario above works.
The question is: How is this possible? Since I declared the (click)
event handler in the template, shouldn't it always trigger change detection when called?
To put my problem simply, I have an element in ponent's template. This element has an ngIf
condition and a (click)
handler. It is not rendered from the very beginning, because the ngIf condition evaluates to false
.
Now es the interesting part: A code running outside the angular zone changes that condition to true
, and after executing detectChanges
on the change detector ref manually, this element gets rendered and the click handler ofc bees active.
It all seems ok so far, but the problem is that when the (click)
callback is run upon user's click, change detection is not triggered for the ponent.
Here is the reproduction https://stackblitz./edit/angular-kea4wi
Steps to reproduce it there:
- Click at the beige area
- Button appears, click it too
- Nothing happens, although message should have appeared below
Description:
The beige area has a click event handler registered via addEventListener, and this event listener's callback is running outside the angular zone. Inside it a ponent's
showButton
property is set fromfalse
totrue
and I trigger change detection there manually by callingdetectChanges()
, otherwise the change in theshowButton
property wouldn't be registered. The code looks like this:this.zone.runOutsideAngular(() => { const el = this.eventTarget.nativeElement as HTMLElement; el.addEventListener('click', e => { this.showButton = true; this.cd.detectChanges(); }) })
Now button appears, which thanks to
*ngIf="showButton"
wasn't rendered initially, and it has a click even handler declared in the template. This handler again changes ponent's property, this timeshowMessage
totrue
.<button *ngIf="showButton" (click)="onButtonClick()">Click me!</button> onButtonClick() { this.showMessage = true; }
When I click it, the handler obviously runs and changes ponent's
showMessage
totrue
, but it doesn't trigger change detection and message below doesn't appear. To make the example work, just set showButton to true from the very beginning, and the scenario above works.
The question is: How is this possible? Since I declared the (click)
event handler in the template, shouldn't it always trigger change detection when called?
- reproduction steps with stackblitz demo would be great! – alt255 Commented May 10, 2020 at 13:46
-
My guess is the change detection is triggered when clicked, but as it starts from the root ponent and root ponent has
OnPush
so it stops there and does not check further ponents down the ponent tree. – alt255 Commented May 10, 2020 at 14:43 -
@alt255 the click event should bypass the
OnPush
hierarchy, and trigger change detection in place - in the ponent where it bubbled from (DOM-wise). Otherwise it would be a rather mon problem and a design flaw in Angular. – Daniel Macak Commented May 10, 2020 at 15:51 - see this for details this validates my point above – alt255 Commented May 10, 2020 at 15:54
- 1 @alt255 if that's the case, why does clicking the arrows in other than described scenarios work then? Like when I refresh the page with the gallery and just click next and prev arrows multiple times. It updates the counter alright. – Daniel Macak Commented May 10, 2020 at 16:47
2 Answers
Reset to default 10I created an issue in Angular's repo, and as it turns out, this behavior is logical, although perhaps unexpected. To rephrase what was written there by Angular team:
The code which causes the element with (click)
handler to render is running outside the Angular zone as stated in the question. Now, although I execute detectChanges()
manually there, it doesn't mean that the code magically runs in angular zone all of a sudden. It runs the change detection all right, but it "stays" in a different zone. And as a result, when the element is about to be rendered, the element's click callback is created in and bound to non-angular zone. This in turn means that when it is triggered by user clicking, it is still called, but doesn't trigger change detection.
The solution is to wrap code, which runs outside the angular zone, but which needs to perform some changes in the ponent, in zone.run(() => {...})
.
So in my stackblitz reproduction, the code running outside the angular zone would look like this:
this.zone.runOutsideAngular(() => {
const el = this.eventTarget.nativeElement as HTMLElement;
el.addEventListener('click', e => {
this.zone.run(() => this.showButton = true);
})
})
This, unlike calling detectChanges()
, makes the this.showButton = true
run in the correct zone, so that also elements created as a result of running that code with their event handlers are bound to the angular zone. This way, the event handlers always trigger change detection when reacting to DOM events.
This all boils down to a following takeaway: Declaring event handlers in a template doesn't automatically guarantee change detection in all scenarios.
In case someone wants to do tasks that don't trigger change detection, here is how:
import { NgZone }from '@angular/core';
taskSelection;
constructor
// paramenters
(
private _ngZone: NgZone,
)
// code block
{}
/*
Angular Lifecycle hooks
*/
ngOnInit() {
this.processOutsideOfAngularZone();
}
processOutsideOfAngularZone () {
var _this = this;
this._ngZone.runOutsideAngular(() => {
document.onselectionchange = function() {
console.log('Outside ngZone Done!');
let selection = document.getSelection();
_this.taskSelection["anchorNode"] = selection.anchorNode.parentElement;
_this.taskSelection["anchorOffset"] = selection.anchorOffset;
_this.taskSelection["focusOffset"] = selection.focusOffset;
_this.taskSelection["focusNode"] = selection.focusNode;
_this.taskSelection["rangeObj"] = selection.getRangeAt(0);
}
});
}
https://angular.io/api/core/NgZone#runOutsideAngular
本文标签: javascriptAngular click event handler not triggering change detectionStack Overflow
版权声明:本文标题:javascript - Angular click event handler not triggering change detection - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741278158a2369851.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论