Lifecycle component
lifecycle یک component توالی stepهایی است که بین ایجاد component و نابودی آن رخ میدهد. هر step نماینده بخش متفاوتی از فرایند Angular برای render کردن componentها و بررسی updateهای آنها در طول زمان است.
در componentهای خود میتوانید lifecycle hookها را پیادهسازی کنید تا در طول این stepها کد اجرا شود. Lifecycle hookهایی که به یک instance مشخص از component مربوطاند، بهصورت method روی کلاس component پیادهسازی میشوند. Lifecycle hookهایی که به کل application Angular مربوطاند، بهصورت functionهایی پیادهسازی میشوند که یک callback میپذیرند.
Lifecycle یک component ارتباط تنگاتنگی با نحوه بررسی تغییرات componentهای شما توسط Angular در طول زمان دارد. برای فهم این lifecycle، کافی است بدانید Angular درخت application شما را از بالا به پایین پیمایش میکند و template bindingها را برای تغییرات بررسی میکند. Lifecycle hookهایی که در ادامه توضیح داده شدهاند، هنگام همین traversal اجرا میشوند. این traversal هر component را دقیقا یک بار visit میکند؛ بنابراین همیشه باید از ایجاد تغییرات state بیشتر در میانه این فرایند پرهیز کنید.
خلاصه
| Phase | Method | Summary |
| Creation | constructor |
constructor استاندارد کلاس JavaScript . زمانی اجرا میشود که Angular component را instantiate میکند. |
| Change Detection |
ngOnInit
|
یک بار بعد از اینکه Angular همه inputهای component را initialize کرد اجرا میشود. |
ngOnChanges |
هر بار که inputهای component تغییر کرده باشند اجرا میشود. | |
ngDoCheck |
هر بار که این component برای تغییرات check میشود اجرا میشود. | |
ngAfterContentInit |
یک بار بعد از initialize شدن content component اجرا میشود. | |
ngAfterContentChecked |
هر بار که content این component برای تغییرات check شده باشد اجرا میشود. | |
ngAfterViewInit |
یک بار بعد از initialize شدن view component اجرا میشود. | |
ngAfterViewChecked |
هر بار که view component برای تغییرات check شده باشد اجرا میشود. | |
| Rendering | afterNextRender |
یک بار، دفعه بعدی که همه componentها در DOM render شدند، اجرا میشود. |
afterEveryRender |
هر بار که همه componentها در DOM render شدند اجرا میشود. | |
| Destruction | ngOnDestroy |
یک بار پیش از destroy شدن component اجرا میشود. |
ngOnInit
متد ngOnInit بعد از اینکه Angular همه inputهای component را با مقدارهای اولیهشان initialize کرد اجرا میشود. ngOnInit یک component دقیقا یک بار اجرا میشود.
این step قبل از initialize شدن template خود component رخ میدهد. یعنی میتوانید state مربوط به component را بر اساس مقدارهای اولیه inputهای آن update کنید.
ngOnChanges
متد ngOnChanges بعد از تغییر هرکدام از inputهای component اجرا میشود.
این step قبل از check شدن template خود component رخ میدهد. یعنی میتوانید state مربوط به component را بر اساس مقدارهای اولیه inputهای آن update کنید.
در زمان initialization، اولین ngOnChanges پیش از ngOnInit اجرا میشود.
بررسی تغییرات
متد ngOnChanges یک argument از نوع SimpleChanges میپذیرد. این object یک Record است که هر نام input مربوط به component را به یک object از نوع SimpleChange map میکند. هر SimpleChange شامل مقدار قبلی input، مقدار فعلی آن و یک flag است که مشخص میکند آیا این اولین بار است که input تغییر کرده یا نه.
برای type checking قویتر، میتوانید کلاس فعلی یا this را بهعنوان اولین generic argument پاس دهید.
@Component({
/* ... */
})
export class UserProfile {
name = input('');
ngOnChanges(changes: SimpleChanges<UserProfile>) {
if (changes.name) {
console.log(`Previous: ${changes.name.previousValue}`);
console.log(`Current: ${changes.name.currentValue}`);
console.log(`Is first ${changes.name.firstChange}`);
}
}
}اگر برای هرکدام از input propertyها یک alias فراهم کنید، SimpleChanges Record همچنان از نام TypeScript property بهعنوان key استفاده میکند، نه از alias.
ngOnDestroy
متد ngOnDestroy یک بار، درست پیش از destroy شدن component اجرا میشود. Angular زمانی یک component را destroy میکند که دیگر روی page نمایش داده نشود، مثل زمانی که با @if hidden میشود یا هنگام navigate به page دیگر.
DestroyRef
بهعنوان جایگزین متد ngOnDestroy، میتوانید یک instance از DestroyRef را inject کنید. میتوانید با فراخوانی متد onDestroy مربوط به DestroyRef یک callback ثبت کنید تا هنگام destruction component invoke شود.
@Component({
/* ... */
})
export class UserProfile {
constructor() {
inject(DestroyRef).onDestroy(() => {
console.log('UserProfile destruction');
});
}
}میتوانید instance مربوط به DestroyRef را به functionها یا classهای بیرون از component خود پاس دهید. اگر کد دیگری دارید که باید هنگام destroy شدن component رفتار cleanup اجرا کند، از این pattern استفاده کنید.
همچنین میتوانید از DestroyRef استفاده کنید تا setup code نزدیک cleanup code بماند، بهجای اینکه همه cleanup code را در متد ngOnDestroy قرار دهید.
##### تشخیص destroy شدن instance
DestroyRef یک property به نام destroyed ارائه میدهد که اجازه میدهد بررسی کنید آیا یک instance مشخص قبلا destroy شده یا نه. این برای جلوگیری از انجام عملیات روی componentهای destroy شده مفید است، بهخصوص هنگام کار با logicهای تاخیری یا asynchronous.
با بررسی destroyRef.destroyed میتوانید از اجرای کد پس از cleanup شدن instance جلوگیری کنید و از خطاهای احتمالی مثل NG0911: View has already been destroyed. دور بمانید.
ngDoCheck
متد ngDoCheck پیش از هر بار که Angular template یک component را برای تغییرات check میکند اجرا میشود.
میتوانید از این lifecycle hook استفاده کنید تا تغییرات state خارج از change detection عادی Angular را بهصورت دستی check کنید و state مربوط به component را بهصورت دستی update کنید.
این متد بسیار پرتکرار اجرا میشود و میتواند بهشکل قابلتوجهی روی performance page شما اثر بگذارد. تا جای ممکن از تعریف این hook پرهیز کنید و فقط زمانی از آن استفاده کنید که هیچ جایگزینی ندارید.
در زمان initialization، اولین ngDoCheck بعد از ngOnInit اجرا میشود.
ngAfterContentInit
متد ngAfterContentInit یک بار بعد از initialize شدن همه childهایی که داخل component nest شدهاند، یعنی content آن، اجرا میشود.
میتوانید از این lifecycle hook برای خواندن نتیجه content queryها استفاده کنید. با اینکه میتوانید به state initialize شده این queryها دسترسی داشته باشید، تلاش برای تغییر دادن هر state در این متد به ExpressionChangedAfterItHasBeenCheckedError منجر میشود.
ngAfterContentChecked
متد ngAfterContentChecked هر بار که childهای nest شده داخل component، یعنی content آن، برای تغییرات check شده باشند اجرا میشود.
این متد بسیار پرتکرار اجرا میشود و میتواند بهشکل قابلتوجهی روی performance page شما اثر بگذارد. تا جای ممکن از تعریف این hook پرهیز کنید و فقط زمانی از آن استفاده کنید که هیچ جایگزینی ندارید.
با اینکه میتوانید در اینجا به state update شده content queryها دسترسی داشته باشید، تلاش برای تغییر دادن هر state در این متد به ExpressionChangedAfterItHasBeenCheckedError منجر میشود.
ngAfterViewInit
متد ngAfterViewInit یک بار بعد از initialize شدن همه childهای داخل template component، یعنی view آن، اجرا میشود.
میتوانید از این lifecycle hook برای خواندن نتیجه view queryها استفاده کنید. با اینکه میتوانید به state initialize شده این queryها دسترسی داشته باشید، تلاش برای تغییر دادن هر state در این متد به ExpressionChangedAfterItHasBeenCheckedError منجر میشود.
ngAfterViewChecked
متد ngAfterViewChecked هر بار که childهای داخل template component، یعنی view آن، برای تغییرات check شده باشند اجرا میشود.
این متد بسیار پرتکرار اجرا میشود و میتواند بهشکل قابلتوجهی روی performance page شما اثر بگذارد. تا جای ممکن از تعریف این hook پرهیز کنید و فقط زمانی از آن استفاده کنید که هیچ جایگزینی ندارید.
با اینکه میتوانید در اینجا به state update شده view queryها دسترسی داشته باشید، تلاش برای تغییر دادن هر state در این متد به ExpressionChangedAfterItHasBeenCheckedError منجر میشود.
afterEveryRender و afterNextRender
تابعهای afterEveryRender و afterNextRender اجازه میدهند یک render callback ثبت کنید تا بعد از اینکه Angular render کردن همه componentها در page را داخل DOM تمام کرد invoke شود.
این functionها با lifecycle hookهای دیگری که در این راهنما توضیح داده شدند متفاوتاند. بهجای اینکه method کلاس باشند، functionهای standalone هستند که یک callback میپذیرند. اجرای render callbackها به هیچ instance مشخصی از component وابسته نیست و در عوض یک hook در سطح کل application است.
afterEveryRender و afterNextRender باید در یک injection context فراخوانی شوند، معمولا constructor یک component.
میتوانید از render callbackها برای انجام عملیات دستی DOM استفاده کنید. برای راهنمایی درباره کار با DOM در Angular، Using DOM APIs را ببینید.
Render callbackها هنگام server-side rendering یا build-time pre-rendering اجرا نمیشوند.
فازهای after\*Render
هنگام استفاده از afterEveryRender یا afterNextRender، میتوانید کار را بهصورت اختیاری به phaseها تقسیم کنید. phase به شما کنترل روی sequencing عملیات DOM میدهد و اجازه میدهد عملیات write را قبل از عملیات read ترتیب دهید تا layout thrashing کمینه شود. برای ارتباط میان phaseها، یک phase function میتواند یک result value برگرداند که در phase بعدی قابل دسترسی باشد.
import {Component, ElementRef, afterNextRender} from '@angular/core';
@Component(/* ... */)
export class UserProfile {
private prevPadding = 0;
private elementHeight = 0;
constructor() {
const elementRef = inject(ElementRef);
const nativeElement = elementRef.nativeElement;
afterNextRender({
// Use the `Write` phase to write to a geometric property.
write: () => {
const padding = computePadding();
const changed = padding !== this.prevPadding;
if (changed) {
nativeElement.style.padding = padding;
}
return changed; // Communicate whether anything changed to the read phase.
},
// Use the `Read` phase to read geometric properties after all writes have occurred.
read: (didWrite) => {
if (didWrite) {
this.elementHeight = nativeElement.getBoundingClientRect().height;
}
},
});
}
}چهار phase وجود دارد که به ترتیب زیر اجرا میشوند:
| Phase | Description |
|---|---|
earlyRead | از این phase برای خواندن propertyها و styleهای DOM که روی layout اثر میگذارند و برای محاسبه بعدی کاملا ضروریاند استفاده کنید. اگر ممکن است از این phase پرهیز کنید و phaseهای write و read را ترجیح دهید. |
write | از این phase برای نوشتن propertyها و styleهای DOM که روی layout اثر میگذارند استفاده کنید. |
mixedReadWrite | phase پیشفرض. برای عملیاتهایی استفاده میشود که هم باید propertyها و styleهای موثر بر layout را بخوانند و هم بنویسند. اگر ممکن است از این phase پرهیز کنید و phaseهای explicit یعنی write و read را ترجیح دهید. |
read | از این phase برای خواندن هر property مربوط به DOM که روی layout اثر میگذارد استفاده کنید. |
Interfaceهای lifecycle
Angular برای هر lifecycle method یک TypeScript interface ارائه میدهد. میتوانید این interfaceها را بهصورت اختیاری import و implement کنید تا مطمئن شوید پیادهسازی شما typo یا غلط املایی ندارد.
هر interface همان نام method متناظر را بدون prefix مربوط به ng دارد. برای مثال، interface مربوط به ngOnInit برابر OnInit است.
@Component({
/* ... */
})
export class UserProfile implements OnInit {
ngOnInit() {
/* ... */
}
}ترتیب اجرا
نمودارهای زیر ترتیب اجرای lifecycle hookهای Angular را نشان میدهند.
هنگام initialization
graph TD;
id[constructor]-->CHANGE;
subgraph CHANGE [Change detection]
direction TB
ngOnChanges-->ngOnInit;
ngOnInit-->ngDoCheck;
ngDoCheck-->ngAfterContentInit;
ngDoCheck-->ngAfterViewInit
ngAfterContentInit-->ngAfterContentChecked
ngAfterViewInit-->ngAfterViewChecked
end
CHANGE--Rendering-->afterNextRender-->afterEveryRenderupdateهای بعدی
graph TD;
subgraph CHANGE [Change detection]
direction TB
ngOnChanges-->ngDoCheck
ngDoCheck-->ngAfterContentChecked;
ngDoCheck-->ngAfterViewChecked
end
CHANGE--Rendering-->afterEveryRenderترتیب همراه directiveها
وقتی یک یا چند directive را روی همان element یک component قرار میدهید، چه در template و چه با property مربوط به hostDirectives، framework هیچ ترتیبی را برای lifecycle hook مشخص بین component و directiveهای روی یک element تضمین نمیکند. هرگز به ترتیبی که مشاهده کردهاید وابسته نشوید، چون ممکن است در نسخههای بعدی Angular تغییر کند.