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
RouterOutleta valid template element.
Minimal example¶
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
importswith 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¶
- Understand: distinguish component imports from providers.
- Apply: add a lazy, titled route.
- Analyze: trace why a provided router does not compile
router-outlet. - Evaluate: defend eager versus lazy loading for a tiny dashboard.
- 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.