Core Concepts
3 min readRapid overview
Angular Core Concepts
Focus on standalone components, strong typing, and predictable data flow. Build on TypeScript fundamentals before applying these patterns.
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.
Questions & Answers
Q: How does Zone.js work?
A: 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?
A: 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.