Nội dung chính
Directives và components có Angular lifecycle khi Angular được khởi tạo, cập nhật hoặc phá hủy chúng. Developer có thể khai thác vòng đời bằng cách triển khai một hoặc nhiều giao diện Angular lifecycle hook trong thư viện lõi (core library) của Angular.
Đọc bài viết sau để được giải đáp chi tiết hơn về:
- Định nghĩa về Angular lifecycle và tại sao chúng lại quan trọng trong quá trình lập trình Angular
- Một số Lifecycle Hook phổ biến trong Angular lifecycle
- Câu hỏi thường gặp về Angular lifecycle hook
Định nghĩa về Angular Lifecycle Hook
Angular lifecycle hook là một tập hợp các giai đoạn cụ thể trong vòng đời của một components (thành phần) hoặc directives (chỉ thị) Angular. Angular Lifecycle Hook bắt đầu bằng việc khởi tạo thành phần gốc của ứng dụng, tiếp theo tạo và hiển thị các thành phần con, rồi đến các thành phần con hoặc directives tương ứng của chúng.
Trong Angular, components được tạo thành một cấu trúc cây (tree structure). Sau khi ứng dụng tải tất cả components này, Angular sẽ khởi tạo quy trình hiển thị ở chế độ xem (view). Để thực hiện điều này, Angular cần kiểm tra và xác minh các thuộc tính đầu vào của các thành phần ứng dụng, hiển thị nội dung được chiếu của ứng dụng, xem xét và đánh giá biểu thức, tính ràng buộc của dữ liệu,… Ngoài ra, Angular cũng xóa các components không cần thiết khỏi DOM.
Angular thực hiện các quá trình này để giúp ứng dụng hoạt động hiệu quả và lifecycle hook của Angular components chịu trách nhiệm thực hiện quy trình này. Angular lifecycle hook còn được gọi là hàm gọi lại (callback functions), khi các loại sự kiện cụ thể xảy ra trong vòng đời của components, cụ thể như:
- ngOnChanges trong Angular được gọi khi ai đó thay đổi thuộc tính đầu vào của components.
- Khi Angular khởi tạo components lần đầu tiên, nó sẽ thực thi ngOnlnit hook.
- Angular gọi hook ngOnDestroy khi nó hủy bất kỳ components nào.
Ngoài ra, Angular cũng sử dụng thêm một số hook khác trong vòng đời components để thực thi quy trình.
Đọc thêm: Angular là gì? Có phải là vũ khí hạng nặng của Front-end Developer?
Tại sao nên sử dụng Angular Lifecycle Hook?
Angular lifecycle hook Hook cung cấp những cách thức tổ chức rõ ràng để tương tác với một components trong suốt vòng đời của nó.
Chúng cung cấp các giai đoạn có cấu trúc, trong đó các loại hành động cụ thể có thể được thực hiện, giúp quản lý tài nguyên, xử lý dữ liệu một cách chủ động.
Đồng thời, phản hồi các thay đổi trong vòng đời một cách hiệu quả.
Tổng quan 8 Angular Lifecycle Hook
Angular có nhiều lifecycle hook cho phép bạn inject code vào thời điểm chính xác trong vòng đời của components hoặc directives. Những hook này hoạt động tương tự như điểm kiểm tra, cho phép bạn thực hiện tác vụ được chỉ định dựa trên trạng thái hiện tại của components.
Trong Angular, có 8 lifecycle hook như sau:
Lifecycle Hook | Công dụng |
ngOnChanges | Được gọi khi một thuộc tính @Input() có sự thay đổi giá trị.Được gọi trước ngOnlnit và bất cứ khi nào một hoặc nhiều thuộc tính đầu vào liên kết dữ liệu thay đổi. |
ngOnlnit: | Được gọi một lần, sau ngOnChanges. Khởi tạo directives hoặc component sau khi Angular hiển thị các thuộc tính liên kết dữ liệu và thiết lập thuộc tính đầu vào cho directive/component. |
ngDoCheck | Phát hiện và hành động dựa trên những thay đổi mà Angular không thể hoặc không muốn tự phát hiện. Được gọi trong mỗi lần chạy quy trình phát hiện thay đổi, ngay sau ngOnChanges và ngOnlnit |
ngAfterContentlnit | Phản hồi sau khi Angular chèn (ngay từ lần đầu) nội dung vào component (phần nội dung trong <ng-content>). Được gọi một lần sau ngDoCheck đầu tiên và hook này chỉ dành cho component |
ngAfterContentChecked | Phản hồi sau khi Angular kiểm tra nội dung được chiếu vào component. Được gọi sau ngAfterContentlnit và mọi ngDoCheck tiếp theo, hook chỉ dành cho component. |
ngAfterViewlnit | Phản hồi sau khi Angular khởi tạo các chế độ xem component và chế độ xem (view). Được gọi một lần sau ngAfterContentChecked đầu tiên và là hook chỉ sử dụng cho component. Hook này chỉ chạy một lần và thích hợp để thao tác trên các view con. |
ngAfterViewChecked | Phản hồi sau khi Angular kiểm tra các chế độ xem component và chế độ xem. Được gọi sau ngAfterViewlnit và ngAfterContentChecked tiếp theo, cũng là hook chỉ sử dụng cho component. |
ngOnDestroy | Dọn dẹp dữ liệu ngay trước khi Angular hủy directive/component. Hủy đăng ký Observables và tách trình xử lý dữ liệu để tránh rò rỉ bộ nhớ. Được gọi ngay trước khi Angular hủy directive hoặc component |
Tìm hiểu chi tiết hơn các Angular lifecycle hook ngay sau đây:
ngOnChanges
ngOnChanges được kích hoạt khi có sự thay đổi của các giá trị @Input được ràng buộc. Dữ liệu bị ràng buộc bởi @Input() đến từ một nguồn bên ngoài. Khi nguồn bên ngoài thay đổi dữ liệu đó theo cách có thể phát hiện được, nó sẽ đi qua thuộc tính @Input một lần nữa.
Khi ngOnChanges được kích hoạt, nó cũng kích hoạt khởi tạo dữ liệu đầu vào. Hook nhận được một tham số bắt buộc có kiểu SimpleChanges, giá trị này chứa thông tin về các thuộc tính @Input đã thay đổi, bao gồm giá trị cũ (previousValue), giá trị mới (currentValue) và flag firstChange.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'app-child', template: ` <h3>Child Component</h3> <p>TICKS: {{ lifecycleTicks }}</p> <p>DATA: {{ data }}</p> ` }) export class ChildComponent implements OnChanges { @Input() data: string; lifecycleTicks: number = 0; ngOnChanges(changes: SimpleChanges) { this.lifecycleTicks++; console.log('Previous value:', changes['data'].previousValue); console.log('Current value:', changes['data'].currentValue); console.log('First change:', changes['data'].firstChange); } } @Component({ selector: 'app-parent', template: ` <h1>ngOnChanges Example</h1> <app-child [data]="arbitraryData"></app-child> ` }) export class ParentComponent { arbitraryData: string = 'initial'; constructor() { setTimeout(() => { this.arbitraryData = 'final'; }, 5000); } }
Ở ví dụ trên, ParentComponent liên kết dữ liệu đầu vào với ChildComponent, thành phần nhận dữ liệu này thông qua thuộc tính @Input. Khi ngOnChanges kích hoạt, sau 5 giây, lệnh gọi lại setTimeout kích hoạt.
Lúc này, ParentComponet sẽ thay đổi nguồn dữ liệu của thuộc tính input-bound của ChildComponent. Dữ liệu mới đi qua thuộc tính đầu vào khiến cho ngOnChanges kích hoạt một lần nữa.
ngOnlnit
ngOnlnit kích hoạt một lần khi khởi tạo thuộc tính input-bound (@Input) của components. Ở ví dụ tiếp theo, hook không kích hoạt khi ChildComponent nhận dữ liệu đầu vào, thay vào đó nó sẽ kích hoạt ngay sau khi dữ liệu được kết xuất thành ChildComponent.
import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'app-child', template: ` <h3>Child Component</h3> <p>TICKS: {{ lifecycleTicks }}</p> <p>DATA: {{ data }}</p> ` }) export class ChildComponent implements OnInit { @Input() data: string; lifecycleTicks: number = 0; ngOnInit() { this.lifecycleTicks++; } } @Component({ selector: 'app-parent', template: ` <h1>ngOnInit Example</h1> <app-child [data]="arbitraryData"></app-child> ` }) export class ParentComponent { arbitraryData: string = 'initial'; constructor() { setTimeout(() => { this.arbitraryData = 'final'; }, 5000); } }
Ở ví dụ này, ParentComponent liên kết dữ liệu đầu vào với ChildComponent và thuộc tính này sẽ nhận dữ liệu thông qua thuộc tính @Input. Dữ liệu được hiển thị thành ngOnlnit và bắt đầu kích hoạt, sau 5 giây lệnh gọi setTimeout sẽ được kích hoạt. ParentComponent thay đổi nguồn dữ liệu của thuộc tính input-bound của ChildComponent.
ngDoCheck
ngDoCheck được kích hoạt với mọi chu kỳ phát hiện sự thay đổi. Angular chạy quá trình phát hiện thay đổi thường xuyên, khi thực hiện bất kỳ hành động nào cũng sẽ khiến nó chạy theo chu kỳ và ngDoCheck sẽ kích hoạt với các chu kỳ này. Do đó, hãy cẩn thận khi sử dụng ngDoCheck bởi vì chúng có thể tạo ra các vấn đề về hiệu suất khi triển khai không đúng cách.
Bên cạnh đó, ngDoCheck cho phép developers kiểm tra dữ liệu của họ theo cách thủ công, họ có thể kích hoạt ngày sử dụng ứng dụng mới theo điều kiện. Kết hợp với thuộc tính ChangeDetectorRef sẽ giúp developers có thể tạo ra cách kiểm tra riêng của họ để phát hiện sự thay đổi trong ứng dụng.
Đây là nơi developers có thể thực hiện kiểm tra thay đổi tùy chỉnh, đặc biệt hữu ích cho những thay đổi mà Angular không thể tự phát hiện được (như thay đổi bên trong objects hoặc arrays).
import { Component, DoCheck, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-example', template: ` <h1>ngDoCheck Example</h1> <p>Lifecycle Ticks: {{ lifecycleTicks }}</p> <p>Current Data: {{ data[data.length - 1] }}</p> ` }) export class ExampleComponent implements DoCheck { lifecycleTicks: number = 0; oldTheData: string; data: string[] = ['initial']; constructor(private changeDetector: ChangeDetectorRef) { this.changeDetector.detach(); // lets the class perform its own change detection setTimeout(() => { this.oldTheData = 'final'; // intentional error this.data.push('intermediate'); }, 3000); setTimeout(() => { this.data.push('final'); this.changeDetector.markForCheck(); }, 6000); } ngDoCheck() { console.log(++this.lifecycleTicks); if (this.data[this.data.length - 1] !== this.oldTheData) { this.changeDetector.detectChanges(); } } }
Ở ví dụ trên, lớp constructor sẽ khởi tạo setTimeout 2 lần, sau ba giây, setTimeout đầu tiên kích hoạt phát hiện thay đổi. ngDoCheck đánh dấu màn hình để cập nhật. Ba giây sau, setTimeout thứ hai sẽ kích hoạt và không cần cập nhật chế độ xem (view) để phát hiện thay đổi.
ngAfterContentlnit
ngAfterContentlnit sẽ được kích hoạt sau khi nội dung DOM của components khởi tạo (tải lần đầu tiên). Chờ truy vấn @ContentChild(ren) là trường hợp sử dụng chính của hook.
Truy vấn @ContentChild(ren) tạo ra các tham chiếu phần tử cho nội dung của DOM. Do đó, chúng sẽ không khả dụng cho đến khi nội dung DOM được tải. Đó cũng là lý do tại sao ngAfterContentlnit và thuộc tính tương tự ngAfterContentChecked được sử dụng.
Hook này đặc biệt hữu ích khi cần truy cập các phần tử được truy vấn bởi @ContentChild hoặc @ContentChildren.
import { Component, ContentChild, AfterContentInit, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` <p>I am C.</p> <p>Hello World!</p> ` }) export class CComponent { } @Component({ selector: 'app-b', template: ` <p>I am B.</p> <ng-content></ng-content> ` }) export class BComponent implements AfterContentInit { @ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef; @ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef; constructor(private renderer: Renderer2) { } ngAfterContentInit() { this.renderer.setStyle(this.hRef.nativeElement, 'background-color', 'yellow') this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', 'pink'); this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', 'red'); } } @Component({ selector: 'app-a', template: ` <h1>ngAfterContentInit Example</h1> <p>I am A.</p> <app-b> <h3 #BHeader>BComponent Content DOM</h3> <app-c></app-c> </app-b> ` }) export class AComponent { }
Quá trình render bắt đầu bằng AComponent. Để hoàn tất quy trình, AComponent phải render BComponent, BComponent chiếu nội dung được lồng trong phần tử của nó thông qua phần tử <ng-content></ng-content>. CComponet là một phần của nội dung được chiếu, nội dung được chiếu hoàn tất quá trình render.
ngAfterContentlnit kích hoạt và BComponent hoàn tất quá trình render. Cho đến khi AComponent hoàn tất quá trình render, ngAfterContentlnit sẽ không kích hoạt lại nữa.
ngAfterContentChecked
ngAfterContentChecked kích hoạt sau mỗi chu kỳ phát hiện thay đổi của projected content (nội dung được truyền vào thông qua ng-content). Điều này cho phép các nhà phát triển tạo điều kiện thuận lợi cho nội dung DOM phản ứng với sự thay đổi trong ứng dụng.
ngAfterContentChecked có thể kích hoạt thường xuyên và gây ra vấn đề về hiệu suất nếu triển khai kém. ngAfterContentChecked cũng kích hoạt trong giai đoạn khởi tạo của components. Nó xuất hiện ngay sau ngAfterContentlnit.
import { Component, ContentChild, AfterContentChecked, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` <p>I am C.</p> <p>Hello World!</p> ` }) export class CComponent { } @Component({ selector: 'app-b', template: ` <p>I am B.</p> <button (click)="$event">CLICK</button> <ng-content></ng-content> ` }) export class BComponent implements AfterContentChecked { @ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef; @ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef; constructor(private renderer: Renderer2) { } randomRGB(): string { return `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`; } ngAfterContentChecked() { this.renderer.setStyle(this.hRef.nativeElement, 'background-color', this.randomRGB()); this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', this.randomRGB()); this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', this.randomRGB()); } } @Component({ selector: 'app-a', template: ` <h1>ngAfterContentChecked Example</h1> <p>I am A.</p> <app-b> <h3 #BHeader>BComponent Content DOM</h3> <app-c></app-c> </app-b> ` }) export class AComponent { }
Quy trình của ngAfterContentChecked hầu như không khác gì ngAfterContentlnit. Chỉ một thuộc tính <button></button> được thêm vào BComponent. Nhấn vào nó sẽ gây ra vòng lặp phát hiện thay đổi. Điều này kích hoạt hook như được chỉ ra bởi sự ngẫu nhiên của background-color.
ngAfterViewlnit
ngAfterViewlnit được kích hoạt một lần sau khi chế độ xem DOM hoàn tất quy trình khởi tạo. Chế độ xem luôn tải ngay sau nội dung, ngAfterViewlnit chờ các truy vấn @ViewChild(ren) để giải quyết. Các phần tử này được truy vấn từ bên trong cùng một chế độ xem của component.
Hook này đặc biệt hữu ích khi cần truy cập các phần tử được truy vấn bằng @ViewChild hoặc @ViewChildren.
Trong ví dụ dưới đây, thẻ H3 của BComponent được truy vấn và ngAfterViewlnit thực thi ngay khi có kết quả truy vấn.
import { Component, ViewChild, AfterViewInit, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` <p>I am C.</p> <p>Hello World!</p> ` }) export class CComponent { } @Component({ selector: 'app-b', template: ` <p #BStatement>I am B.</p> <ng-content></ng-content> ` }) export class BComponent implements AfterViewInit { @ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef; constructor(private renderer: Renderer2) { } ngAfterViewInit() { this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', 'yellow'); } } @Component({ selector: 'app-a', template: ` <h1>ngAfterViewInit Example</h1> <p>I am A.</p> <app-b> <h3>BComponent Content DOM</h3> <app-c></app-c> </app-b> ` }) export class AComponent { }
Renderer2 thay đổi màu nền của BComponet. Điều này cho biết phần tử ở chế độ xem (view) được truy vấn thành công nhờ ngAferViewlnit.
Quy trình render:
- Bắt đầu với AComponent, để hoàn tất quy trình AComponent phải kết xuất BComponent.
- BComponent chiếu nội dung trong phần tử của nó qua thuộc tính <ng-content>.
- CComponent là một phần của nội dung được chiếu và sẽ hoàn tất quá trình kết xuất. BComponent hoàn tất quá trình kết xuất thì ngAfterViewlnit kích hoạt.
- Khi AComponent hoàn tất quá trình kết xuất thì ngAfterViewlnit sẽ không kích hoạt lại.
ngAfterViewChecked
ngAfterViewChecked kích hoạt sau bất kỳ chu kỳ phát hiện thay đổi nào nhắm vào chế độ xem (view) của component. Hook ngAfterViewChecked cho phép developers tạo điều kiện thuận lợi cho cách phát hiện thay đổi ảnh hưởng đến chế độ xem DOM.
import { Component, ViewChild, AfterViewChecked, ElementRef, Renderer2 } from '@angular/core'; @Component({ selector: 'app-c', template: ` <p>I am C.</p> <p>Hello World!</p> ` }) export class CComponent { } @Component({ selector: 'app-b', template: ` <p #BStatement>I am B.</p> <button (click)="$event">CLICK</button> <ng-content></ng-content> ` }) export class BComponent implements AfterViewChecked { @ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef; constructor(private renderer: Renderer2) { } randomRGB(): string { return `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`; } ngAfterViewChecked() { this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', this.randomRGB()); } } @Component({ selector: 'app-a', template: ` <h1>ngAfterViewChecked Example</h1> <p>I am A.</p> <app-b> <h3>BComponent Content DOM</h3> <app-c></app-c> </app-b> ` }) export class AComponent { }
Nhìn chung, quy trình render của hook ngAfterViewChecked gần như tương tự với ngAfterViewlnit. Bên cạnh đó, khi nhấn vào phần tử <button> sẽ khởi tạo một vòng phát hiện thay đổi. ngAfterContentChecked sẽ kích hoạt và ngẫu nhiên hóa màu nền của các phần tử được truy vấn mỗi khi nhấn vào nút.
ngOnDestroy
ngOnDestroy kích hoạt khi component bị xóa khỏi chế độ xem (view) và DOM. Hook này thường được sử dụng để dọn dẹp tài nguyên, hủy các subscription, clearInterval/setTimeout để tránh memory leak trước khi component bị xóa.
import { Directive, Component, OnDestroy } from '@angular/core'; @Directive({ selector: '[appDestroyListener]' }) export class DestroyListenerDirective implements OnDestroy { ngOnDestroy() { console.log("Goodbye World!"); } } @Component({ selector: 'app-example', template: ` <h1>ngOnDestroy Example</h1> <button (click)="toggleDestroy()">TOGGLE DESTROY</button> <p appDestroyListener *ngIf="destroy">I can be destroyed!</p> ` }) export class ExampleComponent { destroy: boolean = true; toggleDestroy() { this.destroy = !this.destroy; } }
Khi khởi chạy quy trình, thẻ destroy của ExampleComponent sẽ chuyển sang false, chỉ thị cấu trúc *nglf được đánh giá là false và ngOnDestroy được kích hoạt. *nglf xóa máy chủ <p> của nó và quá trình này lặp lại bất kỳ số lần nhấn vào nút để chuyển destroy thành false.
Câu hỏi thường gặp về Angular lifecycle hook
Angular lifecycle hook Hook được sử dụng để làm gì?
Khi khởi tạo, cập nhật và hủy directives hoặc components, Angular gọi các phương thức lifecycle hook lên quá trình này. Angular quản lý lifecycle của một components, nó được tạo và hiển thị các thành phần con, được kiểm tra khi thuộc tính liên kết dữ liệu thay đổi và sẽ bị hủy trước khi bị xóa khỏi DOM.
NgOnlnit có công dụng gì trong Angular?
Trong Angular, ngOnlinit lifecycle hook hoạt động sau khi thực thi constructor và trước khi khởi tạo đầu vào của thành phần. Nếu thành phần cần bất kỳ khởi tạo nào nữa, nó có thể được sử dụng thể thực hiện. Các dịch vụ như gọi điện hoặc thiết lập đăng ký là những cách sử dụng phổ biến của ngOnlnit.
Các trường hợp sử dụng phổ biến của ngOnInit:
- Thực hiện các tác vụ khởi tạo phức tạp.
- Gọi API/services để lấy dữ liệu.
- Thiết lập các subscription.
- Xử lý logic phức tạp không nên đặt trong constructor.
Khác với constructor, ngOnInit đảm bảo rằng tất cả các binding đã được thiết lập
Có thể sử dụng lifecycle hook nào để theo dõi tất cả thay đổi đối với các giá trị @input?
ngOnlnit chính là lifecycle hook được sử dụng khi Angular khởi tạo tất cả thuộc tính liên kết dữ liệu của một directives. Để xử lý bất kỳ công việc khởi tạo bổ sung nào, hãy sử dụng hàm ngOnlnit(). Khi phát hiện thay đổi đầu tiên của components được chạy, hook này sẽ được gọi.
Tổng kết về Angular lifecycle hook
Angular lifecycle hook là một khái niệm lớn và bao gồm nhiều loại hook khác nhau trong Angular. Khi một hook được gọi, nó sẽ đóng vai trò quan trọng trong quá trình phát triển ứng dụng Angular. Nếu bạn có định hướng sử dụng Angular trong tương lai thì việc biết về cách thức hoạt động của mỗi hook và những gì có thể đạt được từ hook sẽ giúp bạn phát triển ứng dụng tốt hơn.