Core Concepts
3 min readRapid overview
Core Concepts
TL;DR
Focus on standalone components, strong typing, and predictable data flow. Build on TypeScript fundamentals before applying these patterns.
How it works
Learning Path (60-90 min)
| Step | Focus | Outcome |
|---|---|---|
| 1 | Components + templates | Establish standalone patterns, inputs/outputs, and template clarity. |
| 2 | Data flow + DI | Apply OnPush, service boundaries, and injection scopes. |
| 3 | Routing + forms | Implement lazy routes, guards/resolvers, and reactive forms. |
| 4 | HTTP + RxJS | Compose streams, handle errors, and cancel safely. |
| 5 | State + testing | Choose state patterns and validate behavior with tests. |
| 6 | Performance + architecture | Tune rendering and align tooling/architecture choices. |
Next up: Angular Fundamentals Exercises
Application structure
- Standalone components first: Prefer standalone components over NgModules for most features; use feature modules only when integration requires them.
- Feature slicing: Group by feature/domain (routes, components, services, models) to keep boundaries clear.
- Environment config: Keep runtime config separate from code; avoid leaking secrets.
Components and templates
- Inputs/outputs: Keep
@Input()props simple and typed; emit specific payloads via@Output()EventEmitters. - Change detection: Default
ChangeDetectionStrategy.OnPushfor predictable rendering; push new references instead of mutating objects/arrays. - Pipes and directives: Use pure pipes for presentation transformations; write structural directives sparingly for control flow or access control.
Dependency injection
- Providers: Prefer
providedIn: 'root'for shared services; scope to components for per-instance state. Use injection tokens for primitives/config. - Hierarchies: Understand how child injectors override parent providers; leverage this for feature-specific behavior.
Routing
- Lazy loading: Use
loadChildrenwith standalone route definitions to keep bundles small. - Guards/resolvers: Authenticate/authorize in guards; fetch required data in resolvers and surface via
ActivatedRoute. - Preloading: Apply selective preloading for high-traffic routes; avoid over-preloading on low-bandwidth environments.
Forms
- Reactive forms: Prefer
FormControl,FormGroup, and validators for testable, composable forms. - Validation: Combine built-in and custom validators; provide user-friendly error messages and accessibility cues.
- State sync: Keep form state as source of truth; derive UI state (dirty/touched/errors) from controls.
HttpClient and data flow
- Observables: Keep HTTP calls typed; use
shareReplay(1)for cacheable streams. - Error handling: Centralize interceptors for auth/refresh/logging; return typed error objects.
- Cancellation: Use
takeUntil(destroy$)in components to dispose subscriptions.
RxJS patterns
- Data streams: Compose with
switchMapfor request switching,mergeMapfor fan-out,concatMapfor ordered processing. - State: Use BehaviorSubjects or state services for shared state; expose readonly Observables to consumers.
- Backoff: Implement retry with backoff for transient failures; avoid infinite retries.
State management
- NgRx Store: Centralized state with reducers, actions, and effects for large apps.
- Component Store: Localized state scoped to a feature or component tree.
- Signals vs Observables: Prefer signals for synchronous UI state, observables for async streams.
Testing
- TestBed: Configure minimal TestBed per spec; mock providers and HttpClient.
- Harnesses: Use Angular component harnesses to interact with DOM in tests.
- Async testing: Leverage
fakeAsync/tickorwaitForAsyncdepending on the scenario; prefer deterministic, fast tests. - Jasmine/Karma: Default unit testing stack for many Angular apps.
- Playwright: End-to-end testing for critical user flows.
Accessibility and styling
- Semantic HTML: Use proper elements for structure and screen readers.
- Keyboard support: Ensure tab order and focus states are visible.
- ARIA sparingly: Add roles/labels only when semantics are missing.
- SCSS structure: Keep
global.scssfor shared styles and component SCSS for local scope.
Tooling and architecture
- NX and monorepos: Use affected builds, clear project boundaries, and shared libraries.
- Micro frontends: Align route shells and shared UI packages with ownership boundaries.
Offline and storage
- Service workers: Cache assets and API responses for offline-friendly UX.
- IndexedDB: Store structured data for offline queues or large datasets.
Performance
- Rendering: Combine
OnPushwithtrackByin*ngForto reduce churn. - Change detection triggers: Avoid heavy synchronous work in lifecycle hooks; push work to RxJS pipelines.
- Bundling: Use budgets, lazy routes, and component-level code splitting where applicable.
Quick recall Q&A
Q: How does Zone.js work?
Zone.js patches async APIs (Promises, timers, DOM events, XHR) to track pending microtasks and macrotasks. Angular's NgZone listens for the task queue to stabilize and then runs change detection; run heavy work outside the zone and re-enter for UI updates.
Q: What is the difference between signals and observables?
Signals are synchronous, dependency-tracked values for local UI state. Observables are asynchronous, push-based streams that require subscriptions (or async pipe) and are better for events, HTTP, and multi-value flows.