[英]Angular click event handler not triggering change detection
簡而言之,我在組件模板中有一個元素。 這個元素有一個ngIf
條件和一個(click)
處理程序。 它不是從一開始就呈現的,因為 ngIf 條件的計算結果為false
。
現在是有趣的部分:在 angular 區域之外運行的代碼將該條件更改為true
,並且在手動對更改檢測器 ref 執行detectChanges
之后,該元素被渲染並且點擊處理程序 ofc 變為活動狀態。
到目前為止一切似乎都還不錯,但問題是當用戶單擊時運行(click)
回調時,不會觸發組件的更改檢測。
這是復制品https://stackblitz.com/edit/angular-kea4wi
在那里重現它的步驟:
描述:
米色區域有一個通過addEventListener注冊的點擊事件處理器,這個事件監聽器的回調運行在angular區域之外。 在其中,組件的showButton
屬性從false
設置為true
,我通過調用detectChanges()
在那里手動觸發更改檢測,否則不會注冊showButton
屬性中的更改。 代碼如下所示:
this.zone.runOutsideAngular(() => { const el = this.eventTarget.nativeElement as HTMLElement; el.addEventListener('click', e => { this.showButton = true; this.cd.detectChanges(); }) })
現在按鈕出現了,這要歸功於*ngIf="showButton"
最初沒有呈現,並且它有一個在模板中聲明的點擊事件處理程序。 此處理程序再次更改組件的屬性,這次將showMessage
為true
。
<button *ngIf="showButton" (click)="onButtonClick()">Click me.</button> onButtonClick() { this;showMessage = true; }
當我單擊它時,處理程序顯然會運行並將組件的showMessage
更改為true
,但它不會觸發更改檢測並且不會出現下面的消息。 要使示例正常運行,只需從一開始就將 showButton 設置為 true,上面的場景就可以正常運行。
問題是:這怎么可能? 由於我在模板中聲明了(click)
事件處理程序,它不應該在調用時總是觸發更改檢測嗎?
我在 Angular 的 repo 中創建了一個問題,事實證明,這種行為是合乎邏輯的,盡管可能出乎意料。 改寫 Angular 團隊在那里寫的內容:
如問題中所述,導致帶有(click)
處理程序的元素呈現的代碼在 Angular 區域之外運行。 現在,雖然我在那里手動執行detectChanges()
,但這並不意味着代碼突然神奇地在angular 區域運行。 它可以正常運行更改檢測,但它“停留”在不同的區域。 結果,當元素即將被渲染時,元素的點擊回調被創建並綁定到 non-angular zone 。 這反過來意味着當它被用戶點擊觸發時,它仍然被調用,但不會觸發變化檢測。
解決方案是包裝代碼,該代碼在 angular 區域之外運行,但需要在組件中執行一些更改,在zone.run(() => {...})
中。
所以在我的 stackblitz 復制中,在 angular 區域之外運行的代碼如下所示:
this.zone.runOutsideAngular(() => {
const el = this.eventTarget.nativeElement as HTMLElement;
el.addEventListener('click', e => {
this.zone.run(() => this.showButton = true);
})
})
與調用detectChanges()
不同,這使得this.showButton = true
在正確的區域中運行,因此由於運行該代碼及其事件處理程序而創建的元素也被綁定到 angular 區域。 這樣,事件處理程序在響應 DOM 事件時總是觸發更改檢測。
這一切都歸結為以下要點:在模板中聲明事件處理程序並不能自動保證在所有場景中都能檢測到更改。
如果有人想執行不觸發更改檢測的任務,方法如下:
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);
}
});
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.