ez claps 01-04

This commit is contained in:
constantin 2024-11-06 14:49:13 +01:00
parent 6219a98f9a
commit 0641a268b8
34 changed files with 450 additions and 295 deletions

View File

@ -1,4 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
.vscode/launch.json vendored
View File

@ -1,20 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored
View File

@ -1,42 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

View File

@ -24,7 +24,8 @@
{ {
"glob": "**/*", "glob": "**/*",
"input": "public" "input": "public"
} },
"src/assets"
], ],
"styles": [ "styles": [
"src/styles.css" "src/styles.css"

276
package-lock.json generated
View File

@ -450,6 +450,34 @@
"typescript": ">=5.4 <5.6" "typescript": ">=5.4 <5.6"
} }
}, },
"node_modules/@angular/compiler-cli/node_modules/chokidar": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
"dev": true,
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/compiler-cli/node_modules/readdirp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
"dev": true,
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/core": { "node_modules/@angular/core": {
"version": "18.2.8", "version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.8.tgz", "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.8.tgz",
@ -5531,19 +5559,39 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "4.0.1", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^4.0.1" "anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}, },
"engines": { "engines": {
"node": ">= 14.16.0" "node": ">= 8.10.0"
}, },
"funding": { "funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chokidar/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
} }
}, },
"node_modules/chownr": { "node_modules/chownr": {
@ -8773,31 +8821,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/karma/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/karma/node_modules/cliui": { "node_modules/karma/node_modules/cliui": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -8837,19 +8860,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/karma/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/karma/node_modules/is-fullwidth-code-point": { "node_modules/karma/node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -8860,32 +8870,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/karma/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/karma/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/karma/node_modules/source-map": { "node_modules/karma/node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -11410,17 +11394,27 @@
} }
}, },
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "4.0.2", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true, "dev": true,
"license": "MIT", "dependencies": {
"picomatch": "^2.2.1"
},
"engines": { "engines": {
"node": ">= 14.16.0" "node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
}, },
"funding": { "funding": {
"type": "individual", "url": "https://github.com/sponsors/jonschlinkert"
"url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/reflect-metadata": { "node_modules/reflect-metadata": {
@ -11853,70 +11847,6 @@
} }
} }
}, },
"node_modules/sass/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/sass/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/sass/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/sass/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/sax": { "node_modules/sax": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@ -14102,31 +14032,6 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"node_modules/webpack-dev-server/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/webpack-dev-server/node_modules/glob": { "node_modules/webpack-dev-server/node_modules/glob": {
"version": "10.4.5", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@ -14148,19 +14053,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/webpack-dev-server/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz",
@ -14202,32 +14094,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/webpack-dev-server/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/webpack-dev-server/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/webpack-dev-server/node_modules/rimraf": { "node_modules/webpack-dev-server/node_modules/rimraf": {
"version": "5.0.10", "version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",

View File

@ -1,7 +1,12 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import {provideHttpClient} from '@angular/common/http';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
]
}; };

View File

@ -1,6 +1,12 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import {ProductListComponent} from './product-list/product-list.component'; import {ProductListComponent} from './product/product-list/product-list.component';
import {ProductDetailComponent} from './product/product-detail/product-detail.component';
import {CartComponent} from './product/cart/cart.component';
import {ShippingComponent} from './product/shipping/shipping.component';
export const routes: Routes = [ export const routes: Routes = [
{ path: '', component: ProductListComponent }, { path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailComponent },
{ path: 'cart', component: CartComponent },
{ path: 'shipping', component: ShippingComponent },
]; ];

View File

@ -1 +0,0 @@
<h2>Products</h2>

View File

@ -1,17 +0,0 @@
import { Component } from '@angular/core';
import { products } from '../products';
@Component({
selector: 'app-product-list',
standalone: true,
imports: [],
templateUrl: './product-list.component.html',
styleUrl: './product-list.component.css'
})
export class ProductListComponent {
products = [...products];
share() {
window.alert('The product has been shared!');
}
}

View File

@ -0,0 +1,25 @@
<a [routerLink]="['/shipping']">Shipping Prices</a>
<ng-container *ngFor="let product of _items">
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency }}</p>
<hr>
</ng-container>
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit()">
<div>
<label for="name">
Name
</label>
<input id="name" type="text" formControlName="name">
</div>
<div>
<label for="address">
Address
</label>
<input id="address" type="text" formControlName="address">
</div>
<button class="button" type="submit">Purchase</button>
</form>

View 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();
});
});

View File

@ -0,0 +1,44 @@
import {Component, OnInit} from '@angular/core';
import {CartService} from '../service/cart.service';
import {CurrencyPipe, NgForOf} from '@angular/common';
import {Product} from '../products';
import {RouterLink} from '@angular/router';
import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
@Component({
selector: 'app-cart',
standalone: true,
imports: [
NgForOf,
CurrencyPipe,
RouterLink,
ReactiveFormsModule
],
templateUrl: './cart.component.html',
styleUrl: './cart.component.css'
})
export class CartComponent implements OnInit {
protected _items: Product[] = [];
protected checkoutForm!: FormGroup;
constructor(
private _cartService: CartService,
private _formBuilder: FormBuilder
) {
}
ngOnInit(): void {
this._items = this._cartService.items;
this.checkoutForm = this._formBuilder.group({
name: '',
address: ''
});
}
onSubmit(): void {
this._items = this._cartService.clearCart();
console.warn('Your order has been submitted', this.checkoutForm.value);
this.checkoutForm.reset();
}
}

