2026Angular 21TypeScriptRxJSReactive FormsAngular RouterSCSSBEMVitestESLintPrettier
What I Built
- Standalone Angular 21 components using signals-based reactivity and the inject() pattern — no NgModules, aligned with the current Angular recommended architecture
- Debounced search and multi-filter state (status, gender, page) encoded in URL query params via ActivatedRoute, making every filtered view bookmarkable and shareable
- Reactive HTTP pipeline built with queryParamMap → switchMap → HttpClient (withFetch()) → signals; catchError inside switchMap keeps the stream alive after upstream errors without resetting pagination
- Custom TitleStrategy updating document <title> on every route transition, registered as a single provider at app root
- CSS stagger entrance animation driven by a --card-index custom property set per card inside the @for loop — zero @angular/animations runtime dependency
- Accessible custom Select built as a ControlValueAccessor, integrating natively with Reactive Forms without wrapper hacks
- Full test suite with Vitest, Angular TestBed and HttpTestingController; HTTP interactions mocked at transport level
How It Works
- 1The home route fetches universe-wide stats (characters, episodes, locations) on init via a HomeService signal.
- 2The characters route is lazy-loaded; a FormControl drives the debounced search query appended to the API URL as a query param.
- 3Active filters and current page are also stored in query params — the URL is the single source of truth, and the browser Back button works for free.
- 4Each character card links to a detail page that resolves the character and its episode list in parallel via forkJoin.
- 5The FavoritesService persists selected IDs to localStorage; the custom Select ControlValueAccessor bridges the dropdown to a FormGroup.