0
0
Lập trình
NM

Di Chuyển Thành Phần Trong Angular Mà Không Mất Trạng Thái

Đăng vào 7 tháng trước

• 4 phút đọc

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 Copy
<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 Copy
@if (!dockRight()) {
    <promo-banner></promo-banner>
}

Trong thanh bên:

typescript Copy
@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 Copy
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 Copy
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 Copy
<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 Copy
@if (!dockRight()) {
    <ng-template [ngTemplateOutlet]="promo"></ng-template>
}

Trong thanh bên:

typescript Copy
@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 Copy
npm install @angular/cdk

Nhập PortalModule

Nhập PortalModule vào thành phần:

typescript Copy
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 Copy
protected promoContent!: TemplatePortal<unknown>;

Và thuộc tính promo để truy cập vào template:

typescript Copy
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 Copy
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 Copy
@if (!dockRight()) {
    <ng-template [cdkPortalOutlet]="promoContent"></ng-template>
}

Trong thanh bên:

typescript Copy
@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 Copy
protected promoContent!: DomPortal<HTMLElement>;

Chuyển Đổi promo

Chuyển promo từ TemplateRef sang ElementRef:

typescript Copy
private readonly promo = viewChild.required<ElementRef>('promo');

Cập Nhật Constructor

Trong effect(), chuyển từ TemplatePortal sang DomPortal:

typescript Copy
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 Copy
<div #promo>
    <promo-banner></promo-banner>
</div>

Trong khu vực nội dung chính:

html Copy
@if (!dockRight()) {
    <div [cdkPortalOutlet]="promoContent"></div>
}

Trong thanh bên:

html Copy
@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-templatengTemplateOutlet hay TemplatePortal đều tái tạo view, do đó trạng thái sẽ bị đặt lại.
  • DomPortal thự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

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.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào