Skip to content

Lesson 1 — App anatomy

Objective

Bootstrap Angular without an NgModule and explain how compilation dependencies, runtime providers, routes, and lazy chunks compose an application shell.

Prerequisites

TypeScript classes/modules, semantic HTML, and browser navigation. If these are solid, attempt Verification first.

Mental model

A component combines a class, template, and styles behind a selector. Standalone moves declarable dependencies from an NgModule into component imports; it does not make a component isolated. bootstrapApplication creates the root environment injector and root component.

Why it exists

Local imports make template dependencies explicit, while providers separately configure runtime capabilities. Routes map URL state to lazily created component and injector trees.

Working model: imports make template features available; providers make injectable capabilities available.

Engineering reality: compilation, dynamic loading, injector resolution, and rendering are distinct systems. A router provider does not make RouterOutlet a valid template element.

Minimal example

bootstrapApplication(AppComponent, appConfig);

A route can use loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent) while the shell imports RouterOutlet.

Real-world usage

Feature routes establish code-splitting and lifetime boundaries. Semantic links preserve keyboard use, open-in-new-tab behavior, history, and crawlable URLs.

Common mistakes

  • confusing component imports with DI providers;
  • assuming every lazy chunk improves performance without measuring;
  • eagerly importing a feature into the shell;
  • using click handlers instead of links;
  • accessing browser globals at bootstrap and breaking SSR.

Mini lab

Task: Inspect src/main.ts, app.config.ts, and app.component.ts. Create a standalone, OnPush dashboard; add a semantic shell, RouterOutlet, lazy /dashboard, route title, and wildcard redirect.

Constraints: no AppModule, direct document access, or eager dashboard import. Navigation must work from the keyboard.

Expected result: /, /dashboard, and an unknown path resolve without console errors; the dashboard arrives in a lazy chunk.

Hint 1

Separate what the template compiler needs from what the root injector needs.

Hint 2

Import RouterOutlet, RouterLink, and RouterLinkActive where their template syntax is used; configure routes with provideRouter.

Solution direction — last resort

main.ts calls bootstrapApplication(AppComponent, appConfig). appConfig provides the router. The dashboard route dynamically imports its component.

Verification

Run npm run build and npm start. Remove RouterOutlet from the shell imports temporarily and explain 'router-outlet' is not a known element before restoring it. Add a skip link that moves focus to main.

Mastery evidence: explain compilation dependencies versus providers, demonstrate a lazy route, and diagnose the missing-import failure unaided.

Bloom challenge

  1. Understand: distinguish component imports from providers.
  2. Apply: add a lazy, titled route.
  3. Analyze: trace why a provided router does not compile router-outlet.
  4. Evaluate: defend eager versus lazy loading for a tiny dashboard.
  5. Create: sketch platform, environment, route, and element injector boundaries for the app.

Summary

Standalone composition localizes template dependencies; bootstrap providers configure runtime capabilities; routing adds URL, loading, and lifetime boundaries.

Spaced review

In Lesson 5, predict where a route provider lives relative to the root environment injector. In Lesson 11, reassess whether the lazy chunk improved a measured budget.

What comes next

Continue to templates and control flow, or skip after passing its diagnostic and verification challenge.