codejump

AcademyAngular

Compound Components and Dynamic Outlets

A compound component's parts coordinate through a shared, injected service instead of local state; NgComponentOutlet renders whichever component a mapping picks at runtime.

2 min readUpdated

3 exercises

Waiting at the end of this page.

A compound component coordinates its parts through DI, not local booleans

A tab group's individual <app-tab> elements have no way to know about each other directly — each one only knows its own state, unless something ties them together. The pattern that scales is a shared service, scoped to the parent's providers, that every child tab injects:

typescript
@Injectable()
export class TabsService {
  private activeId = signal<string | null>(null);

  activate(id: string): void {
    this.activeId.set(id);
  }

  isActive(id: string): boolean {
    return this.activeId() === id;
  }
}

Each <app-tab> injects TabsService and calls .activate(this.id) on click, then reads .isActive(this.id) for its own styling. A tab group where "two tabs can end up active at once" is what happens without that shared coordinator — each tab tracking its own isActive boolean has no way to deactivate a sibling, because nothing connects them; the service is what makes "only one active at a time" an invariant the group actually enforces, instead of a hope resting on every tab behaving.

NgComponentOutlet renders whichever component type a mapping resolves to

Rendering "the icon for this status" — a different concrete component per case — does not need a giant @switch in the template if the mapping is a plain function returning a component type:

typescript
iconFor(status: Status): Type<unknown> {
  return status === 'success' ? CheckIcon : status === 'error' ? ErrorIcon : SpinnerIcon;
}
HTML
<ng-container *ngComponentOutlet="iconFor(status)" />

A status icon that "never changes" is frequently a template that hardcoded one component directly — <app-check-icon /> — instead of routing through ngComponentOutlet and the mapping function, so no matter what status becomes, the markup was never wired to ask the mapping in the first place.

What interviewers check

Whether you reach for a shared, injected service the moment sibling components need to coordinate rather than duplicating local state across them, and whether NgComponentOutlet is a pattern you recognize for "which component, decided at runtime" instead of reaching for an oversized conditional block in the template.

Now practice it

1 quiz · 2 drills

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