diff --git a/src/app/app.component.css b/src/app/app.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/app.component.html b/src/app/app.component.html index dfa99d7..315760b 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,13 +1,3 @@ - +

Shopping Cart

- - - + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index a0b86fd..0000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [AppComponent], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have the 'angular-workshop' title`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('angular-workshop'); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-workshop'); - }); -}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 72d6e84..a53788e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,37 +1,12 @@ -import { Component, signal, ChangeDetectionStrategy, inject, WritableSignal } from '@angular/core'; -import { UserService } from './user.service'; -import { NgFor, NgIf } from '@angular/common'; - +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { ProductOverviewComponent } from "./product-overview/product-overview.component"; @Component({ selector: 'app-root', standalone: true, - imports: [NgFor, NgIf], + imports: [ProductOverviewComponent], templateUrl: './app.component.html', - styleUrl: './app.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { title = 'angular-workshop'; - - private userService = inject(UserService); - - selectedUserId = signal(null); - - users: WritableSignal = signal([]); - - todos: WritableSignal = signal([]); - - ngOnInit() { - this.userService.getUsers().subscribe(users => this.users.set(users)); - - if(this.selectedUserId() !== null) { - this.userService.getTodos(this.selectedUserId()!).subscribe(todos => this.todos.set(todos)); - } - } - - onUserSelect(event: Event) { - const userId = Number((event.target as HTMLSelectElement).value); - this.selectedUserId.set(userId); - this.userService.getTodos(userId).subscribe(todos => this.todos.set(todos)); - } } diff --git a/src/app/app.config.ts b/src/app/app.config.ts index ea48cd5..2e965d5 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -7,7 +7,6 @@ import { provideHttpClient, withFetch } from '@angular/common/http'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), provideHttpClient(withFetch()) ] diff --git a/src/app/product-overview/product-overview.component.html b/src/app/product-overview/product-overview.component.html new file mode 100644 index 0000000..44359ad --- /dev/null +++ b/src/app/product-overview/product-overview.component.html @@ -0,0 +1,6 @@ + + + +
+

Total: {{ total() | currency : 'EUR' : 'symbol' : '1.2-2' }}

+
diff --git a/src/app/product-overview/product-overview.component.ts b/src/app/product-overview/product-overview.component.ts new file mode 100644 index 0000000..945ee16 --- /dev/null +++ b/src/app/product-overview/product-overview.component.ts @@ -0,0 +1,28 @@ +import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core'; +import { ProductSelectionComponent, Product } from '../product-selection/product-selection.component'; +import { ProductQuantityComponent } from '../product-quantity/product-quantity.component'; +import { CurrencyPipe } from '@angular/common'; +@Component({ + selector: 'app-product-overview', + standalone: true, + imports: [ProductSelectionComponent, ProductQuantityComponent, CurrencyPipe], + templateUrl: './product-overview.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProductOverviewComponent { + selectedProduct = signal(null); + quantity = signal(1); + + total = computed(() => { + const product = this.selectedProduct(); + return product ? product.price * this.quantity() : 0; + }); + + onProductChange(product: Product) { + this.selectedProduct.set(product); + } + + onQuantityChange(quantity: number) { + this.quantity.set(quantity); + } +} diff --git a/src/app/product-quantity/product-quantity.component.html b/src/app/product-quantity/product-quantity.component.html new file mode 100644 index 0000000..aa78cfc --- /dev/null +++ b/src/app/product-quantity/product-quantity.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/product-quantity/product-quantity.component.ts b/src/app/product-quantity/product-quantity.component.ts new file mode 100644 index 0000000..4c3d07b --- /dev/null +++ b/src/app/product-quantity/product-quantity.component.ts @@ -0,0 +1,33 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { Product } from '../product-selection/product-selection.component'; +import { FormsModule } from '@angular/forms'; +import { ProductService } from '../services/product.service'; + +@Component({ + selector: 'app-product-quantity', + standalone: true, + imports: [FormsModule], + templateUrl: './product-quantity.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProductQuantityComponent { + @Input({ required: true }) set product(value: Product | null) { + this.productService.setProduct(value); + } + get product(): Product | null { + return this.productService.getProduct(); + } + + @Output() quantityChange = new EventEmitter(); + + constructor(private productService: ProductService) {} + + get quantity(): number { + return this.productService.getQuantity(); + } + + onQuantityChange(value: number) { + this.productService.setQuantity(value); + this.quantityChange.emit(value); + } +} diff --git a/src/app/product-selection/product-selection.component.html b/src/app/product-selection/product-selection.component.html new file mode 100644 index 0000000..46f83fd --- /dev/null +++ b/src/app/product-selection/product-selection.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/app/product-selection/product-selection.component.ts b/src/app/product-selection/product-selection.component.ts new file mode 100644 index 0000000..dde4d94 --- /dev/null +++ b/src/app/product-selection/product-selection.component.ts @@ -0,0 +1,58 @@ +import { NgFor } from '@angular/common'; +import { CurrencyPipe } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Component, ChangeDetectionStrategy, Output, EventEmitter, signal, inject, computed, OnInit } from '@angular/core'; +import { ProductService } from '../services/product.service'; + +export type Product = { + id: number; + title: string; + price: number; + description: string; + category: string; + image: string; + rating: { + rate: number; + count: number; + } +} + +@Component({ + selector: 'app-product-selection', + standalone: true, + imports: [NgFor, CurrencyPipe, FormsModule], + templateUrl: './product-selection.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class ProductSelectionComponent implements OnInit { + products = signal([]); + + selectedProductId = signal(null); + + selectedProduct = computed(() => { + const id = this.selectedProductId(); + return id ? this.products().find(p => p.id === id) || null : null; + }); + + quantity = signal(1); + + @Output() quantityChange = new EventEmitter(); + @Output() productChange = new EventEmitter(); + + private productService = inject(ProductService); + + ngOnInit() { + this.productService.getProducts().subscribe(products => this.products.set(products)); + } + + onProductChange(productId: number) { + this.selectedProductId.set(productId); + const product = this.selectedProduct(); + if (product) { + this.productChange.emit(product); + this.quantity.set(1); + this.quantityChange.emit(1); + } + } +} diff --git a/src/app/services/product.service.ts b/src/app/services/product.service.ts new file mode 100644 index 0000000..e4df12d --- /dev/null +++ b/src/app/services/product.service.ts @@ -0,0 +1,43 @@ +import { Injectable, signal, WritableSignal } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, shareReplay } from 'rxjs'; +import { Product } from '../product-selection/product-selection.component'; + +@Injectable({ + providedIn: 'root' +}) +export class ProductService { + private productSignal = signal(null); + private quantitySignal = signal(1); + + constructor(private http: HttpClient) {} + + // Local state management + setProduct(product: Product | null): void { + this.productSignal.set(product); + if (product) { + this.quantitySignal.set(1); + } + } + + getProduct(): Product | null { + return this.productSignal(); + } + + setQuantity(quantity: number): void { + this.quantitySignal.set(quantity); + } + + getQuantity(): number { + return this.quantitySignal(); + } + + // API calls + getProducts(): Observable { + return this.http.get('https://fakestoreapi.com/products'); + } + + getProductById(id: number): Observable { + return this.http.get(`https://fakestoreapi.com/products/${id}`); + } +} \ No newline at end of file diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts new file mode 100644 index 0000000..feab9d5 --- /dev/null +++ b/src/app/services/user.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + constructor(private http: HttpClient) {} +} \ No newline at end of file diff --git a/src/app/user.service.ts b/src/app/user.service.ts deleted file mode 100644 index 08b31a5..0000000 --- a/src/app/user.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, shareReplay } from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class UserService { - constructor(private http: HttpClient) {} - - getUsers(): Observable { - return this.http.get('https://jsonplaceholder.typicode.com/users') - } - - getUser(id: number): Observable { - return this.http.get(`https://jsonplaceholder.typicode.com/users/${id}`) - } - - getTodos(userId: number): Observable { - return this.http.get(`https://jsonplaceholder.typicode.com/todos?userId=${userId}`) - } - - getTodo(id: number): Observable { - return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`) - } -}