├── .gitignore ├── APM-Demo0 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Demo1 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ ├── product.ts │ │ │ └── state │ │ │ │ └── product.reducer.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── state │ │ │ └── user.reducer.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Demo2 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ ├── product.ts │ │ │ └── state │ │ │ │ ├── product.actions.ts │ │ │ │ └── product.reducer.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ ├── state │ │ │ └── app.state.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── state │ │ │ ├── user.actions.ts │ │ │ └── user.reducer.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Demo3 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ ├── product.ts │ │ │ └── state │ │ │ │ ├── product.actions.ts │ │ │ │ ├── product.effects.ts │ │ │ │ └── product.reducer.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ ├── state │ │ │ └── app.state.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── state │ │ │ ├── user.actions.ts │ │ │ └── user.reducer.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Demo4 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ ├── product.ts │ │ │ └── state │ │ │ │ ├── product.actions.ts │ │ │ │ ├── product.effects.ts │ │ │ │ └── product.reducer.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ ├── state │ │ │ └── app.state.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── state │ │ │ ├── user.actions.ts │ │ │ └── user.reducer.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Demo5 ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── menu.component.html │ │ │ ├── menu.component.ts │ │ │ ├── page-not-found.component.ts │ │ │ ├── shell.component.css │ │ │ ├── shell.component.html │ │ │ ├── shell.component.ts │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product-shell │ │ │ │ ├── product-shell.component.html │ │ │ │ └── product-shell.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ ├── product.ts │ │ │ └── state │ │ │ │ ├── actions │ │ │ │ ├── index.ts │ │ │ │ ├── product-api.actions.ts │ │ │ │ └── product-page.actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── product.effects.ts │ │ │ │ └── product.reducer.ts │ │ ├── shared │ │ │ ├── generic-validator.ts │ │ │ ├── number.validator.ts │ │ │ └── shared.module.ts │ │ ├── state │ │ │ └── app.state.ts │ │ └── user │ │ │ ├── auth-guard.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── state │ │ │ ├── actions │ │ │ │ ├── index.ts │ │ │ │ └── user-page.actions.ts │ │ │ └── user.reducer.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ └── logo.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── CHANGELOG.md ├── LICENSE └── README.md /.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-Demo0/.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-Demo0/.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-Demo0/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo0/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo0 2 | 3 | Starter files with no NgRx added. 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 6 | 7 | ## Development server 8 | 9 | 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. 10 | 11 | ## Code scaffolding 12 | 13 | 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`. 14 | 15 | ## Build 16 | 17 | 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. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | 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). 30 | -------------------------------------------------------------------------------- /APM-Demo0/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo0/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo0/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-Demo0/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-Demo0/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo0/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 | -------------------------------------------------------------------------------- /APM-Demo0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve -o", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~9.1.3", 16 | "@angular/common": "~9.1.3", 17 | "@angular/compiler": "~9.1.3", 18 | "@angular/core": "~9.1.3", 19 | "@angular/forms": "~9.1.3", 20 | "@angular/platform-browser": "~9.1.3", 21 | "@angular/platform-browser-dynamic": "~9.1.3", 22 | "@angular/router": "~9.1.3", 23 | "bootstrap": "^4.4.1", 24 | "rxjs": "~6.5.4", 25 | "tslib": "^1.10.0", 26 | "zone.js": "~0.10.2" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.901.3", 30 | "@angular/cli": "~9.1.3", 31 | "@angular/compiler-cli": "~9.1.3", 32 | "@angular/language-service": "~9.1.3", 33 | "@types/node": "^12.11.1", 34 | "@types/jasmine": "~3.5.0", 35 | "@types/jasminewd2": "~2.0.3", 36 | "angular-in-memory-web-api": "^0.9.0", 37 | "codelyzer": "^5.1.2", 38 | "jasmine-core": "~3.8.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~5.0.0", 41 | "karma-chrome-launcher": "~3.1.0", 42 | "karma-coverage-istanbul-reporter": "~2.1.0", 43 | "karma-jasmine": "~3.0.1", 44 | "karma-jasmine-html-reporter": "^1.6.2", 45 | "protractor": "~5.4.3", 46 | "ts-node": "~8.3.0", 47 | "tslint": "~6.1.0", 48 | "typescript": "~3.8.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo0/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo0/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { ProductData } from './products/product-data'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | 11 | import { AppComponent } from './app.component'; 12 | import { ShellComponent } from './home/shell.component'; 13 | import { MenuComponent } from './home/menu.component'; 14 | import { WelcomeComponent } from './home/welcome.component'; 15 | import { PageNotFoundComponent } from './home/page-not-found.component'; 16 | 17 | /* Feature Modules */ 18 | import { UserModule } from './user/user.module'; 19 | 20 | @NgModule({ 21 | imports: [ 22 | BrowserModule, 23 | HttpClientModule, 24 | HttpClientInMemoryWebApiModule.forRoot(ProductData), 25 | UserModule, 26 | AppRoutingModule 27 | ], 28 | declarations: [ 29 | AppComponent, 30 | ShellComponent, 31 | MenuComponent, 32 | WelcomeComponent, 33 | PageNotFoundComponent 34 | ], 35 | bootstrap: [AppComponent] 36 | }) 37 | export class AppModule { } 38 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo0/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo0/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo0/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() { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | description: 'Leaf rake with 48-inch wooden handle', 14 | starRating: 3.2 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | starRating: 4.2 22 | }, 23 | { 24 | id: 5, 25 | productName: 'Hammer', 26 | productCode: 'TBX-0048', 27 | description: 'Curved claw steel hammer', 28 | starRating: 4.8 29 | }, 30 | { 31 | id: 8, 32 | productName: 'Saw', 33 | productCode: 'TBX-0022', 34 | description: '15-inch steel blade hand saw', 35 | starRating: 3.7 36 | }, 37 | { 38 | id: 10, 39 | productName: 'Video Game Controller', 40 | productCode: 'GMG-0042', 41 | description: 'Standard two-button video game controller', 42 | starRating: 4.6 43 | } 44 | ]; 45 | return { products }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { Product } from '../product'; 6 | import { ProductService } from '../product.service'; 7 | 8 | @Component({ 9 | selector: 'pm-product-list', 10 | templateUrl: './product-list.component.html', 11 | styleUrls: ['./product-list.component.css'] 12 | }) 13 | export class ProductListComponent implements OnInit, OnDestroy { 14 | pageTitle = 'Products'; 15 | errorMessage: string; 16 | 17 | displayCode: boolean; 18 | 19 | products: Product[]; 20 | 21 | // Used to highlight the selected product in the list 22 | selectedProduct: Product | null; 23 | sub: Subscription; 24 | 25 | constructor(private productService: ProductService) { } 26 | 27 | ngOnInit(): void { 28 | this.sub = this.productService.selectedProductChanges$.subscribe( 29 | currentProduct => this.selectedProduct = currentProduct 30 | ); 31 | 32 | this.productService.getProducts().subscribe({ 33 | next: (products: Product[]) => this.products = products, 34 | error: err => this.errorMessage = err 35 | }); 36 | } 37 | 38 | ngOnDestroy(): void { 39 | this.sub.unsubscribe(); 40 | } 41 | 42 | checkChanged(): void { 43 | this.displayCode = !this.displayCode; 44 | } 45 | 46 | newProduct(): void { 47 | this.productService.changeSelectedProduct(this.productService.newProduct()); 48 | } 49 | 50 | productSelected(product: Product): void { 51 | this.productService.changeSelectedProduct(product); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product-shell/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent implements OnInit { 7 | 8 | constructor() { } 9 | 10 | ngOnInit() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | const productRoutes: Routes = [ 11 | { path: '', component: ProductShellComponent } 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [ 16 | SharedModule, 17 | RouterModule.forChild(productRoutes) 18 | ], 19 | declarations: [ 20 | ProductShellComponent, 21 | ProductListComponent, 22 | ProductEditComponent 23 | ] 24 | }) 25 | export class ProductModule { } 26 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo0/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-Demo0/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | @Component({ 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.css'] 10 | }) 11 | export class LoginComponent implements OnInit { 12 | pageTitle = 'Log In'; 13 | 14 | maskUserName: boolean; 15 | 16 | constructor(private authService: AuthService, private router: Router) { } 17 | 18 | ngOnInit(): void { 19 | 20 | } 21 | 22 | cancel(): void { 23 | this.router.navigate(['welcome']); 24 | } 25 | 26 | checkChanged(): void { 27 | this.maskUserName = !this.maskUserName; 28 | } 29 | 30 | login(loginForm: NgForm): void { 31 | if (loginForm && loginForm.valid) { 32 | const userName = loginForm.form.value.userName; 33 | const password = loginForm.form.value.password; 34 | this.authService.login(userName, password); 35 | 36 | if (this.authService.redirectUrl) { 37 | this.router.navigateByUrl(this.authService.redirectUrl); 38 | } else { 39 | this.router.navigate(['/products']); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | const userRoutes: Routes = [ 9 | { path: 'login', component: LoginComponent } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [ 14 | SharedModule, 15 | RouterModule.forChild(userRoutes) 16 | ], 17 | declarations: [ 18 | LoginComponent 19 | ] 20 | }) 21 | export class UserModule { } 22 | -------------------------------------------------------------------------------- /APM-Demo0/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo0/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo0/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo0/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo0/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo0/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo0/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-Demo0/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo0/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo0/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo0/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-Demo0/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo0/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-Demo0/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo0/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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-Demo1/.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-Demo1/.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-Demo1/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo1/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo1 2 | 3 | Demo of the most basic implementation of NgRx for a simple boolean flag. 4 | 5 | The goal of this demo is to introduce the parts of NgRx for a simple, easy to reason about, use case. 6 | 7 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 8 | 9 | ## Development server 10 | 11 | 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. 12 | 13 | ## Code scaffolding 14 | 15 | 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`. 16 | 17 | ## Build 18 | 19 | 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. 20 | 21 | ## Running unit tests 22 | 23 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 24 | 25 | ## Running end-to-end tests 26 | 27 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 28 | 29 | ## Further help 30 | 31 | 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). 32 | -------------------------------------------------------------------------------- /APM-Demo1/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo1/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo1/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-Demo1/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-Demo1/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo1/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 | -------------------------------------------------------------------------------- /APM-Demo1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve -o", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~9.1.3", 16 | "@angular/common": "~9.1.3", 17 | "@angular/compiler": "~9.1.3", 18 | "@angular/core": "~9.1.3", 19 | "@angular/forms": "~9.1.3", 20 | "@angular/platform-browser": "~9.1.3", 21 | "@angular/platform-browser-dynamic": "~9.1.3", 22 | "@angular/router": "~9.1.3", 23 | "@ngrx/store": "^9.1.0", 24 | "bootstrap": "^4.4.1", 25 | "rxjs": "~6.5.4", 26 | "tslib": "^1.10.0", 27 | "zone.js": "~0.10.2" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~0.901.3", 31 | "@angular/cli": "~9.1.3", 32 | "@angular/compiler-cli": "~9.1.3", 33 | "@angular/language-service": "~9.1.3", 34 | "@types/node": "^12.11.1", 35 | "@types/jasmine": "~3.5.0", 36 | "@types/jasminewd2": "~2.0.3", 37 | "angular-in-memory-web-api": "^0.9.0", 38 | "codelyzer": "^5.1.2", 39 | "jasmine-core": "~3.8.0", 40 | "jasmine-spec-reporter": "~4.2.1", 41 | "karma": "~5.0.0", 42 | "karma-chrome-launcher": "~3.1.0", 43 | "karma-coverage-istanbul-reporter": "~2.1.0", 44 | "karma-jasmine": "~3.0.1", 45 | "karma-jasmine-html-reporter": "^1.6.2", 46 | "protractor": "~5.4.3", 47 | "ts-node": "~8.3.0", 48 | "tslint": "~6.1.0", 49 | "typescript": "~3.8.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo1/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo1/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { ProductData } from './products/product-data'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | 11 | import { AppComponent } from './app.component'; 12 | import { ShellComponent } from './home/shell.component'; 13 | import { MenuComponent } from './home/menu.component'; 14 | import { WelcomeComponent } from './home/welcome.component'; 15 | import { PageNotFoundComponent } from './home/page-not-found.component'; 16 | 17 | /* Feature Modules */ 18 | import { UserModule } from './user/user.module'; 19 | 20 | /* NgRx */ 21 | import { StoreModule } from '@ngrx/store'; 22 | 23 | @NgModule({ 24 | imports: [ 25 | BrowserModule, 26 | HttpClientModule, 27 | HttpClientInMemoryWebApiModule.forRoot(ProductData), 28 | UserModule, 29 | AppRoutingModule, 30 | StoreModule.forRoot({}) 31 | ], 32 | declarations: [ 33 | AppComponent, 34 | ShellComponent, 35 | MenuComponent, 36 | WelcomeComponent, 37 | PageNotFoundComponent 38 | ], 39 | bootstrap: [AppComponent] 40 | }) 41 | export class AppModule { } 42 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo1/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo1/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo1/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() { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | description: 'Leaf rake with 48-inch wooden handle', 14 | starRating: 3.2 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | starRating: 4.2 22 | }, 23 | { 24 | id: 5, 25 | productName: 'Hammer', 26 | productCode: 'TBX-0048', 27 | description: 'Curved claw steel hammer', 28 | starRating: 4.8 29 | }, 30 | { 31 | id: 8, 32 | productName: 'Saw', 33 | productCode: 'TBX-0022', 34 | description: '15-inch steel blade hand saw', 35 | starRating: 3.7 36 | }, 37 | { 38 | id: 10, 39 | productName: 'Video Game Controller', 40 | productCode: 'GMG-0042', 41 | description: 'Standard two-button video game controller', 42 | starRating: 4.6 43 | } 44 | ]; 45 | return { products }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product-shell/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent implements OnInit { 7 | 8 | constructor() { } 9 | 10 | ngOnInit() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | /* NgRx */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { productReducer } from './state/product.reducer'; 13 | 14 | const productRoutes: Routes = [ 15 | { path: '', component: ProductShellComponent } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [ 20 | SharedModule, 21 | RouterModule.forChild(productRoutes), 22 | StoreModule.forFeature('products', productReducer) 23 | ], 24 | declarations: [ 25 | ProductShellComponent, 26 | ProductListComponent, 27 | ProductEditComponent 28 | ] 29 | }) 30 | export class ProductModule { } 31 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/products/state/product.reducer.ts: -------------------------------------------------------------------------------- 1 | /* NgRx */ 2 | import { createReducer, on, createAction } from '@ngrx/store'; 3 | 4 | export const productReducer = createReducer( 5 | { showProductCode: true }, 6 | on(createAction('[Product] Toggle Product Code'), state => { 7 | return { 8 | ...state, 9 | showProductCode: !state.showProductCode 10 | // showProductCode: state.showProductCode = false, 11 | }; 12 | }) 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /APM-Demo1/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-Demo1/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | /* NgRx */ 8 | import { Store } from '@ngrx/store'; 9 | 10 | @Component({ 11 | templateUrl: './login.component.html', 12 | styleUrls: ['./login.component.css'] 13 | }) 14 | export class LoginComponent implements OnInit { 15 | pageTitle = 'Log In'; 16 | 17 | maskUserName: boolean; 18 | 19 | constructor(private store: Store, private authService: AuthService, private router: Router) { } 20 | 21 | ngOnInit(): void { 22 | // TODO: Unsubscribe 23 | this.store.select('users').subscribe( 24 | users => { 25 | if (users) { 26 | this.maskUserName = users.maskUserName; 27 | } 28 | }); 29 | } 30 | 31 | cancel(): void { 32 | this.router.navigate(['welcome']); 33 | } 34 | 35 | checkChanged(): void { 36 | this.store.dispatch( 37 | { type: '[User] Mask User Name' } 38 | ); 39 | } 40 | 41 | login(loginForm: NgForm): void { 42 | if (loginForm && loginForm.valid) { 43 | const userName = loginForm.form.value.userName; 44 | const password = loginForm.form.value.password; 45 | this.authService.login(userName, password); 46 | 47 | if (this.authService.redirectUrl) { 48 | this.router.navigateByUrl(this.authService.redirectUrl); 49 | } else { 50 | this.router.navigate(['/products']); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/state/user.reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer, on, createAction } from '@ngrx/store'; 2 | 3 | export const userReducer = createReducer( 4 | { maskUserName: true }, 5 | on(createAction('[User] Mask User Name'), state => { 6 | return { 7 | ...state, 8 | maskUserName: !state.maskUserName 9 | }; 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | /* NgRx */ 9 | import { StoreModule } from '@ngrx/store'; 10 | import { userReducer } from './state/user.reducer'; 11 | 12 | const userRoutes: Routes = [ 13 | { path: 'login', component: LoginComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | RouterModule.forChild(userRoutes), 20 | StoreModule.forFeature('users', userReducer) 21 | ], 22 | declarations: [ 23 | LoginComponent 24 | ] 25 | }) 26 | export class UserModule { } 27 | -------------------------------------------------------------------------------- /APM-Demo1/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo1/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo1/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo1/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo1/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo1/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo1/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-Demo1/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo1/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo1/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo1/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-Demo1/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo1/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-Demo1/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo1/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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-Demo2/.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-Demo2/.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-Demo2/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo2/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo2 2 | 3 | Demo of a basic implementation of NgRx for a simple boolean flag, but using strong typing. Includes implementation of the dev tools. 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 6 | 7 | ## Development server 8 | 9 | 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. 10 | 11 | ## Code scaffolding 12 | 13 | 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`. 14 | 15 | ## Build 16 | 17 | 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. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | 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). 30 | -------------------------------------------------------------------------------- /APM-Demo2/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo2/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo2/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-Demo2/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-Demo2/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo2/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 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo2/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo2/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo2/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo2/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo2/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() { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | description: 'Leaf rake with 48-inch wooden handle', 14 | starRating: 3.2 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | starRating: 4.2 22 | }, 23 | { 24 | id: 5, 25 | productName: 'Hammer', 26 | productCode: 'TBX-0048', 27 | description: 'Curved claw steel hammer', 28 | starRating: 4.8 29 | }, 30 | { 31 | id: 8, 32 | productName: 'Saw', 33 | productCode: 'TBX-0022', 34 | description: '15-inch steel blade hand saw', 35 | starRating: 3.7 36 | }, 37 | { 38 | id: 10, 39 | productName: 'Video Game Controller', 40 | productCode: 'GMG-0042', 41 | description: 'Standard two-button video game controller', 42 | starRating: 4.6 43 | } 44 | ]; 45 | return { products }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product-shell/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent implements OnInit { 7 | 8 | constructor() { } 9 | 10 | ngOnInit() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | /* NgRx */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { productReducer } from './state/product.reducer'; 13 | 14 | const productRoutes: Routes = [ 15 | { path: '', component: ProductShellComponent } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [ 20 | SharedModule, 21 | RouterModule.forChild(productRoutes), 22 | StoreModule.forFeature('products', productReducer) 23 | ], 24 | declarations: [ 25 | ProductShellComponent, 26 | ProductListComponent, 27 | ProductEditComponent 28 | ] 29 | }) 30 | export class ProductModule { } 31 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/products/state/product.actions.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../product'; 2 | 3 | /* NgRx */ 4 | import { createAction, props } from '@ngrx/store'; 5 | 6 | export const toggleProductCode = createAction( 7 | '[Product] Toggle Product Code' 8 | ); 9 | 10 | export const setCurrentProduct = createAction( 11 | '[Product] Set Current Product', 12 | props<{ product: Product }>() 13 | ); 14 | 15 | export const clearCurrentProduct = createAction( 16 | '[Product] Clear Current Product' 17 | ); 18 | 19 | export const initializeCurrentProduct = createAction( 20 | '[Product] Initialize Current Product' 21 | ); 22 | 23 | export const loadProducts = createAction( 24 | '[Product] Load' 25 | ); 26 | 27 | export const loadProductsSuccess = createAction( 28 | '[Product] Load Success', 29 | props<{ products: Product[] }>() 30 | ); 31 | 32 | export const loadProductsFailure = createAction( 33 | '[Product] Load Fail', 34 | props<{ error: string }>() 35 | ); 36 | -------------------------------------------------------------------------------- /APM-Demo2/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-Demo2/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/state/app.state.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from '../user/state/user.reducer'; 2 | 3 | // Representation of the entire app state 4 | // Extended by lazy loaded modules 5 | export interface State { 6 | user: UserState; 7 | } 8 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/state/user.actions.ts: -------------------------------------------------------------------------------- 1 | /* NgRx */ 2 | import { createAction } from '@ngrx/store'; 3 | 4 | export const maskUserName = createAction( 5 | '[User] Mask User Name' 6 | ); 7 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/state/user.reducer.ts: -------------------------------------------------------------------------------- 1 | // Homework 2 | import { User } from '../user'; 3 | 4 | /* NgRx */ 5 | import { createReducer, on, createFeatureSelector, createSelector } from '@ngrx/store'; 6 | import * as UserActions from './user.actions'; 7 | 8 | // State for this feature (User) 9 | export interface UserState { 10 | maskUserName: boolean; 11 | currentUser: User; 12 | } 13 | 14 | const initialState: UserState = { 15 | maskUserName: true, 16 | currentUser: null 17 | }; 18 | 19 | // Selector functions 20 | const getUserFeatureState = createFeatureSelector('users'); 21 | 22 | export const getMaskUserName = createSelector( 23 | getUserFeatureState, 24 | state => state.maskUserName 25 | ); 26 | 27 | export const getCurrentUser = createSelector( 28 | getUserFeatureState, 29 | state => state.currentUser 30 | ); 31 | 32 | export const userReducer = createReducer( 33 | initialState, 34 | on(UserActions.maskUserName, (state): UserState => { 35 | return { 36 | ...state, 37 | maskUserName: !state.maskUserName 38 | }; 39 | }) 40 | ); 41 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | /* NgRx */ 9 | import { StoreModule } from '@ngrx/store'; 10 | import { userReducer } from './state/user.reducer'; 11 | 12 | const userRoutes: Routes = [ 13 | { path: 'login', component: LoginComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | RouterModule.forChild(userRoutes), 20 | StoreModule.forFeature('users', userReducer) 21 | ], 22 | declarations: [ 23 | LoginComponent 24 | ] 25 | }) 26 | export class UserModule { } 27 | -------------------------------------------------------------------------------- /APM-Demo2/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo2/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo2/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo2/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo2/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo2/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo2/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-Demo2/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo2/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo2/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo2/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-Demo2/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo2/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-Demo2/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo2/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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-Demo3/.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-Demo3/.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-Demo3/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo3/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo3 2 | 3 | Demo of using effects for retrieving and displaying data. 4 | NOTE: The create, update, and delete operations no longer work in this version of the demo since we moved only the retrieve to NgRx. 5 | The create, update, and delete are implemented with NgRx in Demo4. 6 | 7 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 8 | 9 | ## Development server 10 | 11 | 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. 12 | 13 | ## Code scaffolding 14 | 15 | 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`. 16 | 17 | ## Build 18 | 19 | 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. 20 | 21 | ## Running unit tests 22 | 23 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 24 | 25 | ## Running end-to-end tests 26 | 27 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 28 | 29 | ## Further help 30 | 31 | 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). 32 | -------------------------------------------------------------------------------- /APM-Demo3/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo3/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo3/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-Demo3/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-Demo3/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo3/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 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo3/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo3/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo3/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo3/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo3/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() { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | description: 'Leaf rake with 48-inch wooden handle', 14 | starRating: 3.2 15 | }, 16 | { 17 | id: 2, 18 | productName: 'Garden Cart', 19 | productCode: 'GDN-0023', 20 | description: '15 gallon capacity rolling garden cart', 21 | starRating: 4.2 22 | }, 23 | { 24 | id: 5, 25 | productName: 'Hammer', 26 | productCode: 'TBX-0048', 27 | description: 'Curved claw steel hammer', 28 | starRating: 4.8 29 | }, 30 | { 31 | id: 8, 32 | productName: 'Saw', 33 | productCode: 'TBX-0022', 34 | description: '15-inch steel blade hand saw', 35 | starRating: 3.7 36 | }, 37 | { 38 | id: 10, 39 | productName: 'Video Game Controller', 40 | productCode: 'GMG-0042', 41 | description: 'Standard two-button video game controller', 42 | starRating: 4.6 43 | } 44 | ]; 45 | return { products }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product-shell/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent implements OnInit { 7 | 8 | constructor() { } 9 | 10 | ngOnInit() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | /* NgRx */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { productReducer } from './state/product.reducer'; 13 | import { EffectsModule } from '@ngrx/effects'; 14 | import { ProductEffects } from './state/product.effects'; 15 | 16 | const productRoutes: Routes = [ 17 | { path: '', component: ProductShellComponent } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [ 22 | SharedModule, 23 | RouterModule.forChild(productRoutes), 24 | StoreModule.forFeature('products', productReducer), 25 | EffectsModule.forFeature([ProductEffects]) 26 | ], 27 | declarations: [ 28 | ProductShellComponent, 29 | ProductListComponent, 30 | ProductEditComponent 31 | ] 32 | }) 33 | export class ProductModule { } 34 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/state/product.actions.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../product'; 2 | 3 | /* NgRx */ 4 | import { createAction, props } from '@ngrx/store'; 5 | 6 | export const toggleProductCode = createAction( 7 | '[Product] Toggle Product Code' 8 | ); 9 | 10 | export const setCurrentProduct = createAction( 11 | '[Product] Set Current Product', 12 | props<{ product: Product }>() 13 | ); 14 | 15 | export const clearCurrentProduct = createAction( 16 | '[Product] Clear Current Product' 17 | ); 18 | 19 | export const initializeCurrentProduct = createAction( 20 | '[Product] Initialize Current Product' 21 | ); 22 | 23 | export const loadProducts = createAction( 24 | '[Product] Load' 25 | ); 26 | 27 | export const loadProductsSuccess = createAction( 28 | '[Product] Load Success', 29 | props<{ products: Product[] }>() 30 | ); 31 | 32 | export const loadProductsFailure = createAction( 33 | '[Product] Load Fail', 34 | props<{ error: string }>() 35 | ); 36 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/products/state/product.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { mergeMap, map, catchError } from 'rxjs/operators'; 4 | import { of } from 'rxjs'; 5 | import { ProductService } from '../product.service'; 6 | 7 | /* NgRx */ 8 | import { Actions, createEffect, ofType } from '@ngrx/effects'; 9 | import * as ProductActions from './product.actions'; 10 | 11 | @Injectable() 12 | export class ProductEffects { 13 | 14 | constructor(private actions$: Actions, private productService: ProductService) { } 15 | 16 | loadProducts$ = createEffect(() => { 17 | return this.actions$ 18 | .pipe( 19 | ofType(ProductActions.loadProducts), 20 | mergeMap(() => this.productService.getProducts() 21 | .pipe( 22 | map(products => ProductActions.loadProductsSuccess({ products })), 23 | catchError(error => of(ProductActions.loadProductsFailure({ error }))) 24 | ) 25 | ) 26 | ); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /APM-Demo3/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-Demo3/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/state/app.state.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from '../user/state/user.reducer'; 2 | 3 | // Representation of the entire app state 4 | // Extended by lazy loaded modules 5 | export interface State { 6 | user: UserState; 7 | } 8 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { Observable } from 'rxjs'; 6 | 7 | import { AuthService } from './auth.service'; 8 | 9 | /* NgRx */ 10 | import { Store } from '@ngrx/store'; 11 | import { State } from '../state/app.state'; 12 | import { getMaskUserName } from './state/user.reducer'; 13 | import * as UserActions from './state/user.actions'; 14 | 15 | @Component({ 16 | templateUrl: './login.component.html', 17 | styleUrls: ['./login.component.css'] 18 | }) 19 | export class LoginComponent implements OnInit { 20 | pageTitle = 'Log In'; 21 | 22 | maskUserName$: Observable; 23 | 24 | constructor(private store: Store, private authService: AuthService, private router: Router) { } 25 | 26 | ngOnInit(): void { 27 | this.maskUserName$ = this.store.select(getMaskUserName); 28 | } 29 | 30 | cancel(): void { 31 | this.router.navigate(['welcome']); 32 | } 33 | 34 | checkChanged(): void { 35 | this.store.dispatch(UserActions.maskUserName()); 36 | } 37 | 38 | login(loginForm: NgForm): void { 39 | if (loginForm && loginForm.valid) { 40 | const userName = loginForm.form.value.userName; 41 | const password = loginForm.form.value.password; 42 | this.authService.login(userName, password); 43 | 44 | if (this.authService.redirectUrl) { 45 | this.router.navigateByUrl(this.authService.redirectUrl); 46 | } else { 47 | this.router.navigate(['/products']); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/state/user.actions.ts: -------------------------------------------------------------------------------- 1 | /* NgRx */ 2 | import { createAction } from '@ngrx/store'; 3 | 4 | export const maskUserName = createAction( 5 | '[User] Mask User Name' 6 | ); 7 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/state/user.reducer.ts: -------------------------------------------------------------------------------- 1 | // Homework 2 | import { User } from '../user'; 3 | 4 | /* NgRx */ 5 | import { createReducer, on, createFeatureSelector, createSelector } from '@ngrx/store'; 6 | import * as UserActions from './user.actions'; 7 | 8 | // State for this feature (User) 9 | export interface UserState { 10 | maskUserName: boolean; 11 | currentUser: User; 12 | } 13 | 14 | const initialState: UserState = { 15 | maskUserName: true, 16 | currentUser: null 17 | }; 18 | 19 | // Selector functions 20 | const getUserFeatureState = createFeatureSelector('users'); 21 | 22 | export const getMaskUserName = createSelector( 23 | getUserFeatureState, 24 | state => state.maskUserName 25 | ); 26 | 27 | export const getCurrentUser = createSelector( 28 | getUserFeatureState, 29 | state => state.currentUser 30 | ); 31 | 32 | export const userReducer = createReducer( 33 | initialState, 34 | on(UserActions.maskUserName, (state): UserState => { 35 | return { 36 | ...state, 37 | maskUserName: !state.maskUserName 38 | }; 39 | }) 40 | ); 41 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | /* NgRx */ 9 | import { StoreModule } from '@ngrx/store'; 10 | import { userReducer } from './state/user.reducer'; 11 | 12 | const userRoutes: Routes = [ 13 | { path: 'login', component: LoginComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | RouterModule.forChild(userRoutes), 20 | StoreModule.forFeature('users', userReducer) 21 | ], 22 | declarations: [ 23 | LoginComponent 24 | ] 25 | }) 26 | export class UserModule { } 27 | -------------------------------------------------------------------------------- /APM-Demo3/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo3/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo3/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo3/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo3/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo3/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo3/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-Demo3/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo3/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo3/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo3/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-Demo3/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo3/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-Demo3/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo3/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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-Demo4/.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-Demo4/.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-Demo4/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo4/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo4 2 | 3 | Demo of create, update, and delete operations using NgRx. 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 6 | 7 | ## Development server 8 | 9 | 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. 10 | 11 | ## Code scaffolding 12 | 13 | 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`. 14 | 15 | ## Build 16 | 17 | 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. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | 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). 30 | -------------------------------------------------------------------------------- /APM-Demo4/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo4/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo4/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-Demo4/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-Demo4/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo4/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 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo4/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo4/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo4/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo4/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product-shell/product-shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './product-shell.component.html' 5 | }) 6 | export class ProductShellComponent implements OnInit { 7 | 8 | constructor() { } 9 | 10 | ngOnInit() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | /* NgRx */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { productReducer } from './state/product.reducer'; 13 | import { EffectsModule } from '@ngrx/effects'; 14 | import { ProductEffects } from './state/product.effects'; 15 | 16 | const productRoutes: Routes = [ 17 | { path: '', component: ProductShellComponent } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [ 22 | SharedModule, 23 | RouterModule.forChild(productRoutes), 24 | StoreModule.forFeature('products', productReducer), 25 | EffectsModule.forFeature([ProductEffects]) 26 | ], 27 | declarations: [ 28 | ProductShellComponent, 29 | ProductListComponent, 30 | ProductEditComponent 31 | ] 32 | }) 33 | export class ProductModule { } 34 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo4/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-Demo4/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/state/app.state.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from '../user/state/user.reducer'; 2 | 3 | // Representation of the entire app state 4 | // Extended by lazy loaded modules 5 | export interface State { 6 | user: UserState; 7 | } 8 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { Observable } from 'rxjs'; 6 | 7 | import { AuthService } from './auth.service'; 8 | 9 | /* NgRx */ 10 | import { Store } from '@ngrx/store'; 11 | import { State } from '../state/app.state'; 12 | import { getMaskUserName } from './state/user.reducer'; 13 | import * as UserActions from './state/user.actions'; 14 | 15 | @Component({ 16 | templateUrl: './login.component.html', 17 | styleUrls: ['./login.component.css'] 18 | }) 19 | export class LoginComponent implements OnInit { 20 | pageTitle = 'Log In'; 21 | 22 | maskUserName$: Observable; 23 | 24 | constructor(private store: Store, private authService: AuthService, private router: Router) { } 25 | 26 | ngOnInit(): void { 27 | this.maskUserName$ = this.store.select(getMaskUserName); 28 | } 29 | 30 | cancel(): void { 31 | this.router.navigate(['welcome']); 32 | } 33 | 34 | checkChanged(): void { 35 | this.store.dispatch(UserActions.maskUserName()); 36 | } 37 | 38 | login(loginForm: NgForm): void { 39 | if (loginForm && loginForm.valid) { 40 | const userName = loginForm.form.value.userName; 41 | const password = loginForm.form.value.password; 42 | this.authService.login(userName, password); 43 | 44 | if (this.authService.redirectUrl) { 45 | this.router.navigateByUrl(this.authService.redirectUrl); 46 | } else { 47 | this.router.navigate(['/products']); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/state/user.actions.ts: -------------------------------------------------------------------------------- 1 | /* NgRx */ 2 | import { createAction } from '@ngrx/store'; 3 | 4 | export const maskUserName = createAction( 5 | '[User] Mask User Name' 6 | ); 7 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/state/user.reducer.ts: -------------------------------------------------------------------------------- 1 | // Homework 2 | import { User } from '../user'; 3 | 4 | /* NgRx */ 5 | import { createReducer, on, createFeatureSelector, createSelector } from '@ngrx/store'; 6 | import * as UserActions from './user.actions'; 7 | 8 | // State for this feature (User) 9 | export interface UserState { 10 | maskUserName: boolean; 11 | currentUser: User; 12 | } 13 | 14 | const initialState: UserState = { 15 | maskUserName: true, 16 | currentUser: null 17 | }; 18 | 19 | // Selector functions 20 | const getUserFeatureState = createFeatureSelector('users'); 21 | 22 | export const getMaskUserName = createSelector( 23 | getUserFeatureState, 24 | state => state.maskUserName 25 | ); 26 | 27 | export const getCurrentUser = createSelector( 28 | getUserFeatureState, 29 | state => state.currentUser 30 | ); 31 | 32 | export const userReducer = createReducer( 33 | initialState, 34 | on(UserActions.maskUserName, (state): UserState => { 35 | return { 36 | ...state, 37 | maskUserName: !state.maskUserName 38 | }; 39 | }) 40 | ); 41 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | /* NgRx */ 9 | import { StoreModule } from '@ngrx/store'; 10 | import { userReducer } from './state/user.reducer'; 11 | 12 | const userRoutes: Routes = [ 13 | { path: 'login', component: LoginComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | RouterModule.forChild(userRoutes), 20 | StoreModule.forFeature('users', userReducer) 21 | ], 22 | declarations: [ 23 | LoginComponent 24 | ] 25 | }) 26 | export class UserModule { } 27 | -------------------------------------------------------------------------------- /APM-Demo4/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo4/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo4/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo4/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo4/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo4/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo4/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-Demo4/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo4/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo4/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo4/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-Demo4/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo4/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-Demo4/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo4/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo4/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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-Demo5/.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-Demo5/.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-Demo5/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.autoSave": "afterDelay", 4 | "html.format.wrapAttributes": "force-aligned" 5 | } -------------------------------------------------------------------------------- /APM-Demo5/README.md: -------------------------------------------------------------------------------- 1 | # APM-Demo5 2 | 3 | Demo of container/presentational pattern and OnPush change detection strategy. 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.3. 6 | 7 | ## Development server 8 | 9 | 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. 10 | 11 | ## Code scaffolding 12 | 13 | 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`. 14 | 15 | ## Build 16 | 17 | 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. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | 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). 30 | -------------------------------------------------------------------------------- /APM-Demo5/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /APM-Demo5/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 } = 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({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /APM-Demo5/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-Demo5/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-Demo5/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /APM-Demo5/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 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './user/auth-guard.service'; 5 | 6 | import { ShellComponent } from './home/shell.component'; 7 | import { WelcomeComponent } from './home/welcome.component'; 8 | import { PageNotFoundComponent } from './home/page-not-found.component'; 9 | 10 | const appRoutes: Routes = [ 11 | { 12 | path: '', 13 | component: ShellComponent, 14 | children: [ 15 | { path: 'welcome', component: WelcomeComponent }, 16 | { 17 | path: 'products', 18 | // canActivate: [AuthGuard], 19 | loadChildren: () => 20 | import('./products/product.module').then(m => m.ProductModule) 21 | }, 22 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 23 | ] 24 | }, 25 | { path: '**', component: PageNotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | RouterModule.forRoot(appRoutes) 31 | ], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo5/src/app/app.component.css -------------------------------------------------------------------------------- /APM-Demo5/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from '../user/auth.service'; 5 | 6 | @Component({ 7 | selector: 'pm-menu', 8 | templateUrl: './menu.component.html' 9 | }) 10 | export class MenuComponent implements OnInit { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | get isLoggedIn(): boolean { 14 | return this.authService.isLoggedIn(); 15 | } 16 | 17 | get userName(): string { 18 | if (this.authService.currentUser) { 19 | return this.authService.currentUser.userName; 20 | } 21 | return ''; 22 | } 23 | 24 | constructor(private router: Router, private authService: AuthService) { } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | logOut(): void { 30 | this.authService.logout(); 31 | this.router.navigate(['/welcome']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/shell.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /APM-Demo5/src/app/home/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class ShellComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 |
Developed by:
15 |
17 |

