feat: add header component with navigation and cart functionality

This commit is contained in:
Jesus Navalon
2026-07-12 19:27:27 +02:00
parent 78c3684c18
commit 282bcc9d76
7 changed files with 119 additions and 1 deletions
+2
View File
@@ -1,3 +1,5 @@
<app-header></app-header>
<main class="app-content">
<router-outlet></router-outlet>
</main>
+2 -1
View File
@@ -1,9 +1,10 @@
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {Header} from './layout/header/header';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
imports: [RouterOutlet, Header],
templateUrl: './app.html',
styleUrl: './app.scss'
})
+2
View File
@@ -20,6 +20,8 @@ export class CartService {
const response = await firstValueFrom(
this.http.post<AddCartResponse>(`${API_BASE_URL}/cart`, request)
);
//Note: can return always 1 because server has not SameSite=None. This will be taken as a third cookie, and the browser can ignore it.
this.setCount(response.count);
return response.count;
}
+25
View File
@@ -0,0 +1,25 @@
<mat-toolbar color="primary" class="app-header">
<a routerLink="/" class="app-header__brand">
<mat-icon>smartphone</mat-icon>
<span>Mobile Store</span>
</a>
<nav class="app-header__breadcrumb" aria-label="breadcrumb">
<a routerLink="/">Home</a>
@if (isProductDetail()) {
<span> / Product Detail</span>
}
</nav>
<span class="app-header__spacer"></span>
<button
mat-icon-button
[matBadge]="cartCount()"
[matBadgeHidden]="cartCount() === 0"
matBadgeColor="accent"
aria-label="Cart"
>
<mat-icon>shopping_cart</mat-icon>
</button>
</mat-toolbar>
+32
View File
@@ -0,0 +1,32 @@
.app-header {
display: flex;
align-items: center;
gap: 1.5rem;
&__brand {
display: flex;
align-items: center;
gap: 0.5rem;
color: inherit;
text-decoration: none;
font-weight: 600;
white-space: nowrap;
}
&__breadcrumb {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.9rem;
opacity: 0.9;
a {
color: inherit;
text-decoration: underline;
}
}
&__spacer {
flex: 1 1 auto;
}
}
+23
View File
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Header } from './header';
describe('Header', () => {
let component: Header;
let fixture: ComponentFixture<Header>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Header]
})
.compileComponents();
fixture = TestBed.createComponent(Header);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+33
View File
@@ -0,0 +1,33 @@
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterLink } from '@angular/router';
import { filter, map } from 'rxjs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { CartService } from '../../core/services/cart.service';
// App Header
@Component({
selector: 'app-header',
imports: [RouterLink, MatToolbarModule, MatIconModule, MatBadgeModule, MatButtonModule],
templateUrl: './header.html',
styleUrl: './header.scss'
})
export class Header {
private readonly router = inject(Router);
private readonly cart = inject(CartService);
// Products count in the cart, for the badge in the header.
readonly cartCount = this.cart.count;
readonly isProductDetail = toSignal(
this.router.events.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
map((event) => event.urlAfterRedirects.startsWith('/product/'))
),
{ initialValue: this.router.url.startsWith('/product/') }
);
}