├── APM-Final ├── src │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── app │ │ ├── products │ │ │ ├── product-list.component.css │ │ │ ├── product-list-alt │ │ │ │ ├── product-shell.component.ts │ │ │ │ ├── product-shell.component.html │ │ │ │ ├── product-list-alt.component.html │ │ │ │ ├── product-list-alt.component.ts │ │ │ │ ├── product-detail.component.ts │ │ │ │ └── product-detail.component.html │ │ │ ├── product.ts │ │ │ ├── product.module.ts │ │ │ ├── product-data.ts │ │ │ ├── product-list.component.html │ │ │ ├── product-list.component.ts │ │ │ └── product.service.ts │ │ ├── product-categories │ │ │ ├── product-category.ts │ │ │ ├── product-category-data.ts │ │ │ └── product-category.service.ts │ │ ├── app.component.css │ │ ├── suppliers │ │ │ ├── supplier.ts │ │ │ ├── supplier-data.ts │ │ │ └── supplier.service.ts │ │ ├── home │ │ │ ├── welcome.component.ts │ │ │ └── welcome.component.html │ │ ├── page-not-found.component.ts │ │ ├── shared │ │ │ └── shared.module.ts │ │ ├── app.component.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app-data.ts │ │ └── app.module.ts │ ├── favicon.ico │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── styles.css │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── .vscode │ ├── extensions.json │ ├── settings.json │ ├── launch.json │ └── tasks.json ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .browserslistrc ├── .gitignore ├── tsconfig.json ├── readme.md ├── .eslintrc.json ├── karma.conf.js ├── package.json └── angular.json ├── APM-Start ├── src │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── app │ │ ├── products │ │ │ ├── product-list.component.css │ │ │ ├── product-list-alt │ │ │ │ ├── product-shell.component.ts │ │ │ │ ├── product-shell.component.html │ │ │ │ ├── product-detail.component.ts │ │ │ │ ├── product-list-alt.component.html │ │ │ │ ├── product-list-alt.component.ts │ │ │ │ └── product-detail.component.html │ │ │ ├── product.ts │ │ │ ├── product.module.ts │ │ │ ├── product-list.component.ts │ │ │ ├── product-list.component.html │ │ │ ├── product-data.ts │ │ │ └── product.service.ts │ │ ├── product-categories │ │ │ ├── product-category.ts │ │ │ ├── product-category-data.ts │ │ │ └── product-category.service.ts │ │ ├── app.component.css │ │ ├── suppliers │ │ │ ├── supplier.ts │ │ │ ├── supplier.service.ts │ │ │ └── supplier-data.ts │ │ ├── home │ │ │ ├── welcome.component.ts │ │ │ └── welcome.component.html │ │ ├── page-not-found.component.ts │ │ ├── shared │ │ │ └── shared.module.ts │ │ ├── app.component.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app-data.ts │ │ └── app.module.ts │ ├── favicon.ico │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── styles.css │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── .vscode │ ├── extensions.json │ ├── settings.json │ ├── launch.json │ └── tasks.json ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .browserslistrc ├── .gitignore ├── tsconfig.json ├── readme.md ├── .eslintrc.json ├── karma.conf.js ├── package.json └── angular.json ├── APM-WithExtras ├── src │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── app │ │ ├── products │ │ │ ├── product-list-reget │ │ │ │ ├── product-list-reget.component.css │ │ │ │ ├── product-list-reget.component.spec.ts │ │ │ │ ├── product-list-reget.component.html │ │ │ │ ├── product-list-reget.component.ts │ │ │ │ └── product-reget.service.ts │ │ │ ├── product-list.component.css │ │ │ ├── product-list-edit │ │ │ │ ├── product-list-edit.component.css │ │ │ │ ├── product-list-edit.component.html │ │ │ │ ├── product-list-edit.component.ts │ │ │ │ └── product-edit.service.ts │ │ │ ├── product-list-extras │ │ │ │ ├── product-list-extras.component.css │ │ │ │ ├── product-list-extras.component.ts │ │ │ │ └── product-list-extras.component.html │ │ │ ├── product-list-refresh │ │ │ │ ├── product-list-refresh.component.css │ │ │ │ ├── product-list-refresh.component.html │ │ │ │ └── product-list-refresh.component.ts │ │ │ ├── product-list-alt │ │ │ │ ├── product-shell.component.ts │ │ │ │ ├── product-shell.component.html │ │ │ │ ├── product-list-alt.component.html │ │ │ │ ├── product-list-alt.component.ts │ │ │ │ ├── product-detail.component.ts │ │ │ │ └── product-detail.component.html │ │ │ ├── product.ts │ │ │ ├── product-data-fromAPI.ts │ │ │ ├── product-data.ts │ │ │ ├── product-list.component.html │ │ │ ├── product.module.ts │ │ │ ├── product-list.component.ts │ │ │ └── product.service.ts │ │ ├── product-categories │ │ │ ├── product-category.ts │ │ │ ├── product-category-data.ts │ │ │ └── product-category.service.ts │ │ ├── shared │ │ │ ├── edit-action.ts │ │ │ └── shared.module.ts │ │ ├── home │ │ │ ├── welcome.component.ts │ │ │ └── welcome.component.html │ │ ├── app.component.css │ │ ├── page-not-found.component.ts │ │ ├── cart │ │ │ ├── cart.ts │ │ │ ├── cart-shell │ │ │ │ └── cart-shell.component.ts │ │ │ ├── cart-list │ │ │ │ └── cart-list.component.ts │ │ │ ├── cart-total │ │ │ │ ├── cart-total.component.ts │ │ │ │ └── cart-total.component.html │ │ │ ├── cart-item │ │ │ │ ├── cart-item.component.ts │ │ │ │ └── cart-item.component.html │ │ │ └── cart.service.ts │ │ ├── app.component.ts │ │ ├── suppliers │ │ │ ├── supplier.ts │ │ │ ├── supplier-data.ts │ │ │ └── supplier.service.ts │ │ ├── app-routing.module.ts │ │ ├── app-data.ts │ │ ├── app.module.ts │ │ └── app.component.html │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── styles.css │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── .vscode │ ├── extensions.json │ ├── settings.json │ ├── launch.json │ └── tasks.json ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .browserslistrc ├── .gitignore ├── tsconfig.json ├── readme.md ├── .eslintrc.json ├── karma.conf.js ├── package.json └── angular.json ├── .gitignore ├── README.md ├── CHANGELOG.md ├── LICENSE └── links.md /APM-Final/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Start/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-WithExtras/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-reget/product-list-reget.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Final/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-Final/src/favicon.ico -------------------------------------------------------------------------------- /APM-Start/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-Start/src/favicon.ico -------------------------------------------------------------------------------- /APM-Final/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Start/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-edit/product-list-edit.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-WithExtras/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-WithExtras/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-WithExtras/src/favicon.ico -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-extras/product-list-extras.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-refresh/product-list-refresh.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Final/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-Final/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Start/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-Start/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-WithExtras/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-RxJS/HEAD/APM-WithExtras/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Final/src/app/product-categories/product-category.ts: -------------------------------------------------------------------------------- 1 | export interface ProductCategory { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | } 6 | -------------------------------------------------------------------------------- /APM-Start/src/app/product-categories/product-category.ts: -------------------------------------------------------------------------------- 1 | export interface ProductCategory { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | } 6 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/product-categories/product-category.ts: -------------------------------------------------------------------------------- 1 | export interface ProductCategory { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | } 6 | -------------------------------------------------------------------------------- /APM-Final/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /APM-Start/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | -------------------------------------------------------------------------------- /APM-WithExtras/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /APM-Final/src/app/suppliers/supplier.ts: -------------------------------------------------------------------------------- 1 | /* Defines the supplier entity */ 2 | export interface Supplier { 3 | id: number; 4 | name: string; 5 | cost: number; 6 | minQuantity: number; 7 | } 8 | -------------------------------------------------------------------------------- /APM-Start/src/app/suppliers/supplier.ts: -------------------------------------------------------------------------------- 1 | /* Defines the supplier entity */ 2 | export interface Supplier { 3 | id: number; 4 | name: string; 5 | cost: number; 6 | minQuantity: number; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | typings 2 | **/app/**/*.js 3 | **/app/**/*.map 4 | node_modules 5 | jspm_packages 6 | bower_components 7 | 8 | **/*.log/*.* 9 | **/*.log 10 | 11 | .vs 12 | **/*.sou 13 | **/*.user 14 | bin 15 | obj 16 | packages 17 | -------------------------------------------------------------------------------- /APM-Final/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Start/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/shared/edit-action.ts: -------------------------------------------------------------------------------- 1 | // Edit actions for any entity that supports edit 2 | type ActionType = 'add' | 'update' | 'delete' | 'none'; 3 | 4 | export interface Action { 5 | item: T; 6 | action: ActionType; 7 | } 8 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Final/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /APM-Start/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | 10 | .navbar-brand { 11 | margin-left: 10px; 12 | } -------------------------------------------------------------------------------- /APM-WithExtras/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart.ts: -------------------------------------------------------------------------------- 1 | import { Observable, of } from "rxjs"; 2 | import { Product } from "../products/product"; 3 | 4 | export interface Cart { 5 | cartItems: CartItem[] 6 | } 7 | 8 | export interface CartItem { 9 | product: Product; 10 | quantity: number; 11 | } 12 | -------------------------------------------------------------------------------- /APM-Final/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | CommonModule 7 | ], 8 | exports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class SharedModule { } 13 | -------------------------------------------------------------------------------- /APM-Start/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | CommonModule 7 | ], 8 | exports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class SharedModule { } 13 | -------------------------------------------------------------------------------- /APM-Final/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned", 4 | "html.hover.references": false, 5 | "html.hover.documentation": false, 6 | "editor.glyphMargin": false, 7 | // Use 3 when recording demos 8 | //"window.zoomLevel": 3, 9 | } -------------------------------------------------------------------------------- /APM-Start/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned", 4 | "html.hover.references": false, 5 | "html.hover.documentation": false, 6 | "editor.glyphMargin": false, 7 | // Use 3 when recording demos 8 | //"window.zoomLevel": 3, 9 | } -------------------------------------------------------------------------------- /APM-WithExtras/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | CommonModule 7 | ], 8 | exports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class SharedModule { } 13 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | pageTitle = 'Acme Product Management'; 10 | } 11 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | pageTitle = 'Acme Product Management'; 10 | } 11 | -------------------------------------------------------------------------------- /APM-WithExtras/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned", 4 | "html.hover.references": false, 5 | "html.hover.documentation": false, 6 | "editor.glyphMargin": false, 7 | // Use 3 when recording demos 8 | //"window.zoomLevel": 3, 9 | } -------------------------------------------------------------------------------- /APM-Start/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number; 4 | productName: string; 5 | productCode?: string; 6 | description?: string; 7 | price?: number; 8 | categoryId?: number; 9 | quantityInStock?: number; 10 | searchKey?: string[]; 11 | supplierIds?: number[]; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Final/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Final/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Apm 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Start/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Start/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Apm 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Final/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | 4 | div.card-header { 5 | font-size: large; 6 | } 7 | 8 | div.card { 9 | margin-top: 10px 10 | } 11 | 12 | .table { 13 | margin-top: 10px 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | 4 | div.card-header { 5 | font-size: large; 6 | } 7 | 8 | div.card { 9 | margin-top: 10px 10 | } 11 | 12 | .table { 13 | margin-top: 10px 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | } 19 | -------------------------------------------------------------------------------- /APM-WithExtras/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-WithExtras/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Apm 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-WithExtras/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | 4 | div.card-header { 5 | font-size: large; 6 | } 7 | 8 | div.card { 9 | margin-top: 10px 10 | } 11 | 12 | .table { 13 | margin-top: 10px 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number; 4 | productName: string; 5 | productCode?: string; 6 | description?: string; 7 | price?: number; 8 | categoryId?: number; 9 | category?: string; 10 | quantityInStock?: number; 11 | searchKey?: string[]; 12 | supplierIds?: number[]; 13 | } 14 | -------------------------------------------------------------------------------- /APM-Final/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-WithExtras/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Final/src/app/product-categories/product-category-data.ts: -------------------------------------------------------------------------------- 1 | import { ProductCategory } from './product-category'; 2 | 3 | export class ProductCategoryData { 4 | 5 | static categories: ProductCategory[] = [ 6 | { 7 | id: 1, 8 | name: 'Garden' 9 | }, 10 | { 11 | id: 3, 12 | name: 'Toolbox' 13 | }, 14 | { 15 | id: 5, 16 | name: 'Gaming' 17 | } 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /APM-Start/src/app/product-categories/product-category-data.ts: -------------------------------------------------------------------------------- 1 | import { ProductCategory } from './product-category'; 2 | 3 | export class ProductCategoryData { 4 | 5 | static categories: ProductCategory[] = [ 6 | { 7 | id: 1, 8 | name: 'Garden' 9 | }, 10 | { 11 | id: 3, 12 | name: 'Toolbox' 13 | }, 14 | { 15 | id: 5, 16 | name: 'Gaming' 17 | } 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/product-categories/product-category-data.ts: -------------------------------------------------------------------------------- 1 | import { ProductCategory } from './product-category'; 2 | 3 | export class ProductCategoryData { 4 | 5 | static categories: ProductCategory[] = [ 6 | { 7 | id: 1, 8 | name: 'Garden' 9 | }, 10 | { 11 | id: 3, 12 | name: 'Toolbox' 13 | }, 14 | { 15 | id: 5, 16 | name: 'Gaming' 17 | } 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /APM-Final/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-Start/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-WithExtras/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-WithExtras/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-shell/cart-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | ` 14 | }) 15 | export class CartShellComponent { 16 | 17 | constructor() { } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-list/cart-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CartService } from '../cart.service'; 3 | 4 | @Component({ 5 | selector: 'sw-cart-list', 6 | template: ` 7 |
8 | 9 |
10 | ` 11 | }) 12 | export class CartListComponent { 13 | 14 | cartItems$ = this.cartService.cartItems$; 15 | 16 | constructor(private cartService: CartService) { } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { map } from 'rxjs'; 3 | import { CartService } from './cart/cart.service'; 4 | 5 | @Component({ 6 | selector: 'pm-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | cartCount$ = this.cartService.cartItems$.pipe( 14 | map(items => items.length) 15 | ); 16 | 17 | constructor(private cartService: CartService) {} 18 | } 19 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/suppliers/supplier.ts: -------------------------------------------------------------------------------- 1 | /* Defines the supplier entity */ 2 | export interface Supplier { 3 | id: number; 4 | name: string; 5 | cost: number; 6 | minQuantity: number; 7 | } 8 | 9 | // 10 | // Extras not included in the course 11 | // 12 | 13 | // Provided to demonstrate how to map to 14 | // class instances. 15 | export class SupplierClass { 16 | id: number = 0; 17 | name: string = ''; 18 | cost?: number; 19 | minQuantity?: number; 20 | 21 | upliftCost(): void { 22 | this.cost = this.cost ? this.cost * 1.1 : 0; 23 | } 24 | } -------------------------------------------------------------------------------- /APM-Final/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /APM-WithExtras/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /APM-Final/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 |
18 |
19 |
-------------------------------------------------------------------------------- /APM-Start/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 |
18 |
19 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 |
18 |
19 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-total/cart-total.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CartService } from '../cart.service'; 3 | 4 | @Component({ 5 | selector: 'sw-cart-total', 6 | templateUrl: './cart-total.component.html' 7 | }) 8 | export class CartTotalComponent { 9 | 10 | cartItems$ = this.cartService.cartItems$; 11 | 12 | subTotal$ = this.cartService.subTotal$; 13 | 14 | deliveryFee$ = this.cartService.deliveryFee$; 15 | 16 | tax$ = this.cartService.tax$; 17 | 18 | totalPrice$ = this.cartService.totalPrice$; 19 | 20 | constructor(private cartService: CartService) { } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Supplier } from '../../suppliers/supplier'; 3 | import { Product } from '../product'; 4 | 5 | import { ProductService } from '../product.service'; 6 | 7 | @Component({ 8 | selector: 'pm-product-detail', 9 | templateUrl: './product-detail.component.html' 10 | }) 11 | export class ProductDetailComponent { 12 | pageTitle = 'Product Detail'; 13 | errorMessage = ''; 14 | product: Product | null = null; 15 | productSuppliers: Supplier[] | null = null; 16 | 17 | constructor(private productService: ProductService) { } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /APM-Final/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /APM-Start/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /APM-WithExtras/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-list-alt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 | 6 |
8 |
9 | 16 |
17 |
18 |
19 | 20 |
22 | {{ errorMessage }} 23 |
-------------------------------------------------------------------------------- /APM-Final/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-Final/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Start/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-Start/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-WithExtras/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-WithExtras/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-list-alt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 | 6 |
8 |
9 | 16 |
17 |
18 |
19 | 20 |
22 | {{ errorMessage }} 23 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-list-alt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ pageTitle }} 4 |
5 | 6 |
8 |
9 | 16 |
17 |
18 |
19 | 20 |
22 | {{ errorMessage }} 23 |
-------------------------------------------------------------------------------- /APM-Final/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { WelcomeComponent } from './home/welcome.component'; 5 | import { PageNotFoundComponent } from './page-not-found.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | RouterModule.forRoot([ 10 | { path: 'welcome', component: WelcomeComponent }, 11 | { 12 | path: 'products', 13 | loadChildren: () => 14 | import('./products/product.module').then(m => m.ProductModule) 15 | }, 16 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 17 | { path: '**', component: PageNotFoundComponent } 18 | ]) 19 | ], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule { } 23 | -------------------------------------------------------------------------------- /APM-Start/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { WelcomeComponent } from './home/welcome.component'; 5 | import { PageNotFoundComponent } from './page-not-found.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | RouterModule.forRoot([ 10 | { path: 'welcome', component: WelcomeComponent }, 11 | { 12 | path: 'products', 13 | loadChildren: () => 14 | import('./products/product.module').then(m => m.ProductModule) 15 | }, 16 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 17 | { path: '**', component: PageNotFoundComponent } 18 | ]) 19 | ], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule { } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular-RxJS 2 | Find the associated Pluralsight course here: https://app.pluralsight.com/library/courses/rxjs-angular-reactive-development 3 | 4 | `APM-Start`: The starter files for the course. **Use this to code along with the course**. 5 | 6 | `APM-Final`: The completed files. Use this to see the completed solution from the course. 7 | 8 | `APM-WithExtras`: The completed files with some extra code. Use this to see some additional techniques not included in the course. 9 | 10 | I've developed a few additional examples, including using action streams to "pass" parameters and retrieve multiple related datasets here: https://stackblitz.com/edit/angular-todos-deborahk 11 | 12 | To work through this course with stackblitz, start with the APM-Start project using this link: https://stackblitz.com/github/DeborahK/Angular-RxJS/tree/master/APM-Start 13 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /APM-Final/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /APM-Start/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /APM-WithExtras/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-reget/product-list-reget.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductListRegetComponent } from './product-list-reget.component'; 4 | 5 | describe('ProductListRegetComponent', () => { 6 | let component: ProductListRegetComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProductListRegetComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductListRegetComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /APM-Final/src/app/app-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { ProductData } from './products/product-data'; 4 | import { ProductCategoryData } from './product-categories/product-category-data'; 5 | import { SupplierData } from './suppliers/supplier-data'; 6 | import { Product } from './products/product'; 7 | import { ProductCategory } from './product-categories/product-category'; 8 | import { Supplier } from './suppliers/supplier'; 9 | 10 | export class AppData implements InMemoryDbService { 11 | 12 | createDb(): { products: Product[], productCategories: ProductCategory[], suppliers: Supplier[] } { 13 | const products = ProductData.products; 14 | const productCategories = ProductCategoryData.categories; 15 | const suppliers = SupplierData.suppliers; 16 | return { products, productCategories, suppliers }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start/src/app/app-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { ProductData } from './products/product-data'; 4 | import { ProductCategoryData } from './product-categories/product-category-data'; 5 | import { SupplierData } from './suppliers/supplier-data'; 6 | import { Product } from './products/product'; 7 | import { ProductCategory } from './product-categories/product-category'; 8 | import { Supplier } from './suppliers/supplier'; 9 | 10 | export class AppData implements InMemoryDbService { 11 | 12 | createDb(): { products: Product[], productCategories: ProductCategory[], suppliers: Supplier[] } { 13 | const products = ProductData.products; 14 | const productCategories = ProductCategoryData.categories; 15 | const suppliers = SupplierData.suppliers; 16 | return { products, productCategories, suppliers }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { CartShellComponent } from './cart/cart-shell/cart-shell.component'; 4 | 5 | import { WelcomeComponent } from './home/welcome.component'; 6 | import { PageNotFoundComponent } from './page-not-found.component'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | RouterModule.forRoot([ 11 | { path: 'welcome', component: WelcomeComponent }, 12 | { 13 | path: 'products', 14 | loadChildren: () => 15 | import('./products/product.module').then(m => m.ProductModule) 16 | }, 17 | { path: 'cart', component: CartShellComponent}, 18 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 19 | { path: '**', component: PageNotFoundComponent } 20 | ]) 21 | ], 22 | exports: [RouterModule] 23 | }) 24 | export class AppRoutingModule { } 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changes made to the course since its release: 2 | - February 22, 2022: Updated to RxJS 7 and Angular 13. See the [detailed list of changes here](https://docs.google.com/document/d/1Kv4j4hEk-7cPh3lWgZoxyewU1IvlNOSPHKuKYb689v0/edit?usp=sharing). 3 | 4 | - July 9, 2020: Reviewed the code in the course for any changes required for v10. No code changes required. 5 | 6 | - February 2, 2020: Reviewed the code in the course for any changes required for v9. No code changes required. 7 | 8 | #Changes made to the project files since its release: 9 | - February 22, 2022: Updated to RxJS 7 and Angular 13. See the [detailed list of changes here](https://docs.google.com/document/d/1Kv4j4hEk-7cPh3lWgZoxyewU1IvlNOSPHKuKYb689v0/edit?usp=sharing). 10 | 11 | - July 9, 2020: Updated the code to Angular v10, which modified the packages. Minor changes such as adding function return types. 12 | 13 | - February 2, 2020: Updated the code to Angular v9, which only modified the packages not any of the course code. -------------------------------------------------------------------------------- /APM-Final/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { AppData } from './app-data'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | import { AppComponent } from './app.component'; 11 | import { WelcomeComponent } from './home/welcome.component'; 12 | import { PageNotFoundComponent } from './page-not-found.component'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | BrowserModule, 17 | HttpClientModule, 18 | InMemoryWebApiModule.forRoot(AppData, { delay: 1000 }), 19 | AppRoutingModule 20 | ], 21 | declarations: [ 22 | AppComponent, 23 | WelcomeComponent, 24 | PageNotFoundComponent 25 | ], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { AppData } from './app-data'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | import { AppComponent } from './app.component'; 11 | import { WelcomeComponent } from './home/welcome.component'; 12 | import { PageNotFoundComponent } from './page-not-found.component'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | BrowserModule, 17 | HttpClientModule, 18 | InMemoryWebApiModule.forRoot(AppData, { delay: 1000 }), 19 | AppRoutingModule 20 | ], 21 | declarations: [ 22 | AppComponent, 23 | WelcomeComponent, 24 | PageNotFoundComponent 25 | ], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /APM-Final/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2017", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2017", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-WithExtras/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2017", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/app-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { ProductData } from './products/product-data'; 4 | import { ProductCategoryData } from './product-categories/product-category-data'; 5 | import { SupplierData } from './suppliers/supplier-data'; 6 | import { Product } from './products/product'; 7 | import { ProductCategory } from './product-categories/product-category'; 8 | import { Supplier } from './suppliers/supplier'; 9 | import { ProductDataFromAPI } from './products/product-data-fromAPI'; 10 | 11 | export class AppData implements InMemoryDbService { 12 | 13 | createDb(): { products: Product[], productCategories: ProductCategory[], suppliers: Supplier[], productsFromAPI: ProductDataFromAPI } { 14 | const products = ProductData.products; 15 | const productCategories = ProductCategoryData.categories; 16 | const suppliers = SupplierData.suppliers; 17 | const productsFromAPI = ProductDataFromAPI.productsFromAPI; 18 | return { products, productCategories, suppliers, productsFromAPI }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-list-alt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { Product } from '../product'; 6 | import { ProductService } from '../product.service'; 7 | 8 | @Component({ 9 | selector: 'pm-product-list', 10 | templateUrl: './product-list-alt.component.html' 11 | }) 12 | export class ProductListAltComponent implements OnInit, OnDestroy { 13 | pageTitle = 'Products'; 14 | errorMessage = ''; 15 | selectedProductId = 0; 16 | 17 | products: Product[] = []; 18 | sub!: Subscription; 19 | 20 | constructor(private productService: ProductService) { } 21 | 22 | ngOnInit(): void { 23 | this.sub = this.productService.getProducts().subscribe({ 24 | next: products => this.products = products, 25 | error: err => this.errorMessage = err 26 | }); 27 | } 28 | 29 | ngOnDestroy(): void { 30 | this.sub.unsubscribe(); 31 | } 32 | 33 | onSelected(productId: number): void { 34 | console.log('Not yet implemented'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /APM-Final/readme.md: -------------------------------------------------------------------------------- 1 | # Apm 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.2.2. 4 | 5 | Added packages: 6 | - npm install bootstrap 7 | - npm install angular-in-memory-web-api 8 | - ng add @angular-eslint/schematics 9 | - npm install eslint-plugin-rxjs --save-dev 10 | - npm install eslint-plugin-rxjs-angular --save-dev 11 | 12 | (The above also required changes to .eslintrc.json) 13 | 14 | ## Development server 15 | 16 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 17 | 18 | ## Code scaffolding 19 | 20 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 21 | 22 | ## Build 23 | 24 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 29 | -------------------------------------------------------------------------------- /APM-Start/readme.md: -------------------------------------------------------------------------------- 1 | # Apm 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.2.2. 4 | 5 | Added packages: 6 | - npm install bootstrap 7 | - npm install angular-in-memory-web-api 8 | - ng add @angular-eslint/schematics 9 | - npm install eslint-plugin-rxjs --save-dev 10 | - npm install eslint-plugin-rxjs-angular --save-dev 11 | 12 | (The above also required changes to .eslintrc.json) 13 | 14 | ## Development server 15 | 16 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 17 | 18 | ## Code scaffolding 19 | 20 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 21 | 22 | ## Build 23 | 24 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 29 | -------------------------------------------------------------------------------- /APM-WithExtras/readme.md: -------------------------------------------------------------------------------- 1 | # Apm 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.2.2. 4 | 5 | Added packages: 6 | - npm install bootstrap 7 | - npm install angular-in-memory-web-api 8 | - ng add @angular-eslint/schematics 9 | - npm install eslint-plugin-rxjs --save-dev 10 | - npm install eslint-plugin-rxjs-angular --save-dev 11 | 12 | (The above also required changes to .eslintrc.json) 13 | 14 | ## Development server 15 | 16 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 17 | 18 | ## Code scaffolding 19 | 20 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 21 | 22 | ## Build 23 | 24 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 29 | -------------------------------------------------------------------------------- /APM-Final/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { ProductListComponent } from './product-list.component'; 6 | import { ProductShellComponent } from './product-list-alt/product-shell.component'; 7 | import { ProductDetailComponent } from './product-list-alt/product-detail.component'; 8 | 9 | import { SharedModule } from '../shared/shared.module'; 10 | import { ProductListAltComponent } from './product-list-alt/product-list-alt.component'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | SharedModule, 15 | ReactiveFormsModule, 16 | RouterModule.forChild([ 17 | { 18 | path: '', 19 | component: ProductListComponent 20 | }, 21 | { 22 | path: 'alternate', 23 | component: ProductShellComponent 24 | } 25 | ]) 26 | ], 27 | declarations: [ 28 | ProductListComponent, 29 | ProductShellComponent, 30 | ProductListAltComponent, 31 | ProductDetailComponent 32 | ] 33 | }) 34 | export class ProductModule { } 35 | -------------------------------------------------------------------------------- /APM-Start/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { ProductListComponent } from './product-list.component'; 6 | import { ProductShellComponent } from './product-list-alt/product-shell.component'; 7 | import { ProductDetailComponent } from './product-list-alt/product-detail.component'; 8 | 9 | import { SharedModule } from '../shared/shared.module'; 10 | import { ProductListAltComponent } from './product-list-alt/product-list-alt.component'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | SharedModule, 15 | ReactiveFormsModule, 16 | RouterModule.forChild([ 17 | { 18 | path: '', 19 | component: ProductListComponent 20 | }, 21 | { 22 | path: 'alternate', 23 | component: ProductShellComponent 24 | } 25 | ]) 26 | ], 27 | declarations: [ 28 | ProductListComponent, 29 | ProductShellComponent, 30 | ProductListAltComponent, 31 | ProductDetailComponent 32 | ] 33 | }) 34 | export class ProductModule { } 35 | -------------------------------------------------------------------------------- /APM-WithExtras/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Deborah Kurata 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /APM-Start/src/app/suppliers/supplier.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | 4 | import { throwError, Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class SupplierService { 10 | suppliersUrl = 'api/suppliers'; 11 | 12 | constructor(private http: HttpClient) { } 13 | 14 | private handleError(err: HttpErrorResponse): Observable { 15 | // in a real world app, we may send the server to some remote logging infrastructure 16 | // instead of just logging it to the console 17 | let errorMessage: string; 18 | if (err.error instanceof ErrorEvent) { 19 | // A client-side or network error occurred. Handle it accordingly. 20 | errorMessage = `An error occurred: ${err.error.message}`; 21 | } else { 22 | // The backend returned an unsuccessful response code. 23 | // The response body may contain clues as to what went wrong, 24 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 25 | } 26 | console.error(err); 27 | return throwError(() => errorMessage); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /APM-Start/src/app/product-categories/product-category.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { throwError, Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ProductCategoryService { 10 | private productCategoriesUrl = 'api/productCategories'; 11 | 12 | constructor(private http: HttpClient) { } 13 | 14 | private handleError(err: HttpErrorResponse): Observable { 15 | // in a real world app, we may send the server to some remote logging infrastructure 16 | // instead of just logging it to the console 17 | let errorMessage: string; 18 | if (err.error instanceof ErrorEvent) { 19 | // A client-side or network error occurred. Handle it accordingly. 20 | errorMessage = `An error occurred: ${err.error.message}`; 21 | } else { 22 | // The backend returned an unsuccessful response code. 23 | // The response body may contain clues as to what went wrong, 24 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 25 | } 26 | console.error(err); 27 | return throwError(() => errorMessage); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { Subscription } from 'rxjs'; 4 | import { ProductCategory } from '../product-categories/product-category'; 5 | 6 | import { Product } from './product'; 7 | import { ProductService } from './product.service'; 8 | 9 | @Component({ 10 | templateUrl: './product-list.component.html', 11 | styleUrls: ['./product-list.component.css'] 12 | }) 13 | export class ProductListComponent implements OnInit, OnDestroy { 14 | pageTitle = 'Product List'; 15 | errorMessage = ''; 16 | categories: ProductCategory[] = []; 17 | 18 | products: Product[] = []; 19 | sub!: Subscription; 20 | 21 | constructor(private productService: ProductService) { } 22 | 23 | ngOnInit(): void { 24 | this.sub = this.productService.getProducts() 25 | .subscribe({ 26 | next: products => this.products = products, 27 | error: err => this.errorMessage = err 28 | }); 29 | } 30 | 31 | ngOnDestroy(): void { 32 | this.sub.unsubscribe(); 33 | } 34 | 35 | onAdd(): void { 36 | console.log('Not yet implemented'); 37 | } 38 | 39 | onSelected(categoryId: string): void { 40 | console.log('Not yet implemented'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-list-alt.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | 4 | import { ProductService } from '../product.service'; 5 | 6 | @Component({ 7 | selector: 'pm-product-list', 8 | templateUrl: './product-list-alt.component.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class ProductListAltComponent { 12 | pageTitle = 'Products'; 13 | private errorMessageSubject = new Subject(); 14 | errorMessage$ = this.errorMessageSubject.asObservable(); 15 | 16 | products$ = this.productService.productsWithCategory$ 17 | .pipe( 18 | catchError(err => { 19 | this.errorMessageSubject.next(err); 20 | return EMPTY; 21 | }) 22 | ); 23 | 24 | selectedProduct$ = this.productService.selectedProduct$; 25 | 26 | vm$ = combineLatest([ 27 | this.products$, 28 | this.selectedProduct$ 29 | ]) 30 | .pipe( 31 | map(([products, product]) => 32 | ({ products, productId: product ? product.id : 0 })) 33 | ); 34 | 35 | constructor(private productService: ProductService) { } 36 | 37 | onSelected(productId: number): void { 38 | this.productService.selectedProductChanged(productId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-list-alt.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | 4 | import { ProductService } from '../product.service'; 5 | 6 | @Component({ 7 | selector: 'pm-product-list', 8 | templateUrl: './product-list-alt.component.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class ProductListAltComponent { 12 | pageTitle = 'Products'; 13 | private errorMessageSubject = new Subject(); 14 | errorMessage$ = this.errorMessageSubject.asObservable(); 15 | 16 | products$ = this.productService.productsWithCategory$ 17 | .pipe( 18 | catchError(err => { 19 | this.errorMessageSubject.next(err); 20 | return EMPTY; 21 | }) 22 | ); 23 | 24 | selectedProduct$ = this.productService.selectedProduct$; 25 | 26 | vm$ = combineLatest([ 27 | this.products$, 28 | this.selectedProduct$ 29 | ]) 30 | .pipe( 31 | map(([products, product]) => 32 | ({ products, productId: product ? product.id : 0 })) 33 | ); 34 | 35 | constructor(private productService: ProductService) { } 36 | 37 | onSelected(productId: number): void { 38 | this.productService.selectedProductChanged(productId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-total/cart-total.component.html: -------------------------------------------------------------------------------- 1 |
3 | 4 |
5 |
6 |
Cart Total
7 |
8 |
9 | 10 |
11 |
12 |
Subtotal:
13 |
{{subTotal$ | async | currency}}
14 |
15 |
16 |
Delivery:
17 |
{{deliveryFee | currency}}
19 |
Free
22 |
23 | 24 |
25 |
Estimated Tax:
26 |
{{tax$ | async | currency}}
27 |
28 | 29 |
30 |
Total:
31 |
{{totalPrice$ | async | currency}}
32 |
33 |
34 |
35 | 36 | 37 | No items in cart 38 | -------------------------------------------------------------------------------- /APM-Final/src/app/suppliers/supplier-data.ts: -------------------------------------------------------------------------------- 1 | import { Supplier } from './supplier'; 2 | 3 | export class SupplierData { 4 | 5 | static suppliers: Supplier[] = [ 6 | { 7 | id: 1, 8 | name: 'Acme Gardening Supply', 9 | cost: 16.95, 10 | minQuantity: 12 11 | }, 12 | { 13 | id: 2, 14 | name: 'Standard Gardening', 15 | cost: 15.95, 16 | minQuantity: 24 17 | }, 18 | { 19 | id: 3, 20 | name: 'Acme Gardening Supply', 21 | cost: 12, 22 | minQuantity: 6 23 | }, 24 | { 25 | id: 4, 26 | name: 'Acme General Supply', 27 | cost: 25, 28 | minQuantity: 2 29 | }, 30 | { 31 | id: 5, 32 | name: 'Acme General Supply', 33 | cost: 2, 34 | minQuantity: 24 35 | }, 36 | { 37 | id: 6, 38 | name: 'Acme Tool Supply', 39 | cost: 4, 40 | minQuantity: 12 41 | }, 42 | { 43 | id: 7, 44 | name: 'Tools Are Us', 45 | cost: 8, 46 | minQuantity: 8 47 | }, 48 | { 49 | id: 8, 50 | name: 'Acme Tool Supply', 51 | cost: 4, 52 | minQuantity: 12 53 | }, 54 | { 55 | id: 9, 56 | name: 'Acme Game Supply', 57 | cost: 20, 58 | minQuantity: 6 59 | }, 60 | { 61 | id: 10, 62 | name: 'Acme General Supply', 63 | cost: 12, 64 | minQuantity: 12 65 | } 66 | ]; 67 | } 68 | -------------------------------------------------------------------------------- /APM-Start/src/app/suppliers/supplier-data.ts: -------------------------------------------------------------------------------- 1 | import { Supplier } from './supplier'; 2 | 3 | export class SupplierData { 4 | 5 | static suppliers: Supplier[] = [ 6 | { 7 | id: 1, 8 | name: 'Acme Gardening Supply', 9 | cost: 16.95, 10 | minQuantity: 12 11 | }, 12 | { 13 | id: 2, 14 | name: 'Standard Gardening', 15 | cost: 15.95, 16 | minQuantity: 24 17 | }, 18 | { 19 | id: 3, 20 | name: 'Acme Gardening Supply', 21 | cost: 12, 22 | minQuantity: 6 23 | }, 24 | { 25 | id: 4, 26 | name: 'Acme General Supply', 27 | cost: 25, 28 | minQuantity: 2 29 | }, 30 | { 31 | id: 5, 32 | name: 'Acme General Supply', 33 | cost: 2, 34 | minQuantity: 24 35 | }, 36 | { 37 | id: 6, 38 | name: 'Acme Tool Supply', 39 | cost: 4, 40 | minQuantity: 12 41 | }, 42 | { 43 | id: 7, 44 | name: 'Tools Are Us', 45 | cost: 8, 46 | minQuantity: 8 47 | }, 48 | { 49 | id: 8, 50 | name: 'Acme Tool Supply', 51 | cost: 4, 52 | minQuantity: 12 53 | }, 54 | { 55 | id: 9, 56 | name: 'Acme Game Supply', 57 | cost: 20, 58 | minQuantity: 6 59 | }, 60 | { 61 | id: 10, 62 | name: 'Acme General Supply', 63 | cost: 12, 64 | minQuantity: 12 65 | } 66 | ]; 67 | } 68 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/suppliers/supplier-data.ts: -------------------------------------------------------------------------------- 1 | import { Supplier } from './supplier'; 2 | 3 | export class SupplierData { 4 | 5 | static suppliers: Supplier[] = [ 6 | { 7 | id: 1, 8 | name: 'Acme Gardening Supply', 9 | cost: 16.95, 10 | minQuantity: 12 11 | }, 12 | { 13 | id: 2, 14 | name: 'Standard Gardening', 15 | cost: 15.95, 16 | minQuantity: 24 17 | }, 18 | { 19 | id: 3, 20 | name: 'Acme Gardening Supply', 21 | cost: 12, 22 | minQuantity: 6 23 | }, 24 | { 25 | id: 4, 26 | name: 'Acme General Supply', 27 | cost: 25, 28 | minQuantity: 2 29 | }, 30 | { 31 | id: 5, 32 | name: 'Acme General Supply', 33 | cost: 2, 34 | minQuantity: 24 35 | }, 36 | { 37 | id: 6, 38 | name: 'Acme Tool Supply', 39 | cost: 4, 40 | minQuantity: 12 41 | }, 42 | { 43 | id: 7, 44 | name: 'Tools Are Us', 45 | cost: 8, 46 | minQuantity: 8 47 | }, 48 | { 49 | id: 8, 50 | name: 'Acme Tool Supply', 51 | cost: 4, 52 | minQuantity: 12 53 | }, 54 | { 55 | id: 9, 56 | name: 'Acme Game Supply', 57 | cost: 20, 58 | minQuantity: 6 59 | }, 60 | { 61 | id: 10, 62 | name: 'Acme General Supply', 63 | cost: 12, 64 | minQuantity: 12 65 | } 66 | ]; 67 | } 68 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-item/cart-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { BehaviorSubject, map, tap } from 'rxjs'; 3 | import { CartItem } from '../cart'; 4 | import { CartService } from '../cart.service'; 5 | 6 | @Component({ 7 | selector: 'sw-cart-item', 8 | templateUrl: './cart-item.component.html' 9 | }) 10 | export class CartItemComponent { 11 | // Use a setter to emit whenever a new item is set 12 | _item!: CartItem; 13 | get item(): CartItem { 14 | return this._item; 15 | } 16 | @Input() set item(item: CartItem) { 17 | this._item = item; 18 | this.itemChangedSubject.next(item); 19 | } 20 | 21 | // Hard-coded quantity 22 | // These could instead come from an inventory system 23 | qtyArr = [1, 2, 3, 4, 5, 6, 7, 8]; 24 | 25 | // Item was changed action 26 | private itemChangedSubject = new BehaviorSubject(this.item); 27 | item$ = this.itemChangedSubject.asObservable(); 28 | 29 | // When the item changes, recalculate the extended price 30 | exPrice$ = this.item$.pipe( 31 | map(it => it.quantity * Number(it.product.price)) 32 | ); 33 | 34 | constructor(private cartService: CartService) { } 35 | 36 | onQuantitySelected(quantity: number): void { 37 | // Update the quantity in the item 38 | this.cartService.updateInCart(this.item, quantity); 39 | } 40 | 41 | onRemove(): void { 42 | this.cartService.removeFromCart(this.item); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | import { Supplier, SupplierClass } from "../suppliers/supplier"; 2 | 3 | /* Defines the product entity */ 4 | export interface Product { 5 | id: number; 6 | productName: string; 7 | productCode?: string; 8 | description?: string; 9 | price?: number; 10 | categoryId?: number; 11 | category?: string; 12 | quantityInStock?: number; 13 | searchKey?: string[]; 14 | supplierIds?: number[]; 15 | } 16 | 17 | // 18 | // Extras not included in the course 19 | // 20 | 21 | // Provided to demonstrate how to map to 22 | // class instances. 23 | export class ProductClass { 24 | id: number = 0; 25 | productName: string = ''; 26 | productCode?: string; 27 | description?: string; 28 | price?: number; 29 | category?: string; 30 | quantityInStock?: number; 31 | searchKey?: string[]; 32 | inventoryValuation?: number; 33 | supplierIds?: number[]; 34 | suppliers?: SupplierClass[]; 35 | 36 | calculateValuation(): number { 37 | const price = this.price ? this.price : 0; 38 | const quantity = this.quantityInStock ? this.quantityInStock : 0; 39 | return price * quantity; 40 | } 41 | } 42 | 43 | // Provided to demonstrate merging parent and 44 | // child data into individual rows 45 | export interface ProductWithSupplier { 46 | id: number; 47 | productName: string; 48 | productCode?: string; 49 | description?: string; 50 | categoryId?: number; 51 | category?: string; 52 | supplier?: string; 53 | } 54 | -------------------------------------------------------------------------------- /APM-Final/src/app/suppliers/supplier.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | import { throwError, Observable, tap, shareReplay, catchError } from 'rxjs'; 4 | 5 | import { Supplier } from './supplier'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class SupplierService { 11 | suppliersUrl = 'api/suppliers'; 12 | 13 | suppliers$ = this.http.get(this.suppliersUrl) 14 | .pipe( 15 | tap(data => console.log('suppliers', JSON.stringify(data))), 16 | shareReplay(1), 17 | catchError(this.handleError) 18 | ); 19 | 20 | constructor(private http: HttpClient) { } 21 | 22 | private handleError(err: HttpErrorResponse): Observable { 23 | // in a real world app, we may send the server to some remote logging infrastructure 24 | // instead of just logging it to the console 25 | let errorMessage: string; 26 | if (err.error instanceof ErrorEvent) { 27 | // A client-side or network error occurred. Handle it accordingly. 28 | errorMessage = `An error occurred: ${err.error.message}`; 29 | } else { 30 | // The backend returned an unsuccessful response code. 31 | // The response body may contain clues as to what went wrong, 32 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 33 | } 34 | console.error(err); 35 | return throwError(() => errorMessage); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { AppData } from './app-data'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | import { AppComponent } from './app.component'; 11 | import { WelcomeComponent } from './home/welcome.component'; 12 | import { PageNotFoundComponent } from './page-not-found.component'; 13 | import { CartItemComponent } from './cart/cart-item/cart-item.component'; 14 | import { CartTotalComponent } from './cart/cart-total/cart-total.component'; 15 | import { CartListComponent } from './cart/cart-list/cart-list.component'; 16 | import { CartShellComponent } from './cart/cart-shell/cart-shell.component'; 17 | import { FormsModule } from '@angular/forms'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | FormsModule, 23 | HttpClientModule, 24 | InMemoryWebApiModule.forRoot(AppData, { delay: 1000 }), 25 | AppRoutingModule 26 | ], 27 | declarations: [ 28 | AppComponent, 29 | WelcomeComponent, 30 | CartShellComponent, 31 | CartListComponent, 32 | CartTotalComponent, 33 | CartItemComponent, 34 | PageNotFoundComponent 35 | ], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class AppModule { } 39 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/suppliers/supplier.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | import { throwError, Observable, tap, shareReplay, catchError } from 'rxjs'; 4 | 5 | import { Supplier } from './supplier'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class SupplierService { 11 | suppliersUrl = 'api/suppliers'; 12 | 13 | suppliers$ = this.http.get(this.suppliersUrl) 14 | .pipe( 15 | tap(data => console.log('suppliers', JSON.stringify(data))), 16 | shareReplay(1), 17 | catchError(this.handleError) 18 | ); 19 | 20 | constructor(private http: HttpClient) { } 21 | 22 | private handleError(err: HttpErrorResponse): Observable { 23 | // in a real world app, we may send the server to some remote logging infrastructure 24 | // instead of just logging it to the console 25 | let errorMessage: string; 26 | if (err.error instanceof ErrorEvent) { 27 | // A client-side or network error occurred. Handle it accordingly. 28 | errorMessage = `An error occurred: ${err.error.message}`; 29 | } else { 30 | // The backend returned an unsuccessful response code. 31 | // The response body may contain clues as to what went wrong, 32 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 33 | } 34 | console.error(err); 35 | return throwError(() => errorMessage); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { catchError, combineLatest, EMPTY, filter, map, Subject } from 'rxjs'; 3 | 4 | import { ProductService } from '../product.service'; 5 | 6 | @Component({ 7 | selector: 'pm-product-detail', 8 | templateUrl: './product-detail.component.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class ProductDetailComponent { 12 | private errorMessageSubject = new Subject(); 13 | errorMessage$ = this.errorMessageSubject.asObservable(); 14 | 15 | product$ = this.productService.selectedProduct$ 16 | .pipe( 17 | catchError(err => { 18 | this.errorMessageSubject.next(err); 19 | return EMPTY; 20 | }) 21 | ); 22 | 23 | pageTitle$ = this.product$ 24 | .pipe( 25 | map(p => p ? `Product Detail for: ${p.productName}` : null) 26 | ); 27 | 28 | productSuppliers$ = this.productService.selectedProductSuppliers$ 29 | .pipe( 30 | catchError(err => { 31 | this.errorMessageSubject.next(err); 32 | return EMPTY; 33 | })); 34 | 35 | vm$ = combineLatest([ 36 | this.product$, 37 | this.productSuppliers$, 38 | this.pageTitle$ 39 | ]) 40 | .pipe( 41 | filter(([product]) => Boolean(product)), 42 | map(([product, productSuppliers, pageTitle]) => 43 | ({ product, productSuppliers, pageTitle })) 44 | ); 45 | 46 | constructor(private productService: ProductService) { } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /APM-Final/src/app/product-categories/product-category.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { throwError, Observable, tap, catchError, shareReplay } from 'rxjs'; 5 | import { ProductCategory } from './product-category'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ProductCategoryService { 11 | private productCategoriesUrl = 'api/productCategories'; 12 | 13 | productCategories$ = this.http.get(this.productCategoriesUrl) 14 | .pipe( 15 | tap(data => console.log('categories', JSON.stringify(data))), 16 | shareReplay(1), 17 | catchError(this.handleError) 18 | ); 19 | 20 | constructor(private http: HttpClient) { } 21 | 22 | private handleError(err: HttpErrorResponse): Observable { 23 | // in a real world app, we may send the server to some remote logging infrastructure 24 | // instead of just logging it to the console 25 | let errorMessage: string; 26 | if (err.error instanceof ErrorEvent) { 27 | // A client-side or network error occurred. Handle it accordingly. 28 | errorMessage = `An error occurred: ${err.error.message}`; 29 | } else { 30 | // The backend returned an unsuccessful response code. 31 | // The response body may contain clues as to what went wrong, 32 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 33 | } 34 | console.error(err); 35 | return throwError(() => errorMessage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/product-categories/product-category.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { throwError, Observable, tap, catchError, shareReplay } from 'rxjs'; 5 | import { ProductCategory } from './product-category'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ProductCategoryService { 11 | private productCategoriesUrl = 'api/productCategories'; 12 | 13 | productCategories$ = this.http.get(this.productCategoriesUrl) 14 | .pipe( 15 | tap(data => console.log('categories', JSON.stringify(data))), 16 | shareReplay(1), 17 | catchError(this.handleError) 18 | ); 19 | 20 | constructor(private http: HttpClient) { } 21 | 22 | private handleError(err: HttpErrorResponse): Observable { 23 | // in a real world app, we may send the server to some remote logging infrastructure 24 | // instead of just logging it to the console 25 | let errorMessage: string; 26 | if (err.error instanceof ErrorEvent) { 27 | // A client-side or network error occurred. Handle it accordingly. 28 | errorMessage = `An error occurred: ${err.error.message}`; 29 | } else { 30 | // The backend returned an unsuccessful response code. 31 | // The response body may contain clues as to what went wrong, 32 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 33 | } 34 | console.error(err); 35 | return throwError(() => errorMessage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /APM-Final/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "plugins": ["rxjs", "rxjs-angular"], 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates", 21 | "plugin:rxjs/recommended" 22 | ], 23 | "rules": { 24 | "@angular-eslint/directive-selector": [ 25 | "error", 26 | { 27 | "type": "attribute", 28 | "prefix": "pm", 29 | "style": "camelCase" 30 | } 31 | ], 32 | "@angular-eslint/component-selector": [ 33 | "error", 34 | { 35 | "type": "element", 36 | "prefix": "pm", 37 | "style": "kebab-case" 38 | } 39 | ], 40 | "rxjs/no-implicit-any-catch": [ 41 | "off", 42 | { "allowExplicitAny": true } 43 | ], 44 | "rxjs/no-sharereplay": [ 45 | "off", 46 | { "allowConfig": true } 47 | ], 48 | "rxjs/no-unbound-methods": [ 49 | "off" 50 | ] 51 | } 52 | }, 53 | { 54 | "files": [ 55 | "*.html" 56 | ], 57 | "extends": [ 58 | "plugin:@angular-eslint/template/recommended" 59 | ], 60 | "rules": {} 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /APM-Final/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/apm'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /APM-Start/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "plugins": ["rxjs", "rxjs-angular"], 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates", 21 | "plugin:rxjs/recommended" 22 | ], 23 | "rules": { 24 | "@angular-eslint/directive-selector": [ 25 | "error", 26 | { 27 | "type": "attribute", 28 | "prefix": "pm", 29 | "style": "camelCase" 30 | } 31 | ], 32 | "@angular-eslint/component-selector": [ 33 | "error", 34 | { 35 | "type": "element", 36 | "prefix": "pm", 37 | "style": "kebab-case" 38 | } 39 | ], 40 | "rxjs/no-implicit-any-catch": [ 41 | "off", 42 | { "allowExplicitAny": true } 43 | ], 44 | "rxjs/no-sharereplay": [ 45 | "off", 46 | { "allowConfig": true } 47 | ], 48 | "rxjs/no-unbound-methods": [ 49 | "off" 50 | ] 51 | } 52 | }, 53 | { 54 | "files": [ 55 | "*.html" 56 | ], 57 | "extends": [ 58 | "plugin:@angular-eslint/template/recommended" 59 | ], 60 | "rules": {} 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /APM-Start/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/apm'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /APM-WithExtras/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "plugins": ["rxjs", "rxjs-angular"], 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates", 21 | "plugin:rxjs/recommended" 22 | ], 23 | "rules": { 24 | "@angular-eslint/directive-selector": [ 25 | "error", 26 | { 27 | "type": "attribute", 28 | "prefix": "pm", 29 | "style": "camelCase" 30 | } 31 | ], 32 | "@angular-eslint/component-selector": [ 33 | "error", 34 | { 35 | "type": "element", 36 | "prefix": "pm", 37 | "style": "kebab-case" 38 | } 39 | ], 40 | "rxjs/no-implicit-any-catch": [ 41 | "off", 42 | { "allowExplicitAny": true } 43 | ], 44 | "rxjs/no-sharereplay": [ 45 | "off", 46 | { "allowConfig": true } 47 | ], 48 | "rxjs/no-unbound-methods": [ 49 | "off" 50 | ] 51 | } 52 | }, 53 | { 54 | "files": [ 55 | "*.html" 56 | ], 57 | "extends": [ 58 | "plugin:@angular-eslint/template/recommended" 59 | ], 60 | "rules": {} 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /APM-WithExtras/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/apm'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-data-fromAPI.ts: -------------------------------------------------------------------------------- 1 | // Demonstrates how to map from one shape obtained from an external API 2 | // to an entirely different shape. 3 | export class ProductDataFromAPI { 4 | 5 | static productsFromAPI: ProductFromAPI[] = [ 6 | { 7 | p_id: 1, 8 | p_nam: 'Leaf Rake', 9 | p_cd: 'GDN-0011', 10 | p_des: 'Leaf rake with 48-inch wooden handle', 11 | p_s: 'I', 12 | p_p: 19.95, 13 | p_c_fk_id: 1 14 | }, 15 | { 16 | p_id: 2, 17 | p_nam: 'Garden Cart', 18 | p_cd: 'GDN-0023', 19 | p_des: '15 gallon capacity rolling garden cart', 20 | p_s: 'I', 21 | p_p: 32.99, 22 | p_c_fk_id: 1 23 | }, 24 | { 25 | p_id: 5, 26 | p_nam: 'Hammer', 27 | p_cd: 'TBX-0048', 28 | p_des: 'Curved claw steel hammer', 29 | p_s: 'O', 30 | p_p: 8.9, 31 | p_c_fk_id: 3 32 | }, 33 | { 34 | p_id: 8, 35 | p_nam: 'Saw', 36 | p_cd: 'TBX-0022', 37 | p_des: '15-inch steel blade hand saw', 38 | p_s: 'I', 39 | p_p: 11.55, 40 | p_c_fk_id: 3 41 | }, 42 | { 43 | p_id: 10, 44 | p_nam: 'Video Game Controller', 45 | p_cd: 'GMG-0042', 46 | p_des: 'Standard two-button video game controller', 47 | p_s: 'I', 48 | p_p: 35.95, 49 | p_c_fk_id: 5 50 | } 51 | ]; 52 | } 53 | 54 | /* Defines the product data as retrieved from the backend server API */ 55 | export interface ProductFromAPI { 56 | p_id: number; 57 | p_nam: string; 58 | p_cd: string; 59 | p_des: string; 60 | p_s: string; 61 | p_p: number; 62 | p_c_fk_id: number; 63 | } 64 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart-item/cart-item.component.html: -------------------------------------------------------------------------------- 1 |
3 | 4 |
5 |
6 |
{{item.product.productName}}
7 |
8 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
Price:
19 |
{{item.product.price | currency}}
20 |
21 |
22 |
Category:
23 |
{{item.product.category}}
24 |
25 | 26 |
27 |
Quantity:
28 |
29 | 38 |
39 |
40 | 41 |
42 |
Cost:
43 |
{{exPrice$ | async | currency}}
44 |
45 |
46 |
-------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
8 |
9 |
10 | 16 |
17 |
18 | 21 |
22 |
23 |
24 | 25 |
26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.categoryId }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }}
47 |
48 | 49 |
50 |
51 | 52 |
54 | {{ errorMessage }} 55 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { catchError, combineLatest, EMPTY, filter, map, Subject } from 'rxjs'; 3 | 4 | import { ProductService } from '../product.service'; 5 | import { CartService } from '../../cart/cart.service'; 6 | import { Product } from '../product'; 7 | 8 | @Component({ 9 | selector: 'pm-product-detail', 10 | templateUrl: './product-detail.component.html', 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class ProductDetailComponent { 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | product$ = this.productService.selectedProduct$ 18 | .pipe( 19 | catchError(err => { 20 | this.errorMessageSubject.next(err); 21 | return EMPTY; 22 | }) 23 | ); 24 | 25 | pageTitle$ = this.product$ 26 | .pipe( 27 | map(p => p ? `Product Detail for: ${p.productName}` : null) 28 | ); 29 | 30 | productSuppliers$ = this.productService.selectedProductSuppliers$ 31 | .pipe( 32 | catchError(err => { 33 | this.errorMessageSubject.next(err); 34 | return EMPTY; 35 | })); 36 | 37 | vm$ = combineLatest([ 38 | this.product$, 39 | this.productSuppliers$, 40 | this.pageTitle$ 41 | ]) 42 | .pipe( 43 | filter(([product]) => Boolean(product)), 44 | map(([product, productSuppliers, pageTitle]) => 45 | ({ product, productSuppliers, pageTitle })) 46 | ); 47 | 48 | constructor(private productService: ProductService, 49 | private cartService: CartService) { } 50 | 51 | addToCart(product: Product) { 52 | this.cartService.addToCart(product); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /APM-Final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~13.2.0", 15 | "@angular/common": "~13.2.0", 16 | "@angular/compiler": "~13.2.0", 17 | "@angular/core": "~13.2.0", 18 | "@angular/forms": "~13.2.0", 19 | "@angular/platform-browser": "~13.2.0", 20 | "@angular/platform-browser-dynamic": "~13.2.0", 21 | "@angular/router": "~13.2.0", 22 | "angular-in-memory-web-api": "^0.13.0", 23 | "bootstrap": "^5.1.3", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~13.2.2", 30 | "@angular-eslint/builder": "13.0.1", 31 | "@angular-eslint/eslint-plugin": "13.0.1", 32 | "@angular-eslint/eslint-plugin-template": "13.0.1", 33 | "@angular-eslint/schematics": "13.0.1", 34 | "@angular-eslint/template-parser": "13.0.1", 35 | "@angular/cli": "~13.2.2", 36 | "@angular/compiler-cli": "~13.2.0", 37 | "@types/jasmine": "~3.10.0", 38 | "@types/node": "^12.11.1", 39 | "@typescript-eslint/eslint-plugin": "5.3.0", 40 | "@typescript-eslint/parser": "5.3.0", 41 | "eslint": "^8.2.0", 42 | "eslint-plugin-rxjs": "^5.0.2", 43 | "eslint-plugin-rxjs-angular": "^2.0.0", 44 | "jasmine-core": "~4.0.0", 45 | "karma": "~6.3.0", 46 | "karma-chrome-launcher": "~3.1.0", 47 | "karma-coverage": "~2.1.0", 48 | "karma-jasmine": "~4.0.0", 49 | "karma-jasmine-html-reporter": "~1.7.0", 50 | "typescript": "~4.5.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /APM-Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~13.2.0", 15 | "@angular/common": "~13.2.0", 16 | "@angular/compiler": "~13.2.0", 17 | "@angular/core": "~13.2.0", 18 | "@angular/forms": "~13.2.0", 19 | "@angular/platform-browser": "~13.2.0", 20 | "@angular/platform-browser-dynamic": "~13.2.0", 21 | "@angular/router": "~13.2.0", 22 | "angular-in-memory-web-api": "^0.13.0", 23 | "bootstrap": "^5.1.3", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~13.2.2", 30 | "@angular-eslint/builder": "13.0.1", 31 | "@angular-eslint/eslint-plugin": "13.0.1", 32 | "@angular-eslint/eslint-plugin-template": "13.0.1", 33 | "@angular-eslint/schematics": "13.0.1", 34 | "@angular-eslint/template-parser": "13.0.1", 35 | "@angular/cli": "~13.2.2", 36 | "@angular/compiler-cli": "~13.2.0", 37 | "@types/jasmine": "~3.10.0", 38 | "@types/node": "^12.11.1", 39 | "@typescript-eslint/eslint-plugin": "5.3.0", 40 | "@typescript-eslint/parser": "5.3.0", 41 | "eslint": "^8.2.0", 42 | "eslint-plugin-rxjs": "^5.0.2", 43 | "eslint-plugin-rxjs-angular": "^2.0.0", 44 | "jasmine-core": "~4.0.0", 45 | "karma": "~6.3.0", 46 | "karma-chrome-launcher": "~3.1.0", 47 | "karma-coverage": "~2.1.0", 48 | "karma-jasmine": "~4.0.0", 49 | "karma-jasmine-html-reporter": "~1.7.0", 50 | "typescript": "~4.5.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /APM-WithExtras/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~13.2.0", 15 | "@angular/common": "~13.2.0", 16 | "@angular/compiler": "~13.2.0", 17 | "@angular/core": "~13.2.0", 18 | "@angular/forms": "~13.2.0", 19 | "@angular/platform-browser": "~13.2.0", 20 | "@angular/platform-browser-dynamic": "~13.2.0", 21 | "@angular/router": "~13.2.0", 22 | "angular-in-memory-web-api": "^0.13.0", 23 | "bootstrap": "^5.1.3", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~13.2.2", 30 | "@angular-eslint/builder": "13.0.1", 31 | "@angular-eslint/eslint-plugin": "13.0.1", 32 | "@angular-eslint/eslint-plugin-template": "13.0.1", 33 | "@angular-eslint/schematics": "13.0.1", 34 | "@angular-eslint/template-parser": "13.0.1", 35 | "@angular/cli": "~13.2.2", 36 | "@angular/compiler-cli": "~13.2.0", 37 | "@types/jasmine": "~3.10.0", 38 | "@types/node": "^12.11.1", 39 | "@typescript-eslint/eslint-plugin": "5.3.0", 40 | "@typescript-eslint/parser": "5.3.0", 41 | "eslint": "^8.2.0", 42 | "eslint-plugin-rxjs": "^5.0.2", 43 | "eslint-plugin-rxjs-angular": "^2.0.0", 44 | "jasmine-core": "~4.0.0", 45 | "karma": "~6.3.0", 46 | "karma-chrome-launcher": "~3.1.0", 47 | "karma-coverage": "~2.1.0", 48 | "karma-jasmine": "~4.0.0", 49 | "karma-jasmine-html-reporter": "~1.7.0", 50 | "typescript": "~4.5.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './product'; 2 | 3 | export class ProductData { 4 | 5 | static products: Product[] = [ 6 | { 7 | id: 1, 8 | productName: 'Leaf Rake', 9 | productCode: 'GDN-0011', 10 | description: 'Leaf rake with 48-inch wooden handle', 11 | price: 19.95, 12 | categoryId: 1, 13 | quantityInStock: 15, 14 | supplierIds: [1, 2] 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | price: 32.99, 22 | categoryId: 1, 23 | quantityInStock: 2, 24 | supplierIds: [3, 4] 25 | }, 26 | { 27 | id: 5, 28 | productName: 'Hammer', 29 | productCode: 'TBX-0048', 30 | description: 'Curved claw steel hammer', 31 | price: 8.9, 32 | categoryId: 3, 33 | quantityInStock: 8, 34 | supplierIds: [5, 6] 35 | }, 36 | { 37 | id: 8, 38 | productName: 'Saw', 39 | productCode: 'TBX-0022', 40 | description: '15-inch steel blade hand saw', 41 | price: 11.55, 42 | categoryId: 3, 43 | quantityInStock: 6, 44 | supplierIds: [7, 8] 45 | }, 46 | { 47 | id: 10, 48 | productName: 'Video Game Controller', 49 | productCode: 'GMG-0042', 50 | description: 'Standard two-button video game controller', 51 | price: 35.95, 52 | categoryId: 5, 53 | quantityInStock: 12, 54 | supplierIds: [9, 10] 55 | }, 56 | { 57 | id: 13, 58 | productName: 'Chatty Cathy (no suppliers)', 59 | productCode: 'GMG-0001', 60 | description: 'Doll from the 1960s', 61 | price: 675.00, 62 | categoryId: 5, 63 | quantityInStock: 0 64 | } 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './product'; 2 | 3 | export class ProductData { 4 | 5 | static products: Product[] = [ 6 | { 7 | id: 1, 8 | productName: 'Leaf Rake', 9 | productCode: 'GDN-0011', 10 | description: 'Leaf rake with 48-inch wooden handle', 11 | price: 19.95, 12 | categoryId: 1, 13 | quantityInStock: 15, 14 | supplierIds: [1, 2] 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | price: 32.99, 22 | categoryId: 1, 23 | quantityInStock: 2, 24 | supplierIds: [3, 4] 25 | }, 26 | { 27 | id: 5, 28 | productName: 'Hammer', 29 | productCode: 'TBX-0048', 30 | description: 'Curved claw steel hammer', 31 | price: 8.9, 32 | categoryId: 3, 33 | quantityInStock: 8, 34 | supplierIds: [5, 6] 35 | }, 36 | { 37 | id: 8, 38 | productName: 'Saw', 39 | productCode: 'TBX-0022', 40 | description: '15-inch steel blade hand saw', 41 | price: 11.55, 42 | categoryId: 3, 43 | quantityInStock: 6, 44 | supplierIds: [7, 8] 45 | }, 46 | { 47 | id: 10, 48 | productName: 'Video Game Controller', 49 | productCode: 'GMG-0042', 50 | description: 'Standard two-button video game controller', 51 | price: 35.95, 52 | categoryId: 5, 53 | quantityInStock: 12, 54 | supplierIds: [9, 10] 55 | }, 56 | { 57 | id: 13, 58 | productName: 'Chatty Cathy (no suppliers)', 59 | productCode: 'GMG-0001', 60 | description: 'Doll from the 1960s', 61 | price: 675.00, 62 | categoryId: 5, 63 | quantityInStock: 0 64 | } 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './product'; 2 | 3 | export class ProductData { 4 | 5 | static products: Product[] = [ 6 | { 7 | id: 1, 8 | productName: 'Leaf Rake', 9 | productCode: 'GDN-0011', 10 | description: 'Leaf rake with 48-inch wooden handle', 11 | price: 19.95, 12 | categoryId: 1, 13 | quantityInStock: 15, 14 | supplierIds: [1, 2] 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | price: 32.99, 22 | categoryId: 1, 23 | quantityInStock: 2, 24 | supplierIds: [3, 4] 25 | }, 26 | { 27 | id: 5, 28 | productName: 'Hammer', 29 | productCode: 'TBX-0048', 30 | description: 'Curved claw steel hammer', 31 | price: 8.9, 32 | categoryId: 3, 33 | quantityInStock: 8, 34 | supplierIds: [5, 6] 35 | }, 36 | { 37 | id: 8, 38 | productName: 'Saw', 39 | productCode: 'TBX-0022', 40 | description: '15-inch steel blade hand saw', 41 | price: 11.55, 42 | categoryId: 3, 43 | quantityInStock: 6, 44 | supplierIds: [7, 8] 45 | }, 46 | { 47 | id: 10, 48 | productName: 'Video Game Controller', 49 | productCode: 'GMG-0042', 50 | description: 'Standard two-button video game controller', 51 | price: 35.95, 52 | categoryId: 5, 53 | quantityInStock: 12, 54 | supplierIds: [9, 10] 55 | }, 56 | { 57 | id: 13, 58 | productName: 'Chatty Cathy (no suppliers)', 59 | productCode: 'GMG-0001', 60 | description: 'Doll from the 1960s', 61 | price: 675.00, 62 | categoryId: 5, 63 | quantityInStock: 0 64 | } 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 | 23 |
24 |
25 |
26 | 27 |
28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }}
49 |
50 | 51 |
52 |
53 | 54 |
56 | {{ errorMessage }} 57 |
-------------------------------------------------------------------------------- /APM-Start/src/app/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | 4 | import { catchError, Observable, tap, throwError } from 'rxjs'; 5 | 6 | import { Product } from './product'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class ProductService { 12 | private productsUrl = 'api/products'; 13 | private suppliersUrl = 'api/suppliers'; 14 | 15 | constructor(private http: HttpClient) { } 16 | 17 | getProducts(): Observable { 18 | return this.http.get(this.productsUrl) 19 | .pipe( 20 | tap(data => console.log('Products: ', JSON.stringify(data))), 21 | catchError(this.handleError) 22 | ); 23 | } 24 | 25 | private fakeProduct(): Product { 26 | return { 27 | id: 42, 28 | productName: 'Another One', 29 | productCode: 'TBX-0042', 30 | description: 'Our new product', 31 | price: 8.9, 32 | categoryId: 3, 33 | // category: 'Toolbox', 34 | quantityInStock: 30 35 | }; 36 | } 37 | 38 | private handleError(err: HttpErrorResponse): Observable { 39 | // in a real world app, we may send the server to some remote logging infrastructure 40 | // instead of just logging it to the console 41 | let errorMessage: string; 42 | if (err.error instanceof ErrorEvent) { 43 | // A client-side or network error occurred. Handle it accordingly. 44 | errorMessage = `An error occurred: ${err.error.message}`; 45 | } else { 46 | // The backend returned an unsuccessful response code. 47 | // The response body may contain clues as to what went wrong, 48 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 49 | } 50 | console.error(err); 51 | return throwError(() => errorMessage); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 | 23 |
24 |
25 |
26 | 27 |
28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }}
49 |
50 | 51 |
52 |
53 | 54 |
56 | {{ errorMessage }} 57 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 50 | 51 |
52 | 53 |
-------------------------------------------------------------------------------- /links.md: -------------------------------------------------------------------------------- 1 | ### Useful Links 2 | 3 | Here are some useful links that are referenced in or supplement this course: 4 | 5 | | Name | Description | Link | 6 | | ---- | ----------- | ---- | 7 | | Github Repo | Code for the course | https://github.com/DeborahK/Angular-RxJS 8 | | Course | Pluralsight course | https://app.pluralsight.com/library/courses/rxjs-angular-reactive-development 9 | | Course discussion | Where to post issues/questions | https://github.com/DeborahK/Angular-RxJS/issues 10 | | Angular documentation | Official Angular documentation | https://angular.io/ 11 | | RxJS Documentation | Official RxJS documentation | https://rxjs.dev/ 12 | | RxJS Github Repo | Official RxJS github | https://github.com/ReactiveX/rxjs/ 13 | | Stackblitz | Online editor for first few hands on | https://stackblitz.com/github/DeborahK/Angular-GettingStarted/tree/master/APM-Start 14 | | VS Code | Recommended code editor | https://code.visualstudio.com/ 15 | | node/npm | Download node and npm | https://nodejs.org/en/download 16 | | Angular DevTools | Chrome extension for Angular debugging and profiling | https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh 17 | | Angular ESLint | Enabling ESLint in Angular projects | https://github.com/angular-eslint/angular-eslint 18 | 19 | ### Additional Suggested Pluralsight Courses 20 | 21 | | Name | Link | 22 | | ---- | ----- | 23 | | Angular: Getting Started | https://app.pluralsight.com/library/courses/angular-2-getting-started-update 24 | | Angular: Reactive Forms | https://app.pluralsight.com/library/courses/angular-2-reactive-forms 25 | | Angular Component Communication | https://app.pluralsight.com/library/courses/angular-component-communication 26 | | Angular NgRx: Getting Started | https://app.pluralsight.com/library/courses/angular-ngrx-getting-started 27 | | RxJS: Getting Started | https://app.pluralsight.com/library/courses/rxjs-getting-started 28 | | Learning RxJS Operators by Example Playbook | https://app.pluralsight.com/library/courses/rxjs-operators-by-example-playbook 29 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { ProductListComponent } from './product-list.component'; 6 | import { ProductShellComponent } from './product-list-alt/product-shell.component'; 7 | import { ProductDetailComponent } from './product-list-alt/product-detail.component'; 8 | import { ProductListAltComponent } from './product-list-alt/product-list-alt.component'; 9 | import { ProductListRefreshComponent } from './product-list-refresh/product-list-refresh.component'; 10 | 11 | import { SharedModule } from '../shared/shared.module'; 12 | import { ProductListEditComponent } from './product-list-edit/product-list-edit.component'; 13 | import { ProductListExtrasComponent } from './product-list-extras/product-list-extras.component'; 14 | import { ProductListRegetComponent } from './product-list-reget/product-list-reget.component'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | ReactiveFormsModule, 20 | RouterModule.forChild([ 21 | { 22 | path: '', 23 | component: ProductListComponent 24 | }, 25 | { 26 | path: 'alternate', 27 | component: ProductShellComponent 28 | }, 29 | { 30 | path: 'edit', 31 | component: ProductListEditComponent 32 | }, 33 | { 34 | path: 'extras', 35 | component: ProductListExtrasComponent 36 | }, 37 | { 38 | path: 'refresh', 39 | component: ProductListRefreshComponent 40 | } , 41 | { 42 | path: 'reget', 43 | component: ProductListRegetComponent 44 | } 45 | ]) 46 | ], 47 | declarations: [ 48 | ProductListComponent, 49 | ProductShellComponent, 50 | ProductListAltComponent, 51 | ProductDetailComponent, 52 | ProductListEditComponent, 53 | ProductListExtrasComponent, 54 | ProductListRefreshComponent, 55 | ProductListRegetComponent 56 | ] 57 | }) 58 | export class ProductModule { } 59 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | 4 | import { ProductCategoryService } from '../product-categories/product-category.service'; 5 | import { ProductService } from './product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class ProductListComponent { 13 | pageTitle = 'Product List'; 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | private categorySelectedSubject = new BehaviorSubject(0); 18 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 19 | 20 | products$ = combineLatest([ 21 | this.productService.productsWithAdd$, 22 | this.categorySelectedAction$ 23 | ]) 24 | .pipe( 25 | map(([products, selectedCategoryId]) => 26 | products.filter(product => 27 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 28 | )), 29 | catchError(err => { 30 | this.errorMessageSubject.next(err); 31 | return EMPTY; 32 | }) 33 | ); 34 | 35 | categories$ = this.productCategoryService.productCategories$ 36 | .pipe( 37 | catchError(err => { 38 | this.errorMessageSubject.next(err); 39 | return EMPTY; 40 | }) 41 | ); 42 | 43 | vm$ = combineLatest([ 44 | this.products$, 45 | this.categories$ 46 | ]) 47 | .pipe( 48 | map(([products, categories]) => 49 | ({ products, categories })) 50 | ); 51 | 52 | constructor(private productService: ProductService, 53 | private productCategoryService: ProductCategoryService) { } 54 | 55 | onAdd(): void { 56 | this.productService.addProduct(); 57 | } 58 | 59 | onSelected(categoryId: string): void { 60 | this.categorySelectedSubject.next(+categoryId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | 4 | import { ProductCategoryService } from '../product-categories/product-category.service'; 5 | import { ProductService } from './product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class ProductListComponent { 13 | pageTitle = 'Product List'; 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | private categorySelectedSubject = new BehaviorSubject(0); 18 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 19 | 20 | products$ = combineLatest([ 21 | this.productService.productsWithAdd$, 22 | this.categorySelectedAction$ 23 | ]) 24 | .pipe( 25 | map(([products, selectedCategoryId]) => 26 | products.filter(product => 27 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 28 | )), 29 | catchError(err => { 30 | this.errorMessageSubject.next(err); 31 | return EMPTY; 32 | }) 33 | ); 34 | 35 | categories$ = this.productCategoryService.productCategories$ 36 | .pipe( 37 | catchError(err => { 38 | this.errorMessageSubject.next(err); 39 | return EMPTY; 40 | }) 41 | ); 42 | 43 | vm$ = combineLatest([ 44 | this.products$, 45 | this.categories$ 46 | ]) 47 | .pipe( 48 | map(([products, categories]) => 49 | ({ products, categories })) 50 | ); 51 | 52 | constructor(private productService: ProductService, 53 | private productCategoryService: ProductCategoryService) { } 54 | 55 | onAdd(): void { 56 | this.productService.addProduct(); 57 | } 58 | 59 | onSelected(categoryId: string): void { 60 | this.categorySelectedSubject.next(+categoryId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list-alt/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | {{ pageTitle }} 5 |
6 | 7 |
8 |
9 |
Name:
10 |
{{ product.productName }}
11 |
12 |
13 |
Code:
14 |
{{ product.productCode }}
15 |
16 |
17 |
Category:
18 |
{{ product.categoryId }}
19 |
20 |
21 |
Description:
22 |
{{ product.description }}
23 |
24 |
25 |
Price:
26 |
{{ product.price | currency: "USD":"symbol" }}
27 |
28 |
29 |
In Stock:
30 |
{{ product.quantityInStock }}
31 |
32 | 33 |
35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
SupplierCostMinimum Quantity
{{ supplier.name }}{{ supplier.cost | currency: "USD":"symbol":"1.2-2" }}{{ supplier.minQuantity }}
52 |
53 |
54 | 55 |
56 | No suppliers for product 57 |
58 |
59 |
60 | 61 |
63 | {{ errorMessage }} 64 |
-------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list-alt/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | {{ vm.pageTitle }} 5 |
6 | 7 |
9 |
10 |
Name:
11 |
{{ vm.product.productName }}
12 |
13 |
14 |
Code:
15 |
{{ vm.product.productCode }}
16 |
17 |
18 |
Category:
19 |
{{ vm.product.category }}
20 |
21 |
22 |
Description:
23 |
{{ vm.product.description }}
24 |
25 |
26 |
Price:
27 |
{{ vm.product.price | currency: "USD":"symbol" }}
28 |
29 |
30 |
In Stock:
31 |
{{ vm.product.quantityInStock }}
32 |
33 | 34 |
35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
SupplierCostMinimum Quantity
{{ supplier.name }}{{ supplier.cost | currency: "USD":"symbol":"1.2-2" }}{{ supplier.minQuantity }}
52 |
53 |
54 | 55 |
56 | No suppliers for product 57 |
58 |
59 |
60 | 61 |
63 | {{ errorMessage }} 64 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-extras/product-list-extras.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | 4 | import { ProductCategoryService } from 'src/app/product-categories/product-category.service'; 5 | import { ProductExtrasService } from './product-extras.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list-extras.component.html', 9 | styleUrls: ['./product-list-extras.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class ProductListExtrasComponent { 13 | pageTitle = 'Product List (Extras)'; 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | private categorySelectedSubject = new BehaviorSubject(0); 18 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 19 | 20 | products$ = combineLatest([ 21 | this.productExtrasService.productsWithSupplier$, 22 | this.categorySelectedAction$ 23 | ]) 24 | .pipe( 25 | map(([products, selectedCategoryId]) => 26 | products.filter(product => 27 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 28 | )), 29 | catchError(err => { 30 | this.errorMessageSubject.next(err); 31 | return EMPTY; 32 | }) 33 | ); 34 | 35 | categories$ = this.productCategoryService.productCategories$ 36 | .pipe( 37 | catchError(err => { 38 | this.errorMessageSubject.next(err); 39 | return EMPTY; 40 | }) 41 | ); 42 | 43 | vm$ = combineLatest([ 44 | this.products$, 45 | this.categories$ 46 | ]) 47 | .pipe( 48 | map(([products, categories]) => 49 | ({ products, categories })) 50 | ); 51 | 52 | // Products emitted one at a time. 53 | product$ = this.productExtrasService.productsOneByOne$; 54 | 55 | constructor(private productExtrasService: ProductExtrasService, 56 | private productCategoryService: ProductCategoryService 57 | ) { } 58 | 59 | onSelected(categoryId: string): void { 60 | this.categorySelectedSubject.next(+categoryId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-extras/product-list-extras.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 |
20 | 21 |
22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
ProductCodeCategorySupplier
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.supplier }}
41 |
42 |
43 |
44 | 45 |
46 |
47 | Products one at a time 48 |
49 | 50 |
52 |
53 |
54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
ProductCode
{{ product.productName }}{{ product.productCode }}
67 |
68 |
69 |
70 | 71 |
73 | {{ errorMessage }} 74 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-alt/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | {{ vm.pageTitle }} 5 |
6 | 7 |
9 |
10 |
Name:
11 |
{{vm.product.productName}}
12 |
13 |
14 |
15 |
Code:
16 |
{{ vm.product.productCode }}
17 |
18 |
19 |
Category:
20 |
{{ vm.product.category }}
21 |
22 |
23 |
Description:
24 |
{{ vm.product.description }}
25 |
26 |
27 |
Price:
28 |
{{ vm.product.price | currency: "USD":"symbol" }}
29 |
30 |
31 |
In Stock:
32 |
{{ vm.product.quantityInStock }}
33 |
34 | 35 |
36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
SupplierCostMinimum Quantity
{{ supplier.name }}{{ supplier.cost | currency: "USD":"symbol":"1.2-2" }}{{ supplier.minQuantity }}
53 |
54 |
55 | 56 |
57 | No suppliers for product 58 |
59 |
60 |
61 | 62 |
64 | {{ errorMessage }} 65 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-refresh/product-list-refresh.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 | 23 | 27 |
28 |
29 |
30 | 31 | 32 |
33 |
... Loading (this could display an icon instead)
34 |
35 | 36 | 37 |
38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }}
59 |
60 |
61 | 62 |
63 |
64 | 65 |
67 | {{ errorMessage }} 68 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-edit/product-list-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 | 23 |
24 |
25 |
26 | 27 |
28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 56 | 57 | 58 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }} 47 | 51 | 55 |
59 |
60 | 61 |
62 |
63 | 64 |
66 | {{ errorMessage }} 67 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-reget/product-list-reget.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 |
9 |
10 |
11 | 17 |
18 |
19 | 23 |
24 |
25 |
26 | 27 |
28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 56 | 57 | 58 |
ProductCodeCategoryPriceIn Stock
{{ product.productName }}{{ product.productCode }}{{ product.category }}{{ product.price | currency:"USD":"symbol":"1.2-2" }}{{ product.quantityInStock }} 47 | 51 | 55 |
59 |
60 | 61 |
62 |
63 | 64 |
66 | {{ errorMessage }} 67 |
-------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-edit/product-list-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | import { ProductCategoryService } from 'src/app/product-categories/product-category.service'; 4 | import { Product } from '../product'; 5 | import { ProductEditService } from './product-edit.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list-edit.component.html', 9 | styleUrls: ['./product-list-edit.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class ProductListEditComponent { 13 | pageTitle = 'Product List (Edit)'; 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | private categorySelectedSubject = new BehaviorSubject(0); 18 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 19 | 20 | products$ = combineLatest([ 21 | this.productEditService.productsWithCRUD$, 22 | this.categorySelectedAction$ 23 | ]) 24 | .pipe( 25 | map(([products, selectedCategoryId]) => 26 | products.filter(product => 27 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 28 | )), 29 | catchError(err => { 30 | this.errorMessageSubject.next(err); 31 | return EMPTY; 32 | }) 33 | ); 34 | 35 | categories$ = this.productCategoryService.productCategories$ 36 | .pipe( 37 | catchError(err => { 38 | this.errorMessageSubject.next(err); 39 | return EMPTY; 40 | }) 41 | ); 42 | 43 | vm$ = combineLatest([ 44 | this.products$, 45 | this.categories$ 46 | ]) 47 | .pipe( 48 | map(([products, categories]) => 49 | ({ products, categories })) 50 | ); 51 | 52 | constructor(private productEditService: ProductEditService, 53 | private productCategoryService: ProductCategoryService 54 | ) { } 55 | 56 | onAdd(): void { 57 | this.productEditService.addProduct(); 58 | } 59 | 60 | onDelete(product: Product): void { 61 | this.productEditService.deleteProduct(product); 62 | } 63 | 64 | onSelected(categoryId: string): void { 65 | this.categorySelectedSubject.next(+categoryId); 66 | } 67 | 68 | onUpdate(product: Product): void { 69 | this.productEditService.updateProduct(product); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-refresh/product-list-refresh.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, merge, Subject, tap } from 'rxjs'; 3 | import { ProductCategoryService } from 'src/app/product-categories/product-category.service'; 4 | import { ProductExtrasService } from '../product-list-extras/product-extras.service'; 5 | import { ProductService } from '../product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list-refresh.component.html', 9 | styleUrls: ['./product-list-refresh.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class ProductListRefreshComponent { 13 | pageTitle = 'Product List (Refresh)'; 14 | private errorMessageSubject = new Subject(); 15 | errorMessage$ = this.errorMessageSubject.asObservable(); 16 | 17 | private categorySelectedSubject = new BehaviorSubject(0); 18 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 19 | 20 | products$ = combineLatest([ 21 | this.productExtrasService.productsWithAdd$, 22 | this.categorySelectedAction$ 23 | ]) 24 | .pipe( 25 | map(([products, selectedCategoryId]) => 26 | products.filter(product => 27 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 28 | )), 29 | catchError(err => { 30 | this.errorMessageSubject.next(err); 31 | return EMPTY; 32 | }) 33 | ); 34 | 35 | // Whether data is currently loading 36 | isLoading$ = this.productExtrasService.isLoadingAction$; 37 | 38 | categories$ = this.productCategoryService.productCategories$ 39 | .pipe( 40 | catchError(err => { 41 | this.errorMessageSubject.next(err); 42 | return EMPTY; 43 | }) 44 | ); 45 | 46 | vm$ = combineLatest([ 47 | this.products$, 48 | this.categories$ 49 | ]) 50 | .pipe( 51 | map(([products, categories]) => 52 | ({ products, categories })) 53 | ); 54 | 55 | constructor(private productExtrasService: ProductExtrasService, 56 | private productCategoryService: ProductCategoryService 57 | ) { } 58 | 59 | onAdd(): void { 60 | this.productExtrasService.addProduct(); 61 | } 62 | 63 | onSelected(categoryId: string): void { 64 | this.categorySelectedSubject.next(+categoryId); 65 | } 66 | 67 | onRefresh(): void { 68 | this.productExtrasService.refreshData(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-reget/product-list-reget.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | import { BehaviorSubject, catchError, combineLatest, EMPTY, map, Subject } from 'rxjs'; 3 | import { ProductCategoryService } from 'src/app/product-categories/product-category.service'; 4 | import { Product } from '../product'; 5 | import { ProductRegetService } from './product-reget.service'; 6 | 7 | @Component({ 8 | selector: 'pm-product-list-reget', 9 | templateUrl: './product-list-reget.component.html', 10 | styleUrls: ['./product-list-reget.component.css'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class ProductListRegetComponent { 14 | pageTitle = 'Product List (Edit with Re-get)'; 15 | private errorMessageSubject = new Subject(); 16 | errorMessage$ = this.errorMessageSubject.asObservable(); 17 | 18 | private categorySelectedSubject = new BehaviorSubject(0); 19 | categorySelectedAction$ = this.categorySelectedSubject.asObservable(); 20 | 21 | products$ = combineLatest([ 22 | this.productRegetService.productsWithCRUD$, 23 | this.categorySelectedAction$ 24 | ]) 25 | .pipe( 26 | map(([products, selectedCategoryId]) => 27 | products.filter(product => 28 | selectedCategoryId ? product.categoryId === selectedCategoryId : true 29 | )), 30 | catchError(err => { 31 | this.errorMessageSubject.next(err); 32 | return EMPTY; 33 | }) 34 | ); 35 | 36 | categories$ = this.productCategoryService.productCategories$ 37 | .pipe( 38 | catchError(err => { 39 | this.errorMessageSubject.next(err); 40 | return EMPTY; 41 | }) 42 | ); 43 | 44 | vm$ = combineLatest([ 45 | this.products$, 46 | this.categories$ 47 | ]) 48 | .pipe( 49 | map(([products, categories]) => 50 | ({ products, categories })) 51 | ); 52 | 53 | constructor(private productRegetService: ProductRegetService, 54 | private productCategoryService: ProductCategoryService 55 | ) { } 56 | 57 | onAdd(): void { 58 | this.productRegetService.addProduct(); 59 | } 60 | 61 | onDelete(product: Product): void { 62 | this.productRegetService.deleteProduct(product); 63 | } 64 | 65 | onSelected(categoryId: string): void { 66 | this.categorySelectedSubject.next(+categoryId); 67 | } 68 | 69 | onUpdate(product: Product): void { 70 | this.productRegetService.updateProduct(product); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /APM-Final/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /APM-Start/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /APM-WithExtras/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/cart/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { combineLatest, map, scan, shareReplay, Subject } from "rxjs"; 3 | 4 | import { Action } from "../shared/edit-action"; 5 | import { Product } from "../products/product"; 6 | import { CartItem } from "./cart"; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class CartService { 12 | 13 | // Add item action 14 | private itemSubject = new Subject>(); 15 | itemAction$ = this.itemSubject.asObservable(); 16 | 17 | cartItems$ = this.itemAction$ 18 | .pipe( 19 | scan((items, itemAction) => this.modifyCart(items, itemAction), [] as CartItem[]), 20 | shareReplay(1) 21 | ); 22 | 23 | // Total up the extended price for each item 24 | subTotal$ = this.cartItems$.pipe( 25 | map(items => items.reduce((a, b) => a + (b.quantity * Number(b.product.price)), 0)), 26 | ); 27 | 28 | // Delivery is free if spending more than $30 29 | deliveryFee$ = this.subTotal$.pipe( 30 | map((t) => (t < 30 ? 5.99 : 0)) 31 | ); 32 | 33 | // Tax could be based on shipping address zip code 34 | tax$ = this.subTotal$.pipe( 35 | map((t) => Math.round(t * 10.75) / 100) 36 | ); 37 | 38 | // Total price 39 | totalPrice$ = combineLatest([ 40 | this.subTotal$, 41 | this.deliveryFee$, 42 | this.tax$, 43 | ]).pipe(map(([st, d, t]) => st + d + t)); 44 | 45 | // Add the product to the cart as an Action 46 | addToCart(product: Product): void { 47 | this.itemSubject.next({ 48 | item: { product, quantity: 1 }, 49 | action: 'add' 50 | }); 51 | } 52 | 53 | // Remove the item from the cart 54 | removeFromCart(cartItem: CartItem): void { 55 | this.itemSubject.next({ 56 | item: { product: cartItem.product, quantity: 0 }, 57 | action: 'delete' 58 | }); 59 | } 60 | 61 | updateInCart(cartItem: CartItem, quantity: number) { 62 | this.itemSubject.next({ 63 | item: { product: cartItem.product, quantity }, 64 | action: 'update' 65 | }); 66 | } 67 | 68 | // Return the updated array of cart items 69 | private modifyCart(items: CartItem[], operation: Action): CartItem[] { 70 | if (operation.action === 'add') { 71 | return [...items, operation.item]; 72 | } else if (operation.action === 'update') { 73 | return items.map(item => item.product.id === operation.item.product.id ? operation.item : item) 74 | } else if (operation.action === 'delete') { 75 | return items.filter(item => item.product.id !== operation.item.product.id); 76 | } 77 | return [...items]; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /APM-Final/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": "ab31743f-73f3-4d5a-80fd-892ee674888d", 5 | "defaultCollection": "@angular-eslint/schematics" 6 | }, 7 | "version": 1, 8 | "newProjectRoot": "projects", 9 | "projects": { 10 | "apm": { 11 | "projectType": "application", 12 | "schematics": { 13 | "@schematics/angular:application": { 14 | "strict": true 15 | } 16 | }, 17 | "root": "", 18 | "sourceRoot": "src", 19 | "prefix": "pm", 20 | "architect": { 21 | "build": { 22 | "builder": "@angular-devkit/build-angular:browser", 23 | "options": { 24 | "outputPath": "dist/apm", 25 | "index": "src/index.html", 26 | "main": "src/main.ts", 27 | "polyfills": "src/polyfills.ts", 28 | "tsConfig": "tsconfig.app.json", 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets" 32 | ], 33 | "styles": [ 34 | "src/styles.css" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kb", 44 | "maximumError": "1mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "2kb", 49 | "maximumError": "4kb" 50 | } 51 | ], 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "outputHashing": "all" 59 | }, 60 | "development": { 61 | "buildOptimizer": false, 62 | "optimization": false, 63 | "vendorChunk": true, 64 | "extractLicenses": false, 65 | "sourceMap": true, 66 | "namedChunks": true 67 | } 68 | }, 69 | "defaultConfiguration": "production" 70 | }, 71 | "serve": { 72 | "builder": "@angular-devkit/build-angular:dev-server", 73 | "configurations": { 74 | "production": { 75 | "browserTarget": "apm:build:production" 76 | }, 77 | "development": { 78 | "browserTarget": "apm:build:development" 79 | } 80 | }, 81 | "defaultConfiguration": "development" 82 | }, 83 | "extract-i18n": { 84 | "builder": "@angular-devkit/build-angular:extract-i18n", 85 | "options": { 86 | "browserTarget": "apm:build" 87 | } 88 | }, 89 | "test": { 90 | "builder": "@angular-devkit/build-angular:karma", 91 | "options": { 92 | "main": "src/test.ts", 93 | "polyfills": "src/polyfills.ts", 94 | "tsConfig": "tsconfig.spec.json", 95 | "karmaConfig": "karma.conf.js", 96 | "assets": [ 97 | "src/favicon.ico", 98 | "src/assets" 99 | ], 100 | "styles": [ 101 | "src/styles.css" 102 | ], 103 | "scripts": [] 104 | } 105 | }, 106 | "lint": { 107 | "builder": "@angular-eslint/builder:lint", 108 | "options": { 109 | "lintFilePatterns": [ 110 | "src/**/*.ts", 111 | "src/**/*.html" 112 | ] 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "defaultProject": "apm" 119 | } 120 | -------------------------------------------------------------------------------- /APM-Start/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": "ab31743f-73f3-4d5a-80fd-892ee674888d", 5 | "defaultCollection": "@angular-eslint/schematics" 6 | }, 7 | "version": 1, 8 | "newProjectRoot": "projects", 9 | "projects": { 10 | "apm": { 11 | "projectType": "application", 12 | "schematics": { 13 | "@schematics/angular:application": { 14 | "strict": true 15 | } 16 | }, 17 | "root": "", 18 | "sourceRoot": "src", 19 | "prefix": "pm", 20 | "architect": { 21 | "build": { 22 | "builder": "@angular-devkit/build-angular:browser", 23 | "options": { 24 | "outputPath": "dist/apm", 25 | "index": "src/index.html", 26 | "main": "src/main.ts", 27 | "polyfills": "src/polyfills.ts", 28 | "tsConfig": "tsconfig.app.json", 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets" 32 | ], 33 | "styles": [ 34 | "src/styles.css" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kb", 44 | "maximumError": "1mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "2kb", 49 | "maximumError": "4kb" 50 | } 51 | ], 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "outputHashing": "all" 59 | }, 60 | "development": { 61 | "buildOptimizer": false, 62 | "optimization": false, 63 | "vendorChunk": true, 64 | "extractLicenses": false, 65 | "sourceMap": true, 66 | "namedChunks": true 67 | } 68 | }, 69 | "defaultConfiguration": "production" 70 | }, 71 | "serve": { 72 | "builder": "@angular-devkit/build-angular:dev-server", 73 | "configurations": { 74 | "production": { 75 | "browserTarget": "apm:build:production" 76 | }, 77 | "development": { 78 | "browserTarget": "apm:build:development" 79 | } 80 | }, 81 | "defaultConfiguration": "development" 82 | }, 83 | "extract-i18n": { 84 | "builder": "@angular-devkit/build-angular:extract-i18n", 85 | "options": { 86 | "browserTarget": "apm:build" 87 | } 88 | }, 89 | "test": { 90 | "builder": "@angular-devkit/build-angular:karma", 91 | "options": { 92 | "main": "src/test.ts", 93 | "polyfills": "src/polyfills.ts", 94 | "tsConfig": "tsconfig.spec.json", 95 | "karmaConfig": "karma.conf.js", 96 | "assets": [ 97 | "src/favicon.ico", 98 | "src/assets" 99 | ], 100 | "styles": [ 101 | "src/styles.css" 102 | ], 103 | "scripts": [] 104 | } 105 | }, 106 | "lint": { 107 | "builder": "@angular-eslint/builder:lint", 108 | "options": { 109 | "lintFilePatterns": [ 110 | "src/**/*.ts", 111 | "src/**/*.html" 112 | ] 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "defaultProject": "apm" 119 | } 120 | -------------------------------------------------------------------------------- /APM-WithExtras/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": "ab31743f-73f3-4d5a-80fd-892ee674888d", 5 | "defaultCollection": "@angular-eslint/schematics" 6 | }, 7 | "version": 1, 8 | "newProjectRoot": "projects", 9 | "projects": { 10 | "apm": { 11 | "projectType": "application", 12 | "schematics": { 13 | "@schematics/angular:application": { 14 | "strict": true 15 | } 16 | }, 17 | "root": "", 18 | "sourceRoot": "src", 19 | "prefix": "pm", 20 | "architect": { 21 | "build": { 22 | "builder": "@angular-devkit/build-angular:browser", 23 | "options": { 24 | "outputPath": "dist/apm", 25 | "index": "src/index.html", 26 | "main": "src/main.ts", 27 | "polyfills": "src/polyfills.ts", 28 | "tsConfig": "tsconfig.app.json", 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets" 32 | ], 33 | "styles": [ 34 | "src/styles.css" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kb", 44 | "maximumError": "1mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "2kb", 49 | "maximumError": "4kb" 50 | } 51 | ], 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "outputHashing": "all" 59 | }, 60 | "development": { 61 | "buildOptimizer": false, 62 | "optimization": false, 63 | "vendorChunk": true, 64 | "extractLicenses": false, 65 | "sourceMap": true, 66 | "namedChunks": true 67 | } 68 | }, 69 | "defaultConfiguration": "production" 70 | }, 71 | "serve": { 72 | "builder": "@angular-devkit/build-angular:dev-server", 73 | "configurations": { 74 | "production": { 75 | "browserTarget": "apm:build:production" 76 | }, 77 | "development": { 78 | "browserTarget": "apm:build:development" 79 | } 80 | }, 81 | "defaultConfiguration": "development" 82 | }, 83 | "extract-i18n": { 84 | "builder": "@angular-devkit/build-angular:extract-i18n", 85 | "options": { 86 | "browserTarget": "apm:build" 87 | } 88 | }, 89 | "test": { 90 | "builder": "@angular-devkit/build-angular:karma", 91 | "options": { 92 | "main": "src/test.ts", 93 | "polyfills": "src/polyfills.ts", 94 | "tsConfig": "tsconfig.spec.json", 95 | "karmaConfig": "karma.conf.js", 96 | "assets": [ 97 | "src/favicon.ico", 98 | "src/assets" 99 | ], 100 | "styles": [ 101 | "src/styles.css" 102 | ], 103 | "scripts": [] 104 | } 105 | }, 106 | "lint": { 107 | "builder": "@angular-eslint/builder:lint", 108 | "options": { 109 | "lintFilePatterns": [ 110 | "src/**/*.ts", 111 | "src/**/*.html" 112 | ] 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "defaultProject": "apm" 119 | } 120 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | 4 | import { BehaviorSubject, catchError, combineLatest, filter, forkJoin, map, merge, Observable, of, scan, shareReplay, Subject, switchMap, tap, throwError } from 'rxjs'; 5 | 6 | import { Product } from './product'; 7 | import { ProductCategoryService } from '../product-categories/product-category.service'; 8 | import { SupplierService } from '../suppliers/supplier.service'; 9 | import { Supplier } from '../suppliers/supplier'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class ProductService { 15 | private productsUrl = 'api/products'; 16 | private suppliersUrl = 'api/suppliers'; 17 | 18 | products$ = this.http.get(this.productsUrl) 19 | .pipe( 20 | tap(data => console.log('Products: ', JSON.stringify(data))), 21 | catchError(this.handleError) 22 | ); 23 | 24 | productsWithCategory$ = combineLatest([ 25 | this.products$, 26 | this.productCategoryService.productCategories$ 27 | ]).pipe( 28 | map(([products, categories]) => 29 | products.map(product => ({ 30 | ...product, 31 | price: product.price ? product.price * 1.5 : 0, 32 | category: categories.find(c => product.categoryId === c.id)?.name, 33 | searchKey: [product.productName] 34 | } as Product)) 35 | ), 36 | shareReplay(1) 37 | ); 38 | 39 | private productSelectedSubject = new BehaviorSubject(0); 40 | productSelectedAction$ = this.productSelectedSubject.asObservable(); 41 | 42 | selectedProduct$ = combineLatest([ 43 | this.productsWithCategory$, 44 | this.productSelectedAction$ 45 | ]).pipe( 46 | map(([products, selectedProductId]) => 47 | products.find(product => product.id === selectedProductId) 48 | ), 49 | tap(product => console.log('selectedProduct', product)), 50 | shareReplay(1) 51 | ); 52 | 53 | // selectedProductSuppliers$ = combineLatest([ 54 | // this.selectedProduct$, 55 | // this.supplierService.suppliers$ 56 | // ]).pipe( 57 | // map(([selectedProduct, suppliers]) => 58 | // suppliers.filter(supplier => selectedProduct?.supplierIds?.includes(supplier.id)) 59 | // ) 60 | // ); 61 | 62 | selectedProductSuppliers$ = this.selectedProduct$ 63 | .pipe( 64 | filter(product => Boolean(product)), 65 | switchMap(selectedProduct => { 66 | if (selectedProduct?.supplierIds) { 67 | return forkJoin(selectedProduct.supplierIds.map(supplierId => 68 | this.http.get(`${this.suppliersUrl}/${supplierId}`))) 69 | } else { 70 | return of([]); 71 | } 72 | }), 73 | tap(suppliers => console.log('product suppliers', JSON.stringify(suppliers))) 74 | ); 75 | 76 | private productInsertedSubject = new Subject(); 77 | productInsertedAction$ = this.productInsertedSubject.asObservable(); 78 | 79 | productsWithAdd$ = merge( 80 | this.productsWithCategory$, 81 | this.productInsertedAction$ 82 | ).pipe( 83 | scan((acc, value) => 84 | (value instanceof Array) ? [...value] : [...acc, value], [] as Product[]) 85 | ) 86 | 87 | constructor(private http: HttpClient, 88 | private productCategoryService: ProductCategoryService, 89 | private supplierService: SupplierService) { } 90 | 91 | addProduct(newProduct?: Product) { 92 | newProduct = newProduct || this.fakeProduct(); 93 | this.productInsertedSubject.next(newProduct); 94 | } 95 | 96 | selectedProductChanged(selectedProductId: number): void { 97 | this.productSelectedSubject.next(selectedProductId); 98 | } 99 | 100 | private fakeProduct(): Product { 101 | return { 102 | id: 42, 103 | productName: 'Another One', 104 | productCode: 'TBX-0042', 105 | description: 'Our new product', 106 | price: 8.9, 107 | categoryId: 3, 108 | category: 'Toolbox', 109 | quantityInStock: 30 110 | }; 111 | } 112 | 113 | private handleError(err: HttpErrorResponse): Observable { 114 | // in a real world app, we may send the server to some remote logging infrastructure 115 | // instead of just logging it to the console 116 | let errorMessage: string; 117 | if (err.error instanceof ErrorEvent) { 118 | // A client-side or network error occurred. Handle it accordingly. 119 | errorMessage = `An error occurred: ${err.error.message}`; 120 | } else { 121 | // The backend returned an unsuccessful response code. 122 | // The response body may contain clues as to what went wrong, 123 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 124 | } 125 | console.error(err); 126 | return throwError(() => errorMessage); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | 4 | import { BehaviorSubject, catchError, combineLatest, filter, forkJoin, map, merge, Observable, of, scan, shareReplay, Subject, switchMap, tap, throwError } from 'rxjs'; 5 | 6 | import { Product } from './product'; 7 | import { ProductCategoryService } from '../product-categories/product-category.service'; 8 | import { SupplierService } from '../suppliers/supplier.service'; 9 | import { Supplier } from '../suppliers/supplier'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class ProductService { 15 | private productsUrl = 'api/products'; 16 | private suppliersUrl = 'api/suppliers'; 17 | 18 | products$ = this.http.get(this.productsUrl) 19 | .pipe( 20 | tap(data => console.log('Products: ', JSON.stringify(data))), 21 | catchError(this.handleError) 22 | ); 23 | 24 | productsWithCategory$ = combineLatest([ 25 | this.products$, 26 | this.productCategoryService.productCategories$ 27 | ]).pipe( 28 | map(([products, categories]) => 29 | products.map(product => ({ 30 | ...product, 31 | price: product.price ? product.price * 1.5 : 0, 32 | category: categories.find(c => product.categoryId === c.id)?.name, 33 | searchKey: [product.productName] 34 | } as Product)) 35 | ), 36 | shareReplay(1) 37 | ); 38 | 39 | private productSelectedSubject = new BehaviorSubject(0); 40 | productSelectedAction$ = this.productSelectedSubject.asObservable(); 41 | 42 | selectedProduct$ = combineLatest([ 43 | this.productsWithCategory$, 44 | this.productSelectedAction$ 45 | ]).pipe( 46 | map(([products, selectedProductId]) => 47 | products.find(product => product.id === selectedProductId) 48 | ), 49 | tap(product => console.log('selectedProduct', product)), 50 | shareReplay(1) 51 | ); 52 | 53 | // Suppliers for product using the "get it all" approach 54 | // Rename to selectedProductSuppliers$ to use 55 | // Add a catchError so that the display appears 56 | // even if the suppliers cannot be retrieved. 57 | // Note that it must return an empty array and not EMPTY 58 | // or the stream will complete. 59 | selectedProductSuppliersGetAll$ = combineLatest([ 60 | this.selectedProduct$, 61 | this.supplierService.suppliers$ 62 | .pipe( 63 | catchError(err => of([] as Supplier[])) 64 | ) 65 | ]).pipe( 66 | map(([selectedProduct, suppliers]) => 67 | suppliers.filter(supplier => selectedProduct?.supplierIds?.includes(supplier.id)) 68 | ) 69 | ); 70 | 71 | // Suppliers for product using the "just in time" approach 72 | selectedProductSuppliers$ = this.selectedProduct$ 73 | .pipe( 74 | filter(product => Boolean(product)), 75 | switchMap(selectedProduct => { 76 | if (selectedProduct?.supplierIds) { 77 | return forkJoin(selectedProduct.supplierIds.map(supplierId => 78 | this.http.get(`${this.suppliersUrl}/${supplierId}`))) 79 | } else { 80 | return of([]); 81 | } 82 | }), 83 | tap(suppliers => console.log('product suppliers', JSON.stringify(suppliers))) 84 | ); 85 | 86 | private productInsertedSubject = new Subject(); 87 | productInsertedAction$ = this.productInsertedSubject.asObservable(); 88 | 89 | productsWithAdd$ = merge( 90 | this.productsWithCategory$, 91 | this.productInsertedAction$ 92 | ).pipe( 93 | scan((acc, value) => 94 | (value instanceof Array) ? [...value] : [...acc, value], [] as Product[]) 95 | ) 96 | 97 | constructor(private http: HttpClient, 98 | private productCategoryService: ProductCategoryService, 99 | private supplierService: SupplierService) { } 100 | 101 | addProduct(newProduct?: Product) { 102 | newProduct = newProduct || this.fakeProduct(); 103 | this.productInsertedSubject.next(newProduct); 104 | } 105 | 106 | selectedProductChanged(selectedProductId: number): void { 107 | this.productSelectedSubject.next(selectedProductId); 108 | } 109 | 110 | private fakeProduct(): Product { 111 | return { 112 | id: 42, 113 | productName: 'Another One', 114 | productCode: 'TBX-0042', 115 | description: 'Our new product', 116 | price: 8.9, 117 | categoryId: 3, 118 | category: 'Toolbox', 119 | quantityInStock: 30 120 | }; 121 | } 122 | 123 | private handleError(err: HttpErrorResponse): Observable { 124 | // in a real world app, we may send the server to some remote logging infrastructure 125 | // instead of just logging it to the console 126 | let errorMessage: string; 127 | if (err.error instanceof ErrorEvent) { 128 | // A client-side or network error occurred. Handle it accordingly. 129 | errorMessage = `An error occurred: ${err.error.message}`; 130 | } else { 131 | // The backend returned an unsuccessful response code. 132 | // The response body may contain clues as to what went wrong, 133 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 134 | } 135 | console.error(err); 136 | return throwError(() => errorMessage); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-reget/product-reget.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; 3 | 4 | import { BehaviorSubject, catchError, combineLatest, combineLatestWith, concatMap, map, merge, Observable, of, scan, shareReplay, Subject, tap, throwError } from 'rxjs'; 5 | 6 | import { Product } from '../product'; 7 | import { ProductCategoryService } from '../../product-categories/product-category.service'; 8 | import { Action } from '../../shared/edit-action'; 9 | 10 | /* 11 | Demonstrates create, update, and delete operations 12 | With a re-get to ensure fresh data after every 13 | operation. 14 | */ 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class ProductRegetService { 19 | private productsUrl = 'api/products'; 20 | private emptyProduct!: Product; 21 | 22 | private productsSubject = new BehaviorSubject([]); 23 | products$ = this.productsSubject.asObservable(); 24 | 25 | // Action Stream for adding/updating/deleting products 26 | private productModifiedSubject = new BehaviorSubject>({ 27 | item: this.emptyProduct, 28 | action: 'none' 29 | }); 30 | productModifiedAction$ = this.productModifiedSubject.asObservable(); 31 | 32 | // Save the product via http 33 | // And then reget the products to have fresh data. 34 | productsWithCRUD$ = merge( 35 | this.products$, 36 | this.productModifiedAction$ 37 | .pipe( 38 | concatMap(operation => this.saveProduct(operation)), 39 | concatMap(() => this.getProducts()) 40 | )); 41 | 42 | // Support methods 43 | 44 | getProducts(): Observable { 45 | return this.http.get(this.productsUrl) 46 | .pipe( 47 | combineLatestWith(this.productCategoryService.productCategories$), 48 | map(([products, categories]) => 49 | products.map(product => ({ 50 | ...product, 51 | price: product.price ? product.price * 1.5 : 0, 52 | category: categories.find(c => product.categoryId === c.id)?.name, 53 | searchKey: [product.productName] 54 | } as Product)) 55 | ), 56 | // Emit the data into the stream 57 | tap(productsWithCategories => this.productsSubject.next(productsWithCategories)), 58 | tap(data => console.log('Products: ', JSON.stringify(data))), 59 | catchError(this.handleError) 60 | ) 61 | }; 62 | 63 | // Save the product to the backend server 64 | // NOTE: This could be broken into three additional methods. 65 | headers = new HttpHeaders({ 'Content-Type': 'application/json' }); 66 | 67 | saveProduct(operation: Action): Observable> { 68 | const product = operation.item; 69 | console.log('saveProduct', JSON.stringify(operation.item)); 70 | if (operation.action === 'add') { 71 | // Assigning the id to null is required for the inmemory Web API 72 | // Return the product from the server 73 | return this.http.post(this.productsUrl, { ...product, id: null }, { headers: this.headers }) 74 | .pipe( 75 | map(product => ({ item: product, action: operation.action })), 76 | catchError(this.handleError) 77 | ); 78 | } 79 | if (operation.action === 'delete') { 80 | const url = `${this.productsUrl}/${product.id}`; 81 | return this.http.delete(url, { headers: this.headers }) 82 | .pipe( 83 | // Return the original product so it can be removed from the array 84 | map(() => ({ item: product, action: operation.action })), 85 | catchError(this.handleError) 86 | ); 87 | } 88 | if (operation.action === 'update') { 89 | const url = `${this.productsUrl}/${product.id}`; 90 | return this.http.put(url, product, { headers: this.headers }) 91 | .pipe( 92 | // Return the original product so it can replace the product in the array 93 | map(() => ({ item: product, action: operation.action })), 94 | catchError(this.handleError) 95 | ); 96 | } 97 | // If there is no operation, return the product 98 | return of(operation); 99 | } 100 | 101 | constructor(private http: HttpClient, 102 | private productCategoryService: ProductCategoryService) { 103 | } 104 | 105 | addProduct(newProduct?: Product): void { 106 | newProduct = newProduct || this.fakeProduct(); 107 | this.productModifiedSubject.next({ 108 | item: newProduct, 109 | action: 'add' 110 | }); 111 | } 112 | 113 | deleteProduct(selectedProduct: Product): void { 114 | this.productModifiedSubject.next({ 115 | item: selectedProduct, 116 | action: 'delete' 117 | }); 118 | } 119 | 120 | updateProduct(selectedProduct: Product): void { 121 | // Update a copy of the selected product 122 | const updatedProduct = { 123 | ...selectedProduct, 124 | quantityInStock: selectedProduct.quantityInStock ? selectedProduct.quantityInStock + 1 : 1 125 | } as Product; 126 | this.productModifiedSubject.next({ 127 | item: updatedProduct, 128 | action: 'update' 129 | }); 130 | } 131 | 132 | private fakeProduct(): Product { 133 | return { 134 | id: 42, 135 | productName: 'Another One', 136 | productCode: 'TBX-0042', 137 | description: 'Our new product', 138 | price: 8.9, 139 | categoryId: 3, 140 | category: 'Toolbox', 141 | quantityInStock: 30 142 | }; 143 | } 144 | 145 | private handleError(err: HttpErrorResponse): Observable { 146 | // in a real world app, we may send the server to some remote logging infrastructure 147 | // instead of just logging it to the console 148 | let errorMessage: string; 149 | if (err.error instanceof ErrorEvent) { 150 | // A client-side or network error occurred. Handle it accordingly. 151 | errorMessage = `An error occurred: ${err.error.message}`; 152 | } else { 153 | // The backend returned an unsuccessful response code. 154 | // The response body may contain clues as to what went wrong, 155 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 156 | } 157 | console.error(err); 158 | return throwError(() => errorMessage); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /APM-WithExtras/src/app/products/product-list-edit/product-edit.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; 3 | 4 | import { catchError, combineLatest, concatMap, map, merge, Observable, of, scan, shareReplay, Subject, tap, throwError } from 'rxjs'; 5 | 6 | import { Product } from '../product'; 7 | import { ProductCategoryService } from '../../product-categories/product-category.service'; 8 | import { Action } from '../../shared/edit-action'; 9 | 10 | /* 11 | Demonstrates create, update, and delete operations 12 | */ 13 | @Injectable({ 14 | providedIn: 'root' 15 | }) 16 | export class ProductEditService { 17 | private productsUrl = 'api/products'; 18 | 19 | products$ = this.http.get(this.productsUrl) 20 | .pipe( 21 | tap(data => console.log('Products: ', JSON.stringify(data))), 22 | catchError(this.handleError) 23 | ); 24 | 25 | productsWithCategory$ = combineLatest([ 26 | this.products$, 27 | this.productCategoryService.productCategories$ 28 | ]).pipe( 29 | map(([products, categories]) => 30 | products.map(product => ({ 31 | ...product, 32 | price: product.price ? product.price * 1.5 : 0, 33 | category: categories.find(c => product.categoryId === c.id)?.name, 34 | searchKey: [product.productName] 35 | } as Product)) 36 | ), 37 | shareReplay(1) 38 | ); 39 | 40 | // Action Stream for adding/updating/deleting products 41 | private productModifiedSubject = new Subject>(); 42 | productModifiedAction$ = this.productModifiedSubject.asObservable(); 43 | 44 | // Save the product via http 45 | // And then create and buffer a new array of products with scan. 46 | productsWithCRUD$ = merge( 47 | this.productsWithCategory$, 48 | this.productModifiedAction$ 49 | .pipe( 50 | concatMap(operation => this.saveProduct(operation)) 51 | )) 52 | .pipe( 53 | scan((acc, value) => 54 | (value instanceof Array) ? [...value] : this.modifyProducts(acc, value), [] as Product[]), 55 | shareReplay(1) 56 | ); 57 | 58 | // Support methods 59 | // Save the product to the backend server 60 | // NOTE: This could be broken into three additional methods. 61 | headers = new HttpHeaders({ 'Content-Type': 'application/json' }); 62 | 63 | saveProduct(operation: Action): Observable> { 64 | const product = operation.item; 65 | console.log('saveProduct', JSON.stringify(operation.item)); 66 | if (operation.action === 'add') { 67 | // Assigning the id to null is required for the inmemory Web API 68 | // Return the product from the server 69 | return this.http.post(this.productsUrl, { ...product, id: null }, { headers: this.headers }) 70 | .pipe( 71 | map(product => ({ item: product, action: operation.action })), 72 | catchError(this.handleError) 73 | ); 74 | } 75 | if (operation.action === 'delete') { 76 | const url = `${this.productsUrl}/${product.id}`; 77 | return this.http.delete(url, { headers: this.headers }) 78 | .pipe( 79 | // Return the original product so it can be removed from the array 80 | map(() => ({ item: product, action: operation.action })), 81 | catchError(this.handleError) 82 | ); 83 | } 84 | if (operation.action === 'update') { 85 | const url = `${this.productsUrl}/${product.id}`; 86 | return this.http.put(url, product, { headers: this.headers }) 87 | .pipe( 88 | tap(data => console.log('Updated Product: ' + JSON.stringify(data))), 89 | // Return the original product so it can replace the product in the array 90 | map(() => ({ item: product, action: operation.action })), 91 | catchError(this.handleError) 92 | ); 93 | } 94 | // If there is no operation, return the product 95 | return of(operation); 96 | } 97 | 98 | // Modify the array of products 99 | modifyProducts(products: Product[], operation: Action): Product[] { 100 | if (operation.action === 'add') { 101 | // Return a new array with the added product pushed to it 102 | return [...products, operation.item]; 103 | } else if (operation.action === 'update') { 104 | // Return a new array with the updated product replaced 105 | console.log('after modify', operation.item); 106 | return products.map(product => product.id === operation.item.id ? operation.item : product) 107 | } else if (operation.action === 'delete') { 108 | // Filter out the deleted product 109 | return products.filter(product => product.id !== operation.item.id); 110 | } 111 | return [...products]; 112 | } 113 | 114 | constructor(private http: HttpClient, 115 | private productCategoryService: ProductCategoryService) { 116 | } 117 | 118 | addProduct(newProduct?: Product): void { 119 | newProduct = newProduct || this.fakeProduct(); 120 | this.productModifiedSubject.next({ 121 | item: newProduct, 122 | action: 'add' 123 | }); 124 | } 125 | 126 | deleteProduct(selectedProduct: Product): void { 127 | this.productModifiedSubject.next({ 128 | item: selectedProduct, 129 | action: 'delete' 130 | }); 131 | } 132 | 133 | updateProduct(selectedProduct: Product): void { 134 | // Update a copy of the selected product 135 | const updatedProduct = { 136 | ...selectedProduct, 137 | quantityInStock: selectedProduct.quantityInStock ? selectedProduct.quantityInStock + 1 : 1 138 | } as Product; 139 | this.productModifiedSubject.next({ 140 | item: updatedProduct, 141 | action: 'update' 142 | }); 143 | } 144 | 145 | private fakeProduct(): Product { 146 | return { 147 | id: 42, 148 | productName: 'Another One', 149 | productCode: 'TBX-0042', 150 | description: 'Our new product', 151 | price: 8.9, 152 | categoryId: 3, 153 | category: 'Toolbox', 154 | quantityInStock: 30 155 | }; 156 | } 157 | 158 | private handleError(err: HttpErrorResponse): Observable { 159 | // in a real world app, we may send the server to some remote logging infrastructure 160 | // instead of just logging it to the console 161 | let errorMessage: string; 162 | if (err.error instanceof ErrorEvent) { 163 | // A client-side or network error occurred. Handle it accordingly. 164 | errorMessage = `An error occurred: ${err.error.message}`; 165 | } else { 166 | // The backend returned an unsuccessful response code. 167 | // The response body may contain clues as to what went wrong, 168 | errorMessage = `Backend returned code ${err.status}: ${err.message}`; 169 | } 170 | console.error(err); 171 | return throwError(() => errorMessage); 172 | } 173 | 174 | } 175 | --------------------------------------------------------------------------------