在 Angular 19 中,實驗性生命週期鉤子 afterRenderEffect
允許開發人員更新 DOM。
與 afterNextRender
和 afterRender
一樣,afterRenderEffect
也有四個階段:earlyRead
、write
、mixedReadWrite
和 read
。
在此示範中,我將使用 afterRenderEffect
附加新的圖表數據和變更圖表顏色。 afterRenderEffect
註冊 effect
並在渲染所有元件後運行它。
write
階段之前從 DOM 讀取的階段。如果讀取可以等到寫入階段之後,則優先選擇read
階段。。在此階段切勿讀取 DOM。在這篇文章中,我描述如何使用 afterNextRender
在畫布上新增圖表,以及如何使用 afterRenderEffect
重繪圖表。
在此示範中,我想使用第三方圖表庫 Chart.js
在畫布元素上渲染長條圖。因此,我實作了 afterNextRender
鉤子來將圖表插入到畫布中。然後,我使用 RxJS timer
運算子和 toSignal
產生新的圖表數據,並每秒將其附加到圖表數組中。
為了使示範具有互動性,顏色下拉清單會變更條形的顏色。我將實作兩個 afterRenderEffect
鉤子;第一個鉤子將新的圖表資料附加到圖表資料數組中,第二個鉤子修改圖表顏色。
@Component({
selector: 'app-root',
imports: [FormsModule],
template: `
<h1>Hello from AfterRenderEffect!</h1>
<div style="width: 400px;">
<div>
<label>
Bar Color:
<select [(ngModel)]="barColor">
@for (c of barColors(); track c.id) {
<option [value]="c.id">{{ c.color }}</option>
}
</select>
</label>
</div>
<canvas #canvas></canvas>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App implements OnDestroy {
barColors = signal([
{ id: 'red', color: 'Red' },
{ id: 'pink', color: 'Pink' },
]);
data = signal([
{ year: 2022, count: 30 },
{ year: 2023, count: 4 },
]);
barColor = signal('red');
numBars = signal(0);
chart: Chart | null = null;
constructor() {
// Implement afterNextRender and afterRenderEffect here
afterNextRender({});
afterRenderEffect({});
afterRenderEffect({});
}
ngOnDestroy(): void {
this.chart?.destroy();
}
}
npm i --save-exact chart.js
afterNextRender
生命週期鉤子在下次變更偵測後執行一次。因此,write
階段是將新圖表插入 DOM 的理想入口點。
canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas')
nativeElement = computed(() => this.canvas().nativeElement);
首先,我使用 viewChild
函數來取得畫布的 ElementRef
。 nativeElement
是一個存取 HTMLCanvasElement
實例的計算訊號。 在建構函式中, afterNextRender
生命週期鉤子用這些訊號初始化圖表。
type ChartDataType = {
year: number;
count: number;
};
function initializeChart(canvas: HTMLCanvasElement, data: ChartDataType[], backgroundColor: string) {
return new Chart(canvas,
{
type: 'bar',
data: {
labels: data.map(row => row.year),
datasets: [
{
label: 'Acquisitions by year',
data: data.map(row => row.count),
backgroundColor,
}
]
}
}
);
}
上面的輔助函數接受畫布、初始圖表資料和圖表顏色來建立並傳回圖表。
constructor() {
afterNextRender({
write: () => {
console.log('afterNextRender write is called');
this.chart = initializeChart(this.nativeElement(), this.data(), this.barColor());
}
});
}
在建構函式中,我實作了 afterNextRender
生命週期鉤,並在 write
階段將圖表附加到畫布。
我已成功將 JavaScript 圖表插入 DOM 中。 DOM 讀取和寫入只執行一次,因此畫布不會錯誤地顯示多個圖表。
我想在鉤子的 earlyRead
和 write
階段附加圖表資料。然後,我將重構鉤子以避免 earlyRead
階段並僅在 write
階段執行更新。
chartData = toSignal(timer(100, 1000)
.pipe(take(15)), { initialValue: -1 });
這個 timer
運算子發出 15 個整數,我將在 earlyRead
階段每秒匯出圖表資料。產生資料後,write
階段接收圖表資料物件並在圖表中顯示新的長條圖。 toSignal
從 Observable
創建一個唯讀訊號,以便 afterRenderEffect
鉤子可以在 earlyRead
階段讀取它。
constructor() {
afterRenderEffect({
earlyRead: () => {
const index = this.chartData();
return index < 0 ? undefined :
{ year: 2024 + index, count: Math.floor(Math.random() * 20) + 2 } as ChartDataType;
},
write: (randomData) => {
const chartData = randomData();
if (chartData) {
const { year, count } = chartData;
const chart = this.chart?.data;
chart?.labels?.push(year);
chart?.datasets.forEach(({ data }) => data.push(count));
this.chart?.update();
}
return chartData;
}
});
earlyRead
階段讀取圖表資料訊號,產生並傳回隨機資料。在 write
階段,回呼函數的第一個參數是隨機數據,它被附加到圖表的資料數組中。
如果讀取操作可以在寫入操作之後發生,則 Angular 文件偏好使用讀取階段。我將重構 Observable 以產生圖表數據,以消除 earlyRead
階段。
chartData = toSignal(timer(100, 1000)
.pipe(
take(15),
map((i) => ({ year: 2024 + i, count: Math.floor(Math.random() * 20) + 2 } as ChartDataType))
), { initialValue: undefined });
Observable 使用 map
運算子來產生圖表資料。
afterRenderEffect({
write: () => {
const chartData = this.chartData();
if (chartData) {
const { year, count } = chartData;
const chart = this.chart?.data;
chart?.labels?.push(year);
chart?.datasets.forEach((dataset) => dataset.data.push(count));
this.chart?.update();
}
return chartData;
}
});
重構 chartData
訊號後,write
階段存取訊號的值並執行相同的邏輯以將 chartData
附加到圖表的data array 。
另一個案例是當使用者從下拉清單中選擇顏色名稱時更新圖表顏色。這個簡單的下拉清單應用 two-way data binding 將 ngModel
綁定到 barColor
訊號。
<div>
<label>
Bar Color:
<select [(ngModel)]="barColor">
@for (c of barColors(); track c.id) {
<option [value]="c.id">{{ c.color }}</option>
}
</select>
</label>
</div>
barColor = signal('red');
當使用者進行選擇時,應用程式會更新 this.barColor
並觸發 afterRenderEffect
生命週期鉤子。因此,我將邏輯放在 afterRenderEffect
生命週期鉤子中來更新圖表顏色。
afterRenderEffect({
write: () => {
this.chart?.data.datasets.forEach((dataset) => dataset.backgroundColor = this.barColor());
this.chart?.update();
return this.barColor();
}
});
在 afterRenderEffect
鉤子中, dataset.backgroundColor = this.barColor();
更新顏色和 this.chart.update();
再次更新圖表。