codejump

AcademyAngular

Derived State and Strongly-Typed Inputs

computed() replaces a hand-managed boolean that tracks the same thing state already implies; a discriminated-union @Input() lets the compiler enforce every branch.

2 min readUpdated

3 exercises

Waiting at the end of this page.

A hand-managed boolean and computed() can disagree

The moment a component tracks isLoading as its own signal, separately from the requests it is supposedly describing, there are now two sources of truth that have to be kept in sync by hand — and overlapping async calls are exactly the case that breaks that sync:

typescript
// fragile: two overlapping searches both toggle the same flag
isLoading = signal(false);

async search(query: string): Promise<void> {
  this.isLoading.set(true);
  const results = await this.api.search(query);
  this.isLoading.set(false); // the FIRST search's callback can fire after the second's
}

If a user types quickly enough to fire two searches, the first request's .set(false) can resolve after the second request's .set(true), turning the loading flag off while a search is still genuinely in flight. The fix deletes the flag entirely and derives it instead:

typescript
activeRequests = signal(0);
isLoading = computed(() => this.activeRequests() > 0);

Now there is exactly one number to get right — increment on request start, decrement on completion — and "loading" is never a separately-tracked fact that can drift from it, because computed() recalculates from the count every time, rather than being told what to be.

A discriminated union input needs the class field to actually narrow on it

Typing an @Input() as a union of tagged variants only pays off if the component's own logic branches on the tag:

typescript
type LinkVariant =
  | { kind: 'internal'; route: string }
  | { kind: 'external'; url: string; newTab: boolean };

@Input() link!: LinkVariant;

A SmartLink that "always renders as a button, even for the external variant" typically has a template or method that checks link.route for truthiness instead of switching on link.kind — which works by accident for the internal case and silently falls through to a wrong default for every other one, because nothing forced the code to actually look at the discriminant TypeScript is offering it for free.

What interviewers check

Whether you reach for computed() instead of a second hand-toggled flag the instant a value is fully determined by other signals, and whether a discriminated union in your own code is something you branch on by its tag — not by guessing which field happens to be present.

Now practice it

1 quiz · 2 drills

Reading this page is the cheap half. These are the exercises that make you use it.