Music catalog platform for browsing albums, artists, and live concerts. Built with Angular 22 and Angular Material.
- Tech Stack
- Getting Started
- Project Structure
- Architecture Overview
- Available Scripts
- Configuration
- Handoff
| Layer | Technology |
|---|---|
| Framework | Angular 22.x (@angular/core) |
| UI Library | Angular Material 22.x (@angular/material, @angular/cdk) |
| Language | TypeScript ~6.0 |
| RxJS | ~7.8 |
| Forms | Angular Reactive Forms |
| Routing | Angular Router (lazy loading via loadComponent) |
| Styling | SCSS + Angular Material Design 3 theming |
| Testing | Vitest via @angular/build:unit-test |
| Package Manager | npm 11.x |
| Linting / Formatting | EditorConfig + Prettier (w/ Angular parser for HTML) |
- Node.js >= 20.x
- npm >= 11.x
npm install
ng serve # http://localhost:4200ng build # production build → dist/
ng build --configuration development # dev build with source mapsng test # Vitest runnerpublic/assets/
├── brand/ # Favicon, OG image, Apple touch icon
└── data/ # Static JSON data sources
├── albums.json # Artists, tracks, releases
└── concerts.json # Live concerts with dates and venues
src/
├── index.html # Entry HTML (SEO meta, Open Graph, Twitter Card)
├── main.ts # App bootstrap (locale: it-IT)
├── styles.scss # Angular Material theme (cyan primary, orange tertiary)
└── app/
├── app.ts # Root component
├── app.html # Root template (header + router-outlet + footer)
├── app.scss # Global layout (max-width 1400px container)
├── app.config.ts # Providers: router, HttpClient, locale it-IT
├── app.routes.ts # Route definitions (lazy loaded)
├── components/
│ ├── album-card/ # Reusable album card component
│ ├── album-detail/ # Album detail page (/music/:id)
│ ├── artist-detail/ # Artist detail page (/artist/:id)
│ ├── booking-page/ # Booking history page (/bookings)
│ ├── catalog-page/ # Catalog with filters (/catalog)
│ ├── concert-page/ # All concerts listing (/concerts)
│ ├── empty-state/ # Reusable empty state component
│ ├── favorites-page/ # Favorites page (/favorites)
│ ├── filter-bar/ # Genre/type/year filter bar
│ ├── footer/ # App footer
│ ├── header/ # Navbar (desktop + mobile sidenav)
│ ├── home-page/ # Home page (/)
│ ├── not-found/ # 404 page (/not-found)
│ └── tickets/ # Ticket booking form (/tickets/:concertId)
├── enums/
│ ├── content-type.ts # Single, EP, Album
│ └── genre.ts # Rock, Pop, Electronic, Jazz
├── models/
│ ├── album.ts # Album (extends WithId)
│ ├── artist.ts # Artist (extends WithId)
│ ├── booking.ts # Booking (extends WithId)
│ ├── concert.ts # Concert (extends WithId)
│ ├── playlist.ts # Playlist (extends WithId)
│ ├── ticket.ts # Ticket (extends WithId)
│ ├── track.ts # Track (extends WithId)
│ └── with-id.ts # Base interface with `id: string`
├── pipes/
│ └── duration.ts # Duration formatting pipe
├── services/
│ ├── booking.service.ts # Booking CRUD + localStorage persistence
│ ├── catalog.service.ts # Albums/artists/tracks loader
│ ├── concert.service.ts # Concerts loader
│ ├── favorites.service.ts # Favorites toggle + localStorage persistence
│ ├── playlist.service.ts # Playlists CRUD + localStorage persistence
│ └── theme.service.ts # Theme toggle (light/dark) + localStorage persistence
└── validators/
└── ticket.validator.ts # Ticket seats validator (min 1, max 6, availability)
| Path | Component | Lazy |
|---|---|---|
/ |
HomePageComponent |
✓ |
/catalog |
CatalogPageComponent |
✓ |
/music/:id |
AlbumDetailComponent |
✓ |
/artist/:id |
ArtistDetailComponent |
✓ |
/tickets/:concertId |
TicketsComponent |
✓ |
/favorites |
FavoritesPageComponent |
✓ |
/playlists |
PlaylistsPageComponent |
✓ |
/concerts |
ConcertPageComponent |
✓ |
/bookings |
BookingComponent |
✓ |
/not-found |
NotFoundComponent |
✓ |
** |
redirect → /not-found |
— |
All routes except the home page (/) use loadComponent to defer loading of each page's JavaScript until the user navigates to it. This reduces the initial bundle from ~770 kB to ~580 kB, keeping it under the 600 kB warning threshold.
Each lazy route produces a separate chunk file loaded on demand:
Initial: main.js (572 kB) ← HomePageComponent only
Lazy: catalog-page.js ← loaded on /catalog
Lazy: artist-detail.js ← loaded on /artist/:id
Lazy: concert-page.js ← loaded on /concerts
Lazy: tickets.js ← loaded on /tickets/:concertId
...etc
Route definition example:
{
path: 'concerts',
loadComponent: () =>
import('./components/concert-page/concert-page').then((m) => m.ConcertPageComponent),
}Once loaded, chunks are cached by the browser and reused on subsequent visits.
All state is managed through Angular Signals (WritableSignal, computed, effect) — no NgRx or external state library. Services fetch data via HttpClient at construction time and expose signals.
- Static JSON files in
public/assets/data/are fetched by services on app load - Services parse raw data into typed models and expose signals
- Components consume signals via
computed()and render with Angular@if/@forcontrol flow - Booking and Favorites persist to
localStorage
- Standalone components — no NgModules
- Signals — synchronous reactive state with
computedandeffect - Lazy loading —
loadComponentfor all non-initial routes - Reactive Forms — with custom validators (
ticketValidator) - Angular Material 3 — theming via
mat.theme()with cyan primary, orange tertiary
Observables are used only where Angular requires them and are immediately converted to Signals or properly unsubscribed:
| Usage | File | Pattern |
|---|---|---|
| HTTP data fetch | services/catalog.service.ts:74 |
http.get().subscribe() → writes to WritableSignal |
| HTTP data fetch | services/concert.service.ts:35 |
http.get().subscribe() → writes to WritableSignal |
| Route param to signal | components/album-detail/album-detail.ts:43 |
toSignal(route.paramMap.pipe(map(...))) |
| Route param to signal | components/artist-detail/artist-detail.ts:47 |
toSignal(route.paramMap.pipe(map(...))) |
| Form value changes | components/tickets/tickets.ts:101-103 |
.pipe(takeUntilDestroyed()).subscribe() for auto-cleanup |
No Subject, BehaviorSubject, or manual Observable types are used anywhere.
| Setting | Value |
|---|---|
| Default config | production |
| Budget (initial) | Warning at 600 kB, Error at 1 MB |
| Budget (component style) | Warning at 4 kB, Error at 8 kB |
| Output hashing | Enabled in production |
| Style | SCSS (inline) |
- Target: ES2022
- Module:
preserve(esbuild bundling) - Strict: enabled via
strictInjectionParameters,strictInputAccessModifiers - Decorators:
experimentalDecorators: true
Italian locale (it-IT) is registered at bootstrap for date, currency, and number formatting.
Angular Material 3 theme:
- Primary palette:
$cyan-palette - Tertiary palette:
$orange-palette - Typography:
Roboto - Color scheme:
light
- Indentation: 2 spaces
- Quotes: single for TypeScript
- Print width: 100
- HTML parser:
angular
| Item | Value |
|---|---|
| Repository | git@github.com:Rhaastrake/SoundWave-Music_Team-Apulia.env.git |
| Branches | main (stable), dev (integration), feature branches from dev |
| Remote origin | https://github.com/Rhaastrake/SoundWave-Music_Team-Apulia.env.git |
ng build # outputs to dist/
# Serve dist/ with any static file serverNo SSR, no server-side dependencies. The app is a fully static SPA.
- All data is loaded from static JSON files (
public/assets/data/). To add or modify content, edit those files. - No backend API — the app is entirely client-side. If a backend is added later, replace
HttpClientcalls to JSON files with API endpoints. - localStorage is used for booking and favorites. Clearing browser storage will reset these.
- Create a component in
src/app/components/<name>/ - Add a route in
app.routes.tsusingloadComponentfor lazy loading - Add a link in
header.html(desktop nav + mobile sidenav)
- Follow the existing patterns: standalone components, signals,
computed,effect - Use Angular Material components (
mat-card,mat-button,mat-icon, etc.) - Keep page-level SCSS in the component's own
.scssfile - Reuse
.event-cardand.event-metastyles fromartist-detail.scssfor concert-like cards
The production build currently exceeds the 600 kB initial bundle warning threshold by ~170 kB (pre-existing, introduced by Angular Material itself). The error threshold is 1 MB, so the build still succeeds. Lazy loading has been applied to all non-initial routes to mitigate this.
git checkout dev
git pull origin dev
git checkout -b feature/<name>
# ... work ...
git push origin feature/<name>
# Create PR → merge into dev → eventually into main