Deborah Kurata

18 | 19 |
@deborahkurata
20 | 23 |
24 |
26 |

Duncan Hunter

27 | 28 |
@dunchunter
29 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /APM-Demo5/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .card-body { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{pageTitle}} 5 |
6 | 7 |
9 |
10 | 18 |
19 |
20 | 21 | 40 |
41 |
43 | Error: {{ errorMessage }} 44 |
-------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Product } from '../product'; 3 | 4 | @Component({ 5 | selector: 'pm-product-list', 6 | templateUrl: './product-list.component.html', 7 | styleUrls: ['./product-list.component.css'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | export class ProductListComponent { 11 | pageTitle = 'Products'; 12 | 13 | @Input() errorMessage: string; 14 | @Input() products: Product[]; 15 | @Input() displayCode: boolean; 16 | @Input() selectedProduct: Product; 17 | @Output() displayCodeChanged = new EventEmitter(); 18 | @Output() initializeNewProduct = new EventEmitter(); 19 | @Output() productWasSelected = new EventEmitter(); 20 | 21 | checkChanged(): void { 22 | this.displayCodeChanged.emit(); 23 | } 24 | 25 | newProduct(): void { 26 | this.initializeNewProduct.emit(); 27 | } 28 | 29 | productSelected(product: Product): void { 30 | this.productWasSelected.emit(product); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product-shell/product-shell.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 |
11 |
12 | 18 |
19 |
-------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { ProductShellComponent } from './product-shell/product-shell.component'; 7 | import { ProductListComponent } from './product-list/product-list.component'; 8 | import { ProductEditComponent } from './product-edit/product-edit.component'; 9 | 10 | /* NgRx */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { productReducer } from './state/product.reducer'; 13 | import { EffectsModule } from '@ngrx/effects'; 14 | import { ProductEffects } from './state/product.effects'; 15 | 16 | const productRoutes: Routes = [ 17 | { path: '', component: ProductShellComponent } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [ 22 | SharedModule, 23 | RouterModule.forChild(productRoutes), 24 | StoreModule.forFeature('products', productReducer), 25 | EffectsModule.forFeature([ProductEffects]) 26 | ], 27 | declarations: [ 28 | ProductShellComponent, 29 | ProductListComponent, 30 | ProductEditComponent 31 | ] 32 | }) 33 | export class ProductModule { } 34 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number | null; 4 | productName: string; 5 | productCode: string; 6 | description: string; 7 | starRating: number; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/state/actions/index.ts: -------------------------------------------------------------------------------- 1 | import * as ProductPageActions from './product-page.actions'; 2 | import * as ProductApiActions from './product-api.actions'; 3 | 4 | export { ProductPageActions, ProductApiActions }; 5 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/state/actions/product-api.actions.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../../product'; 2 | 3 | /* NgRx */ 4 | import { createAction, props } from '@ngrx/store'; 5 | 6 | export const loadProductsSuccess = createAction( 7 | '[Product API] Load Success', 8 | props<{ products: Product[] }>() 9 | ); 10 | 11 | export const loadProductsFailure = createAction( 12 | '[Product API] Load Fail', 13 | props<{ error: string }>() 14 | ); 15 | 16 | export const updateProductSuccess = createAction( 17 | '[Product API] Update Product Success', 18 | props<{ product: Product }>() 19 | ); 20 | 21 | export const updateProductFailure = createAction( 22 | '[Product API] Update Product Fail', 23 | props<{ error: string }>() 24 | ); 25 | 26 | export const createProductSuccess = createAction( 27 | '[Product API] Create Product Success', 28 | props<{ product: Product }>() 29 | ); 30 | 31 | export const createProductFailure = createAction( 32 | '[Product API] Create Product Fail', 33 | props<{ error: string }>() 34 | ); 35 | 36 | export const deleteProductSuccess = createAction( 37 | '[Product API] Delete Product Success', 38 | props<{ productId: number }>() 39 | ); 40 | 41 | export const deleteProductFailure = createAction( 42 | '[Product API] Delete Product Fail', 43 | props<{ error: string }>() 44 | ); 45 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/products/state/actions/product-page.actions.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../../product'; 2 | 3 | /* NgRx */ 4 | import { createAction, props } from '@ngrx/store'; 5 | 6 | export const toggleProductCode = createAction( 7 | '[Product Page] Toggle Product Code' 8 | ); 9 | 10 | export const setCurrentProduct = createAction( 11 | '[Product Page] Set Current Product', 12 | props<{ currentProductId: number }>() 13 | ); 14 | 15 | export const clearCurrentProduct = createAction( 16 | '[Product Page] Clear Current Product' 17 | ); 18 | 19 | export const initializeCurrentProduct = createAction( 20 | '[Product Page] Initialize Current Product' 21 | ); 22 | 23 | export const loadProducts = createAction( 24 | '[Product Page] Load' 25 | ); 26 | 27 | export const updateProduct = createAction( 28 | '[Product Page] Update Product', 29 | props<{ product: Product }>() 30 | ); 31 | 32 | export const createProduct = createAction( 33 | '[Product Page] Create Product', 34 | props<{ product: Product }>() 35 | ); 36 | 37 | export const deleteProduct = createAction( 38 | '[Product Page] Delete Product', 39 | props<{ productId: number }>() 40 | ); 41 | -------------------------------------------------------------------------------- /APM-Demo5/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-Demo5/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | exports: [ 10 | CommonModule, 11 | FormsModule, 12 | ReactiveFormsModule 13 | ] 14 | }) 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/state/app.state.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from '../user/state/user.reducer'; 2 | 3 | // Representation of the entire app state 4 | // Extended by lazy loaded modules 5 | export interface State { 6 | user: UserState; 7 | } 8 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 14 | return this.checkLoggedIn(state.url); 15 | } 16 | 17 | checkLoggedIn(url: string): boolean { 18 | if (this.authService.isLoggedIn()) { 19 | return true; 20 | } 21 | 22 | // Retain the attempted URL for redirection 23 | this.authService.redirectUrl = url; 24 | this.router.navigate(['/login']); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AuthService { 9 | currentUser: User | null; 10 | redirectUrl: string; 11 | 12 | constructor() { } 13 | 14 | isLoggedIn(): boolean { 15 | return !!this.currentUser; 16 | } 17 | 18 | login(userName: string, password: string): void { 19 | // Code here would log into a back end service 20 | // and return user information 21 | // This is just hard-coded here. 22 | this.currentUser = { 23 | id: 2, 24 | userName, 25 | isAdmin: false 26 | }; 27 | } 28 | 29 | logout(): void { 30 | this.currentUser = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/login.component.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin-top: 25px; 3 | } -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { Observable } from 'rxjs'; 6 | 7 | import { AuthService } from './auth.service'; 8 | 9 | /* NgRx */ 10 | import { Store } from '@ngrx/store'; 11 | import { State } from '../state/app.state'; 12 | import { getMaskUserName } from './state/user.reducer'; 13 | import { UserPageActions } from './state/actions'; 14 | 15 | @Component({ 16 | templateUrl: './login.component.html', 17 | styleUrls: ['./login.component.css'] 18 | }) 19 | export class LoginComponent implements OnInit { 20 | pageTitle = 'Log In'; 21 | 22 | maskUserName$: Observable; 23 | 24 | constructor(private store: Store, private authService: AuthService, private router: Router) { } 25 | 26 | ngOnInit(): void { 27 | this.maskUserName$ = this.store.select(getMaskUserName); 28 | } 29 | 30 | cancel(): void { 31 | this.router.navigate(['welcome']); 32 | } 33 | 34 | checkChanged(): void { 35 | this.store.dispatch(UserPageActions.maskUserName()); 36 | } 37 | 38 | login(loginForm: NgForm): void { 39 | if (loginForm && loginForm.valid) { 40 | const userName = loginForm.form.value.userName; 41 | const password = loginForm.form.value.password; 42 | this.authService.login(userName, password); 43 | 44 | if (this.authService.redirectUrl) { 45 | this.router.navigateByUrl(this.authService.redirectUrl); 46 | } else { 47 | this.router.navigate(['/products']); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/state/actions/index.ts: -------------------------------------------------------------------------------- 1 | import * as UserPageActions from './user-page.actions'; 2 | 3 | export { UserPageActions }; 4 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/state/actions/user-page.actions.ts: -------------------------------------------------------------------------------- 1 | /* NgRx */ 2 | import { createAction } from '@ngrx/store'; 3 | 4 | export const maskUserName = createAction( 5 | '[User Page] Mask User Name' 6 | ); 7 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/state/user.reducer.ts: -------------------------------------------------------------------------------- 1 | // Homework 2 | import { User } from '../user'; 3 | 4 | /* NgRx */ 5 | import { createReducer, on, createFeatureSelector, createSelector } from '@ngrx/store'; 6 | import { UserPageActions } from './actions'; 7 | 8 | // State for this feature (User) 9 | export interface UserState { 10 | maskUserName: boolean; 11 | currentUser: User; 12 | } 13 | 14 | const initialState: UserState = { 15 | maskUserName: true, 16 | currentUser: null 17 | }; 18 | 19 | // Selector functions 20 | const getUserFeatureState = createFeatureSelector('users'); 21 | 22 | export const getMaskUserName = createSelector( 23 | getUserFeatureState, 24 | state => state.maskUserName 25 | ); 26 | 27 | export const getCurrentUser = createSelector( 28 | getUserFeatureState, 29 | state => state.currentUser 30 | ); 31 | 32 | export const userReducer = createReducer( 33 | initialState, 34 | on(UserPageActions.maskUserName, (state): UserState => { 35 | return { 36 | ...state, 37 | maskUserName: !state.maskUserName 38 | }; 39 | }) 40 | ); 41 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | /* NgRx */ 9 | import { StoreModule } from '@ngrx/store'; 10 | import { userReducer } from './state/user.reducer'; 11 | 12 | const userRoutes: Routes = [ 13 | { path: 'login', component: LoginComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | SharedModule, 19 | RouterModule.forChild(userRoutes), 20 | StoreModule.forFeature('users', userReducer) 21 | ], 22 | declarations: [ 23 | LoginComponent 24 | ] 25 | }) 26 | export class UserModule { } 27 | -------------------------------------------------------------------------------- /APM-Demo5/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | /* Defines the user entity */ 2 | export interface User { 3 | id: number; 4 | userName: string; 5 | isAdmin: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /APM-Demo5/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo5/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Demo5/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo5/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Demo5/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Demo5/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-Demo5/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-NgRx-GettingStarted/eeb263213acc961e36e249c309355e3608260a34/APM-Demo5/src/favicon.ico -------------------------------------------------------------------------------- /APM-Demo5/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Acme Product Management 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /APM-Demo5/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-Demo5/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | li { 3 | font-size: large; 4 | } 5 | 6 | div.panel-heading { 7 | font-size: x-large; 8 | } 9 | 10 | body { 11 | margin-top: 50px; 12 | } 13 | -------------------------------------------------------------------------------- /APM-Demo5/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-Demo5/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /APM-Demo5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /APM-Demo5/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular-ngrx-GettingStarted 2 | Materials for NgRx course. 3 | 4 | `APM-Demo0`: The starter files for this course. **Use this to code along with the course**. 5 | 6 | `APM-Demo1`: Completed files after the *First Look at NgRx* module. It demonstrates a very simple NgRx example. 7 | 8 | `APM-Demo2`: Completed files after the *Strongly Typing Actions with Action Creators* module. It refactors the simple example to include developer tooling support and strong typing. 9 | 10 | `APM-Demo3`: Completed files after the *Working with Effects* module. It adds an effect to retrieve data via http. NOTE: Once we move the data retrieval to actions and the store, the create, update, and delete operations no longer work. These features are implemented with the store in the next demo. 11 | 12 | `APM-Demo4`: Completed files after the *Performing Update Operations* module. It adds the code needed for create, update, and delete operations via http. 13 | 14 | `APM-Demo5`: Completed files after the *Architectural Considerations* module. It implements the container/presentational component pattern and the OnPush change detection strategy. 15 | 16 | NOTE: 17 | - June 30, 2020: This code was modified to Angular version 9 and NgRx version 9. See the CHANGELOG.md file for details. 18 | --------------------------------------------------------------------------------