├── APM ├── src │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── saw.png │ │ │ ├── logo.jpg │ │ │ ├── hammer.png │ │ │ ├── leaf_rake.png │ │ │ ├── garden_cart.png │ │ │ └── xbox-controller.png │ ├── app │ │ ├── products │ │ │ ├── product-detail.component.css │ │ │ ├── product-list.component.css │ │ │ ├── product.ts │ │ │ ├── product-edit.guard.spec.ts │ │ │ ├── product-detail.component.spec.ts │ │ │ ├── product-edit.guard.ts │ │ │ ├── product-detail.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product-list.component.ts │ │ │ ├── product-data.ts │ │ │ ├── product-list.component.html │ │ │ ├── product-detail.component.html │ │ │ ├── product.service.ts │ │ │ ├── product-edit.component.html │ │ │ └── product-edit.component.ts │ │ ├── app.component.css │ │ ├── shared │ │ │ ├── star.component.css │ │ │ ├── shared.module.spec.ts │ │ │ ├── star.component.html │ │ │ ├── number.validator.ts │ │ │ ├── shared.module.ts │ │ │ ├── star.component.ts │ │ │ └── generic-validator.ts │ │ ├── home │ │ │ ├── welcome.component.ts │ │ │ └── welcome.component.html │ │ ├── app.component.spec.ts │ │ ├── app.module.ts │ │ └── app.component.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── styles.css │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── tsconfig.json ├── .vscode │ └── settings.json ├── .editorconfig ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.json │ └── protractor.conf.js ├── tsconfig.app.json ├── tsconfig.spec.json ├── tsconfig.base.json ├── .gitignore ├── .browserslistrc ├── karma.conf.js ├── readme.md ├── package.json ├── tslint.json └── angular.json ├── Demo-Final ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.css │ │ ├── customers │ │ │ ├── customer.component.css │ │ │ ├── customer.ts │ │ │ ├── customer.component.spec.ts │ │ │ ├── customer.component.ts │ │ │ └── customer.component.html │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── app.component.spec.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── styles.css │ ├── index.html │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── tsconfig.json ├── .vscode │ └── settings.json ├── .editorconfig ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.json │ └── protractor.conf.js ├── tsconfig.app.json ├── tsconfig.spec.json ├── tsconfig.base.json ├── .gitignore ├── .browserslistrc ├── karma.conf.js ├── readme.md ├── package.json ├── tslint.json └── angular.json ├── Demo-Start ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.css │ │ ├── customers │ │ │ ├── customer.component.css │ │ │ ├── customer.ts │ │ │ ├── customer.component.ts │ │ │ ├── customer.component.spec.ts │ │ │ └── customer.component.html │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── app.component.spec.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── styles.css │ ├── index.html │ ├── main.ts │ ├── test.ts │ └── polyfills.ts ├── .vscode │ └── settings.json ├── .editorconfig ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.json │ └── protractor.conf.js ├── tsconfig.app.json ├── tsconfig.spec.json ├── tsconfig.base.json ├── tsconfig.json ├── .gitignore ├── .browserslistrc ├── karma.conf.js ├── readme.md ├── package.json ├── tslint.json └── angular.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── LICENSE └── CHANGELOG.md /APM/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Final/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Start/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Final/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Start/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM/src/app/products/product-detail.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Final/src/app/customers/customer.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo-Start/src/app/customers/customer.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | } 4 | -------------------------------------------------------------------------------- /APM/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM/src/app/shared/star.component.css: -------------------------------------------------------------------------------- 1 | .crop { 2 | overflow: hidden; 3 | } 4 | div { 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /APM/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/favicon.ico -------------------------------------------------------------------------------- /APM/tsconfig.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/tsconfig.json -------------------------------------------------------------------------------- /Demo-Final/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /Demo-Start/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /Demo-Final/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /Demo-Start/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /Demo-Final/tsconfig.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/Demo-Final/tsconfig.json -------------------------------------------------------------------------------- /Demo-Final/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/Demo-Final/src/favicon.ico -------------------------------------------------------------------------------- /Demo-Start/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/Demo-Start/src/favicon.ico -------------------------------------------------------------------------------- /APM/src/assets/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/saw.png -------------------------------------------------------------------------------- /Demo-Final/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned" 4 | } -------------------------------------------------------------------------------- /Demo-Start/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned" 4 | } -------------------------------------------------------------------------------- /APM/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM/src/assets/images/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/hammer.png -------------------------------------------------------------------------------- /APM/src/assets/images/leaf_rake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/leaf_rake.png -------------------------------------------------------------------------------- /APM/src/assets/images/garden_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/garden_cart.png -------------------------------------------------------------------------------- /APM/src/assets/images/xbox-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandu-muthyala/Angular-ReactiveForms/master/APM/src/assets/images/xbox-controller.png -------------------------------------------------------------------------------- /APM/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned", 4 | "workbench.colorCustomizations": {} 5 | } -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/*.js": true, 7 | "**/*.map": true 8 | } 9 | } -------------------------------------------------------------------------------- /Demo-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 | } -------------------------------------------------------------------------------- /Demo-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 | } -------------------------------------------------------------------------------- /Demo-Final/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'Demo'; 10 | } 11 | -------------------------------------------------------------------------------- /Demo-Start/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'Demo'; 10 | } 11 | -------------------------------------------------------------------------------- /APM/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number; 4 | productName: string; 5 | productCode: string; 6 | tags?: string[]; 7 | releaseDate: string; 8 | price: number; 9 | description: string; 10 | starRating: number; 11 | imageUrl: string; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /APM/.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/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM/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 | @import "~font-awesome/css/font-awesome.min.css"; 4 | 5 | div.card-header { 6 | font-size: large; 7 | } 8 | 9 | div.card { 10 | margin-top: 10px 11 | } 12 | 13 | .table { 14 | margin-top: 10px 15 | } -------------------------------------------------------------------------------- /APM/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('pm-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /APM/src/app/shared/shared.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from './shared.module'; 2 | 3 | describe('SharedModule', () => { 4 | let sharedModule: SharedModule; 5 | 6 | beforeEach(() => { 7 | sharedModule = new SharedModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(sharedModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Demo-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 | -------------------------------------------------------------------------------- /Demo-Final/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demo-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 | -------------------------------------------------------------------------------- /Demo-Start/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demo-Final/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo-Start/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /APM/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /APM/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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 | -------------------------------------------------------------------------------- /Demo-Final/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Demo-Final/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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 | -------------------------------------------------------------------------------- /Demo-Start/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Demo-Start/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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/src/app/shared/star.component.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /Demo-Final/src/app/customers/customer.ts: -------------------------------------------------------------------------------- 1 | export class Customer { 2 | 3 | constructor( 4 | public firstName = '', 5 | public lastName = '', 6 | public email = '', 7 | public sendCatalog = false, 8 | public addressType = 'home', 9 | public street1?: string, 10 | public street2?: string, 11 | public city?: string, 12 | public state = '', 13 | public zip?: string) { } 14 | } 15 | -------------------------------------------------------------------------------- /Demo-Start/src/app/customers/customer.ts: -------------------------------------------------------------------------------- 1 | export class Customer { 2 | 3 | constructor( 4 | public firstName = '', 5 | public lastName = '', 6 | public email = '', 7 | public sendCatalog = false, 8 | public addressType = 'home', 9 | public street1?: string, 10 | public street2?: string, 11 | public city?: string, 12 | public state = '', 13 | public zip?: string) { } 14 | } 15 | -------------------------------------------------------------------------------- /APM/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/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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 | -------------------------------------------------------------------------------- /Demo-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 | -------------------------------------------------------------------------------- /Demo-Final/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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 | -------------------------------------------------------------------------------- /Demo-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 | -------------------------------------------------------------------------------- /Demo-Start/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.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/src/app/shared/number.validator.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, ValidatorFn } from '@angular/forms'; 2 | 3 | export class NumberValidators { 4 | 5 | static range(min: number, max: number): ValidatorFn { 6 | return (c: AbstractControl): { [key: string]: boolean } | null => { 7 | if (c.value && (isNaN(c.value) || c.value < min || c.value > max)) { 8 | return { range: true }; 9 | } 10 | return null; 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM/src/app/products/product-edit.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { ProductEditGuard } from './product-edit.guard'; 4 | 5 | describe('ProductEditGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [ProductEditGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([ProductEditGuard], (guard: ProductEditGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /APM/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { StarComponent } from './star.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | StarComponent 13 | ], 14 | exports: [ 15 | StarComponent, 16 | CommonModule, 17 | FormsModule 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /APM/tsconfig.base.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 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo-Start/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { CustomerComponent } from './customers/customer.component'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent, 11 | CustomerComponent 12 | ], 13 | imports: [ 14 | BrowserModule, 15 | FormsModule 16 | ], 17 | bootstrap: [AppComponent] 18 | }) 19 | export class AppModule { } 20 | -------------------------------------------------------------------------------- /Demo-Final/tsconfig.base.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 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo-Start/tsconfig.base.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 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo-Start/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | }, 16 | { 17 | "path": "./e2e/tsconfig.json" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Demo-Final/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { CustomerComponent } from './customers/customer.component'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent, 11 | CustomerComponent 12 | ], 13 | imports: [ 14 | BrowserModule, 15 | ReactiveFormsModule 16 | ], 17 | bootstrap: [AppComponent] 18 | }) 19 | export class AppModule { } 20 | -------------------------------------------------------------------------------- /APM/src/app/shared/star.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-star', 5 | templateUrl: './star.component.html', 6 | styleUrls: ['./star.component.css'] 7 | }) 8 | export class StarComponent implements OnChanges { 9 | @Input() rating = 0; 10 | starWidth = 0; 11 | @Output() ratingClicked: EventEmitter = 12 | new EventEmitter(); 13 | 14 | ngOnChanges(): void { 15 | this.starWidth = this.rating * 75 / 5; 16 | } 17 | 18 | onClick(): void { 19 | this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo-Start/src/app/customers/customer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { Customer } from './customer'; 5 | 6 | @Component({ 7 | selector: 'app-customer', 8 | templateUrl: './customer.component.html', 9 | styleUrls: ['./customer.component.css'] 10 | }) 11 | export class CustomerComponent implements OnInit { 12 | customer = new Customer(); 13 | 14 | constructor() { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | save(customerForm: NgForm): void { 20 | console.log(customerForm.form); 21 | console.log('Saved: ' + JSON.stringify(customerForm.value)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Reactive Forms 2 | Materials for my Pluralsight course: ["Angular Reactive Forms"](https://app.pluralsight.com/library/courses/angular-2-reactive-forms). 3 | 4 | `Demo-Start`: The starter files. **Use this to code along with the course**. 5 | 6 | `Demo-Final`: The completed files. Use this to see the completed solution from the course. 7 | 8 | `APM`: Angular reactive form in the context of a more full-featured application. Includes examples of CRUD (Create, Read, Update, and Delete) operations. 9 | 10 | See the `README.md` file under each folder for details on installing and running the application. 11 | 12 | Please see the `CHANGELOG.md` for the most recent changes to this repo. 13 | -------------------------------------------------------------------------------- /Demo-Final/src/app/customers/customer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CustomerComponent } from './customer.component'; 4 | 5 | describe('CustomerComponent', () => { 6 | let component: CustomerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CustomerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CustomerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /Demo-Start/src/app/customers/customer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CustomerComponent } from './customer.component'; 4 | 5 | describe('CustomerComponent', () => { 6 | let component: CustomerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CustomerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CustomerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /APM/src/app/products/product-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductDetailComponent } from './product-detail.component'; 4 | 5 | describe('ProductDetailComponent', () => { 6 | let component: ProductDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /APM/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` 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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('APM app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /APM/src/app/products/product-edit.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanDeactivate } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ProductEditComponent } from './product-edit.component'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ProductEditGuard implements CanDeactivate { 11 | canDeactivate(component: ProductEditComponent): Observable | Promise | boolean { 12 | if (component.productForm.dirty) { 13 | const productName = component.productForm.get('productName').value || 'New Product'; 14 | return confirm(`Navigate away and lose all changes to ${productName}?`); 15 | } 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo-Final/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Demo app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /Demo-Final/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` 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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /Demo-Start/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Demo app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /Demo-Start/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` 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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM/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 |
@deborahkurata
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /Demo-Final/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'Demo'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('Demo'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /Demo-Start/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'Demo'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('Demo'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /APM/.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 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /Demo-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 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /Demo-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 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /APM/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'Acme Product Management'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.pageTitle).toEqual('Acme Product Management'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /APM/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/dist/zone-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 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /Demo-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/dist/zone-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 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /Demo-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/dist/zone-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 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /APM/.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 version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /APM/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { ProductModule } from './products/product.module'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | WelcomeComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | HttpClientModule, 18 | RouterModule.forRoot([ 19 | { path: 'welcome', component: WelcomeComponent }, 20 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 21 | { path: '**', redirectTo: 'welcome', pathMatch: 'full' } 22 | ]), 23 | ProductModule 24 | ], 25 | bootstrap: [AppComponent] 26 | }) 27 | export class AppModule { } 28 | -------------------------------------------------------------------------------- /Demo-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 version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /Demo-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 version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /APM/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /Demo-Final/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /Demo-Start/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /APM/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | template: ` 6 | 20 |
21 | 22 |
23 | `, 24 | styleUrls: ['./app.component.css'] 25 | }) 26 | export class AppComponent { 27 | pageTitle = 'Acme Product Management'; 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 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/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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/APM'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /Demo-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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/Demo'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /Demo-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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/Demo'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /Demo-Final/readme.md: -------------------------------------------------------------------------------- 1 | # Demo-Final: These are the final files for the Angular Reactive Forms demo form. 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 4 | 5 | ## Development server 6 | 7 | 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. 8 | 9 | ## Code scaffolding 10 | 11 | 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`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /APM/readme.md: -------------------------------------------------------------------------------- 1 | # APM: These are the files showing an Angular Reactive Form in the context of an application. 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 4 | 5 | ## Development server 6 | 7 | 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. 8 | 9 | ## Code scaffolding 10 | 11 | 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`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /Demo-Start/readme.md: -------------------------------------------------------------------------------- 1 | # Demo-Start: These are the starter files for the Angular Reactive Forms demo form. 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 4 | 5 | ## Development server 6 | 7 | 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. 8 | 9 | ## Code scaffolding 10 | 11 | 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`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /APM/src/app/products/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | 4 | import { Product } from './product'; 5 | import { ProductService } from './product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-detail.component.html', 9 | styleUrls: ['./product-detail.component.css'] 10 | }) 11 | export class ProductDetailComponent implements OnInit { 12 | pageTitle = 'Product Detail'; 13 | errorMessage = ''; 14 | product: Product | undefined; 15 | 16 | constructor(private route: ActivatedRoute, 17 | private router: Router, 18 | private productService: ProductService) { 19 | } 20 | 21 | ngOnInit(): void { 22 | const param = this.route.snapshot.paramMap.get('id'); 23 | if (param) { 24 | const id = +param; 25 | this.getProduct(id); 26 | } 27 | } 28 | 29 | getProduct(id: number): void { 30 | this.productService.getProduct(id).subscribe({ 31 | next: product => this.product = product, 32 | error: err => this.errorMessage = err 33 | }); 34 | } 35 | 36 | onBack(): void { 37 | this.router.navigate(['/products']); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /APM/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 { SharedModule } from '../shared/shared.module'; 6 | 7 | // Imports for loading & configuring the in-memory web api 8 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 9 | import { ProductData } from './product-data'; 10 | 11 | import { ProductListComponent } from './product-list.component'; 12 | import { ProductDetailComponent } from './product-detail.component'; 13 | import { ProductEditComponent } from './product-edit.component'; 14 | import { ProductEditGuard } from './product-edit.guard'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | ReactiveFormsModule, 20 | InMemoryWebApiModule.forRoot(ProductData), 21 | RouterModule.forChild([ 22 | { path: 'products', component: ProductListComponent }, 23 | { path: 'products/:id', component: ProductDetailComponent }, 24 | { 25 | path: 'products/:id/edit', 26 | canDeactivate: [ProductEditGuard], 27 | component: ProductEditComponent 28 | } 29 | ]) 30 | ], 31 | declarations: [ 32 | ProductListComponent, 33 | ProductDetailComponent, 34 | ProductEditComponent 35 | ] 36 | }) 37 | export class ProductModule { } 38 | -------------------------------------------------------------------------------- /Demo-Final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.3", 15 | "@angular/common": "~10.0.3", 16 | "@angular/compiler": "~10.0.3", 17 | "@angular/core": "~10.0.3", 18 | "@angular/forms": "~10.0.3", 19 | "@angular/platform-browser": "~10.0.3", 20 | "@angular/platform-browser-dynamic": "~10.0.3", 21 | "@angular/router": "~10.0.3", 22 | "bootstrap": "^4.5.0", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.3" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1000.1", 30 | "@angular/cli": "~10.0.1", 31 | "@angular/compiler-cli": "~10.0.3", 32 | "@types/node": "^12.11.1", 33 | "@types/jasmine": "~3.5.0", 34 | "@types/jasminewd2": "~2.0.3", 35 | "codelyzer": "^5.1.2", 36 | "jasmine-core": "~3.5.0", 37 | "jasmine-spec-reporter": "~5.0.0", 38 | "karma": "~5.0.0", 39 | "karma-chrome-launcher": "~3.1.0", 40 | "karma-coverage-istanbul-reporter": "~3.0.2", 41 | "karma-jasmine": "~3.3.0", 42 | "karma-jasmine-html-reporter": "^1.5.0", 43 | "protractor": "~7.0.0", 44 | "ts-node": "~8.3.0", 45 | "tslint": "~6.1.0", 46 | "typescript": "~3.9.6" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Demo-Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.3", 15 | "@angular/common": "~10.0.3", 16 | "@angular/compiler": "~10.0.3", 17 | "@angular/core": "~10.0.3", 18 | "@angular/forms": "~10.0.3", 19 | "@angular/platform-browser": "~10.0.3", 20 | "@angular/platform-browser-dynamic": "~10.0.3", 21 | "@angular/router": "~10.0.3", 22 | "bootstrap": "^4.5.0", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.3" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1000.1", 30 | "@angular/cli": "~10.0.1", 31 | "@angular/compiler-cli": "~10.0.3", 32 | "@types/node": "^12.11.1", 33 | "@types/jasmine": "~3.5.0", 34 | "@types/jasminewd2": "~2.0.3", 35 | "codelyzer": "^5.1.2", 36 | "jasmine-core": "~3.5.0", 37 | "jasmine-spec-reporter": "~5.0.0", 38 | "karma": "~5.0.0", 39 | "karma-chrome-launcher": "~3.1.0", 40 | "karma-coverage-istanbul-reporter": "~3.0.2", 41 | "karma-jasmine": "~3.3.0", 42 | "karma-jasmine-html-reporter": "^1.5.0", 43 | "protractor": "~7.0.0", 44 | "ts-node": "~8.3.0", 45 | "tslint": "~6.1.0", 46 | "typescript": "~3.9.6" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /APM/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 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.3", 15 | "@angular/common": "~10.0.3", 16 | "@angular/compiler": "~10.0.3", 17 | "@angular/core": "~10.0.3", 18 | "@angular/forms": "~10.0.3", 19 | "@angular/platform-browser": "~10.0.3", 20 | "@angular/platform-browser-dynamic": "~10.0.3", 21 | "@angular/router": "~10.0.3", 22 | "bootstrap": "^4.5.0", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.3" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1000.1", 30 | "@angular/cli": "~10.0.1", 31 | "@angular/compiler-cli": "~10.0.3", 32 | "@types/node": "^12.11.1", 33 | "@types/jasmine": "~3.5.0", 34 | "@types/jasminewd2": "~2.0.3", 35 | "angular-in-memory-web-api": "^0.11.0", 36 | "codelyzer": "^5.1.2", 37 | "jasmine-core": "~3.5.0", 38 | "jasmine-spec-reporter": "~5.0.0", 39 | "karma": "~5.0.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage-istanbul-reporter": "~3.0.2", 42 | "karma-jasmine": "~3.3.0", 43 | "karma-jasmine-html-reporter": "^1.5.0", 44 | "protractor": "~7.0.0", 45 | "ts-node": "~8.3.0", 46 | "tslint": "~6.1.0", 47 | "typescript": "~3.9.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /APM/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Product } from './product'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | templateUrl: './product-list.component.html', 8 | styleUrls: ['./product-list.component.css'] 9 | }) 10 | export class ProductListComponent implements OnInit { 11 | pageTitle = 'Product List'; 12 | imageWidth = 50; 13 | imageMargin = 2; 14 | showImage = false; 15 | errorMessage = ''; 16 | 17 | _listFilter = ''; 18 | get listFilter(): string { 19 | return this._listFilter; 20 | } 21 | set listFilter(value: string) { 22 | this._listFilter = value; 23 | this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products; 24 | } 25 | 26 | filteredProducts: Product[] = []; 27 | products: Product[] = []; 28 | 29 | constructor(private productService: ProductService) { } 30 | 31 | performFilter(filterBy: string): Product[] { 32 | filterBy = filterBy.toLocaleLowerCase(); 33 | return this.products.filter((product: Product) => 34 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1); 35 | } 36 | 37 | // Checks both the product name and tags 38 | performFilter2(filterBy: string): Product[] { 39 | filterBy = filterBy.toLocaleLowerCase(); 40 | return this.products.filter((product: Product) => 41 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1 || 42 | (product.tags && product.tags.some(tag => tag.toLocaleLowerCase().indexOf(filterBy) !== -1))); 43 | } 44 | 45 | toggleImage(): void { 46 | this.showImage = !this.showImage; 47 | } 48 | 49 | ngOnInit(): void { 50 | this.productService.getProducts().subscribe({ 51 | next: products => { 52 | this.products = products; 53 | this.filteredProducts = this.products; 54 | }, 55 | error: err => this.errorMessage = err 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /APM/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { Product } from './product'; 4 | 5 | export class ProductData implements InMemoryDbService { 6 | 7 | createDb(): { products: Product[]} { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | releaseDate: 'March 19, 2018', 14 | description: 'Leaf rake with 48-inch wooden handle', 15 | price: 19.95, 16 | starRating: 3.2, 17 | imageUrl: 'assets/images/leaf_rake.png', 18 | tags: ['rake', 'leaf', 'yard', 'home'] 19 | }, 20 | { 21 | id: 2, 22 | productName: 'Garden Cart', 23 | productCode: 'GDN-0023', 24 | releaseDate: 'March 18, 2018', 25 | description: '15 gallon capacity rolling garden cart', 26 | price: 32.99, 27 | starRating: 4.2, 28 | imageUrl: 'assets/images/garden_cart.png' 29 | }, 30 | { 31 | id: 5, 32 | productName: 'Hammer', 33 | productCode: 'TBX-0048', 34 | releaseDate: 'May 21, 2018', 35 | description: 'Curved claw steel hammer', 36 | price: 8.9, 37 | starRating: 4.8, 38 | imageUrl: 'assets/images/hammer.png', 39 | tags: ['tools', 'hammer', 'construction'] 40 | }, 41 | { 42 | id: 8, 43 | productName: 'Saw', 44 | productCode: 'TBX-0022', 45 | releaseDate: 'May 15, 2018', 46 | description: '15-inch steel blade hand saw', 47 | price: 11.55, 48 | starRating: 3.7, 49 | imageUrl: 'assets/images/saw.png' 50 | }, 51 | { 52 | id: 10, 53 | productName: 'Video Game Controller', 54 | productCode: 'GMG-0042', 55 | releaseDate: 'October 15, 2018', 56 | description: 'Standard two-button video game controller', 57 | price: 35.95, 58 | starRating: 4.6, 59 | imageUrl: 'assets/images/xbox-controller.png' 60 | } 61 | ]; 62 | return { products }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /APM/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
8 |
Filter by:
9 |
10 | 12 |
13 |
14 |
16 |
17 |

Filtered by: {{listFilter}}

18 |
19 |
20 | 21 |
22 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 54 | 55 | 56 | 57 | 61 | 67 | 68 | 69 |
27 | 31 | ProductCodeAvailablePrice5 Star Rating
43 | 48 | 50 | 51 | {{ product.productName }} 52 | 53 | {{ product.productCode }}{{ product.releaseDate }}{{ product.price | currency:"USD":"symbol":"1.2-2" }} 58 | 59 | 60 | 62 | 66 |
70 |
71 | 72 |
73 |
74 | 75 |
77 | Error: {{ errorMessage }} 78 |
-------------------------------------------------------------------------------- /APM/src/app/products/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
4 | {{pageTitle + ": " + product.productName}} 5 |
6 | 7 |
9 | 10 |
11 | 12 |
13 |
14 |
Name:
15 |
{{product.productName}}
16 |
17 |
18 |
Code:
19 |
{{product.productCode}}
20 |
21 |
22 |
Description:
23 |
{{product.description}}
24 |
25 |
26 |
Availability:
27 |
{{product.releaseDate}}
28 |
29 |
30 |
Price:
31 |
{{product.price|currency:"USD":"symbol"}}
32 |
33 |
34 |
5 Star Rating:
35 |
36 | 37 | 38 |
39 |
40 |
41 |
Tags:
42 |
{{product.tags}}
43 |
44 |
45 | 46 |
48 | 53 |
54 |
55 | 56 |
57 |
58 | 63 | 68 |
69 |
70 | 71 |
72 | 73 |
{{errorMessage}} 75 |
76 |
-------------------------------------------------------------------------------- /APM/src/app/shared/generic-validator.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup } from '@angular/forms'; 2 | 3 | // Generic validator for Reactive forms 4 | // Implemented as a class, not a service, so it can retain state for multiple forms. 5 | // NOTE: This validator does NOT support validation of controls or groups within a FormArray. 6 | export class GenericValidator { 7 | 8 | // Provide the set of valid validation messages 9 | // Stucture: 10 | // controlName1: { 11 | // validationRuleName1: 'Validation Message.', 12 | // validationRuleName2: 'Validation Message.' 13 | // }, 14 | // controlName2: { 15 | // validationRuleName1: 'Validation Message.', 16 | // validationRuleName2: 'Validation Message.' 17 | // } 18 | constructor(private validationMessages: { [key: string]: { [key: string]: string } }) { 19 | 20 | } 21 | 22 | // Processes each control within a FormGroup 23 | // And returns a set of validation messages to display 24 | // Structure 25 | // controlName1: 'Validation Message.', 26 | // controlName2: 'Validation Message.' 27 | processMessages(container: FormGroup): { [key: string]: string } { 28 | const messages = {}; 29 | for (const controlKey in container.controls) { 30 | if (container.controls.hasOwnProperty(controlKey)) { 31 | const c = container.controls[controlKey]; 32 | // If it is a FormGroup, process its child controls. 33 | if (c instanceof FormGroup) { 34 | const childMessages = this.processMessages(c); 35 | Object.assign(messages, childMessages); 36 | } else { 37 | // Only validate if there are validation messages for the control 38 | if (this.validationMessages[controlKey]) { 39 | messages[controlKey] = ''; 40 | if ((c.dirty || c.touched) && c.errors) { 41 | Object.keys(c.errors).map(messageKey => { 42 | if (this.validationMessages[controlKey][messageKey]) { 43 | messages[controlKey] += this.validationMessages[controlKey][messageKey] + ' '; 44 | } 45 | }); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | return messages; 52 | } 53 | 54 | getErrorCount(container: FormGroup): number { 55 | let errorCount = 0; 56 | for (const controlKey in container.controls) { 57 | if (container.controls.hasOwnProperty(controlKey)) { 58 | if (container.controls[controlKey].errors) { 59 | errorCount += Object.keys(container.controls[controlKey].errors).length; 60 | console.log(errorCount); 61 | } 62 | } 63 | } 64 | return errorCount; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changes made to the course since its release: 2 | - July 9, 2020 - Reviewed the code in the course for any changes required for v10. No code changes required. 3 | 4 | - February 2, 2020 - Reviewed the code in the course for any changes required for v9. No code changes required. 5 | 6 | - September 5, 2019 - Updated the course files from Angular v7 to Angular v8. For details on the list of course changes, see: https://docs.google.com/document/d/1HzZdEhbOC-1OVNym2ZIwLahuBi-YUo4ZqV_f2w-eig0/edit?usp=sharing The primary changes were in the `Create, Read, Update, and Delete (CRUD) Using HTTP` module and involve changes to the subscribe method. 7 | 8 | - November 6, 2018 - Updated the course from Angular v2 to Angular v7 including an update to the Angular CLI, Bootstrap 4, and FontAwesome. For details on the list of course changes, see: https://blogs.msmvps.com/deborahk/angular-reactive-forms-course-update-for-v7/ 9 | 10 | #Changes made to the project files since its release: 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. 14 | 15 | - September 5, 2019 - Updated the course files from Angular v7 to Angular v8. Also moved the images to a local folder so the course no longer depends on a third-party web site for the images. 16 | 17 | - November 6, 2018 - Updated the course files from Angular v2 to Angular v7 including an update to the Angular CLI, Bootstrap 4, and FontAwesome. 18 | - November 6, 2018 - Removed the xxx-Updated files as the code is currently up to date. 19 | 20 | - July 27, 2018 - APM-Updated were modified to Angular v6, RxJS v6, Bootrap v4, and FontAwesome. The changes include moving the code from systemjs to the Angular CLI and all of the new style classes for Bootrap 4. 21 | - July 27, 2018 - Demo-Start-Updated and Demo-Final-Updated were modified to Angular v6 and Bootrap v4. The changes include moving the code from systemjs to the Angular CLI and all of the new style classes for Bootrap 4. 22 | 23 | - March 24, 2017 - APM-Updated was added. This is an update to the sample application for Angular version 4. The changes include modifications to the boilerplate files and the addition of an src folder to make these files compatible with the current version of the Angular Quick Start files. 24 | - March 24, 2017 - Demo-Start-Updated and Demo-Final-Updated were added. These are an update to the demo application for Angular version 4. The changes include modifications to the boilerplate files and the addition of an src folder to make these files compatible with the current version of the Angular Quick Start files. 25 | -------------------------------------------------------------------------------- /APM/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 Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /Demo-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 Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /Demo-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 Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /APM/src/app/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | 4 | import { Observable, of, throwError } from 'rxjs'; 5 | import { catchError, tap, map } from 'rxjs/operators'; 6 | 7 | import { Product } from './product'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class ProductService { 13 | private productsUrl = 'api/products'; 14 | 15 | constructor(private http: HttpClient) { } 16 | 17 | getProducts(): Observable { 18 | return this.http.get(this.productsUrl) 19 | .pipe( 20 | tap(data => console.log(JSON.stringify(data))), 21 | catchError(this.handleError) 22 | ); 23 | } 24 | 25 | getProduct(id: number): Observable { 26 | if (id === 0) { 27 | return of(this.initializeProduct()); 28 | } 29 | const url = `${this.productsUrl}/${id}`; 30 | return this.http.get(url) 31 | .pipe( 32 | tap(data => console.log('getProduct: ' + JSON.stringify(data))), 33 | catchError(this.handleError) 34 | ); 35 | } 36 | 37 | createProduct(product: Product): Observable { 38 | const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); 39 | product.id = null; 40 | return this.http.post(this.productsUrl, product, { headers }) 41 | .pipe( 42 | tap(data => console.log('createProduct: ' + JSON.stringify(data))), 43 | catchError(this.handleError) 44 | ); 45 | } 46 | 47 | deleteProduct(id: number): Observable<{}> { 48 | const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); 49 | const url = `${this.productsUrl}/${id}`; 50 | return this.http.delete(url, { headers }) 51 | .pipe( 52 | tap(data => console.log('deleteProduct: ' + id)), 53 | catchError(this.handleError) 54 | ); 55 | } 56 | 57 | updateProduct(product: Product): Observable { 58 | const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); 59 | const url = `${this.productsUrl}/${product.id}`; 60 | return this.http.put(url, product, { headers }) 61 | .pipe( 62 | tap(() => console.log('updateProduct: ' + product.id)), 63 | // Return the product on an update 64 | map(() => product), 65 | catchError(this.handleError) 66 | ); 67 | } 68 | 69 | private handleError(err): Observable { 70 | // in a real world app, we may send the server to some remote logging infrastructure 71 | // instead of just logging it to the console 72 | let errorMessage: string; 73 | if (err.error instanceof ErrorEvent) { 74 | // A client-side or network error occurred. Handle it accordingly. 75 | errorMessage = `An error occurred: ${err.error.message}`; 76 | } else { 77 | // The backend returned an unsuccessful response code. 78 | // The response body may contain clues as to what went wrong, 79 | errorMessage = `Backend returned code ${err.status}: ${err.body.error}`; 80 | } 81 | console.error(err); 82 | return throwError(errorMessage); 83 | } 84 | 85 | private initializeProduct(): Product { 86 | // Return an initialized object 87 | return { 88 | id: 0, 89 | productName: null, 90 | productCode: null, 91 | tags: [''], 92 | releaseDate: null, 93 | price: null, 94 | description: null, 95 | starRating: null, 96 | imageUrl: null 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Demo-Final/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef": [ 98 | true, 99 | "call-signature" 100 | ], 101 | "typedef-whitespace": { 102 | "options": [ 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | }, 110 | { 111 | "call-signature": "onespace", 112 | "index-signature": "onespace", 113 | "parameter": "onespace", 114 | "property-declaration": "onespace", 115 | "variable-declaration": "onespace" 116 | } 117 | ] 118 | }, 119 | "variable-name": { 120 | "options": [ 121 | "ban-keywords", 122 | "check-format", 123 | "allow-pascal-case" 124 | ] 125 | }, 126 | "whitespace": { 127 | "options": [ 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type", 133 | "check-typecast" 134 | ] 135 | }, 136 | "no-conflicting-lifecycle": true, 137 | "no-host-metadata-property": true, 138 | "no-input-rename": true, 139 | "no-inputs-metadata-property": true, 140 | "no-output-native": true, 141 | "no-output-on-prefix": true, 142 | "no-output-rename": true, 143 | "no-outputs-metadata-property": true, 144 | "template-banana-in-box": true, 145 | "template-no-negated-async": true, 146 | "use-lifecycle-interface": true, 147 | "use-pipe-transform-interface": true 148 | }, 149 | "rulesDirectory": [ 150 | "codelyzer" 151 | ] 152 | } -------------------------------------------------------------------------------- /Demo-Start/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef": [ 98 | true, 99 | "call-signature" 100 | ], 101 | "typedef-whitespace": { 102 | "options": [ 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | }, 110 | { 111 | "call-signature": "onespace", 112 | "index-signature": "onespace", 113 | "parameter": "onespace", 114 | "property-declaration": "onespace", 115 | "variable-declaration": "onespace" 116 | } 117 | ] 118 | }, 119 | "variable-name": { 120 | "options": [ 121 | "ban-keywords", 122 | "check-format", 123 | "allow-pascal-case" 124 | ] 125 | }, 126 | "whitespace": { 127 | "options": [ 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type", 133 | "check-typecast" 134 | ] 135 | }, 136 | "no-conflicting-lifecycle": true, 137 | "no-host-metadata-property": true, 138 | "no-input-rename": true, 139 | "no-inputs-metadata-property": true, 140 | "no-output-native": true, 141 | "no-output-on-prefix": true, 142 | "no-output-rename": true, 143 | "no-outputs-metadata-property": true, 144 | "template-banana-in-box": true, 145 | "template-no-negated-async": true, 146 | "use-lifecycle-interface": true, 147 | "use-pipe-transform-interface": true 148 | }, 149 | "rulesDirectory": [ 150 | "codelyzer" 151 | ] 152 | } -------------------------------------------------------------------------------- /APM/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "pm", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "pm", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef": [ 98 | true, 99 | "call-signature" 100 | ], 101 | "typedef-whitespace": { 102 | "options": [ 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | }, 110 | { 111 | "call-signature": "onespace", 112 | "index-signature": "onespace", 113 | "parameter": "onespace", 114 | "property-declaration": "onespace", 115 | "variable-declaration": "onespace" 116 | } 117 | ] 118 | }, 119 | "variable-name": { 120 | "options": [ 121 | "ban-keywords", 122 | "check-format", 123 | "allow-pascal-case", 124 | "allow-leading-underscore" 125 | ] 126 | }, 127 | "whitespace": { 128 | "options": [ 129 | "check-branch", 130 | "check-decl", 131 | "check-operator", 132 | "check-separator", 133 | "check-type", 134 | "check-typecast" 135 | ] 136 | }, 137 | "no-conflicting-lifecycle": true, 138 | "no-host-metadata-property": true, 139 | "no-input-rename": true, 140 | "no-inputs-metadata-property": true, 141 | "no-output-native": true, 142 | "no-output-on-prefix": true, 143 | "no-output-rename": true, 144 | "no-outputs-metadata-property": true, 145 | "template-banana-in-box": true, 146 | "template-no-negated-async": true, 147 | "use-lifecycle-interface": true, 148 | "use-pipe-transform-interface": true 149 | }, 150 | "rulesDirectory": [ 151 | "codelyzer" 152 | ] 153 | } -------------------------------------------------------------------------------- /APM/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "APM": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "pm", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/APM", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "6kb", 56 | "maximumError": "10kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "APM:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "APM:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "APM:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "tsconfig.spec.json", 85 | "karmaConfig": "karma.conf.js", 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ], 90 | "styles": [ 91 | "src/styles.css" 92 | ], 93 | "scripts": [] 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": [ 100 | "tsconfig.app.json", 101 | "tsconfig.spec.json", 102 | "e2e/tsconfig.json" 103 | ], 104 | "exclude": [ 105 | "**/node_modules/**" 106 | ] 107 | } 108 | }, 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "APM:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "APM:serve:production" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "APM" 125 | } -------------------------------------------------------------------------------- /Demo-Final/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "Demo": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/Demo", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "6kb", 56 | "maximumError": "10kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "Demo:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "Demo:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "Demo:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "tsconfig.spec.json", 85 | "karmaConfig": "karma.conf.js", 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ], 90 | "styles": [ 91 | "src/styles.css" 92 | ], 93 | "scripts": [] 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": [ 100 | "tsconfig.app.json", 101 | "tsconfig.spec.json", 102 | "e2e/tsconfig.json" 103 | ], 104 | "exclude": [ 105 | "**/node_modules/**" 106 | ] 107 | } 108 | }, 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "Demo:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "Demo:serve:production" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "Demo" 125 | } -------------------------------------------------------------------------------- /Demo-Start/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "Demo": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/Demo", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "6kb", 56 | "maximumError": "10kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "Demo:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "Demo:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "Demo:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "tsconfig.spec.json", 85 | "karmaConfig": "karma.conf.js", 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ], 90 | "styles": [ 91 | "src/styles.css" 92 | ], 93 | "scripts": [] 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": [ 100 | "tsconfig.app.json", 101 | "tsconfig.spec.json", 102 | "e2e/tsconfig.json" 103 | ], 104 | "exclude": [ 105 | "**/node_modules/**" 106 | ] 107 | } 108 | }, 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "Demo:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "Demo:serve:production" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "Demo" 125 | } -------------------------------------------------------------------------------- /Demo-Final/src/app/customers/customer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators, AbstractControl, ValidatorFn, FormArray } from '@angular/forms'; 3 | 4 | import { debounceTime } from 'rxjs/operators'; 5 | 6 | import { Customer } from './customer'; 7 | 8 | function emailMatcher(c: AbstractControl): { [key: string]: boolean } | null { 9 | const emailControl = c.get('email'); 10 | const confirmControl = c.get('confirmEmail'); 11 | 12 | if (emailControl.pristine || confirmControl.pristine) { 13 | return null; 14 | } 15 | 16 | if (emailControl.value === confirmControl.value) { 17 | return null; 18 | } 19 | return { match: true }; 20 | } 21 | 22 | function ratingRange(min: number, max: number): ValidatorFn { 23 | return (c: AbstractControl): { [key: string]: boolean } | null => { 24 | if (c.value !== null && (isNaN(c.value) || c.value < min || c.value > max)) { 25 | return { range: true }; 26 | } 27 | return null; 28 | }; 29 | } 30 | 31 | @Component({ 32 | selector: 'app-customer', 33 | templateUrl: './customer.component.html', 34 | styleUrls: ['./customer.component.css'] 35 | }) 36 | export class CustomerComponent implements OnInit { 37 | customerForm: FormGroup; 38 | customer = new Customer(); 39 | emailMessage: string; 40 | 41 | get addresses(): FormArray { 42 | return this.customerForm.get('addresses') as FormArray; 43 | } 44 | 45 | private validationMessages = { 46 | required: 'Please enter your email address.', 47 | email: 'Please enter a valid email address.' 48 | }; 49 | 50 | constructor(private fb: FormBuilder) { } 51 | 52 | ngOnInit(): void { 53 | this.customerForm = this.fb.group({ 54 | firstName: ['', [Validators.required, Validators.minLength(3)]], 55 | lastName: ['', [Validators.required, Validators.maxLength(50)]], 56 | emailGroup: this.fb.group({ 57 | email: ['', [Validators.required, Validators.email]], 58 | confirmEmail: ['', Validators.required], 59 | }, { validator: emailMatcher }), 60 | phone: '', 61 | notification: 'email', 62 | rating: [null, ratingRange(1, 5)], 63 | sendCatalog: true, 64 | addresses: this.fb.array([this.buildAddress()]) 65 | }); 66 | 67 | this.customerForm.get('notification').valueChanges.subscribe( 68 | value => this.setNotification(value) 69 | ); 70 | 71 | const emailControl = this.customerForm.get('emailGroup.email'); 72 | emailControl.valueChanges.pipe( 73 | debounceTime(1000) 74 | ).subscribe( 75 | value => this.setMessage(emailControl) 76 | ); 77 | } 78 | 79 | addAddress(): void { 80 | this.addresses.push(this.buildAddress()); 81 | } 82 | 83 | buildAddress(): FormGroup { 84 | return this.fb.group({ 85 | addressType: 'home', 86 | street1: ['', Validators.required], 87 | street2: '', 88 | city: '', 89 | state: '', 90 | zip: '' 91 | }); 92 | } 93 | 94 | populateTestData(): void { 95 | this.customerForm.patchValue({ 96 | firstName: 'Jack', 97 | lastName: 'Harkness', 98 | emailGroup: { email: 'jack@torchwood.com', confirmEmail: 'jack@torchwood.com' } 99 | }); 100 | const addressGroup = this.fb.group({ 101 | addressType: 'work', 102 | street1: 'Mermaid Quay', 103 | street2: '', 104 | city: 'Cardiff Bay', 105 | state: 'CA', 106 | zip: '' 107 | }); 108 | this.customerForm.setControl('addresses', this.fb.array([addressGroup])); 109 | } 110 | 111 | save(): void { 112 | console.log(this.customerForm); 113 | console.log('Saved: ' + JSON.stringify(this.customerForm.value)); 114 | } 115 | 116 | setMessage(c: AbstractControl): void { 117 | this.emailMessage = ''; 118 | if ((c.touched || c.dirty) && c.errors) { 119 | this.emailMessage = Object.keys(c.errors).map( 120 | key => this.validationMessages[key]).join(' '); 121 | } 122 | } 123 | 124 | setNotification(notifyVia: string): void { 125 | const phoneControl = this.customerForm.get('phone'); 126 | if (notifyVia === 'text') { 127 | phoneControl.setValidators(Validators.required); 128 | } else { 129 | phoneControl.clearValidators(); 130 | } 131 | phoneControl.updateValueAndValidity(); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /APM/src/app/products/product-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
10 | 11 |
12 | 14 |
15 | 21 | 22 | {{displayMessage.productName}} 23 | 24 |
25 |
26 | 27 |
28 | 30 |
31 | 37 | 38 | {{displayMessage.productCode}} 39 | 40 |
41 |
42 | 43 |
44 | 46 | 47 |
48 | 54 | 55 | {{displayMessage.starRating}} 56 | 57 |
58 |
59 | 60 |
61 |
63 | 65 | 66 |
67 | 72 |
73 | 78 |
79 |
80 | 81 |
82 |
83 | 88 |
89 |
90 | 91 |
92 | 94 | 95 |
96 | 102 | 103 | {{ displayMessage.description}} 104 | 105 |
106 |
107 | 108 |
109 |
110 | 117 | 124 | 131 |
132 |
133 |
134 |
135 | 136 |
{{errorMessage}} 138 |
139 |
-------------------------------------------------------------------------------- /APM/src/app/products/product-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, AfterViewInit, OnDestroy, ViewChildren, ElementRef } from '@angular/core'; 2 | import { FormBuilder, FormGroup, FormControl, FormArray, Validators, FormControlName } from '@angular/forms'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | 5 | import { Observable, Subscription, fromEvent, merge } from 'rxjs'; 6 | import { debounceTime } from 'rxjs/operators'; 7 | 8 | import { Product } from './product'; 9 | import { ProductService } from './product.service'; 10 | 11 | import { NumberValidators } from '../shared/number.validator'; 12 | import { GenericValidator } from '../shared/generic-validator'; 13 | 14 | @Component({ 15 | templateUrl: './product-edit.component.html' 16 | }) 17 | export class ProductEditComponent implements OnInit, AfterViewInit, OnDestroy { 18 | @ViewChildren(FormControlName, { read: ElementRef }) formInputElements: ElementRef[]; 19 | 20 | pageTitle = 'Product Edit'; 21 | errorMessage: string; 22 | productForm: FormGroup; 23 | 24 | product: Product; 25 | private sub: Subscription; 26 | 27 | // Use with the generic validation message class 28 | displayMessage: { [key: string]: string } = {}; 29 | private validationMessages: { [key: string]: { [key: string]: string } }; 30 | private genericValidator: GenericValidator; 31 | 32 | get tags(): FormArray { 33 | return this.productForm.get('tags') as FormArray; 34 | } 35 | 36 | constructor(private fb: FormBuilder, 37 | private route: ActivatedRoute, 38 | private router: Router, 39 | private productService: ProductService) { 40 | 41 | // Defines all of the validation messages for the form. 42 | // These could instead be retrieved from a file or database. 43 | this.validationMessages = { 44 | productName: { 45 | required: 'Product name is required.', 46 | minlength: 'Product name must be at least three characters.', 47 | maxlength: 'Product name cannot exceed 50 characters.' 48 | }, 49 | productCode: { 50 | required: 'Product code is required.' 51 | }, 52 | starRating: { 53 | range: 'Rate the product between 1 (lowest) and 5 (highest).' 54 | } 55 | }; 56 | 57 | // Define an instance of the validator for use with this form, 58 | // passing in this form's set of validation messages. 59 | this.genericValidator = new GenericValidator(this.validationMessages); 60 | } 61 | 62 | ngOnInit(): void { 63 | this.productForm = this.fb.group({ 64 | productName: ['', [Validators.required, 65 | Validators.minLength(3), 66 | Validators.maxLength(50)]], 67 | productCode: ['', Validators.required], 68 | starRating: ['', NumberValidators.range(1, 5)], 69 | tags: this.fb.array([]), 70 | description: '' 71 | }); 72 | 73 | // Read the product Id from the route parameter 74 | this.sub = this.route.paramMap.subscribe( 75 | params => { 76 | const id = +params.get('id'); 77 | this.getProduct(id); 78 | } 79 | ); 80 | } 81 | 82 | ngOnDestroy(): void { 83 | this.sub.unsubscribe(); 84 | } 85 | 86 | ngAfterViewInit(): void { 87 | // Watch for the blur event from any input element on the form. 88 | // This is required because the valueChanges does not provide notification on blur 89 | const controlBlurs: Observable[] = this.formInputElements 90 | .map((formControl: ElementRef) => fromEvent(formControl.nativeElement, 'blur')); 91 | 92 | // Merge the blur event observable with the valueChanges observable 93 | // so we only need to subscribe once. 94 | merge(this.productForm.valueChanges, ...controlBlurs).pipe( 95 | debounceTime(800) 96 | ).subscribe(value => { 97 | this.displayMessage = this.genericValidator.processMessages(this.productForm); 98 | }); 99 | } 100 | 101 | addTag(): void { 102 | this.tags.push(new FormControl()); 103 | } 104 | 105 | deleteTag(index: number): void { 106 | this.tags.removeAt(index); 107 | this.tags.markAsDirty(); 108 | } 109 | 110 | getProduct(id: number): void { 111 | this.productService.getProduct(id) 112 | .subscribe({ 113 | next: (product: Product) => this.displayProduct(product), 114 | error: err => this.errorMessage = err 115 | }); 116 | } 117 | 118 | displayProduct(product: Product): void { 119 | if (this.productForm) { 120 | this.productForm.reset(); 121 | } 122 | this.product = product; 123 | 124 | if (this.product.id === 0) { 125 | this.pageTitle = 'Add Product'; 126 | } else { 127 | this.pageTitle = `Edit Product: ${this.product.productName}`; 128 | } 129 | 130 | // Update the data on the form 131 | this.productForm.patchValue({ 132 | productName: this.product.productName, 133 | productCode: this.product.productCode, 134 | starRating: this.product.starRating, 135 | description: this.product.description 136 | }); 137 | this.productForm.setControl('tags', this.fb.array(this.product.tags || [])); 138 | } 139 | 140 | deleteProduct(): void { 141 | if (this.product.id === 0) { 142 | // Don't delete, it was never saved. 143 | this.onSaveComplete(); 144 | } else { 145 | if (confirm(`Really delete the product: ${this.product.productName}?`)) { 146 | this.productService.deleteProduct(this.product.id) 147 | .subscribe({ 148 | next: () => this.onSaveComplete(), 149 | error: err => this.errorMessage = err 150 | }); 151 | } 152 | } 153 | } 154 | 155 | saveProduct(): void { 156 | if (this.productForm.valid) { 157 | if (this.productForm.dirty) { 158 | const p = { ...this.product, ...this.productForm.value }; 159 | 160 | if (p.id === 0) { 161 | this.productService.createProduct(p) 162 | .subscribe({ 163 | next: () => this.onSaveComplete(), 164 | error: err => this.errorMessage = err 165 | }); 166 | } else { 167 | this.productService.updateProduct(p) 168 | .subscribe({ 169 | next: () => this.onSaveComplete(), 170 | error: err => this.errorMessage = err 171 | }); 172 | } 173 | } else { 174 | this.onSaveComplete(); 175 | } 176 | } else { 177 | this.errorMessage = 'Please correct the validation errors.'; 178 | } 179 | } 180 | 181 | onSaveComplete(): void { 182 | // Reset the form to clear the flags 183 | this.productForm.reset(); 184 | this.router.navigate(['/products']); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Demo-Start/src/app/customers/customer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Sign Up! 4 |
5 | 6 |
7 |
10 | 11 |
12 | 14 |
15 | 25 | 26 | 27 | Please enter your first name. 28 | 29 | 30 | The first name must be longer than 3 characters. 31 | 32 | 33 |
34 |
35 | 36 |
37 | 39 |
40 | 50 | 51 | 52 | Please enter your last name. 53 | 54 | 55 | The last name must be less than 50 characters. 56 | 57 | 58 |
59 |
60 | 61 |
62 | 64 |
65 | 75 | 76 | 77 | Please enter your email address. 78 | 79 | 80 | Please enter a valid email address. 81 | 82 | 83 |
84 |
85 | 86 |
87 |
88 |
89 | 96 |
97 |
98 |
99 | 100 |
101 |
102 | 103 |
104 |
105 | 113 |
114 |
115 | 123 |
124 |
125 | 133 |
134 |
135 |
136 | 137 |
138 | 140 |
141 | 147 |
148 |
149 | 150 |
151 | 153 |
154 | 160 |
161 |
162 | 163 |
164 | 166 |
167 | 173 |
174 |
175 | 192 |
193 |
194 | 200 |
201 |
202 |
203 | 204 |
205 |
206 | 213 |
214 |
215 |
216 |
217 |
218 |
Dirty: {{ signupForm.dirty }} 219 |
Touched: {{ signupForm.touched }} 220 |
Valid: {{ signupForm.valid }} 221 |
Value: {{ signupForm.value | json }} -------------------------------------------------------------------------------- /Demo-Final/src/app/customers/customer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Sign Up! 4 |
5 | 6 |
7 |
10 | 11 |
12 | 14 |
15 | 23 | 24 | 25 | Please enter your first name. 26 | 27 | 28 | The first name must be longer than 3 characters. 29 | 30 | 31 |
32 |
33 | 34 |
35 | 37 |
38 | 46 | 47 | 48 | Please enter your last name. 49 | 50 | 51 | The last name must be less than 50 characters. 52 | 53 | 54 |
55 |
56 | 57 |
58 |
59 | 61 |
62 | 68 | 69 | {{ emailMessage }} 70 | 71 |
72 |
73 | 74 |
75 | 77 |
78 | 87 | 88 | 89 | Please confirm your email address. 90 | 91 | 92 | The confirmation does not match the email address. 93 | 94 | 95 |
96 |
97 |
98 | 99 |
100 | 102 |
103 | 109 | 110 | 111 | Please enter your phone number. 112 | 113 | 114 |
115 |
116 | 117 |
118 | 119 |
120 |
121 | 127 |
128 |
129 | 135 |
136 |
137 |
138 | 139 |
140 | 142 |
143 | 150 | 151 | 152 | Please rate your experience from 1 to 5. 153 | 154 | 155 |
156 |
157 | 158 |
159 |
160 |
161 | 167 |
168 |
169 |
170 | 171 |
172 |
173 | 174 |
176 |
177 | 178 |
179 |
180 | 187 |
188 |
189 | 196 |
197 |
198 | 205 |
206 |
207 |
208 | 209 |
210 | 212 |
213 | 221 | 222 | 223 | Please enter your street address. 224 | 225 | 226 |
227 |
228 | 229 |
230 | 232 |
233 | 238 |
239 |
240 | 241 |
242 | 244 |
245 | 250 |
251 |
252 | 268 |
269 |
270 | 275 |
276 |
277 |
278 | 279 |
280 | 281 |
282 |
283 | 290 |
291 |
292 |
293 | 294 |
295 |
296 | 303 | 308 |
309 |
310 |
311 |
312 |
313 |
Dirty: {{ customerForm.dirty }} 314 |
Touched: {{ customerForm.touched }} 315 |
Valid: {{ customerForm.valid }} 316 |
Value: {{ customerForm.value | json }} 317 |
Street: {{ addresses.get('0.street1')?.value }} 318 |
  --------------------------------------------------------------------------------