Angular Learning
Nền tảng Angular

DOM và template

Hiểu cách Angular dùng template để biến trạng thái thành DOM, xử lý sự kiện và cập nhật giao diện một cách có kiểm soát.

DOM là nơi giao diện thật sự tồn tại trong trình duyệt; template là bản mô tả để Angular tạo và cập nhật giao diện đó. Phân biệt hai khái niệm này giúp bạn biết nên thay đổi trạng thái nào thay vì tìm và sửa từng thẻ HTML.

Mục tiêu

Sau bài này, bạn có thể lần theo luồng state → template → DOM, chọn đúng loại binding và nhận ra khi nào thao tác DOM trực tiếp là một dấu hiệu cần xem lại thiết kế.

Mục lục

DOM là gì?

DOM (Document Object Model) là mô hình đối tượng mà trình duyệt tạo ra để biểu diễn tài liệu đang hiển thị. Mỗi phần tử HTML, thuộc tính và đoạn văn bản trở thành một node trong một cây. JavaScript có thể đọc hoặc thay đổi cây này qua các API như document.querySelector(), element.textContent và element.classList.

Cây đối tượng mà trình duyệt đang quản lý

Từ HTML sau:

<main>
  <h1>Chào An</h1>
  <button disabled>Theo dõi</button>
</main>

trình duyệt có thể biểu diễn giao diện bằng mô hình đơn giản sau:

direction: right
document: "Document"
main: "main"
heading: "h1\n#text: Chào An"
button: "button\nProperty disabled: true\n#text: Theo dõi"
document -> main
main -> heading
main -> button
Documentmainh1#text: Chào AnbuttonProperty disabled: true#text: Theo dõi
Documentmainh1#text: Chào AnbuttonProperty disabled: true#text: Theo dõi
Mô hình DOM đơn giản của main

Node button không chỉ là một dòng chữ trong tệp HTML. Nó là một object đang sống trong bộ nhớ của trang, có property disabled, danh sách class, event listener và trạng thái do trình duyệt quản lý. Khi DOM đổi, trình duyệt có thể phải tính lại bố cục (layout) và vẽ lại một phần giao diện (paint).

HTML nguồn và DOM đang chạy không luôn giống nhau

HTML trong tệp ban đầu là đầu vào. DOM trong tab trình duyệt là trạng thái hiện tại sau khi trình duyệt phân tích HTML và sau khi mã JavaScript đã chạy. Ví dụ, người dùng nhập vào một ô input: giá trị hiện tại của phần tử là property input.value; nội dung attribute value ban đầu trong HTML có thể không đổi.

Điểm này quan trọng vì Angular làm việc để giữ DOM khớp với trạng thái ứng dụng. Đừng xem DOM như một bản HTML bất biến, cũng đừng xem HTML nguồn là nơi duy nhất quyết định những gì người dùng đang thấy.

Template Angular là gì?

Template là phần khai báo giao diện của một component. Nó trông gần HTML, nhưng Angular đọc thêm các cú pháp như {{ name }}, [disabled], (click) và @if. Khi biên dịch ứng dụng, Angular hiểu các cú pháp này như các chỉ dẫn để tạo node, gắn dữ liệu và lắng nghe sự kiện.

Component sở hữu trạng thái, template mô tả giao diện

Một component thường chia trách nhiệm thành hai nửa:

PhầnTrách nhiệm chínhVí dụ
Lớp componentGiữ trạng thái, nhận dữ liệu, thực hiện hành độngisFollowing, toggleFollow()
TemplateMô tả giao diện phải trông ra sao với trạng thái hiện tạiNút hiện Theo dõi hay Đang theo dõi

Mô hình tinh thần hữu ích là:

direction: right

n0: "Người dùng / API" {
  width: 152
  height: 58
}

n1: "State component" {
  width: 144
  height: 58
}

n2: "Angular render\nvà cập nhật" {
  width: 136
  height: 58
}