View File

@ -0,0 +1,3 @@
<p *ngIf="product && product.price > 700">
<button type="button" (click)="notify.emit()">Notify Me</button>
</p>

View 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();
});
});

View File

@ -0,0 +1,18 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {Product} from '../products';
import {NgIf} from '@angular/common';
@Component({
selector: 'app-product-alerts',
standalone: true,
imports: [
NgIf
],
templateUrl: './product-alerts.component.html',
styleUrl: './product-alerts.component.css'
})
export class ProductAlertsComponent {
@Input() product: Product | undefined;
@Output() notify: EventEmitter<null> = new EventEmitter();
}

View File

@ -0,0 +1,6 @@
<ng-container *ngIf="_product">
<p>{{_product.name}}</p>
<p>{{_product.description}}</p>
<p>Price: {{_product.price | currency }}</p>
<button (click)="addToCart(_product)">Add to Cart</button>
</ng-container>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductDetailComponent } from './product-detail.component';
describe('ProductDetailComponent', () => {
let component: ProductDetailComponent;
let fixture: ComponentFixture<ProductDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProductDetailComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ProductDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,39 @@
import {Component, OnInit} from '@angular/core';
import {Product, products} from '../products';
import {CurrencyPipe, NgIf} from '@angular/common';
import {ActivatedRoute} from '@angular/router';
import {CartService} from '../service/cart.service';
@Component({
selector: 'app-product-detail',
standalone: true,
imports: [
NgIf,
CurrencyPipe
],
templateUrl: './product-detail.component.html',
styleUrl: './product-detail.component.css'
})
export class ProductDetailComponent implements OnInit{
_product: Product | undefined;
constructor(
private route: ActivatedRoute,
private cartService: CartService
) {
}
ngOnInit(): void {
const routeParams = this.route.snapshot.paramMap;
const productIdFromRoute = Number(routeParams.get('productId'));
this._product = products.find(product => product.id === productIdFromRoute);
}
addToCart(product: Product) {
console.log(product)
this.cartService.addToCart(product);
window.alert('Your product has been added to the cart!');
}
}

View File

@ -0,0 +1,17 @@
<h2>Products</h2>
<ng-container *ngFor="let product of products">
<h3>
<a
[title]="product.name + 'details'"
[routerLink]="['/products', product.id]"
>
{{ product.name }}
</a>
</h3>
<p *ngIf="product.description">
{{ product.description }}
</p>
<button (click)="share()">Share</button>
<app-product-alerts [product]="product" (notify)="onNotify()" />
<hr>
</ng-container>

View File

@ -0,0 +1,32 @@
import { Component } from '@angular/core';
import { products } from '../products';
import {ProductDetailComponent} from '../product-detail/product-detail.component';
import {NgForOf, NgIf} from '@angular/common';
import {ProductAlertsComponent} from '../product-alerts/product-alerts.component';
import {RouterLink} from '@angular/router';
@Component({
selector: 'app-product-list',
standalone: true,
imports: [
ProductDetailComponent,
NgForOf,
NgIf,
ProductAlertsComponent,
RouterLink
],
templateUrl: './product-list.component.html',
styleUrl: './product-list.component.css'
})
export class ProductListComponent {
products = [...products];
share() {
window.alert('The product has been shared!');
}
onNotify() {
window.alert('You will be notified when the product goes on sale');
}
}

View File

@ -5,7 +5,7 @@ export interface Product {
description: string; description: string;
} }
export const products = [ export const products: Product[] = [
{ {
id: 1, id: 1,
name: 'Phone XL', name: 'Phone XL',

View 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();
});
});

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import {Product} from '../products';
import {HttpClient} from '@angular/common/http';
import {Shipping} from '../shipping';
import {Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CartService {
private _items: Product[] = [];
constructor(private http: HttpClient) {
}
public addToCart(product: Product): void {
this._items.push(product);
}
public get items() {
return this._items;
}
public clearCart(): Product[] {
this._items = [];
return this._items;
}
public getShippingPrices(): Observable<Shipping[]> {
return this.http.get<Shipping[]>('/assets/shipping.json');
}
}

View File

@ -0,0 +1,4 @@
export interface Shipping {
type: string;
price: number;
}

View File

@ -0,0 +1,5 @@
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>

View 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();
});
});

View File

@ -0,0 +1,28 @@
import {Component, OnInit} from '@angular/core';
import {CartService} from '../service/cart.service';
import {Observable} from 'rxjs';
import {Shipping} from '../shipping';
import {AsyncPipe, CurrencyPipe, NgForOf} from '@angular/common';
@Component({
selector: 'app-shipping',
standalone: true,
imports: [
AsyncPipe,
CurrencyPipe,
NgForOf
],
templateUrl: './shipping.component.html',
styleUrl: './shipping.component.css'
})
export class ShippingComponent implements OnInit {
protected shippingCosts!: Observable<Shipping[]>;
constructor(private cartService: CartService) {
}
ngOnInit(): void {
this.shippingCosts = this.cartService.getShippingPrices();
}
}

View File

@ -1,5 +1,4 @@
<a [routerLink]="['/']"> <a [routerLink]="['/']">
<h1>My Store</h1> <h1>My Store</h1>
</a> </a>
<a class="button fancy-button" [routerLink]="['/cart']"><em class="material-icons">shopping_cart</em>Checkout</a>
<a class="button fancy-button"><em class="material-icons">shopping_cart</em>Checkout</a>