Giới Thiệu
Bạn đã bao giờ di chuyển một thành phần trong bố cục của mình và chứng kiến trạng thái của nó biến mất? Trong bài viết này, chúng ta sẽ xem xét ba cách khác nhau để di chuyển một thành phần trong Angular mà không làm mất trạng thái của nó: sử dụng ng-template với ngTemplateOutlet, CDK TemplatePortal, và CDK DomPortal. Đến cuối bài, bạn sẽ có một quy tắc đơn giản để ghi nhớ và áp dụng cho các dự án của mình.
Mục Tiêu Bài Viết
- Di chuyển một thành phần mà không làm mất trạng thái.
- Hiểu rõ cách Angular tái tạo các view và cách di chuyển một thành phần đang hoạt động.
Thiết Lập Demo: Di Chuyển Một Thành Phần
Chúng ta sẽ làm việc với một ứng dụng bảng điều khiển quản trị nhỏ, có một "biểu ngữ khuyến mãi" được hiển thị ở thanh bên:
Mô Tả Ứng Dụng
Khi chúng ta nhấn nút, biểu ngữ sẽ nhảy vào khu vực nội dung chính. Biểu ngữ này bao gồm một nút yêu thích và một bộ đếm thời gian. Nhưng khi chúng ta chuyển đổi vị trí, trạng thái của nó sẽ được đặt lại, bộ đếm thời gian sẽ khởi động lại, và nút yêu thích sẽ bị bỏ thích vì thành phần đang được khởi tạo lại mỗi khi nó di chuyển.
Mã Hiện Tại
Hãy xem xét mã của thành phần gốc nơi biểu ngữ được hiển thị:
typescript
<button class="btn" (click)="togglePlacement()">
Di Chuyển Khuyến Mãi {{ dockRight() ? 'đến Nội Dung' : 'đến Thanh Bên' }}
</button>
Chúng ta có hai vùng điều kiện dựa trên tín hiệu dockRight():
Trong khu vực nội dung chính:
typescript
@if (!dockRight()) {
<promo-banner></promo-banner>
}
Trong thanh bên:
typescript
@if (dockRight()) {
<promo-banner></promo-banner>
}
TypeScript của Thành Phần
Mã TypeScript hiện tại của chúng ta khá đơn giản:
typescript
const dockRight = signal(false);
togglePlacement() {
dockRight.set(!dockRight());
}
Phần 1: Sử Dụng ng-template và ngTemplateOutlet
Đầu tiên, chúng ta sẽ thử sử dụng ng-template và chỉ thị ngTemplateOutlet:
Thêm NgTemplateOutlet
Thêm NgTemplateOutlet vào mảng imports của thành phần:
typescript
import { NgTemplateOutlet } from '@angular/common';
@Component({
selector: 'app-root',
imports: [ NgTemplateOutlet ],
}
Định Nghĩa NgTemplate
Trong template, chúng ta định nghĩa một ng-template với thành phần promo-banner:
typescript
<ng-template #promo>
<promo-banner></promo-banner>
</ng-template>
Sử Dụng NgTemplateOutlet
Thay thế các thành phần promo-banner hiện có bằng chỉ thị ngTemplateOutlet:
Trong khu vực nội dung chính:
typescript
@if (!dockRight()) {
<ng-template [ngTemplateOutlet]="promo"></ng-template>
}
Trong thanh bên:
typescript
@if (dockRight()) {
<ng-template [ngTemplateOutlet]="promo"></ng-template>
}
Mặc dù banner vẫn xuất hiện nhưng trạng thái vẫn bị đặt lại mỗi khi di chuyển.
Phần 2: Thử Nghiệm với CDK TemplatePortal
Tiếp theo, chúng ta sẽ sử dụng Angular CDK Portal Module. CDK cung cấp ba loại portal:
- TemplatePortal
- ComponentPortal
- DomPortal
Chúng ta sẽ thử TemplatePortal trước.
Cài Đặt CDK
Chạy lệnh sau để cài đặt CDK:
bash
npm install @angular/cdk
Nhập PortalModule
Nhập PortalModule vào thành phần:
typescript
import { PortalModule } from '@angular/cdk/portal';
@Component({
selector: 'app-root',
imports: [ PortalModule ],
}
Định Nghĩa Thuộc Tính
Thêm thuộc tính promoContent để lưu giá trị portal:
typescript
protected promoContent!: TemplatePortal<unknown>;
Và thuộc tính promo để truy cập vào template:
typescript
private readonly promo = viewChild.required<TemplateRef<unknown>>('promo');
Cập Nhật Trong Constructor
Cập nhật constructor để thiết lập portal:
typescript
constructor() {
effect(() => {
if (this.promo()) {
this.promoContent = new TemplatePortal(this.promo(), this.viewContainerRef);
}
})
}
Cập Nhật Template
Thay ngTemplateOutlet bằng cdkPortalOutlet:
Trong khu vực nội dung chính:
typescript
@if (!dockRight()) {
<ng-template [cdkPortalOutlet]="promoContent"></ng-template>
}
Trong thanh bên:
typescript
@if (dockRight()) {
<ng-template [cdkPortalOutlet]="promoContent"></ng-template>
}
Mặc dù chúng ta đã thử nghiệm với TemplatePortal, trạng thái vẫn không được giữ lại.
Phần 3: Sử Dụng DomPortal
Sử dụng DomPortal, chúng ta có thể thực sự di chuyển cùng một thể hiện của thành phần:
Chuyển Đổi promoContent
Thay đổi promoContent thành DomPortal:
typescript
protected promoContent!: DomPortal<HTMLElement>;
Chuyển Đổi promo
Chuyển promo từ TemplateRef sang ElementRef:
typescript
private readonly promo = viewChild.required<ElementRef>('promo');
Cập Nhật Constructor
Trong effect(), chuyển từ TemplatePortal sang DomPortal:
typescript
constructor() {
effect(() => {
if (this.promo()) {
this.promoContent = new DomPortal(this.promo());
}
})
}
Cập Nhật Template
Thay đổi template để sử dụng các phần tử thực:
html
<div #promo>
<promo-banner></promo-banner>
</div>
Trong khu vực nội dung chính:
html
@if (!dockRight()) {
<div [cdkPortalOutlet]="promoContent"></div>
}
Trong thanh bên:
html
@if (dockRight()) {
<div [cdkPortalOutlet]="promoContent"></div>
}
Kiểm Tra Hoạt Động
Khi chúng ta di chuyển biểu ngữ:
- Bộ đếm thời gian vẫn chạy
- Tình trạng thích vẫn được giữ nguyên
- Không có reset xảy ra!
Kết Luận
Những Ghi Nhớ Chính
- Việc sử dụng
ng-templatevàngTemplateOutlethayTemplatePortalđều tái tạo view, do đó trạng thái sẽ bị đặt lại. DomPortalthực sự di chuyển cùng một thể hiện, vì vậy trạng thái sẽ được giữ lại.
Hãy theo dõi để khám phá thêm nhiều tính năng thú vị trong Angular CDK!
Tài Nguyên Bổ Sung
- Tài liệu Angular CDK Portal
- Hướng dẫn về NgTemplateOutlet
- Tổng quan về Angular Signals
- Khóa học "Angular: Styling Applications"
- Khóa học "Angular in Practice: Zoneless Change Detection"
Bạn Muốn Xem Nó Hoạt Động?
Hãy khám phá demo hoàn chỉnh dưới đây. Nếu bạn có bất kỳ câu hỏi hay ý kiến nào, đừng ngần ngại để lại bình luận.