n3: "DOM thật" {
  width: 130
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
Người dùng / APIState componentAngular rendervà cập nhậtDOM thật
Người dùng / APIState componentAngular rendervà cập nhậtDOM thật
Từ thay đổi state đến giao diện nhìn thấy (1/2)
direction: right

n3: "DOM thật" {
  width: 130
  height: 58
}

n4: "Giao diện người dùng" {
  width: 184
  height: 58
}

n3 -> n4
DOM thậtGiao diện người dùng
DOM thậtGiao diện người dùng
Từ thay đổi state đến giao diện nhìn thấy (2/2)

Template không thay thế DOM. Nó là mô tả có quy tắc để Angular tạo và đồng bộ DOM. DOM vẫn là thứ trình duyệt dùng để hiển thị và nhận sự kiện.

Template không phải chuỗi HTML tĩnh

Trong HTML thuần, muốn đổi nhãn nút, bạn thường tìm phần tử rồi sửa nó:

const button = document.querySelector('#follow-button');
button?.textContent = 'Đang theo dõi';

Trong Angular, template mô tả kết quả cần có, còn component thay đổi trạng thái:

<button>{{ isFollowing() ? 'Đang theo dõi' : 'Theo dõi' }}</button>
isFollowing.set(true);

Angular đọc lại phần template phụ thuộc vào isFollowing() và làm DOM hiển thị Đang theo dõi. Bạn không cần tự tìm nút hay tự nhớ phải thay text, class và attribute nào. Cách khai báo này không có nghĩa Angular thay toàn bộ trang bằng một chuỗi HTML sau mỗi lần thay đổi; framework tạo và cập nhật các node DOM cần thiết theo template.

Vì sao không nên thao tác DOM trực tiếp?

Thao tác DOM trực tiếp không luôn bị cấm. Nó cần thiết cho một vài API của trình duyệt hoặc thư viện bên thứ ba. Tuy nhiên, đối với giao diện thông thường, thay đổi state rồi để template phản ánh state là đường đi mặc định an toàn hơn.

Hai nguồn sự thật tạo ra lỗi khó đoán

Hãy xét component có template sau:

<button [class.following]="isFollowing()">
  {{ isFollowing() ? 'Đang theo dõi' : 'Theo dõi' }}
</button>

Nếu một hàm khác làm việc này:

this.button.nativeElement.classList.add('following');

thì state vẫn có thể là false, trong khi DOM đã có class following. Bây giờ ứng dụng có hai câu trả lời mâu thuẫn cho câu hỏi “người dùng đã theo dõi chưa?”:

  • isFollowing() nói chưa.
  • DOM lại trông như rồi.

Trong lần cập nhật kế tiếp, Angular được quyền áp dụng lại kết quả của template từ isFollowing(). Class bạn tự thêm có thể biến mất. Lỗi thường xuất hiện không phải ngay lúc thêm class, mà sau một sự kiện khác, dữ liệu API trả về hoặc component được render lại.

Quy tắc một nguồn sự thật

Không lưu trạng thái giao diện chỉ trong DOM. Nếu một nút đang mở, đang tải, bị vô hiệu hoá hoặc được chọn, hãy biểu diễn điều đó bằng state của component và bind state vào template.

Tính di động, kiểm thử và vòng đời

Khi code gọi document hoặc giả định luôn có một phần tử trong trang, nó bị buộc chặt vào môi trường trình duyệt và thời điểm DOM đã tồn tại. Điều này gây khó khăn trong các tình huống sau:

  • Server-side rendering (SSR): lúc render trên server, API DOM của trình duyệt không có sẵn.
  • Hydration: Angular có thể nối ứng dụng phía client vào HTML đã render từ server. DOM bị sửa sớm hoặc sai thời điểm làm quá trình khớp cấu trúc kém tin cậy.
  • Kiểm thử: logic phụ thuộc selector toàn cục như document.querySelector('.save') khó cô lập hơn logic chỉ thay đổi state.
  • Vòng đời component: selector toàn cục có thể lấy nhầm phần tử của component khác; phần tử cũng có thể chưa được tạo, đã bị @if gỡ đi hoặc đã bị huỷ.

Nói ngắn gọn: hãy để template sở hữu cấu trúc và trạng thái hiển thị. Chỉ dùng API DOM ở rìa giao tiếp với trình duyệt, sau khi đã cân nhắc vòng đời và SSR.

Angular render và binding như thế nào?

Render là quá trình Angular tạo DOM từ template, rồi giữ DOM phù hợp với dữ liệu mà template đọc. Binding là cầu nối có hướng giữa biểu thức template và DOM, hoặc giữa DOM event và code component.

Lần render đầu tiên

Khi Angular tạo một component, framework khởi tạo instance của lớp component, đánh giá template với state hiện tại và tạo các node DOM tương ứng. Nếu template có {{ user().name }}, Angular lấy giá trị hiện tại của signal user để đặt text node.

direction: right

n0: "Tạo component + state" {
  width: 192
  height: 58
}

n1: "Đọc template\nTạo element / text\nnode" {
  width: 168
  height: 79
}

n2: "Đánh giá binding\nText, property,\nattribute, class,\nstyle" {
  width: 160
  height: 100
}

n3: "Gắn event listener\nClick, input" {
  width: 168
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
Tạo component + stateĐọc templateTạo element / textnodeĐánh giá bindingText, property,attribute, class,styleGắn event listenerClick, input
Tạo component + stateĐọc templateTạo element / textnodeĐánh giá bindingText, property,attribute, class,styleGắn event listenerClick, input
Các bước render component ban đầu (1/2)
direction: right

n3: "Gắn event listener\nClick, input" {
  width: 168
  height: 58
}

n4: "Chèn DOM vào\nvị trí component" {
  width: 152
  height: 58
}

n3 -> n4
Gắn event listenerClick, inputChèn DOM vàovị trí component
Gắn event listenerClick, inputChèn DOM vàovị trí component
Các bước render component ban đầu (2/2)

Các bước là mô hình khái niệm. Chi tiết nội bộ có thể khác theo phiên bản Angular và chế độ render, nhưng trách nhiệm không đổi: template mô tả, Angular đồng bộ, trình duyệt hiển thị.

Khi trạng thái thay đổi

Sau lần render đầu, Angular cần biết phần nào phải được kiểm tra lại. Với signal, Angular theo dõi nơi template đã đọc signal đó. Khi signal thay đổi, Angular lên lịch cập nhật phần giao diện phụ thuộc vào nó. Với các luồng thay đổi khác, Angular cũng có cơ chế phát hiện thay đổi để kiểm tra template theo cấu hình ứng dụng.

Luồng của một event đơn giản thường là:

direction: right

n0: "Click trên DOM" {
  width: 136
  height: 58
}

n1: "(click) gọi method" {
  width: 168
  height: 58
}

n2: "Method đổi state\nsignal.set(...)" {
  width: 152
  height: 58
}

n3: "Angular cập nhật\nbinding phụ thuộc" {
  width: 160
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
Click trên DOM(click) gọi methodMethod đổi statesignal.set(...)Angular cập nhậtbinding phụ thuộc
Click trên DOM(click) gọi methodMethod đổi statesignal.set(...)Angular cập nhậtbinding phụ thuộc
Event cập nhật state và binding phụ thuộc (1/2)
direction: right

n3: "Angular cập nhật\nbinding phụ thuộc" {
  width: 160
  height: 58
}

n4: "DOM đổi text,\nproperty, class, cấu\ntrúc" {
  width: 184
  height: 79
}

n3 -> n4
Angular cập nhậtbinding phụ thuộcDOM đổi text,property, class, cấutrúc
Angular cập nhậtbinding phụ thuộcDOM đổi text,property, class, cấutrúc
Event cập nhật state và binding phụ thuộc (2/2)

Điều quan trọng là event không tự sửa giao diện. Event gọi code của bạn; code thay đổi state; template là nơi nói giao diện cần trở thành gì.

Các loại binding cần phân biệt

LoạiCú phápHướng dữ liệuDùng khi
Interpolation{{ expression }}Component → textHiển thị văn bản hoặc giá trị được chuyển thành chuỗi
Property binding[disabled]="expression"Component → property DOM/directiveĐiều khiển property như disabled, value, checked
Attribute binding[attr.aria-label]="expression"Component → HTML attributeĐặt attribute không có property phù hợp, nhất là aria-*, data-*
Class/style binding[class.active]="expression", [style.width.px]="size"Component → presentationThêm/bỏ class hoặc style theo state
Event binding(click)="save()"DOM → componentPhản hồi hành động của người dùng
Two-way binding[(ngModel)]="name"Hai chiềuForm đơn giản khi sự đồng bộ hai chiều thực sự rõ ràng

Interpolation phù hợp để hiển thị text. Angular chuyển kết quả biểu thức thành chuỗi:

<p>Xin chào, {{ user().name }}.</p>

Property binding nên là lựa chọn đầu tiên khi phần tử có property tương ứng. Giá trị boolean được giữ đúng kiểu boolean, thay vì thành chuỗi:

<button [disabled]="isSaving()">Lưu</button>

Attribute binding đặc biệt hữu ích cho khả năng truy cập. aria-expanded là attribute, nên dùng tiền tố attr.:

<button [attr.aria-expanded]="isOpen()">Chi tiết</button>

Event binding nhận event của trình duyệt. Nếu cần dữ liệu từ event, truyền $event vào hàm:

<input (input)="updateQuery($event)" />

Cách chọn nhanh

Muốn hiển thị chữ, dùng {{ expression }}. Muốn điều khiển một property có thật của phần tử, dùng [property]. Muốn nhận thao tác người dùng, dùng (event). Đừng chọn cú pháp chỉ vì nó cho kết quả giống nhau ở một ví dụ nhỏ.

Ví dụ hoàn chỉnh: thẻ hồ sơ tương tác

Ví dụ dưới đây dùng standalone component và signal. Thẻ hồ sơ cần hiển thị số người theo dõi, đổi nhãn nút, đổi class và cập nhật thông tin hỗ trợ trình đọc màn hình khi người dùng nhấp nút.

Component: giữ trạng thái và xử lý ý định

import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-profile-card',
  templateUrl: './profile-card.component.html',
  styleUrl: './profile-card.component.css',
})
export class ProfileCardComponent {
  readonly profile = signal({
    name: 'An Nguyễn',
    role: 'Angular Developer',
    followers: 128,
  });

  readonly isFollowing = signal(false);

  readonly followLabel = computed(() =>
    this.isFollowing() ? 'Đang theo dõi' : 'Theo dõi',
  );

  toggleFollow(): void {
    const wasFollowing = this.isFollowing();

    this.isFollowing.set(!wasFollowing);
    this.profile.update((profile) => ({
      ...profile,
      followers: profile.followers + (wasFollowing ? -1 : 1),
    }));
  }
}

Lớp này không tìm button, không sửa textContent và không thêm class. Nó chỉ mô tả dữ liệu và ý định toggleFollow(). computed() tạo một signal dẫn xuất: nhãn được tính từ isFollowing, vì vậy không có biến nhãn riêng có thể quên cập nhật.

Template: khai báo DOM mong muốn

<article class="profile-card">
  <h2>{{ profile().name }}</h2>
  <p>{{ profile().role }}</p>

  <p aria-live="polite">
    {{ profile().followers }} người theo dõi
  </p>

  <button
    type="button"
    [class.is-following]="isFollowing()"
    [attr.aria-pressed]="isFollowing()"
    (click)="toggleFollow()"
  >
    {{ followLabel() }}
  </button>
</article>

Một state isFollowing() ảnh hưởng đến ba vị trí: class để CSS trang trí, aria-pressed để công nghệ hỗ trợ hiểu trạng thái nút, và text để người dùng thấy hành động hiện tại. Đó là lợi ích thực tế của việc khai báo giao diện từ state: các biểu diễn khác nhau cùng đi từ một sự thật.

Ví dụ CSS chỉ cần mô tả cách class được hiển thị, không cần biết ai thêm class:

.profile-card button.is-following {
  background: #0f766e;
  color: white;
}

Lần theo một cú nhấp

Giả sử isFollowing() ban đầu là false và followers là 128.

  1. Template render nút với text Theo dõi, không có class is-following, và aria-pressed="false".
  2. Người dùng nhấp nút. Listener (click) gọi toggleFollow().
  3. Hàm cập nhật isFollowing thành true và cập nhật object profile với followers: 129.
  4. Angular thấy các binding đã đọc hai signal này cần được đồng bộ lại.
  5. DOM nhận text Đang theo dõi, class is-following, attribute aria-pressed="true" và text 129 người theo dõi.

Không bước nào yêu cầu bạn giữ một tham chiếu đến node nút. Nếu trạng thái được cập nhật từ API thay vì từ click, template vẫn cho kết quả đúng theo cùng quy tắc.

Điều kiện và danh sách: để template quyết định cấu trúc

DOM không chỉ thay đổi text hay class. Có lúc một node cần tồn tại hoặc không tồn tại. Có lúc cần tạo một node cho mỗi item. Thay vì gọi appendChild() và removeChild() trong component, hãy thể hiện quy tắc cấu trúc trong template bằng control flow.

readonly projects = signal([
  { id: 1, name: 'Cổng quản trị', archived: false },
  { id: 2, name: 'Báo cáo tháng', archived: true },
]);

readonly showArchived = signal(false);
<button type="button" (click)="showArchived.set(!showArchived())">
  {{ showArchived() ? 'Ẩn' : 'Hiện' }} dự án đã lưu trữ
</button>

<ul>
  @for (project of projects(); track project.id) {
    @if (showArchived() || !project.archived) {
      <li [class.archived]="project.archived">
        {{ project.name }}
      </li>
    }
  }
</ul>

@if nói khi nào một khối được đưa vào DOM. @for nói cách tạo các node lặp lại. Biểu thức track project.id cung cấp định danh ổn định để Angular ghép mỗi item dữ liệu với node DOM tương ứng khi danh sách đổi thứ tự, thêm hoặc xoá phần tử.

Đừng track theo index khi danh sách có thể thay đổi

Index chỉ ổn định khi thứ tự và số phần tử không đổi. Nếu chèn một dự án lên đầu danh sách, Angular có thể ghép nhầm DOM cũ với dữ liệu mới. Hãy dùng khóa định danh duy nhất như project.id khi có thể.

Lỗi phổ biến và điểm đánh đổi

Binding làm luồng dữ liệu dễ theo dõi, nhưng cú pháp gần HTML cũng khiến vài lỗi trông có vẻ hợp lý. Những lỗi dưới đây thường gây giao diện không đồng bộ hoặc làm code khó bảo trì.

Sửa DOM rồi chờ Angular giữ nguyên

Đoạn mã sau có thể hoạt động khi vừa chạy:

this.elementRef.nativeElement.querySelector('.status')!.textContent = 'Đã lưu';

Nhưng nếu template có {{ saveStatus() }}, lần đồng bộ sau của Angular sẽ ghi lại theo saveStatus(), không theo text bạn tự đặt. Cách sửa là đổi state:

this.saveStatus.set('Đã lưu');

rồi để template giữ {{ saveStatus() }}. Đây không chỉ là khác cú pháp; nó loại bỏ trạng thái ngầm nằm trong DOM.

Nhầm interpolation, property và attribute

Ba cú pháp sau đều có thể xuất hiện cạnh nhau nhưng không tương đương:

<!-- Text node -->
<p>{{ isSaving() }}</p>

<!-- Property boolean của button -->
<button [disabled]="isSaving()">Lưu</button>

<!-- Attribute cho accessibility -->
<button [attr.aria-busy]="isSaving()">Lưu</button>

Một lỗi hay gặp là viết disabled="false". Trong HTML, sự hiện diện của boolean attribute disabled đã làm nút bị vô hiệu hoá; chuỗi "false" không có nghĩa là boolean false. Khi giá trị thay đổi theo state, hãy dùng [disabled]="isSaving()".

Tương tự, không phải attribute nào cũng có property DOM cùng tên. Với aria-*, data-*, colspan và các trường hợp tương tự, cú pháp [attr.tên] làm ý định rõ ràng hơn.

Dùng hai chiều khi một chiều đã đủ

[(ngModel)] tiện cho form nhỏ vì nó ghép property binding với event cập nhật. Tuy nhiên, hai chiều che bớt câu hỏi quan trọng: giá trị đổi ở đâu và lúc nào? Với một nút chỉ phát ra ý định hoặc một biểu mẫu có quy tắc xử lý rõ ràng, event binding một chiều thường dễ đọc hơn:

<input
  [value]="query()"
  (input)="query.set($any($event.target).value)"
/>

Ví dụ này cho thấy rõ input hiển thị query() và mỗi lần gõ sẽ cập nhật signal. Trong form lớn, Angular Reactive Forms thường là lựa chọn có cấu trúc hơn vì trạng thái, validation và luồng submit được gom vào mô hình form. Kết luận thực dụng: dùng hai chiều cho trường hợp đơn giản, nhưng không dùng nó để né việc thiết kế state.

Chèn HTML không tin cậy

[innerHTML] có thể cần khi hiển thị nội dung giàu định dạng, nhưng không biến dữ liệu từ người dùng thành HTML rồi chèn tùy ý. Nội dung HTML không tin cậy có nguy cơ XSS (cross-site scripting): kẻ tấn công đưa script hoặc hành vi nguy hiểm vào trang của người dùng khác.

<!-- Chỉ dùng với nội dung đã được kiểm soát hoặc làm sạch phù hợp. -->
<div [innerHTML]="trustedContent()"></div>

Angular có cơ chế làm sạch cho nhiều ngữ cảnh, nhưng đó không phải lý do để bỏ qua chính sách dữ liệu đầu vào. Không dùng các API “bypass security” chỉ để loại bỏ cảnh báo. Nếu cần hiển thị Markdown hay HTML do người dùng tạo, hãy áp dụng một chiến lược sanitize được xem xét riêng.

Tối ưu quá sớm hoặc render quá nhiều

Không phải mọi giá trị đều cần trở thành signal, và không phải mọi đoạn template đều cần bị tách thành component nhỏ. Tách quá mức làm luồng dữ liệu khó lần theo. Ngược lại, một template rất lớn, gọi hàm tốn kém trực tiếp trong binding hoặc render danh sách dài mà không có track cũng tạo chi phí không cần thiết.

Một thứ tự xử lý hợp lý là:

  1. Giữ template thuần mô tả và để state là nguồn sự thật.
  2. Đo hiệu năng bằng Angular DevTools hoặc profiler của trình duyệt khi đã có dấu hiệu chậm.
  3. Chỉ tối ưu điểm đã đo được: dùng khóa track ổn định, tiền tính toán dữ liệu bằng computed, phân trang/ảo hoá danh sách dài, hoặc tách component ở ranh giới có ý nghĩa.

Đừng thay đổi DOM thủ công để “tối ưu” trước khi đo. Bạn có thể đổi một vấn đề hiệu năng chưa chắc tồn tại thành lỗi đồng bộ chắc chắn tồn tại.

Khi nào được phép chạm vào DOM?

Một số công việc vốn là giao tiếp với nền tảng trình duyệt, ví dụ focus một input sau khi nó xuất hiện, đo kích thước phần tử, dùng IntersectionObserver, tích hợp biểu đồ/canvas hoặc thư viện không có Angular wrapper. Các trường hợp này là ngoại lệ có chủ đích, không phải cách render giao diện thường ngày.

Khi cần làm việc với DOM, hãy giữ các nguyên tắc sau:

  • Chờ đúng thời điểm phần tử đã được render, chẳng hạn dùng query view và hook render phù hợp; đừng giả định phần tử đã tồn tại ngay trong constructor.
  • Ưu tiên binding cho mọi thay đổi mà template có thể diễn tả. DOM API chỉ nên làm phần template không diễn tả được, như gọi focus() hoặc khởi tạo thư viện canvas.
  • Cô lập tích hợp trong directive hoặc component nhỏ thay vì rải document.querySelector() khắp ứng dụng.
  • Tính đến SSR: kiểm tra môi trường trình duyệt trước khi gọi API chỉ có ở browser, hoặc trì hoãn phần tích hợp sang phía client.
  • Dọn listener, observer và instance thư viện khi component bị huỷ để tránh rò rỉ bộ nhớ.

Ví dụ ngoại lệ hợp lý

Nếu sau khi mở một hộp thoại bạn cần đưa bàn phím vào ô tìm kiếm, việc gọi input.focus() tại thời điểm render thích hợp là hợp lý. State vẫn quyết định hộp thoại có mở hay không; lệnh DOM chỉ thực hiện hiệu ứng trình duyệt không thể biểu diễn bằng HTML thuần.

Tóm tắt và tự kiểm tra

Hãy giữ chuỗi tư duy này khi viết Angular:

direction: right

n0: "State thay đổi" {
  width: 136
  height: 58
}

n1: "Template đọc state\nqua binding" {
  width: 168
  height: 58
}

n2: "Đồng bộ DOM liên quan" {
  width: 192
  height: 58
}

n3: "Browser hiển thị\nvà phát event mới" {
  width: 160
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
State thay đổiTemplate đọc statequa bindingĐồng bộ DOM liên quanBrowser hiển thịvà phát event mới
State thay đổiTemplate đọc statequa bindingĐồng bộ DOM liên quanBrowser hiển thịvà phát event mới
State, binding và DOM tạo vòng tương tác

Trước khi thêm querySelector, classList.add hoặc innerHTML, tự hỏi:

  • Thay đổi này có phải là trạng thái giao diện không? Nếu có, hãy đặt nó trong component/service phù hợp và bind từ template.
  • Đây là text, property, attribute, class/style hay event? Chọn loại binding theo bản chất dữ liệu.
  • Node có thể biến mất do @if, thay đổi do @for hoặc chưa tồn tại vì component chưa render không?
  • Có chạy với SSR/hydration không? API trình duyệt này có cần được cô lập ở client không?
  • Có dữ liệu không tin cậy đi vào [innerHTML] hay URL không?

Nếu trả lời rõ các câu hỏi này, bạn đang dùng Angular theo mô hình khai báo: dữ liệu mô tả giao diện, còn framework chịu trách nhiệm đưa mô tả đó xuống DOM.

Bước tiếp theo

Bài này giải thích đường đi từ state đến giao diện. Để hiểu Angular nhận biết thay đổi của state bằng cách nào, hãy học tiếp về signals và Zone.js.

On this page