前言

在 Angular 中,每個 Component 和 Directive 都有生命週期,由一系列的生命週期 Hook 所組成。理解這些生命週期鉤子函數,可以讓你在適當的時機執行特定的邏輯。

LifeCycle Hook Function

LifeCycle Hook Function 讓開發者在 Component 和 Directive 的不同階段進行操作。

Angular LifeCycle Hook

  • constructor:ES6 提供給 class 的建構子,並不屬於 Angular
  • ngOnChanges:當 Component 的一個輸入屬性發生變化時調用
  • ngOnInit:當 Component 初始化完成時調用,此時 Component 的輸入屬性和輸出屬性都已經確定
  • ngDoCheck:當 Angular 檢查 Component 的變化時調用,通常是由於輸入屬性發生變化或因為事件觸發引起的
  • ngAfterContentInit:當 Component 的 Content 完成初始化之後調用,通常用於對 Component 的 Content 執行操作
  • ngAfterContentChecked:當 Angular 檢查 Component 的 Content 發生變化時調用
  • ngAfterViewInit:當 Component 的 View 完成初始化之後調用,通常用於對 Component 的 View 執行操作
  • ngAfterViewChecked:當 Angular 檢查 Component 的 View 發生變化時調用
  • ngOnDestroy:當 Component 被摧毀時調用,通常用於釋放 Component 所占用的資源

生命週期執行順序

完整的生命週期執行順序如下:

  1. constructor:建立 Component 實例
  2. ngOnChanges:輸入屬性變更時(首次也會執行)
  3. ngOnInit:Component 初始化完成
  4. ngDoCheck:變更檢測
  5. ngAfterContentInit:外部內容初始化完成
  6. ngAfterContentChecked:外部內容變更檢測
  7. ngAfterViewInit:視圖初始化完成
  8. ngAfterViewChecked:視圖變更檢測
  9. ngOnDestroy:Component 銷毀前

常用生命週期範例

ngOnInit

最常使用的生命週期 Hook,通常用於初始化資料。

export class UserComponent implements OnInit {
  users: User[] = [];

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    // 在這裡初始化資料
    this.loadUsers();
  }

  loadUsers(): void {
    this.userService.getUsers().subscribe((users) => (this.users = users));
  }
}

ngOnChanges

@Input() 屬性值改變時觸發。

export class ChildComponent implements OnChanges {
  @Input() data: string;

  ngOnChanges(changes: SimpleChanges): void {
    console.log("Previous value:", changes["data"].previousValue);
    console.log("Current value:", changes["data"].currentValue);
  }
}

ngOnDestroy

Component 銷毀前執行,通常用於清理資源、取消訂閱。

export class MyComponent implements OnInit, OnDestroy {
  private subscription: Subscription;

  ngOnInit(): void {
    this.subscription = this.dataService
      .getData()
      .subscribe((data) => console.log(data));
  }

  ngOnDestroy(): void {
    // 取消訂閱,避免記憶體洩漏
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

ngAfterViewInit

視圖初始化完成後執行,可以安全地訪問 DOM 元素。

export class MyComponent implements AfterViewInit {
  @ViewChild("myInput") myInput: ElementRef;

  ngAfterViewInit(): void {
    // 這時可以安全地訪問 DOM 元素
    this.myInput.nativeElement.focus();
  }
}

constructor vs ngOnInit

很多人會疑惑:什麼時候用 constructor,什麼時候用 ngOnInit

constructor

  • ES6 的 class 建構子
  • 用於依賴注入(Dependency Injection)
  • 此時 Component 的屬性可能還未初始化
  • 不應該在這裡執行複雜的邏輯或 HTTP 請求
constructor(
  private userService: UserService,
  private router: Router
) {
  // 只做簡單的初始化
  console.log('Component created');
}

ngOnInit

  • Angular 的生命週期 Hook
  • Component 的輸入屬性已經初始化完成
  • 應該在這裡執行資料載入、訂閱等操作
ngOnInit(): void {
  // 執行資料初始化
  this.loadData();
}

實用技巧

1. 使用 OnPush 策略優化效能

搭配 ngOnChanges 使用 ChangeDetectionStrategy.OnPush 可以提升效能。

@Component({
  selector: "app-my-component",
  templateUrl: "./my-component.component.html",
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MyComponent implements OnChanges {
  @Input() data: any;

  ngOnChanges(changes: SimpleChanges): void {
    // 只有當 input 改變時才會觸發變更檢測
  }
}

2. 避免在 ngDoCheck 中執行繁重操作

ngDoCheck 會在每次變更檢測時執行,頻率非常高,應避免執行繁重的操作。

3. 記得取消訂閱

ngOnDestroy 中取消所有訂閱,避免記憶體洩漏。

private destroy$ = new Subject<void>();

ngOnInit(): void {
  this.dataService.getData()
    .pipe(takeUntil(this.destroy$))
    .subscribe(data => console.log(data));
}

ngOnDestroy(): void {
  this.destroy$.next();
  this.destroy$.complete();
}

總結

理解 Angular 的生命週期 Hook 是開發 Angular 應用的關鍵:

  • ngOnInit:最常用,用於初始化資料
  • ngOnChanges:監聽 Input 屬性變化
  • ngOnDestroy:清理資源,避免記憶體洩漏
  • ngAfterViewInit:訪問 DOM 元素
  • constructor vs ngOnInit:constructor 用於依賴注入,ngOnInit 用於資料初始化

掌握這些生命週期 Hook 的執行順序和調用時機,能夠更好地理解 Angular 中 Component 和 Directive 的運行機制,寫出更高效且穩定的應用程式。

參考資料