Add section headers to the virtual list

Angular · Angular · intermediate · modification

Adds sticky section headers to the virtual list. Rows are now either items (48px) or shorter section headers (32px), so totalHeight sums per-kind heights and firstVisibleIndex branches on the row kind while walking down to the first partially-visible row. Verified against a grouped contacts list.

firstVisibleIndex is a signal computed re-read on every scroll event to drive the rendered window; if it doesn't return, change detection never completes and the tab freezes.

Requirements

Files touched

--- src/app/contacts/virtual-list.component.ts
   id: number;
   label: string;
+  kind: 'header' | 'item';
 }
 
-const ROW_HEIGHT = 48;
+const ITEM_HEIGHT = 48;
+const HEADER_HEIGHT = 32;
 
 @Component({
   scrollTop = signal(0);
 
-  totalHeight = computed(() => this.rows().length * ROW_HEIGHT);
+  totalHeight = computed(() =>
+    this.rows().reduce(
+      (sum, row) => sum + (row.kind === 'header' ? HEADER_HEIGHT : ITEM_HEIGHT),
+      0,
+    ),
+  );
 
-  // Index of the first row at least partially inside the viewport.
+  // Index of the first row at least partially inside the viewport. Walks the
+  // rows top-down, consuming each row that sits fully above the viewport top,
+  // and stops at the first row the viewport top falls inside.
   firstVisibleIndex = computed(() => {
+    const rows = this.rows();
     let remaining = this.scrollTop();
     let index = 0;
     while (remaining > 0) {

Review this PR

Angular practice