Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Angular Component Input, Output, and Row Identity

Last updated: 5 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

A component input supplies data from its parent; an output reports an event back to the owner. Those directions are easy to blur when a child receives a mutable object and changes a nested field directly. The user may see an optimistic status that no parent mutation record can undo. In a repeated list, Angular needs a stable tracking expression to associate rendered views with records. An index is stable only if the collection never reorders or inserts ahead of a row. A case ID carries identity across those operations. A private note editor has its own lifetime: its draft can belong to the selected case, survive navigation under that ID, or be intentionally discarded. The view structure must match that decision.

Working case

A reviewer opens case 47 and types an internal note. A new case arrives ahead of it in a queue rendered with position tracking. The editor view is reused at another position, and its input appears next to case 62. A card component also sets permitCase.status to approved before a server response arrives; when approval fails, the list and detail view disagree. The repaired queue tracks rows by case ID, makes the card emit an approval request, and lets the parent own pending state and reconciliation. The note editor reads and writes a draft store indexed by case ID, so switching records cannot give case 81 the text from case 47.

Implementation boundary

typescript
import { Component, input, output } from "@angular/core";

type PermitSummary = { id: number; title: string; status: string };

@Component({
  selector: "permit-card",
  standalone: true,
  template: `<p>{{ permitCase().title }}: {{ permitCase().status }}</p>
    <button type="button" (click)="requestApproval()">Approve</button>`
})
export class PermitCardComponent {
  readonly permitCase = input.required<PermitSummary>();
  readonly approvalRequested = output<{ caseId: number }>();
  requestApproval(): void {
    this.approvalRequested.emit({ caseId: this.permitCase().id });
  }
}

// Parent template: @for (permitCase of cases(); track permitCase.id) {
//   <permit-card [permitCase]="permitCase"
//     (approvalRequested)="approve($event.caseId)" />
// }

Define a component contract with a required case input and an approval output carrying a stable case ID. The card does not mutate the case object, even if the input type allows nested JavaScript mutation. The parent generates an operation identity, marks the case pending, calls the server, and updates from the authoritative response or restores the previous status on failure. In the queue template, track each case by its immutable ID. Keep draft text in a map whose keys are case IDs if drafts must survive switching, and clear it on submission, logout, or expiry. If drafts should be discarded, create a fresh editor state at selection change and test focus behavior. Avoid keying every row by a random value: that recreates views and loses input on every update. Test an insert, reverse sort, filter, rapid double click, and failed approval.

Cost and boundaries

Stable tracking lets Angular reuse the correct row views instead of rebuilding them after every reorder. A draft map uses O(d) memory for d retained drafts and needs privacy-aware cleanup. A pending operation map uses O(p) for p in-flight approvals. Copying an array or replacing one case costs work proportional to the chosen data structure, but it provides a clear before/after state for rollback and verification. Recreating a heavy editor on selection change can reset focus and subscriptions; preserving every editor keeps more memory alive. Measure queue update time and draft retention under realistic case counts, then choose the smallest component lifetime boundary consistent with ownership.

Failure trace

Type a note for case 47, insert case 29 at the top, reverse sort, and verify the note still belongs to 47. Switch to case 81 and back, then check the declared preservation rule. Change an unrelated toolbar label and confirm the note does not reset. Reject a case 62 approval and ensure all status displays agree with the server. Click Approve twice rapidly; the parent should apply its pending or idempotency gate. Sign out with private drafts open and check that no draft appears in the next account. Remove a case during an in-flight response and prevent its result from changing another record.

Verification

  • Approval is emitted without child mutation of a record input.
  • A reordered row retains its case identity and draft owner.
  • A failed mutation restores one authoritative status.

Practice drill

Build four row cards and a selected-case editor. Track 29, 47, 62, and 81 by ID and instrument view creation count. Let a card emit approval without changing its input. Insert case 17, sort descending, switch editor selection, and reject one approval. Record every draft owner and pending operation after each step. Compare stable-ID tracking with position tracking under the same sequence. Choose a draft expiry rule and prove account switch clears private text. Measure view churn and focus changes alongside correctness.

Decision note

The parent owns case state and commits commands; a row's tracking identity is its stable case ID.

Common Mistakes

  • Tracking a changing queue by position.
  • Changing a nested input object inside the child.
  • Using fresh random tracking values on each render.

Related lessons

Angular Signals and Delivery Boundaries; Angular Signal Sources, Computed Views, and Effect Scope; Angular HttpClient Streams and Stale Detail Requests; Angular SSR Hydration and Private Transfer State; React Component Identity and Keyed Draft Lifetime; Vue Props, Emits, and Keyed Editor Ownership.

Apply and check

Build Project: Angular permit operations queue and review Web Development: Angular signals and delivery quiz.

web-tech
web-development
Storage details