codejump

AcademyAngular

Generic Pipes and Type Guards

A pipe that stays generic over its input works for any array shape; a runtime type guard is the only thing standing between an untyped API response and a crash.

2 min readUpdated

3 exercises

Waiting at the end of this page.

A pipe typed <T> works for whatever array is handed to it

A pipe pinned to a concrete type only ever transforms that one shape; a generic pipe stays useful across every model the app has:

typescript
@Pipe({ name: 'unique' })
export class UniquePipe implements PipeTransform {
  transform<T>(items: T[], keyFn: (item: T) => unknown): T[] {
    const seen = new Set<unknown>();
    return items.filter((item) => {
      const key = keyFn(item);
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  }
}

A "unique" pipe that never removes a single duplicate is, very often, deduplicating with a plain Set<T> over the objects themselves instead of over a derived key — a Set compares objects by reference, so two separately-created objects with identical fields are never equal to it, no matter how many fields they share. Deduplicating by keyFn(item) — an id, a slug, whatever field actually identifies "the same thing" — is what makes the comparison mean anything.

Pure pipes recompute only when their input reference changes

An Angular pipe is pure by default, meaning it re-runs only when the reference passed to it changes, not when something inside that reference mutates — mutating an array in place and expecting a pure pipe to notice is the same category of bug as OnPush change detection missing an in-place mutation, because both rely on identity, not deep equality.

A type guard has to check every field it claims to guarantee

function isUser(value: unknown): value is User is only trustworthy if its body actually inspects the fields the User type promises — a guard that checks typeof value === 'object' && value !== null and stops there has verified almost nothing, yet TypeScript will happily narrow to User on the strength of that return type alone:

typescript
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    typeof (value as User).id === 'string' &&
    typeof (value as User).email === 'string'
  );
}

Malformed API responses that "get through" a guard are exactly this gap: the guard's declared return type asserts a shape the guard's own body never actually confirmed, so a response missing email still passes, and every caller downstream trusts a type that was never really checked.

What interviewers check

Whether you dedupe or compare by key instead of by reference the moment objects are involved, and whether a type guard you write actually checks every field its return type claims to guarantee — the gap between the two is invisible to the compiler and visible only in production.

Now practice it

1 quiz · 2 drills

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