feat: add cart and product details features with tests
This commit is contained in:
parent
6219a98f9a
commit
99c2c09b8d
@ -1,7 +1,8 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient()]
|
||||
};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import {ProductListComponent} from './product-list/product-list.component';
|
||||
import { ProductDetailsComponent } from './product-details/product-details.component';
|
||||
import { CartComponent } from './cart/cart.component';
|
||||
import { ShippingComponent } from './shipping/shipping.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: ProductListComponent },
|
||||
{ path: 'products/:productId', component: ProductDetailsComponent },
|
||||
{ path: 'cart', component: CartComponent },
|
||||
{ path: 'shipping', component: ShippingComponent },
|
||||
];
|
||||
|
16
src/app/cart.service.spec.ts
Normal file
16
src/app/cart.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CartService } from './cart.service';
|
||||
|
||||
describe('CartService', () => {
|
||||
let service: CartService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(CartService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
30
src/app/cart.service.ts
Normal file
30
src/app/cart.service.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Product } from './products';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CartService {
|
||||
items: Product[] = [];
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
) { }
|
||||
|
||||
addToCart(product: Product) {
|
||||
this.items.push(product);
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
clearCart() {
|
||||
this.items = [];
|
||||
return this.items;
|
||||
}
|
||||
|
||||
getShippingPrices() {
|
||||
return this.http.get<{type: string, price: number}[]>('/assets/shiping.json');
|
||||
}
|
||||
}
|
0
src/app/cart/cart.component.css
Normal file
0
src/app/cart/cart.component.css
Normal file
12
src/app/cart/cart.component.html
Normal file
12
src/app/cart/cart.component.html
Normal file
@ -0,0 +1,12 @@
|
||||
<h3>Cart</h3>
|
||||
|
||||
<p>
|
||||
<a routerLink="/shipping">Shipping Prices</a>
|
||||
</p>
|
||||
|
||||
@for(item of items; track item.id){
|
||||
<div class="cart-item">
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.price | currency }}</span>
|
||||
</div>
|
||||
}
|
23
src/app/cart/cart.component.spec.ts
Normal file
23
src/app/cart/cart.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CartComponent } from './cart.component';
|
||||
|
||||
describe('CartComponent', () => {
|
||||
let component: CartComponent;
|
||||
let fixture: ComponentFixture<CartComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CartComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
21
src/app/cart/cart.component.ts
Normal file
21
src/app/cart/cart.component.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CartService } from '../cart.service';
|
||||
import { Product } from '../products';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cart',
|
||||
standalone: true,
|
||||
imports: [CurrencyPipe],
|
||||
templateUrl: './cart.component.html',
|
||||
styleUrl: './cart.component.css'
|
||||
})
|
||||
export class CartComponent {
|
||||
items: Product[] | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private cartService: CartService,
|
||||
) {
|
||||
this.items = this.cartService.getItems();
|
||||
}
|
||||
}
|
0
src/app/product-alerts/product-alerts.component.css
Normal file
0
src/app/product-alerts/product-alerts.component.css
Normal file
5
src/app/product-alerts/product-alerts.component.html
Normal file
5
src/app/product-alerts/product-alerts.component.html
Normal file
@ -0,0 +1,5 @@
|
||||
@if(product && product.price > 700){
|
||||
<p>
|
||||
<button (click)="notify.emit()" type="button">Notify Me</button>
|
||||
</p>
|
||||
}
|
23
src/app/product-alerts/product-alerts.component.spec.ts
Normal file
23
src/app/product-alerts/product-alerts.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProductAlertsComponent } from './product-alerts.component';
|
||||
|
||||
describe('ProductAlertsComponent', () => {
|
||||
let component: ProductAlertsComponent;
|
||||
let fixture: ComponentFixture<ProductAlertsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductAlertsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProductAlertsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
14
src/app/product-alerts/product-alerts.component.ts
Normal file
14
src/app/product-alerts/product-alerts.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Product } from '../products';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-alerts',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './product-alerts.component.html',
|
||||
styleUrl: './product-alerts.component.css'
|
||||
})
|
||||
export class ProductAlertsComponent {
|
||||
@Input() product: Product | undefined;
|
||||
@Output() notify = new EventEmitter();
|
||||
}
|
0
src/app/product-details/product-details.component.css
Normal file
0
src/app/product-details/product-details.component.css
Normal file
7
src/app/product-details/product-details.component.html
Normal file
7
src/app/product-details/product-details.component.html
Normal file
@ -0,0 +1,7 @@
|
||||
<h2>Product Details</h2>
|
||||
|
||||
@if(product){ <div *ngIf="product">
|
||||
<h3>{{ product.name }}</h3>
|
||||
<h4>{{ product.price | currency }}</h4>
|
||||
<p>{{ product.description }}</p> <button type="button" (click)="addToCart(product)">Buy</button>
|
||||
</div> }
|
23
src/app/product-details/product-details.component.spec.ts
Normal file
23
src/app/product-details/product-details.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProductDetailsComponent } from './product-details.component';
|
||||
|
||||
describe('ProductDetailsComponent', () => {
|
||||
let component: ProductDetailsComponent;
|
||||
let fixture: ComponentFixture<ProductDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductDetailsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProductDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
30
src/app/product-details/product-details.component.ts
Normal file
30
src/app/product-details/product-details.component.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Product, products } from '../products';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CurrencyPipe, NgIf } from '@angular/common';
|
||||
import { CartService } from '../cart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-details',
|
||||
standalone: true,
|
||||
imports: [CurrencyPipe, NgIf],
|
||||
templateUrl: './product-details.component.html',
|
||||
styleUrl: './product-details.component.css'
|
||||
})
|
||||
export class ProductDetailsComponent implements OnInit {
|
||||
product: Product | undefined;
|
||||
|
||||
constructor(private route: ActivatedRoute, private cartService: CartService) { }
|
||||
|
||||
addToCart(product: Product) {
|
||||
this.cartService.addToCart(product);
|
||||
window.alert('Your product has genn thrown into the cart!');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const routeParams = this.route.snapshot.paramMap;
|
||||
const productIdFromRoute = Number(routeParams.get('productId'));
|
||||
|
||||
this.product = products.find(product => product.id === productIdFromRoute);
|
||||
}
|
||||
}
|
@ -1 +1,21 @@
|
||||
<h2>Products</h2>
|
||||
|
||||
@for(product of products; track product.id){
|
||||
<div>
|
||||
<h3>
|
||||
<a [title]="product.name + ' details'" [routerLink]="['/products', product.id]">
|
||||
{{ product.name }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
@if(product.description){
|
||||
<p>Description: {{product.description}}</p>
|
||||
}
|
||||
<button type="button" (click)="share()">
|
||||
Share
|
||||
</button>
|
||||
|
||||
<app-product-alerts [product]="product" (notify)="onNotify()">
|
||||
</app-product-alerts>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { products } from '../products';
|
||||
import { ProductAlertsComponent } from '../product-alerts/product-alerts.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [ProductAlertsComponent, RouterModule],
|
||||
templateUrl: './product-list.component.html',
|
||||
styleUrl: './product-list.component.css'
|
||||
})
|
||||
@ -14,4 +16,8 @@ export class ProductListComponent {
|
||||
share() {
|
||||
window.alert('The product has been shared!');
|
||||
}
|
||||
|
||||
onNotify() {
|
||||
window.alert("You will be notified when the product goes on sale");
|
||||
}
|
||||
}
|
||||
|
0
src/app/shipping/shipping.component.css
Normal file
0
src/app/shipping/shipping.component.css
Normal file
8
src/app/shipping/shipping.component.html
Normal file
8
src/app/shipping/shipping.component.html
Normal file
@ -0,0 +1,8 @@
|
||||
<h3>Shipping Prices</h3>
|
||||
|
||||
@for(shipping of shippingCosts | async; track shipping.type) {
|
||||
<div class="shipping-item">
|
||||
<span>{{ shipping.type }}</span>
|
||||
<span>{{ shipping.price | currency }}</span>
|
||||
</div>
|
||||
}
|
23
src/app/shipping/shipping.component.spec.ts
Normal file
23
src/app/shipping/shipping.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ShippingComponent } from './shipping.component';
|
||||
|
||||
describe('ShippingComponent', () => {
|
||||
let component: ShippingComponent;
|
||||
let fixture: ComponentFixture<ShippingComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ShippingComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ShippingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
20
src/app/shipping/shipping.component.ts
Normal file
20
src/app/shipping/shipping.component.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CartService } from '../cart.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AsyncPipe, CurrencyPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shipping',
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, CurrencyPipe],
|
||||
templateUrl: './shipping.component.html',
|
||||
styleUrl: './shipping.component.css'
|
||||
})
|
||||
export class ShippingComponent {
|
||||
constructor(private cartService: CartService) { }
|
||||
shippingCosts!: Observable<{ type: string, price: number }[]>;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.shippingCosts = this.cartService.getShippingPrices();
|
||||
}
|
||||
}
|
@ -2,4 +2,6 @@
|
||||
<h1>My Store</h1>
|
||||
</a>
|
||||
|
||||
<a class="button fancy-button"><em class="material-icons">shopping_cart</em>Checkout</a>
|
||||
<a routerLink="/cart" class="button fancy-button">
|
||||
<em class="material-icons">shopping_cart</em>Checkout
|
||||
</a>
|
||||
|
Loading…
Reference in New Issue
Block a user