A Single Page Application built with Angular for a university Web Technologies course (L3). The app presents a fictional luxury fragrance brand called Maison Électra, featuring three perfumes: Storm Circuit, Volt Aria, and Onyx Noir.
This project was built as part of a practical assignment to learn Angular from the ground up. The technical reference used throughout the project is a Star Wars episode browser app provided by the course instructor, and all Angular patterns strictly follow what was taught in class.
The application allows users to:
- Browse a perfume catalogue
- View the details of each perfume
- Mark perfumes as favourites and view them on a dedicated page
- Read and navigate through user reviews for each perfume
- View the detail of a specific review
- Submit a new review via a reactive form with a live preview
- Delete or recommend/unrecommend a review
| Technology | Details |
|---|---|
| Framework | Angular (NgModule, non-standalone) |
| Language | TypeScript |
| Styles | SCSS |
| Forms | Template-driven (FormsModule) + Reactive (ReactiveFormsModule) |
| Routing | RouterModule with nested parameterised routes |
| Reactivity | RxJS Observable + async pipe |
| Font | Raleway (Google Fonts) |
- Components -
AppModule-declared,standalone: false - Routing -
RouterModule.forRoot,routerLink,routerLinkActive,ActivatedRoute,Router - Services -
@Injectable({ providedIn: 'root' }), injected viainject() - Models - TypeScript classes with typed constructors and auto-incremented
id - Input binding -
@Input()for parent-to-child communication - Directives -
[ngClass],@for,@if,@empty,routerLink - Custom directive -
MaDirective(attribute directive, highlights text on hover) - Custom pipe -
NoteEtoilesPipe(converts a numeric rating into a text label) - Native pipes -
titlecase,date,number,async - Template-driven form -
NgForm,ngModel, two-way binding - Reactive form -
FormGroup,FormControl,Validators, live preview viavalueChanges.pipe(map(...))and| async - Lifecycle hook -
OnInit/ngOnInit
src/app/
├── Models/ # TypeScript data models (ParfumModel, AvisModel)
├── services/ # Business logic & in-memory data (ParfumService, AvisService)
├── pipes/ # Custom pipe (NoteEtoilesPipe)
├── ma-directive.ts # Custom attribute directive
├── header/ # Global navigation bar
├── footer/ # Footer with copyright year
├── landing-page/ # Home page (/home)
├── parfum-list/ # Perfume catalogue (/catalogue)
├── parfum/ # Single perfume card (child of parfum-list & favoris)
├── parfum-detail/ # Perfume detail page (/catalogue/:parfumId)
├── favoris/ # Favourites page (/favoris)
├── avis-list/ # Reviews list for a perfume (/catalogue/:parfumId/avis)
├── avis/ # Single review card (child of avis-list)
├── avis-detail/ # Review detail page (/catalogue/:parfumId/avis/:avisId)
├── nouvel-avis/ # New review form (/catalogue/:parfumId/nouvel-avis)
└── page-not-found/ # 404 page (**)
| Path | Component |
|---|---|
/ |
Redirects to /home |
/home |
LandingPage |
/catalogue |
ParfumList |
/catalogue/:parfumId |
ParfumDetail |
/favoris |
Favoris |
/catalogue/:parfumId/avis |
AvisList |
/catalogue/:parfumId/avis/:avisId |
AvisDetail |
/catalogue/:parfumId/nouvel-avis |
NouvelAvis |
** |
PageNotFound |
Prerequisites: Node.js and Angular CLI installed.
# Clone the repository
git clone <repo-url>
# Install dependencies
npm install
# Start the development server
ng serveThen open your browser at http://localhost:4200.