Course outline

Angular / Basic / Lesson 4

Control flow and list identity

Render loading, empty, and populated states deliberately, and give repeated rows a stable identity that matches the data rather than their current position.

About 5 min readContent reviewed
On this page

Lesson goals

  • Use built-in if, for, and empty template blocks.
  • Choose stable unique track keys for changing collections.
  • Test reordered data and preserved user interaction.

Give every view state an explicit meaning

A collection view needs a policy for loading, failure, empty success, and populated success. An empty array alone does not distinguish all four states. Model those alternatives so the template can explain what the user should do next. Angular built-in control flow uses @if, @else, and @for blocks; these blocks do not require importing the older structural directives merely to use their syntax. They still need valid component expressions and imported child dependencies. Keep conditions readable and avoid making a false value such as a legitimate zero accidentally look like missing data.

Keep the identifier when content changes

The complete model changes one row title while preserving its identifier and order. An identifier represents a logical entity, whereas the row object reference and display position can change during refresh. The model prints the resulting data only; it does not measure DOM reuse. A real list test should additionally inspect focus, input state, and rendered rows after reordering. Do not assign a new random identifier every time a response is mapped, because that tells the renderer every row is a different entity. Validate duplicate identifiers at the data boundary rather than asking a tracking expression to repair them.

typescript A complete stable-identity data update
export {};
type Row = { id: number; title: string };
const rows: readonly Row[] = [{ id: 1, title: "Basics" }, { id: 2, title: "Forms" }];
const changed = rows.map(row => row.id === 2 ? { ...row, title: "Typed forms" } : row);
console.log(JSON.stringify(changed));
Expected output
[{"id":1,"title":"Basics"},{"id":2,"title":"Typed forms"}]

The row with id two gets a new title while both logical identifiers remain unchanged. DOM reconciliation is not executed.

Track entities and describe empty success

The contextual template assumes loading and items are signals and every item has a unique stable id and title. track item.id connects rows to their logical entities when order or object references change. @empty provides a fallback when the iteration has no items, while the outer @if separately handles loading. Use $index tracking only when position is genuinely the intended stable identity, such as a static collection that never changes. An editable or reorderable list normally needs entity keys. The framework can then preserve the appropriate DOM association instead of confusing one row’s state with another.

html Contextual loading and keyed-list template
@if (loading()) {
  <p role="status">Loading courses</p>
} @else {
  <ul>
    @for (item of items(); track item.id) {
      <li>{{ item.title }}</li>
    } @empty {
      <li>No courses match</li>
    }
  </ul>
}

A loaded empty list would show No courses match; a populated list would show its titles. The actual host state and DOM behavior are not executed by this fragment.

Use block scope without hiding the data contract

Control-flow blocks introduce local values such as the current item and contextual loop variables. Alias a long expression when it improves readability, but keep derived filtering or sorting in a tested computation when it carries business meaning. @for does not support JavaScript break or continue; produce the intended collection before rendering. A track expression is about identity, not permission to display a record. A client-side hidden row can still exist in memory, so the server must restrict returned data according to authorization. Empty and error messages should have distinct wording and an accessible place in the view.

Test reorder, replacement, and interaction state

Use a fixture with two editable rows, focus one input, and reorder the data without changing entity identifiers. A component or browser test should verify the logical row still owns its intended value and focus policy. Add duplicate-key and empty-result cases. A pure array test cannot establish those DOM guarantees, and a fast benchmark does not prove correctness. Keep loading and error transitions deterministic in tests so a missing request does not look like a valid empty response. Stable identity improves both reasoning and potential rendering efficiency, but it must reflect the real domain and be verified at the user-facing boundary.

Common errors and fixes

Row input state follows the wrong item after reordering.

Why it happens
Position was used as identity for changing entities.
How to fix it
Track a stable unique entity identifier.
Prevent it next time
Test editable rows during reorder, not only text output.

A failed request displays an empty-success message.

Why it happens
One empty array represented several different states.
How to fix it
Model loading, error, and successful data separately.
Prevent it next time
Test every state transition explicitly.

Every refresh recreates all logical row identities.

Why it happens
Keys were generated anew instead of preserved from the domain.
How to fix it
Keep stable keys across refreshed representations.
Prevent it next time
Validate uniqueness and identity before rendering.

Check your understanding

1. Reorder the model rows to place id two first. Which values should change and which identity property must remain stable?

Show worked solution

The display order changes to two then one, but each row keeps its original id and associated title. A track expression based on id describes that relationship; an index key describes positions instead. The array model can assert the id sequence, while a component test must check that editable state and focus are still associated with the intended row.

2. The server returns two records with id one. Explain why changing track to the title is not a general repair.

Show worked solution

Titles can also repeat or change, so they do not establish a reliable entity identity. Diagnose the data or mapping contract and reject, reconcile, or assign genuinely stable identifiers according to the domain. Add a duplicate-identifier test and a visible error policy if the collection cannot be represented safely. A tracking trick should not hide malformed data.

Key takeaways

  • Model view states explicitly.
  • Track logical identity, not accidental position.
  • Verify user state across list changes.

Official references