├── .gitignore ├── APM-Final v14 ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── angular.json ├── karma.conf.js ├── package.json ├── readme.md ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.animation.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── messages │ │ │ ├── message.component.html │ │ │ ├── message.component.ts │ │ │ ├── message.module.ts │ │ │ └── message.service.ts │ │ ├── page-not-found.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-detail.component.css │ │ │ ├── product-detail.component.html │ │ │ ├── product-detail.component.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit-info.component.html │ │ │ │ ├── product-edit-info.component.ts │ │ │ │ ├── product-edit-tags.component.html │ │ │ │ ├── product-edit-tags.component.ts │ │ │ │ ├── product-edit.component.css │ │ │ │ ├── product-edit.component.html │ │ │ │ ├── product-edit.component.ts │ │ │ │ ├── product-edit.guard.spec.ts │ │ │ │ └── product-edit.guard.ts │ │ │ ├── product-list.component.css │ │ │ ├── product-list.component.html │ │ │ ├── product-list.component.ts │ │ │ ├── product-resolver.service.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.ts │ │ ├── selective-strategy.service.ts │ │ ├── shared │ │ │ ├── shared.module.ts │ │ │ ├── star.component.css │ │ │ ├── star.component.html │ │ │ └── star.component.ts │ │ └── user │ │ │ ├── auth.guard.spec.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── garden_cart.png │ │ │ ├── hammer.png │ │ │ ├── leaf_rake.png │ │ │ ├── logo.jpg │ │ │ ├── saw.png │ │ │ └── xbox-controller.png │ ├── 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 ├── APM-Final ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── readme.md ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.animation.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── messages │ │ │ ├── message.component.html │ │ │ ├── message.component.ts │ │ │ ├── message.module.ts │ │ │ └── message.service.ts │ │ ├── page-not-found.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-detail.component.css │ │ │ ├── product-detail.component.html │ │ │ ├── product-detail.component.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit-info.component.html │ │ │ │ ├── product-edit-info.component.ts │ │ │ │ ├── product-edit-tags.component.html │ │ │ │ ├── product-edit-tags.component.ts │ │ │ │ ├── product-edit.component.css │ │ │ │ ├── product-edit.component.html │ │ │ │ ├── product-edit.component.ts │ │ │ │ ├── product-edit.guard.spec.ts │ │ │ │ └── product-edit.guard.ts │ │ │ ├── product-list.component.css │ │ │ ├── product-list.component.html │ │ │ ├── product-list.component.ts │ │ │ ├── product-resolver.service.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.ts │ │ ├── selective-strategy.service.ts │ │ ├── shared │ │ │ ├── shared.module.ts │ │ │ ├── star.component.css │ │ │ ├── star.component.html │ │ │ └── star.component.ts │ │ └── user │ │ │ ├── auth.guard.spec.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.service.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── garden_cart.png │ │ │ ├── hammer.png │ │ │ ├── leaf_rake.png │ │ │ ├── logo.jpg │ │ │ ├── saw.png │ │ │ └── xbox-controller.png │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.base.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── APM-Start v14 ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── angular.json ├── karma.conf.js ├── package.json ├── readme.md ├── src │ ├── app │ │ ├── app.animation.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── messages │ │ │ ├── message.component.html │ │ │ ├── message.component.ts │ │ │ ├── message.module.ts │ │ │ └── message.service.ts │ │ ├── page-not-found.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-detail.component.css │ │ │ ├── product-detail.component.html │ │ │ ├── product-detail.component.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit-info.component.html │ │ │ │ ├── product-edit-info.component.ts │ │ │ │ ├── product-edit-tags.component.html │ │ │ │ ├── product-edit-tags.component.ts │ │ │ │ ├── product-edit.component.css │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list.component.css │ │ │ ├── product-list.component.html │ │ │ ├── product-list.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.ts │ │ ├── shared │ │ │ ├── shared.module.ts │ │ │ ├── star.component.css │ │ │ ├── star.component.html │ │ │ └── star.component.ts │ │ └── user │ │ │ ├── auth.service.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── garden_cart.png │ │ │ ├── hammer.png │ │ │ ├── leaf_rake.png │ │ │ ├── logo.jpg │ │ │ ├── saw.png │ │ │ └── xbox-controller.png │ ├── 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 ├── APM-Start ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── readme.md ├── src │ ├── app │ │ ├── app.animation.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── welcome.component.html │ │ │ └── welcome.component.ts │ │ ├── messages │ │ │ ├── message.component.html │ │ │ ├── message.component.ts │ │ │ ├── message.module.ts │ │ │ └── message.service.ts │ │ ├── page-not-found.component.ts │ │ ├── products │ │ │ ├── product-data.ts │ │ │ ├── product-detail.component.css │ │ │ ├── product-detail.component.html │ │ │ ├── product-detail.component.ts │ │ │ ├── product-edit │ │ │ │ ├── product-edit-info.component.html │ │ │ │ ├── product-edit-info.component.ts │ │ │ │ ├── product-edit-tags.component.html │ │ │ │ ├── product-edit-tags.component.ts │ │ │ │ ├── product-edit.component.css │ │ │ │ ├── product-edit.component.html │ │ │ │ └── product-edit.component.ts │ │ │ ├── product-list.component.css │ │ │ ├── product-list.component.html │ │ │ ├── product-list.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.ts │ │ ├── shared │ │ │ ├── shared.module.ts │ │ │ ├── star.component.css │ │ │ ├── star.component.html │ │ │ └── star.component.ts │ │ └── user │ │ │ ├── auth.service.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── garden_cart.png │ │ │ ├── hammer.png │ │ │ ├── leaf_rake.png │ │ │ ├── logo.jpg │ │ │ ├── saw.png │ │ │ └── xbox-controller.png │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.base.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── CHANGELOG.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | typings 2 | **/app/**/*.js 3 | **/app/**/*.map 4 | **/*.log 5 | **/*.log/* 6 | node_modules 7 | jspm_packages 8 | bower_components 9 | 10 | .vs 11 | **/*.sou 12 | **/*.user 13 | bin 14 | obj 15 | packages 16 | -------------------------------------------------------------------------------- /APM-Final v14/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /APM-Final v14/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Final v14/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-Final v14/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /APM-Final v14/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /APM-Final v14/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned" 4 | } -------------------------------------------------------------------------------- /APM-Final v14/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /APM-Final v14/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/apm'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /APM-Final v14/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^14.2.0", 14 | "@angular/common": "^14.2.0", 15 | "@angular/compiler": "^14.2.0", 16 | "@angular/core": "^14.2.0", 17 | "@angular/forms": "^14.2.0", 18 | "@angular/platform-browser": "^14.2.0", 19 | "@angular/platform-browser-dynamic": "^14.2.0", 20 | "@angular/router": "^14.2.0", 21 | "angular-in-memory-web-api": "^0.14.0", 22 | "bootstrap": "^5.2.1", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "^14.2.2", 30 | "@angular/cli": "~14.2.2", 31 | "@angular/compiler-cli": "^14.2.0", 32 | "@types/jasmine": "~4.0.0", 33 | "jasmine-core": "~4.3.0", 34 | "karma": "~6.4.0", 35 | "karma-chrome-launcher": "~3.1.0", 36 | "karma-coverage": "~2.2.0", 37 | "karma-jasmine": "~5.1.0", 38 | "karma-jasmine-html-reporter": "~2.0.0", 39 | "typescript": "~4.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /APM-Final v14/readme.md: -------------------------------------------------------------------------------- 1 | # Apm 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.2. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { WelcomeComponent } from './home/welcome.component'; 5 | import { PageNotFoundComponent } from './page-not-found.component'; 6 | import { AuthGuard } from './user/auth.guard'; 7 | import { SelectiveStrategy } from './selective-strategy.service'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | RouterModule.forRoot([ 12 | { path: 'welcome', component: WelcomeComponent }, 13 | { 14 | path: 'products', 15 | canActivate: [AuthGuard], 16 | data: { preload: false }, 17 | loadChildren: () => 18 | import('./products/product.module').then(m => m.ProductModule) 19 | }, 20 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 21 | { path: '**', component: PageNotFoundComponent } 22 | ], { preloadingStrategy: SelectiveStrategy, relativeLinkResolution: 'legacy' }) 23 | ], 24 | exports: [RouterModule] 25 | }) 26 | export class AppRoutingModule { } 27 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/app.animation.ts: -------------------------------------------------------------------------------- 1 | import { trigger, animate, transition, style, group, query } from '@angular/animations'; 2 | 3 | export const slideInAnimation = trigger('slideInAnimation', [ 4 | // Transition between any two states 5 | transition('* <=> *', [ 6 | // Events to apply 7 | // Defined style and animation function to apply 8 | // Config object with optional set to true to handle when element not yet added to the DOM 9 | query(':enter, :leave', style({ position: 'fixed', width: '100%', zIndex: 2 }), { optional: true }), 10 | // group block executes in parallel 11 | group([ 12 | query(':enter', [ 13 | style({ transform: 'translateX(100%)' }), 14 | animate('0.5s ease-out', style({ transform: 'translateX(0%)' })) 15 | ], { optional: true }), 16 | query(':leave', [ 17 | style({ transform: 'translateX(0%)' }), 18 | animate('0.5s ease-out', style({ transform: 'translateX(-100%)' })) 19 | ], { optional: true }) 20 | ]) 21 | ]) 22 | ]); 23 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | 10 | /* Spinner */ 11 | .spinner { 12 | font-size:300%; 13 | position:absolute; 14 | top: 50%; 15 | left: 50%; 16 | z-index:10 17 | } 18 | 19 | .fa-spinner { 20 | -webkit-animation: spin 1000ms infinite linear; 21 | animation: spin 1000ms infinite linear; 22 | } 23 | @-webkit-keyframes spin { 24 | 0% { 25 | -webkit-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 100% { 29 | -webkit-transform: rotate(359deg); 30 | transform: rotate(359deg); 31 | } 32 | } 33 | @keyframes spin { 34 | 0% { 35 | -webkit-transform: rotate(0deg); 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | -webkit-transform: rotate(359deg); 40 | transform: rotate(359deg); 41 | } 42 | } -------------------------------------------------------------------------------- /APM-Final v14/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 55 | 56 |
57 |
58 |
60 | 61 |
62 |
63 | 64 |
65 |
66 |
-------------------------------------------------------------------------------- /APM-Final v14/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router, Event, NavigationStart, NavigationEnd, NavigationError, NavigationCancel } from '@angular/router'; 3 | 4 | import { AuthService } from './user/auth.service'; 5 | import { slideInAnimation } from './app.animation'; 6 | import { MessageService } from './messages/message.service'; 7 | 8 | @Component({ 9 | selector: 'pm-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'], 12 | animations: [slideInAnimation] 13 | }) 14 | export class AppComponent { 15 | pageTitle = 'Acme Product Management'; 16 | loading = true; 17 | 18 | get isLoggedIn(): boolean { 19 | return this.authService.isLoggedIn; 20 | } 21 | 22 | get isMessageDisplayed(): boolean { 23 | return this.messageService.isDisplayed; 24 | } 25 | 26 | get userName(): string { 27 | if (this.authService.currentUser) { 28 | return this.authService.currentUser.userName; 29 | } 30 | return ''; 31 | } 32 | 33 | constructor(private authService: AuthService, 34 | private router: Router, 35 | private messageService: MessageService) { 36 | router.events.subscribe((routerEvent: Event) => { 37 | this.checkRouterEvent(routerEvent); 38 | }); 39 | } 40 | 41 | checkRouterEvent(routerEvent: Event): void { 42 | if (routerEvent instanceof NavigationStart) { 43 | this.loading = true; 44 | } 45 | 46 | if (routerEvent instanceof NavigationEnd || 47 | routerEvent instanceof NavigationCancel || 48 | routerEvent instanceof NavigationError) { 49 | this.loading = false; 50 | } 51 | } 52 | 53 | displayMessages(): void { 54 | // Example of primary and secondary routing together 55 | // this.router.navigate(['/login', {outlets: { popup: ['messages']}}]); // Does not work 56 | // this.router.navigate([{outlets: { primary: ['login'], popup: ['messages']}}]); // Works 57 | this.router.navigate([{ outlets: { popup: ['messages'] } }]); // Works 58 | this.messageService.isDisplayed = true; 59 | } 60 | 61 | hideMessages(): void { 62 | this.router.navigate([{ outlets: { popup: null } }]); 63 | this.messageService.isDisplayed = false; 64 | } 65 | 66 | logOut(): void { 67 | this.authService.logout(); 68 | this.router.navigateByUrl('/welcome'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | // Imports for loading & configuring the in-memory web api 7 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 8 | import { ProductData } from './products/product-data'; 9 | 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { AppComponent } from './app.component'; 12 | import { WelcomeComponent } from './home/welcome.component'; 13 | import { PageNotFoundComponent } from './page-not-found.component'; 14 | 15 | /* Feature Modules */ 16 | import { UserModule } from './user/user.module'; 17 | import { MessageModule } from './messages/message.module'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | BrowserAnimationsModule, 23 | HttpClientModule, 24 | InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }), 25 | UserModule, 26 | MessageModule, 27 | AppRoutingModule 28 | ], 29 | declarations: [ 30 | AppComponent, 31 | WelcomeComponent, 32 | PageNotFoundComponent 33 | ], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule { } 37 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 | 18 |
@deborahkurata
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /APM-Final v14/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/messages/message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Messages 3 | 7 |
8 |
9 |
10 |
12 | {{ message }} 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /APM-Final v14/src/app/messages/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { MessageService } from './message.service'; 5 | 6 | @Component({ 7 | templateUrl: './message.component.html', 8 | styles: [ 9 | '.message-row { margin-bottom: 10px }' 10 | ] 11 | }) 12 | export class MessageComponent { 13 | get messages(): string[] { 14 | return this.messageService.messages; 15 | } 16 | 17 | constructor(private messageService: MessageService, 18 | private router: Router) { } 19 | 20 | close(): void { 21 | // Close the popup. 22 | this.router.navigate([{ outlets: { popup: null } }]); 23 | this.messageService.isDisplayed = false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/messages/message.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { MessageComponent } from './message.component'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | SharedModule, 11 | RouterModule.forChild([ 12 | { 13 | path: 'messages', 14 | component: MessageComponent, 15 | outlet: 'popup' 16 | } 17 | ]) 18 | ], 19 | declarations: [ 20 | MessageComponent 21 | ] 22 | }) 23 | export class MessageModule { } 24 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/messages/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class MessageService { 7 | private _messages: string[] = []; 8 | isDisplayed = false; 9 | 10 | get messages(): string[] { 11 | return this._messages; 12 | } 13 | 14 | addMessage(message: string): void { 15 | const currentDate = new Date(); 16 | this.messages.unshift(message + ' at ' + currentDate.toLocaleString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { Product } from './product'; 4 | 5 | export class ProductData implements InMemoryDbService { 6 | 7 | createDb(): { products: Product[]} { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | releaseDate: 'March 19, 2018', 14 | description: 'Leaf rake with 48-inch wooden handle', 15 | price: 19.95, 16 | starRating: 3.2, 17 | imageUrl: 'assets/images/leaf_rake.png', 18 | category: 'Garden', 19 | tags: ['rake', 'leaf', 'yard', 'home'] 20 | }, 21 | { 22 | id: 2, 23 | productName: 'Garden Cart', 24 | productCode: 'GDN-0023', 25 | releaseDate: 'March 18, 2018', 26 | description: '15 gallon capacity rolling garden cart', 27 | price: 32.99, 28 | starRating: 4.2, 29 | imageUrl: 'assets/images/garden_cart.png', 30 | category: 'Garden' 31 | }, 32 | { 33 | id: 5, 34 | productName: 'Hammer', 35 | productCode: 'TBX-0048', 36 | releaseDate: 'May 21, 2018', 37 | description: 'Curved claw steel hammer', 38 | price: 8.9, 39 | starRating: 4.8, 40 | imageUrl: 'assets/images/hammer.png', 41 | category: 'Toolbox', 42 | tags: ['tools', 'hammer', 'construction'] 43 | }, 44 | { 45 | id: 8, 46 | productName: 'Saw', 47 | productCode: 'TBX-0022', 48 | releaseDate: 'May 15, 2018', 49 | description: '15-inch steel blade hand saw', 50 | price: 11.55, 51 | starRating: 3.7, 52 | imageUrl: 'assets/images/saw.png', 53 | category: 'Toolbox' 54 | }, 55 | { 56 | id: 10, 57 | productName: 'Video Game Controller', 58 | productCode: 'GMG-0042', 59 | releaseDate: 'October 15, 2018', 60 | description: 'Standard two-button video game controller', 61 | price: 35.95, 62 | starRating: 4.6, 63 | imageUrl: 'assets/images/xbox-controller.png', 64 | category: 'Gaming' 65 | } 66 | ]; 67 | return { products }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/app/products/product-detail.component.css -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | 4 | import { Product, ProductResolved } from './product'; 5 | 6 | @Component({ 7 | templateUrl: './product-detail.component.html', 8 | styleUrls: ['./product-detail.component.css'] 9 | }) 10 | export class ProductDetailComponent implements OnInit { 11 | pageTitle = 'Product Detail'; 12 | product: Product | null = null; 13 | errorMessage = ''; 14 | 15 | constructor(private route: ActivatedRoute, 16 | private router: Router) { } 17 | 18 | ngOnInit(): void { 19 | const resolvedData: ProductResolved = 20 | this.route.snapshot.data['resolvedData']; 21 | this.errorMessage = String(resolvedData.error); 22 | this.onProductRetrieved(resolvedData.product); 23 | console.log('error message', this.errorMessage) 24 | } 25 | 26 | onProductRetrieved(product: Product | null): void { 27 | this.product = product; 28 | 29 | if (this.product) { 30 | this.pageTitle = `Product Detail: ${this.product.productName}`; 31 | } else { 32 | this.pageTitle = 'No product found'; 33 | } 34 | } 35 | 36 | doRouting(): void { 37 | this.router.navigate( 38 | ['/products'], 39 | { queryParamsHandling: "preserve", queryParams: { message: '' } } 40 | ); 41 | // [routerLink]="['/products']" 42 | // queryParamsHandling="preserve" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { NgForm } from '@angular/forms'; 4 | 5 | import { Product } from '../product'; 6 | 7 | @Component({ 8 | templateUrl: './product-edit-info.component.html' 9 | }) 10 | export class ProductEditInfoComponent implements OnInit { 11 | @ViewChild(NgForm) productForm?: NgForm; 12 | 13 | errorMessage = ''; 14 | product?: Product; 15 | 16 | constructor(private route: ActivatedRoute) { } 17 | 18 | ngOnInit(): void { 19 | this.route.parent?.data.subscribe(data => { 20 | if (this.productForm) { 21 | this.productForm.reset(); 22 | } 23 | 24 | this.product = data['resolvedData'].product; 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit-tags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from '../product'; 5 | 6 | @Component({ 7 | templateUrl: './product-edit-tags.component.html' 8 | }) 9 | export class ProductEditTagsComponent implements OnInit { 10 | errorMessage = ''; 11 | newTags = ''; 12 | product?: Product; 13 | 14 | constructor(private route: ActivatedRoute) { } 15 | 16 | ngOnInit(): void { 17 | this.route?.parent?.data.subscribe(data => { 18 | this.product = data['resolvedData'].product; 19 | }); 20 | } 21 | 22 | // Add the defined tags 23 | addTags(): void { 24 | if (this.product) { 25 | if (!this.newTags) { 26 | this.errorMessage = 'Enter the search keywords separated by commas and then press Add'; 27 | } else { 28 | const tagArray = this.newTags.split(','); 29 | this.product.tags = this.product.tags ? this.product.tags.concat(tagArray) : tagArray; 30 | this.newTags = ''; 31 | this.errorMessage = ''; 32 | } 33 | } 34 | } 35 | 36 | // Remove the tag from the array of tags. 37 | removeTag(idx: number): void { 38 | this.product?.tags?.splice(idx, 1); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit.component.css: -------------------------------------------------------------------------------- 1 | .fa-exclamation { 2 | color: red; 3 | } 4 | 5 | .wizard a { 6 | background: #efefef; 7 | display: inline-block; 8 | margin-right: 5px; 9 | min-width: 150px; 10 | outline: none; 11 | padding: 10px 40px 10px; 12 | position: relative; 13 | text-decoration: none; 14 | } 15 | 16 | .wizard a:hover { 17 | cursor: pointer; 18 | text-decoration: underline; 19 | } 20 | 21 | /* Adds the cut out on the left side of the tab */ 22 | .wizard a:before { 23 | width: 0; 24 | height: 0; 25 | border-top: 20px inset transparent; 26 | border-bottom: 24px inset transparent; 27 | border-left: 20px solid #fff; 28 | position: absolute; 29 | content: ""; 30 | top: 0; 31 | left: 0; 32 | } 33 | 34 | /* Adds the arrow on the right side of the tab */ 35 | .wizard a:after { 36 | width: 0; 37 | height: 0; 38 | border-top: 20px inset transparent; 39 | border-bottom: 24px inset transparent; 40 | border-left: 21px solid #efefef; 41 | position: absolute; 42 | content: ""; 43 | top: 0; 44 | right: -20px; 45 | z-index: 2; 46 | } 47 | 48 | /* Squares the start and end of the tab bar */ 49 | .wizard a:first-child:before, 50 | .wizard a:last-child:after { 51 | border: none; 52 | } 53 | 54 | /* Rounds the corners */ 55 | .wizard a:first-child { 56 | -webkit-border-radius: 8px 0 0 0px; 57 | -moz-border-radius: 8px 0 0 0px; 58 | border-radius: 8px 0 0 0px; 59 | } 60 | 61 | .wizard a:last-child { 62 | -webkit-border-radius: 0 8px 0px 0; 63 | -moz-border-radius: 0 8px 0px 0; 64 | border-radius: 0 8px 0px 0; 65 | } 66 | 67 | .wizard .active { 68 | background: #007ACC; 69 | color: #fff; 70 | } 71 | 72 | /* Adds the right arrow after the tab */ 73 | .wizard .active:after { 74 | border-left-color: #007ACC; 75 | } 76 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 | 18 | 19 | 20 | 21 |
22 |
23 | 31 | 38 | 45 |
46 |
47 |
48 |
49 | 50 |
{{errorMessage}} 52 |
-------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { ProductEditGuard } from './product-edit.guard'; 5 | 6 | describe('ProductEditGuard', () => { 7 | let guard: ProductEditGuard; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ RouterTestingModule ] 12 | }); 13 | guard = TestBed.inject(ProductEditGuard); 14 | }); 15 | 16 | it('should be created', () => { 17 | expect(guard).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-edit/product-edit.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanDeactivate } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { ProductEditComponent } from './product-edit.component'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ProductEditGuard implements CanDeactivate { 10 | 11 | canDeactivate(component: ProductEditComponent, 12 | currentRoute: ActivatedRouteSnapshot, 13 | currentState: RouterStateSnapshot, 14 | nextState?: RouterStateSnapshot): boolean | Observable | Promise { 15 | 16 | if (component.isDirty) { 17 | const productName = component.product?.productName || 'New Product'; 18 | return confirm(`Navigate away and lose all changes to ${productName}?`); 19 | } 20 | return true; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from './product'; 5 | import { ProductService } from './product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'] 10 | }) 11 | export class ProductListComponent implements OnInit { 12 | pageTitle = 'Product List'; 13 | imageWidth = 50; 14 | imageMargin = 2; 15 | showImage = false; 16 | errorMessage = ''; 17 | 18 | _listFilter = ''; 19 | get listFilter(): string { 20 | return this._listFilter; 21 | } 22 | set listFilter(value: string) { 23 | this._listFilter = value; 24 | this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products; 25 | } 26 | 27 | filteredProducts: Product[] = []; 28 | products: Product[] = []; 29 | 30 | constructor(private productService: ProductService, 31 | private route: ActivatedRoute) { } 32 | 33 | ngOnInit(): void { 34 | this.listFilter = this.route.snapshot.queryParamMap.get('filterBy') || ''; 35 | this.showImage = this.route.snapshot.queryParamMap.get('showImage') === 'true'; 36 | 37 | this.productService.getProducts().subscribe({ 38 | next: products => { 39 | this.products = products; 40 | this.filteredProducts = this.performFilter(this.listFilter); 41 | }, 42 | error: err => this.errorMessage = err 43 | }); 44 | } 45 | 46 | performFilter(filterBy: string): Product[] { 47 | filterBy = filterBy.toLocaleLowerCase(); 48 | return this.products.filter((product: Product) => 49 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1); 50 | } 51 | 52 | toggleImage(): void { 53 | this.showImage = !this.showImage; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product-resolver.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | 4 | import { Observable, of } from 'rxjs'; 5 | import { map, catchError } from 'rxjs/operators'; 6 | 7 | import { ProductResolved } from './product'; 8 | import { ProductService } from './product.service'; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class ProductResolver implements Resolve { 14 | 15 | constructor(private productService: ProductService) { } 16 | 17 | resolve(route: ActivatedRouteSnapshot, 18 | state: RouterStateSnapshot): Observable { 19 | const id = Number(route.paramMap.get('id')); 20 | if (isNaN(id)) { 21 | const message = `Product id was not a number: ${id}`; 22 | console.error(message); 23 | return of({ product: null, error: message }); 24 | } 25 | 26 | return this.productService.getProduct(id) 27 | .pipe( 28 | map(product => ({ product, error: '' })), 29 | catchError(error => { 30 | const message = `Retrieval error: ${error}`; 31 | console.error(message); 32 | return of({ product: null, error: message }); 33 | }) 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { ProductListComponent } from './product-list.component'; 5 | import { ProductDetailComponent } from './product-detail.component'; 6 | import { ProductEditComponent } from './product-edit/product-edit.component'; 7 | import { ProductEditInfoComponent } from './product-edit/product-edit-info.component'; 8 | import { ProductEditTagsComponent } from './product-edit/product-edit-tags.component'; 9 | import { ProductResolver } from './product-resolver.service'; 10 | 11 | import { SharedModule } from '../shared/shared.module'; 12 | import { ProductEditGuard } from './product-edit/product-edit.guard'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | SharedModule, 17 | RouterModule.forChild([ 18 | { 19 | path: '', 20 | component: ProductListComponent 21 | }, 22 | { 23 | path: ':id', 24 | component: ProductDetailComponent, 25 | resolve: { resolvedData: ProductResolver } 26 | }, 27 | { 28 | path: ':id/edit', 29 | component: ProductEditComponent, 30 | canDeactivate: [ProductEditGuard], 31 | resolve: { resolvedData: ProductResolver }, 32 | children: [ 33 | { path: '', redirectTo: 'info', pathMatch: 'full' }, 34 | { path: 'info', component: ProductEditInfoComponent }, 35 | { path: 'tags', component: ProductEditTagsComponent } 36 | ] 37 | } 38 | ]) 39 | ], 40 | declarations: [ 41 | ProductListComponent, 42 | ProductDetailComponent, 43 | ProductEditComponent, 44 | ProductEditInfoComponent, 45 | ProductEditTagsComponent 46 | ] 47 | }) 48 | export class ProductModule { } 49 | -------------------------------------------------------------------------------- /APM-Final v14/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 | category: string; 7 | tags?: string[]; 8 | releaseDate: string; 9 | price: number; 10 | description: string; 11 | starRating: number; 12 | imageUrl: string; 13 | } 14 | 15 | export interface ProductResolved { 16 | product: Product | null; 17 | error?: string; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/selective-strategy.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Route, PreloadingStrategy } from '@angular/router'; 3 | 4 | import { Observable, of } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class SelectiveStrategy implements PreloadingStrategy { 10 | 11 | preload(route: Route, load: Function): Observable { 12 | if (route.data && route.data['preload']) { 13 | return load(); 14 | } 15 | return of(null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { StarComponent } from './star.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | StarComponent 13 | ], 14 | exports: [ 15 | StarComponent, 16 | CommonModule, 17 | FormsModule 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/shared/star.component.css: -------------------------------------------------------------------------------- 1 | .crop { 2 | overflow: hidden; 3 | } 4 | div { 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /APM-Final v14/src/app/shared/star.component.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /APM-Final v14/src/app/shared/star.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-star', 5 | templateUrl: './star.component.html', 6 | styleUrls: ['./star.component.css'] 7 | }) 8 | export class StarComponent implements OnChanges { 9 | @Input() rating = 0; 10 | starWidth = 0; 11 | @Output() ratingClicked = new EventEmitter(); 12 | 13 | ngOnChanges(): void { 14 | this.starWidth = this.rating * 75 / 5; 15 | } 16 | 17 | onClick(): void { 18 | this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/user/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { AuthGuard } from './auth.guard'; 5 | 6 | describe('AuthGuard', () => { 7 | let guard: AuthGuard; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ RouterTestingModule ] 12 | }); 13 | guard = TestBed.inject(AuthGuard); 14 | }); 15 | 16 | it('should be created', () => { 17 | expect(guard).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/user/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, CanLoad, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, Route, UrlSegment } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate, CanLoad { 10 | 11 | constructor(private authService: AuthService, 12 | private router: Router) { } 13 | 14 | canActivate( 15 | route: ActivatedRouteSnapshot, 16 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { 17 | return this.checkLoggedIn(state.url); 18 | } 19 | 20 | // Use the segments to build the full route 21 | // when using canLoad 22 | canLoad(route: Route, segments: UrlSegment[]): boolean { 23 | return this.checkLoggedIn(segments.join('/')); 24 | } 25 | 26 | checkLoggedIn(url: string): boolean { 27 | if (this.authService.isLoggedIn) { 28 | return true; 29 | } 30 | 31 | // Retain the attempted URL for redirection 32 | this.authService.redirectUrl = url; 33 | this.router.navigate(['/login']); 34 | return false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | import { MessageService } from '../messages/message.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | currentUser?: User | undefined; 11 | redirectUrl = ''; 12 | 13 | get isLoggedIn(): boolean { 14 | return !!this.currentUser; 15 | } 16 | 17 | constructor(private messageService: MessageService) { } 18 | 19 | login(userName: string, password: string): void { 20 | if (!userName || !password) { 21 | this.messageService.addMessage('Please enter your userName and password'); 22 | return; 23 | } 24 | if (userName === 'admin') { 25 | this.currentUser = { 26 | id: 1, 27 | userName, 28 | isAdmin: true 29 | }; 30 | this.messageService.addMessage('Admin login'); 31 | return; 32 | } 33 | this.currentUser = { 34 | id: 2, 35 | userName, 36 | isAdmin: false 37 | }; 38 | this.messageService.addMessage(`User: ${this.currentUser.userName} logged in`); 39 | } 40 | 41 | logout(): void { 42 | this.currentUser = undefined; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } 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 | }) 10 | export class LoginComponent { 11 | errorMessage = ''; 12 | pageTitle = 'Log In'; 13 | 14 | constructor(private authService: AuthService, 15 | private router: Router) { } 16 | 17 | login(loginForm: NgForm): void { 18 | if (loginForm && loginForm.valid) { 19 | const userName = loginForm.form.value.userName; 20 | const password = loginForm.form.value.password; 21 | this.authService.login(userName, password); 22 | 23 | // Navigate to the Product List page after log in. 24 | if (this.authService.redirectUrl) { 25 | this.router.navigateByUrl(this.authService.redirectUrl); 26 | } else { 27 | this.router.navigate(['/products']); 28 | } 29 | } else { 30 | this.errorMessage = 'Please enter a user name and password.'; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Final v14/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { LoginComponent } from './login.component'; 5 | 6 | import { SharedModule } from '../shared/shared.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | SharedModule, 11 | RouterModule.forChild([ 12 | { path: 'login', component: LoginComponent } 13 | ]) 14 | ], 15 | declarations: [ 16 | LoginComponent 17 | ] 18 | }) 19 | export class UserModule { } 20 | -------------------------------------------------------------------------------- /APM-Final v14/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-Final v14/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/garden_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/garden_cart.png -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/hammer.png -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/leaf_rake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/leaf_rake.png -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/saw.png -------------------------------------------------------------------------------- /APM-Final v14/src/assets/images/xbox-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/assets/images/xbox-controller.png -------------------------------------------------------------------------------- /APM-Final v14/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Final v14/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Final v14/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final v14/src/favicon.ico -------------------------------------------------------------------------------- /APM-Final v14/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /APM-Final v14/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-Final v14/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /APM-Final v14/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | @import "~font-awesome/css/font-awesome.min.css"; 4 | 5 | div.card-header { 6 | font-size: large; 7 | } 8 | 9 | div.card { 10 | margin-top: 10px 11 | } 12 | 13 | .table { 14 | margin-top: 10px 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } -------------------------------------------------------------------------------- /APM-Final v14/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /APM-Final v14/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Final v14/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2020", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Final v14/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /APM-Final/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Final/.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-Final/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned", 4 | "workbench.colorCustomizations": {} 5 | } -------------------------------------------------------------------------------- /APM-Final/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /APM-Final/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to APM!'); 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-Final/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('pm-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /APM-Final/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /APM-Final/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-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-Final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.2", 15 | "@angular/common": "~10.0.2", 16 | "@angular/compiler": "~10.0.2", 17 | "@angular/core": "~10.0.2", 18 | "@angular/forms": "~10.0.2", 19 | "@angular/platform-browser": "~10.0.2", 20 | "@angular/platform-browser-dynamic": "~10.0.2", 21 | "@angular/router": "~10.0.2", 22 | "bootstrap": "^4.5.0", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.3" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1000.1", 30 | "@angular/cli": "~10.0.1", 31 | "@angular/compiler-cli": "~10.0.2", 32 | "@types/node": "^12.11.1", 33 | "@types/jasmine": "~3.5.0", 34 | "@types/jasminewd2": "~2.0.3", 35 | "angular-in-memory-web-api": "^0.11.0", 36 | "codelyzer": "^5.1.2", 37 | "jasmine-core": "~3.5.0", 38 | "jasmine-spec-reporter": "~5.0.0", 39 | "karma": "~5.0.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage-istanbul-reporter": "~3.0.2", 42 | "karma-jasmine": "~3.3.0", 43 | "karma-jasmine-html-reporter": "^1.5.0", 44 | "protractor": "~7.0.0", 45 | "ts-node": "~8.3.0", 46 | "tslint": "~6.1.0", 47 | "typescript": "~3.9.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /APM-Final/readme.md: -------------------------------------------------------------------------------- 1 | # APM 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 4 | 5 | To install it, use `npm install --force`. 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-Final/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { WelcomeComponent } from './home/welcome.component'; 5 | import { PageNotFoundComponent } from './page-not-found.component'; 6 | import { AuthGuard } from './user/auth.guard'; 7 | import { SelectiveStrategy } from './selective-strategy.service'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | RouterModule.forRoot([ 12 | { path: 'welcome', component: WelcomeComponent }, 13 | { 14 | path: 'products', 15 | canActivate: [AuthGuard], 16 | data: { preload: false }, 17 | loadChildren: () => 18 | import('./products/product.module').then(m => m.ProductModule) 19 | }, 20 | { path: '', redirectTo: 'welcome', pathMatch: 'full' }, 21 | { path: '**', component: PageNotFoundComponent } 22 | ], { enableTracing: true, preloadingStrategy: SelectiveStrategy }) 23 | ], 24 | exports: [RouterModule] 25 | }) 26 | export class AppRoutingModule { } 27 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.animation.ts: -------------------------------------------------------------------------------- 1 | import { trigger, animate, transition, style, group, query } from '@angular/animations'; 2 | 3 | export const slideInAnimation = trigger('slideInAnimation', [ 4 | // Transition between any two states 5 | transition('* <=> *', [ 6 | // Events to apply 7 | // Defined style and animation function to apply 8 | // Config object with optional set to true to handle when element not yet added to the DOM 9 | query(':enter, :leave', style({ position: 'fixed', width: '100%', zIndex: 2 }), { optional: true }), 10 | // group block executes in parallel 11 | group([ 12 | query(':enter', [ 13 | style({ transform: 'translateX(100%)' }), 14 | animate('0.5s ease-out', style({ transform: 'translateX(0%)' })) 15 | ], { optional: true }), 16 | query(':leave', [ 17 | style({ transform: 'translateX(0%)' }), 18 | animate('0.5s ease-out', style({ transform: 'translateX(-100%)' })) 19 | ], { optional: true }) 20 | ]) 21 | ]) 22 | ]); 23 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | 10 | /* Spinner */ 11 | .spinner { 12 | font-size:300%; 13 | position:absolute; 14 | top: 50%; 15 | left: 50%; 16 | z-index:10 17 | } 18 | 19 | .fa-spinner { 20 | -webkit-animation: spin 1000ms infinite linear; 21 | animation: spin 1000ms infinite linear; 22 | } 23 | @-webkit-keyframes spin { 24 | 0% { 25 | -webkit-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 100% { 29 | -webkit-transform: rotate(359deg); 30 | transform: rotate(359deg); 31 | } 32 | } 33 | @keyframes spin { 34 | 0% { 35 | -webkit-transform: rotate(0deg); 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | -webkit-transform: rotate(359deg); 40 | transform: rotate(359deg); 41 | } 42 | } -------------------------------------------------------------------------------- /APM-Final/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 55 | 56 |
57 |
58 |
60 | 61 |
62 |
63 | 64 |
65 |
66 |
-------------------------------------------------------------------------------- /APM-Final/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router, Event, NavigationStart, NavigationEnd, NavigationError, NavigationCancel } from '@angular/router'; 3 | 4 | import { AuthService } from './user/auth.service'; 5 | import { slideInAnimation } from './app.animation'; 6 | import { MessageService } from './messages/message.service'; 7 | 8 | @Component({ 9 | selector: 'pm-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'], 12 | animations: [slideInAnimation] 13 | }) 14 | export class AppComponent { 15 | pageTitle = 'Acme Product Management'; 16 | loading = true; 17 | 18 | get isLoggedIn(): boolean { 19 | return this.authService.isLoggedIn; 20 | } 21 | 22 | get isMessageDisplayed(): boolean { 23 | return this.messageService.isDisplayed; 24 | } 25 | 26 | get userName(): string { 27 | if (this.authService.currentUser) { 28 | return this.authService.currentUser.userName; 29 | } 30 | return ''; 31 | } 32 | 33 | constructor(private authService: AuthService, 34 | private router: Router, 35 | private messageService: MessageService) { 36 | router.events.subscribe((routerEvent: Event) => { 37 | this.checkRouterEvent(routerEvent); 38 | }); 39 | } 40 | 41 | checkRouterEvent(routerEvent: Event): void { 42 | if (routerEvent instanceof NavigationStart) { 43 | this.loading = true; 44 | } 45 | 46 | if (routerEvent instanceof NavigationEnd || 47 | routerEvent instanceof NavigationCancel || 48 | routerEvent instanceof NavigationError) { 49 | this.loading = false; 50 | } 51 | } 52 | 53 | displayMessages(): void { 54 | // Example of primary and secondary routing together 55 | // this.router.navigate(['/login', {outlets: { popup: ['messages']}}]); // Does not work 56 | // this.router.navigate([{outlets: { primary: ['login'], popup: ['messages']}}]); // Works 57 | this.router.navigate([{ outlets: { popup: ['messages'] } }]); // Works 58 | this.messageService.isDisplayed = true; 59 | } 60 | 61 | hideMessages(): void { 62 | this.router.navigate([{ outlets: { popup: null } }]); 63 | this.messageService.isDisplayed = false; 64 | } 65 | 66 | logOut(): void { 67 | this.authService.logout(); 68 | this.router.navigateByUrl('/welcome'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /APM-Final/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | // Imports for loading & configuring the in-memory web api 7 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 8 | import { ProductData } from './products/product-data'; 9 | 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { AppComponent } from './app.component'; 12 | import { WelcomeComponent } from './home/welcome.component'; 13 | import { PageNotFoundComponent } from './page-not-found.component'; 14 | 15 | /* Feature Modules */ 16 | import { UserModule } from './user/user.module'; 17 | import { MessageModule } from './messages/message.module'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | BrowserAnimationsModule, 23 | HttpClientModule, 24 | InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }), 25 | UserModule, 26 | MessageModule, 27 | AppRoutingModule 28 | ], 29 | declarations: [ 30 | AppComponent, 31 | WelcomeComponent, 32 | PageNotFoundComponent 33 | ], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule { } 37 | -------------------------------------------------------------------------------- /APM-Final/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 | 18 |
@deborahkurata
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /APM-Final/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './welcome.component.html' 5 | }) 6 | export class WelcomeComponent { 7 | public pageTitle = 'Welcome'; 8 | } 9 | -------------------------------------------------------------------------------- /APM-Final/src/app/messages/message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Messages 3 | 7 |
8 |
9 |
10 |
12 | {{ message }} 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /APM-Final/src/app/messages/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { MessageService } from './message.service'; 5 | 6 | @Component({ 7 | templateUrl: './message.component.html', 8 | styles: [ 9 | '.message-row { margin-bottom: 10px }' 10 | ] 11 | }) 12 | export class MessageComponent { 13 | get messages(): string[] { 14 | return this.messageService.messages; 15 | } 16 | 17 | constructor(private messageService: MessageService, 18 | private router: Router) { } 19 | 20 | close(): void { 21 | // Close the popup. 22 | this.router.navigate([{ outlets: { popup: null } }]); 23 | this.messageService.isDisplayed = false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /APM-Final/src/app/messages/message.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | import { MessageComponent } from './message.component'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | SharedModule, 11 | RouterModule.forChild([ 12 | { 13 | path: 'messages', 14 | component: MessageComponent, 15 | outlet: 'popup' 16 | } 17 | ]) 18 | ], 19 | declarations: [ 20 | MessageComponent 21 | ] 22 | }) 23 | export class MessageModule { } 24 | -------------------------------------------------------------------------------- /APM-Final/src/app/messages/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class MessageService { 7 | private _messages: string[] = []; 8 | isDisplayed = false; 9 | 10 | get messages(): string[] { 11 | return this._messages; 12 | } 13 | 14 | addMessage(message: string): void { 15 | const currentDate = new Date(); 16 | this.messages.unshift(message + ' at ' + currentDate.toLocaleString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { Product } from './product'; 4 | 5 | export class ProductData implements InMemoryDbService { 6 | 7 | createDb(): { products: Product[]} { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | releaseDate: 'March 19, 2018', 14 | description: 'Leaf rake with 48-inch wooden handle', 15 | price: 19.95, 16 | starRating: 3.2, 17 | imageUrl: 'assets/images/leaf_rake.png', 18 | category: 'Garden', 19 | tags: ['rake', 'leaf', 'yard', 'home'] 20 | }, 21 | { 22 | id: 2, 23 | productName: 'Garden Cart', 24 | productCode: 'GDN-0023', 25 | releaseDate: 'March 18, 2018', 26 | description: '15 gallon capacity rolling garden cart', 27 | price: 32.99, 28 | starRating: 4.2, 29 | imageUrl: 'assets/images/garden_cart.png', 30 | category: 'Garden' 31 | }, 32 | { 33 | id: 5, 34 | productName: 'Hammer', 35 | productCode: 'TBX-0048', 36 | releaseDate: 'May 21, 2018', 37 | description: 'Curved claw steel hammer', 38 | price: 8.9, 39 | starRating: 4.8, 40 | imageUrl: 'assets/images/hammer.png', 41 | category: 'Toolbox', 42 | tags: ['tools', 'hammer', 'construction'] 43 | }, 44 | { 45 | id: 8, 46 | productName: 'Saw', 47 | productCode: 'TBX-0022', 48 | releaseDate: 'May 15, 2018', 49 | description: '15-inch steel blade hand saw', 50 | price: 11.55, 51 | starRating: 3.7, 52 | imageUrl: 'assets/images/saw.png', 53 | category: 'Toolbox' 54 | }, 55 | { 56 | id: 10, 57 | productName: 'Video Game Controller', 58 | productCode: 'GMG-0042', 59 | releaseDate: 'October 15, 2018', 60 | description: 'Standard two-button video game controller', 61 | price: 35.95, 62 | starRating: 4.6, 63 | imageUrl: 'assets/images/xbox-controller.png', 64 | category: 'Gaming' 65 | } 66 | ]; 67 | return { products }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/app/products/product-detail.component.css -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product, ProductResolved } from './product'; 5 | 6 | @Component({ 7 | templateUrl: './product-detail.component.html', 8 | styleUrls: ['./product-detail.component.css'] 9 | }) 10 | export class ProductDetailComponent implements OnInit { 11 | pageTitle = 'Product Detail'; 12 | product: Product; 13 | errorMessage: string; 14 | 15 | constructor(private route: ActivatedRoute) { } 16 | 17 | ngOnInit(): void { 18 | const resolvedData: ProductResolved = 19 | this.route.snapshot.data['resolvedData']; 20 | this.errorMessage = resolvedData.error; 21 | this.onProductRetrieved(resolvedData.product); 22 | } 23 | 24 | onProductRetrieved(product: Product): void { 25 | this.product = product; 26 | 27 | if (this.product) { 28 | this.pageTitle = `Product Detail: ${this.product.productName}`; 29 | } else { 30 | this.pageTitle = 'No product found'; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { NgForm } from '@angular/forms'; 4 | 5 | import { Product } from '../product'; 6 | 7 | @Component({ 8 | templateUrl: './product-edit-info.component.html' 9 | }) 10 | export class ProductEditInfoComponent implements OnInit { 11 | @ViewChild(NgForm) productForm: NgForm; 12 | 13 | errorMessage: string; 14 | product: Product; 15 | 16 | constructor(private route: ActivatedRoute) { } 17 | 18 | ngOnInit(): void { 19 | this.route.parent.data.subscribe(data => { 20 | if (this.productForm) { 21 | this.productForm.reset(); 22 | } 23 | 24 | this.product = data['resolvedData'].product; 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit-tags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from '../product'; 5 | 6 | @Component({ 7 | templateUrl: './product-edit-tags.component.html' 8 | }) 9 | export class ProductEditTagsComponent implements OnInit { 10 | errorMessage: string; 11 | newTags = ''; 12 | product: Product; 13 | 14 | constructor(private route: ActivatedRoute) { } 15 | 16 | ngOnInit(): void { 17 | this.route.parent.data.subscribe(data => { 18 | this.product = data['resolvedData'].product; 19 | }); 20 | } 21 | 22 | // Add the defined tags 23 | addTags(): void { 24 | if (!this.newTags) { 25 | this.errorMessage = 'Enter the search keywords separated by commas and then press Add'; 26 | } else { 27 | const tagArray = this.newTags.split(','); 28 | this.product.tags = this.product.tags ? this.product.tags.concat(tagArray) : tagArray; 29 | this.newTags = ''; 30 | this.errorMessage = ''; 31 | } 32 | } 33 | 34 | // Remove the tag from the array of tags. 35 | removeTag(idx: number): void { 36 | this.product.tags.splice(idx, 1); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit.component.css: -------------------------------------------------------------------------------- 1 | .fa-exclamation { 2 | color: red; 3 | } 4 | 5 | .wizard a { 6 | background: #efefef; 7 | display: inline-block; 8 | margin-right: 5px; 9 | min-width: 150px; 10 | outline: none; 11 | padding: 10px 40px 10px; 12 | position: relative; 13 | text-decoration: none; 14 | } 15 | 16 | .wizard a:hover { 17 | cursor: pointer; 18 | text-decoration: underline; 19 | } 20 | 21 | /* Adds the cut out on the left side of the tab */ 22 | .wizard a:before { 23 | width: 0; 24 | height: 0; 25 | border-top: 20px inset transparent; 26 | border-bottom: 24px inset transparent; 27 | border-left: 20px solid #fff; 28 | position: absolute; 29 | content: ""; 30 | top: 0; 31 | left: 0; 32 | } 33 | 34 | /* Adds the arrow on the right side of the tab */ 35 | .wizard a:after { 36 | width: 0; 37 | height: 0; 38 | border-top: 20px inset transparent; 39 | border-bottom: 24px inset transparent; 40 | border-left: 21px solid #efefef; 41 | position: absolute; 42 | content: ""; 43 | top: 0; 44 | right: -20px; 45 | z-index: 2; 46 | } 47 | 48 | /* Squares the start and end of the tab bar */ 49 | .wizard a:first-child:before, 50 | .wizard a:last-child:after { 51 | border: none; 52 | } 53 | 54 | /* Rounds the corners */ 55 | .wizard a:first-child { 56 | -webkit-border-radius: 8px 0 0 0px; 57 | -moz-border-radius: 8px 0 0 0px; 58 | border-radius: 8px 0 0 0px; 59 | } 60 | 61 | .wizard a:last-child { 62 | -webkit-border-radius: 0 8px 0px 0; 63 | -moz-border-radius: 0 8px 0px 0; 64 | border-radius: 0 8px 0px 0; 65 | } 66 | 67 | .wizard .active { 68 | background: #007ACC; 69 | color: #fff; 70 | } 71 | 72 | /* Adds the right arrow after the tab */ 73 | .wizard .active:after { 74 | border-left-color: #007ACC; 75 | } 76 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 | 18 | 19 | 20 | 21 |
22 |
23 | 31 | 38 | 45 |
46 |
47 |
48 |
49 | 50 |
{{errorMessage}} 52 |
-------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { ProductEditGuard } from './product-edit.guard'; 4 | 5 | describe('ProductEditGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [ProductEditGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([ProductEditGuard], (guard: ProductEditGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-edit/product-edit.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanDeactivate } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { ProductEditComponent } from './product-edit.component'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ProductEditGuard implements CanDeactivate { 10 | 11 | canDeactivate(component: ProductEditComponent, 12 | currentRoute: ActivatedRouteSnapshot, 13 | currentState: RouterStateSnapshot, 14 | nextState?: RouterStateSnapshot): boolean | Observable | Promise { 15 | 16 | if (component.isDirty) { 17 | const productName = component.product.productName || 'New Product'; 18 | return confirm(`Navigate away and lose all changes to ${productName}?`); 19 | } 20 | return true; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
8 |
Filter by:
9 |
10 | 12 |
13 |
14 |
16 |
17 |

Filtered by: {{listFilter}}

18 |
19 |
20 | 21 |
22 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 55 | 56 | 57 | 58 | 62 | 68 | 69 | 70 |
27 | 31 | ProductCodeAvailablePrice5 Star Rating
43 | 48 | 50 | 52 | {{ product.productName }} 53 | 54 | {{ product.productCode }}{{ product.releaseDate }}{{ product.price | currency:"USD":"symbol":"1.2-2" }} 59 | 60 | 61 | 63 | 67 |
71 |
72 | 73 |
74 |
75 | 76 |
78 | Error: {{ errorMessage }} 79 |
-------------------------------------------------------------------------------- /APM-Final/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from './product'; 5 | import { ProductService } from './product.service'; 6 | 7 | @Component({ 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'] 10 | }) 11 | export class ProductListComponent implements OnInit { 12 | pageTitle = 'Product List'; 13 | imageWidth = 50; 14 | imageMargin = 2; 15 | showImage = false; 16 | errorMessage = ''; 17 | 18 | _listFilter = ''; 19 | get listFilter(): string { 20 | return this._listFilter; 21 | } 22 | set listFilter(value: string) { 23 | this._listFilter = value; 24 | this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products; 25 | } 26 | 27 | filteredProducts: Product[] = []; 28 | products: Product[] = []; 29 | 30 | constructor(private productService: ProductService, 31 | private route: ActivatedRoute) { } 32 | 33 | ngOnInit(): void { 34 | this.listFilter = this.route.snapshot.queryParamMap.get('filterBy') || ''; 35 | this.showImage = this.route.snapshot.queryParamMap.get('showImage') === 'true'; 36 | 37 | this.productService.getProducts().subscribe({ 38 | next: products => { 39 | this.products = products; 40 | this.filteredProducts = this.performFilter(this.listFilter); 41 | }, 42 | error: err => this.errorMessage = err 43 | }); 44 | } 45 | 46 | performFilter(filterBy: string): Product[] { 47 | filterBy = filterBy.toLocaleLowerCase(); 48 | return this.products.filter((product: Product) => 49 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1); 50 | } 51 | 52 | toggleImage(): void { 53 | this.showImage = !this.showImage; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product-resolver.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | 4 | import { Observable, of } from 'rxjs'; 5 | import { map, catchError } from 'rxjs/operators'; 6 | 7 | import { ProductResolved } from './product'; 8 | import { ProductService } from './product.service'; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class ProductResolver implements Resolve { 14 | 15 | constructor(private productService: ProductService) { } 16 | 17 | resolve(route: ActivatedRouteSnapshot, 18 | state: RouterStateSnapshot): Observable { 19 | const id = route.paramMap.get('id'); 20 | if (isNaN(+id)) { 21 | const message = `Product id was not a number: ${id}`; 22 | console.error(message); 23 | return of({ product: null, error: message }); 24 | } 25 | 26 | return this.productService.getProduct(+id) 27 | .pipe( 28 | map(product => ({ product })), 29 | catchError(error => { 30 | const message = `Retrieval error: ${error}`; 31 | console.error(message); 32 | return of({ product: null, error: message }); 33 | }) 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { ProductListComponent } from './product-list.component'; 5 | import { ProductDetailComponent } from './product-detail.component'; 6 | import { ProductEditComponent } from './product-edit/product-edit.component'; 7 | import { ProductEditInfoComponent } from './product-edit/product-edit-info.component'; 8 | import { ProductEditTagsComponent } from './product-edit/product-edit-tags.component'; 9 | import { ProductResolver } from './product-resolver.service'; 10 | 11 | import { SharedModule } from '../shared/shared.module'; 12 | import { ProductEditGuard } from './product-edit/product-edit.guard'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | SharedModule, 17 | RouterModule.forChild([ 18 | { 19 | path: '', 20 | component: ProductListComponent 21 | }, 22 | { 23 | path: ':id', 24 | component: ProductDetailComponent, 25 | resolve: { resolvedData: ProductResolver } 26 | }, 27 | { 28 | path: ':id/edit', 29 | component: ProductEditComponent, 30 | canDeactivate: [ProductEditGuard], 31 | resolve: { resolvedData: ProductResolver }, 32 | children: [ 33 | { path: '', redirectTo: 'info', pathMatch: 'full' }, 34 | { path: 'info', component: ProductEditInfoComponent }, 35 | { path: 'tags', component: ProductEditTagsComponent } 36 | ] 37 | } 38 | ]) 39 | ], 40 | declarations: [ 41 | ProductListComponent, 42 | ProductDetailComponent, 43 | ProductEditComponent, 44 | ProductEditInfoComponent, 45 | ProductEditTagsComponent 46 | ] 47 | }) 48 | export class ProductModule { } 49 | -------------------------------------------------------------------------------- /APM-Final/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number; 4 | productName: string; 5 | productCode: string; 6 | category: string; 7 | tags?: string[]; 8 | releaseDate: string; 9 | price: number; 10 | description: string; 11 | starRating: number; 12 | imageUrl: string; 13 | } 14 | 15 | export interface ProductResolved { 16 | product: Product; 17 | error?: any; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Final/src/app/selective-strategy.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Route, PreloadingStrategy } from '@angular/router'; 3 | 4 | import { Observable, of } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class SelectiveStrategy implements PreloadingStrategy { 10 | 11 | preload(route: Route, load: Function): Observable { 12 | if (route.data && route.data['preload']) { 13 | return load(); 14 | } 15 | return of(null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /APM-Final/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { StarComponent } from './star.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | StarComponent 13 | ], 14 | exports: [ 15 | StarComponent, 16 | CommonModule, 17 | FormsModule 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /APM-Final/src/app/shared/star.component.css: -------------------------------------------------------------------------------- 1 | .crop { 2 | overflow: hidden; 3 | } 4 | div { 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /APM-Final/src/app/shared/star.component.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /APM-Final/src/app/shared/star.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-star', 5 | templateUrl: './star.component.html', 6 | styleUrls: ['./star.component.css'] 7 | }) 8 | export class StarComponent implements OnChanges { 9 | @Input() rating = 0; 10 | starWidth = 0; 11 | @Output() ratingClicked: EventEmitter = 12 | new EventEmitter(); 13 | 14 | ngOnChanges(): void { 15 | this.starWidth = this.rating * 75 / 5; 16 | } 17 | 18 | onClick(): void { 19 | this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /APM-Final/src/app/user/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { AuthGuard } from './auth.guard'; 4 | 5 | describe('AuthGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AuthGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([AuthGuard], (guard: AuthGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /APM-Final/src/app/user/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, CanLoad, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, Route, UrlSegment } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthGuard implements CanActivate, CanLoad { 10 | 11 | constructor(private authService: AuthService, 12 | private router: Router) { } 13 | 14 | canActivate( 15 | next: ActivatedRouteSnapshot, 16 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { 17 | return this.checkLoggedIn(state.url); 18 | } 19 | 20 | // Use the segments to build the full route 21 | // when using canLoad 22 | canLoad(route: Route, segments: UrlSegment[]): boolean { 23 | return this.checkLoggedIn(segments.join('/')); 24 | } 25 | 26 | checkLoggedIn(url: string): boolean { 27 | if (this.authService.isLoggedIn) { 28 | return true; 29 | } 30 | 31 | // Retain the attempted URL for redirection 32 | this.authService.redirectUrl = url; 33 | this.router.navigate(['/login']); 34 | return false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /APM-Final/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | import { MessageService } from '../messages/message.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | currentUser: User; 11 | redirectUrl: string; 12 | 13 | get isLoggedIn(): boolean { 14 | return !!this.currentUser; 15 | } 16 | 17 | constructor(private messageService: MessageService) { } 18 | 19 | login(userName: string, password: string): void { 20 | if (!userName || !password) { 21 | this.messageService.addMessage('Please enter your userName and password'); 22 | return; 23 | } 24 | if (userName === 'admin') { 25 | this.currentUser = { 26 | id: 1, 27 | userName, 28 | isAdmin: true 29 | }; 30 | this.messageService.addMessage('Admin login'); 31 | return; 32 | } 33 | this.currentUser = { 34 | id: 2, 35 | userName, 36 | isAdmin: false 37 | }; 38 | this.messageService.addMessage(`User: ${this.currentUser.userName} logged in`); 39 | } 40 | 41 | logout(): void { 42 | this.currentUser = null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /APM-Final/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } 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 | }) 10 | export class LoginComponent { 11 | errorMessage: string; 12 | pageTitle = 'Log In'; 13 | 14 | constructor(private authService: AuthService, 15 | private router: Router) { } 16 | 17 | login(loginForm: NgForm): void { 18 | if (loginForm && loginForm.valid) { 19 | const userName = loginForm.form.value.userName; 20 | const password = loginForm.form.value.password; 21 | this.authService.login(userName, password); 22 | 23 | // Navigate to the Product List page after log in. 24 | if (this.authService.redirectUrl) { 25 | this.router.navigateByUrl(this.authService.redirectUrl); 26 | } else { 27 | this.router.navigate(['/products']); 28 | } 29 | } else { 30 | this.errorMessage = 'Please enter a user name and password.'; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Final/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { LoginComponent } from './login.component'; 5 | 6 | import { SharedModule } from '../shared/shared.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | SharedModule, 11 | RouterModule.forChild([ 12 | { path: 'login', component: LoginComponent } 13 | ]) 14 | ], 15 | declarations: [ 16 | LoginComponent 17 | ] 18 | }) 19 | export class UserModule { } 20 | -------------------------------------------------------------------------------- /APM-Final/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-Final/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Final/src/assets/images/garden_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/garden_cart.png -------------------------------------------------------------------------------- /APM-Final/src/assets/images/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/hammer.png -------------------------------------------------------------------------------- /APM-Final/src/assets/images/leaf_rake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/leaf_rake.png -------------------------------------------------------------------------------- /APM-Final/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Final/src/assets/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/saw.png -------------------------------------------------------------------------------- /APM-Final/src/assets/images/xbox-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/assets/images/xbox-controller.png -------------------------------------------------------------------------------- /APM-Final/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Final/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Final/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Final/src/favicon.ico -------------------------------------------------------------------------------- /APM-Final/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /APM-Final/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-Final/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | @import "~font-awesome/css/font-awesome.min.css"; 4 | 5 | div.card-header { 6 | font-size: large; 7 | } 8 | 9 | div.card { 10 | margin-top: 10px 11 | } 12 | 13 | .table { 14 | margin-top: 10px 15 | } 16 | -------------------------------------------------------------------------------- /APM-Final/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/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-Final/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Final/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Final/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | }, 16 | { 17 | "path": "./e2e/tsconfig.json" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /APM-Final/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start v14/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /APM-Start v14/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Start v14/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /APM-Start v14/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /APM-Start v14/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start v14/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned" 4 | } -------------------------------------------------------------------------------- /APM-Start v14/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /APM-Start v14/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/apm'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /APM-Start v14/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^14.2.0", 14 | "@angular/common": "^14.2.0", 15 | "@angular/compiler": "^14.2.0", 16 | "@angular/core": "^14.2.0", 17 | "@angular/forms": "^14.2.0", 18 | "@angular/platform-browser": "^14.2.0", 19 | "@angular/platform-browser-dynamic": "^14.2.0", 20 | "@angular/router": "^14.2.0", 21 | "angular-in-memory-web-api": "^0.14.0", 22 | "bootstrap": "^5.2.1", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "^14.2.2", 30 | "@angular/cli": "~14.2.2", 31 | "@angular/compiler-cli": "^14.2.0", 32 | "@types/jasmine": "~4.0.0", 33 | "jasmine-core": "~4.3.0", 34 | "karma": "~6.4.0", 35 | "karma-chrome-launcher": "~3.1.0", 36 | "karma-coverage": "~2.2.0", 37 | "karma-jasmine": "~5.1.0", 38 | "karma-jasmine-html-reporter": "~2.0.0", 39 | "typescript": "~4.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /APM-Start v14/readme.md: -------------------------------------------------------------------------------- 1 | # Apm 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.2. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/app.animation.ts: -------------------------------------------------------------------------------- 1 | import { trigger, animate, transition, style, group, query } from '@angular/animations'; 2 | 3 | export const slideInAnimation = trigger('slideInAnimation', [ 4 | // Transition between any two states 5 | transition('* <=> *', [ 6 | // Events to apply 7 | // Defined style and animation function to apply 8 | // Config object with optional set to true to handle when element not yet added to the DOM 9 | query(':enter, :leave', style({ position: 'fixed', width: '100%', zIndex: 2 }), { optional: true }), 10 | // group block executes in parallel 11 | group([ 12 | query(':enter', [ 13 | style({ transform: 'translateX(100%)' }), 14 | animate('0.5s ease-out', style({ transform: 'translateX(0%)' })) 15 | ], { optional: true }), 16 | query(':leave', [ 17 | style({ transform: 'translateX(0%)' }), 18 | animate('0.5s ease-out', style({ transform: 'translateX(-100%)' })) 19 | ], { optional: true }) 20 | ]) 21 | ]) 22 | ]); 23 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | 10 | /* Spinner */ 11 | .spinner { 12 | font-size:300%; 13 | position:absolute; 14 | top: 50%; 15 | left: 50%; 16 | z-index:10 17 | } 18 | 19 | .fa-spinner { 20 | -webkit-animation: spin 1000ms infinite linear; 21 | animation: spin 1000ms infinite linear; 22 | } 23 | @-webkit-keyframes spin { 24 | 0% { 25 | -webkit-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 100% { 29 | -webkit-transform: rotate(359deg); 30 | transform: rotate(359deg); 31 | } 32 | } 33 | @keyframes spin { 34 | 0% { 35 | -webkit-transform: rotate(0deg); 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | -webkit-transform: rotate(359deg); 40 | transform: rotate(359deg); 41 | } 42 | } -------------------------------------------------------------------------------- /APM-Start v14/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AuthService } from './user/auth.service'; 4 | 5 | @Component({ 6 | selector: 'pm-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | 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 authService: AuthService) { } 25 | 26 | logOut(): void { 27 | this.authService.logout(); 28 | console.log('Log out'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { ProductData } from './products/product-data'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { WelcomeComponent } from './home/welcome.component'; 11 | import { PageNotFoundComponent } from './page-not-found.component'; 12 | 13 | /* Feature Modules */ 14 | import { ProductModule } from './products/product.module'; 15 | import { UserModule } from './user/user.module'; 16 | import { MessageModule } from './messages/message.module'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | BrowserModule, 21 | HttpClientModule, 22 | InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }), 23 | ProductModule, 24 | UserModule, 25 | MessageModule 26 | ], 27 | declarations: [ 28 | AppComponent, 29 | WelcomeComponent, 30 | PageNotFoundComponent 31 | ], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { } 35 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 | 18 |
@deborahkurata
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-home', 5 | templateUrl: './welcome.component.html' 6 | }) 7 | export class WelcomeComponent { 8 | public pageTitle = 'Welcome'; 9 | } 10 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/messages/message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Messages 3 | 7 |
8 |
9 |
10 |
12 | {{ message }} 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/messages/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { MessageService } from './message.service'; 5 | 6 | @Component({ 7 | templateUrl: './message.component.html', 8 | styles: [ 9 | '.message-row { margin-bottom: 10px }' 10 | ] 11 | }) 12 | export class MessageComponent { 13 | get messages(): string[] { 14 | return this.messageService.messages; 15 | } 16 | 17 | constructor(private messageService: MessageService, 18 | private router: Router) { } 19 | 20 | close(): void { 21 | // Close the popup. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/messages/message.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { MessageComponent } from './message.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | SharedModule 10 | ], 11 | declarations: [ 12 | MessageComponent 13 | ] 14 | }) 15 | export class MessageModule { } 16 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/messages/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class MessageService { 7 | private _messages: string[] = []; 8 | 9 | get messages(): string[] { 10 | return this._messages; 11 | } 12 | 13 | addMessage(message: string): void { 14 | const currentDate = new Date(); 15 | this.messages.unshift(message + ' at ' + currentDate.toLocaleString()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { Product } from './product'; 4 | 5 | export class ProductData implements InMemoryDbService { 6 | 7 | createDb(): { products: Product[]} { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | releaseDate: 'March 19, 2018', 14 | description: 'Leaf rake with 48-inch wooden handle', 15 | price: 19.95, 16 | starRating: 3.2, 17 | imageUrl: 'assets/images/leaf_rake.png', 18 | category: 'Garden', 19 | tags: ['rake', 'leaf', 'yard', 'home'] 20 | }, 21 | { 22 | id: 2, 23 | productName: 'Garden Cart', 24 | productCode: 'GDN-0023', 25 | releaseDate: 'March 18, 2018', 26 | description: '15 gallon capacity rolling garden cart', 27 | price: 32.99, 28 | starRating: 4.2, 29 | imageUrl: 'assets/images/garden_cart.png', 30 | category: 'Garden' 31 | }, 32 | { 33 | id: 5, 34 | productName: 'Hammer', 35 | productCode: 'TBX-0048', 36 | releaseDate: 'May 21, 2018', 37 | description: 'Curved claw steel hammer', 38 | price: 8.9, 39 | starRating: 4.8, 40 | imageUrl: 'assets/images/hammer.png', 41 | category: 'Toolbox', 42 | tags: ['tools', 'hammer', 'construction'] 43 | }, 44 | { 45 | id: 8, 46 | productName: 'Saw', 47 | productCode: 'TBX-0022', 48 | releaseDate: 'May 15, 2018', 49 | description: '15-inch steel blade hand saw', 50 | price: 11.55, 51 | starRating: 3.7, 52 | imageUrl: 'assets/images/saw.png', 53 | category: 'Toolbox' 54 | }, 55 | { 56 | id: 10, 57 | productName: 'Video Game Controller', 58 | productCode: 'GMG-0042', 59 | releaseDate: 'October 15, 2018', 60 | description: 'Standard two-button video game controller', 61 | price: 35.95, 62 | starRating: 4.6, 63 | imageUrl: 'assets/images/xbox-controller.png', 64 | category: 'Gaming' 65 | } 66 | ]; 67 | return { products }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/app/products/product-detail.component.css -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 | 9 |
10 | 11 |
12 |
13 |
Name:
14 |
{{product.productName}}
15 |
16 |
17 |
Code:
18 |
{{product.productCode}}
19 |
20 |
21 |
Description:
22 |
{{product.description}}
23 |
24 |
25 |
Availability:
26 |
{{product.releaseDate}}
27 |
28 |
29 |
Price:
30 |
{{product.price|currency:"USD":"symbol"}}
31 |
32 |
33 |
5 Star Rating:
34 |
35 | 36 | 37 |
38 |
39 |
40 |
Category:
41 |
{{product.category}}
42 |
43 |
44 |
Tags:
45 |
{{product.tags}}
46 |
47 |
48 | 49 |
51 | 56 |
57 |
58 | 59 |
60 |
61 | 65 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
{{errorMessage}} 77 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { Product } from './product'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | templateUrl: './product-detail.component.html', 8 | styleUrls: ['./product-detail.component.css'] 9 | }) 10 | export class ProductDetailComponent { 11 | pageTitle = 'Product Detail'; 12 | product: Product | null = null; 13 | errorMessage = ''; 14 | 15 | constructor(private productService: ProductService) { } 16 | 17 | getProduct(id: number): void { 18 | this.productService.getProduct(id).subscribe({ 19 | next: product => this.onProductRetrieved(product), 20 | error: err => this.errorMessage = err 21 | }); 22 | } 23 | 24 | onProductRetrieved(product: Product): void { 25 | this.product = product; 26 | 27 | if (this.product) { 28 | this.pageTitle = `Product Detail: ${this.product.productName}`; 29 | } else { 30 | this.pageTitle = 'No product found'; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-edit/product-edit-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { NgForm } from '@angular/forms'; 4 | 5 | import { Product } from '../product'; 6 | 7 | @Component({ 8 | templateUrl: './product-edit-info.component.html' 9 | }) 10 | export class ProductEditInfoComponent implements OnInit { 11 | @ViewChild(NgForm) productForm?: NgForm; 12 | 13 | errorMessage = ''; 14 | product = { id: 1, productName: 'test', productCode: 'test', description: 'test' }; 15 | 16 | constructor(private route: ActivatedRoute) { } 17 | 18 | ngOnInit(): void { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-edit/product-edit-tags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from '../product'; 5 | 6 | @Component({ 7 | templateUrl: './product-edit-tags.component.html' 8 | }) 9 | export class ProductEditTagsComponent implements OnInit { 10 | errorMessage = ''; 11 | newTags = ''; 12 | product = { id: 1, category: 'test', tags: ['test'] }; 13 | 14 | constructor(private route: ActivatedRoute) { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | // Add the defined tags 20 | addTags(): void { 21 | if (this.product) { 22 | if (!this.newTags) { 23 | this.errorMessage = 'Enter the search keywords separated by commas and then press Add'; 24 | } else { 25 | const tagArray = this.newTags.split(','); 26 | this.product.tags = this.product.tags ? this.product.tags.concat(tagArray) : tagArray; 27 | this.newTags = ''; 28 | this.errorMessage = ''; 29 | } 30 | } 31 | } 32 | 33 | // Remove the tag from the array of tags. 34 | removeTag(idx: number): void { 35 | this.product?.tags?.splice(idx, 1); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-edit/product-edit.component.css: -------------------------------------------------------------------------------- 1 | .fa-exclamation { 2 | color: red; 3 | } 4 | 5 | .wizard a { 6 | background: #efefef; 7 | display: inline-block; 8 | margin-right: 5px; 9 | min-width: 150px; 10 | outline: none; 11 | padding: 10px 40px 10px; 12 | position: relative; 13 | text-decoration: none; 14 | } 15 | 16 | .wizard a:hover { 17 | cursor: pointer; 18 | text-decoration: underline; 19 | } 20 | 21 | /* Adds the cut out on the left side of the tab */ 22 | .wizard a:before { 23 | width: 0; 24 | height: 0; 25 | border-top: 20px inset transparent; 26 | border-bottom: 24px inset transparent; 27 | border-left: 20px solid #fff; 28 | position: absolute; 29 | content: ""; 30 | top: 0; 31 | left: 0; 32 | } 33 | 34 | /* Adds the arrow on the right side of the tab */ 35 | .wizard a:after { 36 | width: 0; 37 | height: 0; 38 | border-top: 20px inset transparent; 39 | border-bottom: 24px inset transparent; 40 | border-left: 21px solid #efefef; 41 | position: absolute; 42 | content: ""; 43 | top: 0; 44 | right: -20px; 45 | z-index: 2; 46 | } 47 | 48 | /* Squares the start and end of the tab bar */ 49 | .wizard a:first-child:before, 50 | .wizard a:last-child:after { 51 | border: none; 52 | } 53 | 54 | /* Rounds the corners */ 55 | .wizard a:first-child { 56 | -webkit-border-radius: 8px 0 0 0px; 57 | -moz-border-radius: 8px 0 0 0px; 58 | border-radius: 8px 0 0 0px; 59 | } 60 | 61 | .wizard a:last-child { 62 | -webkit-border-radius: 0 8px 0px 0; 63 | -moz-border-radius: 0 8px 0px 0; 64 | border-radius: 0 8px 0px 0; 65 | } 66 | 67 | .wizard .active { 68 | background: #007ACC; 69 | color: #fff; 70 | } 71 | 72 | /* Adds the right arrow after the tab */ 73 | .wizard .active:after { 74 | border-left-color: #007ACC; 75 | } 76 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
8 |
Filter by:
9 |
10 | 12 |
13 |
14 |
16 |
17 |

Filtered by: {{listFilter}}

18 |
19 |
20 | 21 |
22 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 54 | 55 | 56 | 57 | 61 | 66 | 67 | 68 |
27 | 31 | ProductCodeAvailablePrice5 Star Rating
43 | 48 | 50 | 51 | {{ product.productName }} 52 | 53 | {{ product.productCode }}{{ product.releaseDate }}{{ product.price | currency:"USD":"symbol":"1.2-2" }} 58 | 59 | 60 | 62 | 65 |
69 |
70 | 71 |
72 |
73 | 74 |
76 | Error: {{ errorMessage }} 77 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Product } from './product'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | templateUrl: './product-list.component.html', 8 | styleUrls: ['./product-list.component.css'] 9 | }) 10 | export class ProductListComponent implements OnInit { 11 | pageTitle = 'Product List'; 12 | imageWidth = 50; 13 | imageMargin = 2; 14 | showImage = false; 15 | errorMessage = ''; 16 | 17 | _listFilter = ''; 18 | get listFilter(): string { 19 | return this._listFilter; 20 | } 21 | set listFilter(value: string) { 22 | this._listFilter = value; 23 | this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products; 24 | } 25 | 26 | filteredProducts: Product[] = []; 27 | products: Product[] = []; 28 | 29 | constructor(private productService: ProductService) { } 30 | 31 | ngOnInit(): void { 32 | this.productService.getProducts().subscribe({ 33 | next: products => { 34 | this.products = products; 35 | this.filteredProducts = this.performFilter(this.listFilter); 36 | }, 37 | error: err => this.errorMessage = err 38 | }); 39 | } 40 | 41 | performFilter(filterBy: string): Product[] { 42 | filterBy = filterBy.toLocaleLowerCase(); 43 | return this.products.filter((product: Product) => 44 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1); 45 | } 46 | 47 | toggleImage(): void { 48 | this.showImage = !this.showImage; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { ProductListComponent } from './product-list.component'; 4 | import { ProductDetailComponent } from './product-detail.component'; 5 | import { ProductEditComponent } from './product-edit/product-edit.component'; 6 | 7 | import { SharedModule } from '../shared/shared.module'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | SharedModule 12 | ], 13 | declarations: [ 14 | ProductListComponent, 15 | ProductDetailComponent, 16 | ProductEditComponent 17 | ] 18 | }) 19 | export class ProductModule { } 20 | -------------------------------------------------------------------------------- /APM-Start v14/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 | category: string; 7 | tags?: string[]; 8 | releaseDate: string; 9 | price: number; 10 | description: string; 11 | starRating: number; 12 | imageUrl: string; 13 | } 14 | 15 | export interface ProductResolved { 16 | product: Product | null; 17 | error?: string; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { StarComponent } from './star.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | StarComponent 13 | ], 14 | exports: [ 15 | StarComponent, 16 | CommonModule, 17 | FormsModule 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/shared/star.component.css: -------------------------------------------------------------------------------- 1 | .crop { 2 | overflow: hidden; 3 | } 4 | div { 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /APM-Start v14/src/app/shared/star.component.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /APM-Start v14/src/app/shared/star.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-star', 5 | templateUrl: './star.component.html', 6 | styleUrls: ['./star.component.css'] 7 | }) 8 | export class StarComponent implements OnChanges { 9 | @Input() rating = 0; 10 | starWidth = 0; 11 | @Output() ratingClicked = new EventEmitter(); 12 | 13 | ngOnChanges(): void { 14 | this.starWidth = this.rating * 75 / 5; 15 | } 16 | 17 | onClick(): void { 18 | this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | import { MessageService } from '../messages/message.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | currentUser?: User | undefined; 11 | 12 | get isLoggedIn(): boolean { 13 | return !!this.currentUser; 14 | } 15 | 16 | constructor(private messageService: MessageService) { } 17 | 18 | login(userName: string, password: string): void { 19 | if (!userName || !password) { 20 | this.messageService.addMessage('Please enter your userName and password'); 21 | return; 22 | } 23 | if (userName === 'admin') { 24 | this.currentUser = { 25 | id: 1, 26 | userName, 27 | isAdmin: true 28 | }; 29 | this.messageService.addMessage('Admin login'); 30 | return; 31 | } 32 | this.currentUser = { 33 | id: 2, 34 | userName, 35 | isAdmin: false 36 | }; 37 | this.messageService.addMessage(`User: ${this.currentUser.userName} logged in`); 38 | } 39 | 40 | logout(): void { 41 | this.currentUser = undefined; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Component({ 7 | templateUrl: './login.component.html' 8 | }) 9 | export class LoginComponent { 10 | errorMessage = ''; 11 | pageTitle = 'Log In'; 12 | 13 | constructor(private authService: AuthService) { } 14 | 15 | login(loginForm: NgForm): void { 16 | if (loginForm && loginForm.valid) { 17 | const userName = loginForm.form.value.userName; 18 | const password = loginForm.form.value.password; 19 | this.authService.login(userName, password); 20 | 21 | // Navigate to the Product List page after log in. 22 | } else { 23 | this.errorMessage = 'Please enter a user name and password.'; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /APM-Start v14/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | import { SharedModule } from '../shared/shared.module'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | SharedModule 10 | ], 11 | declarations: [ 12 | LoginComponent 13 | ] 14 | }) 15 | export class UserModule { } 16 | -------------------------------------------------------------------------------- /APM-Start v14/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-Start v14/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/garden_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/garden_cart.png -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/hammer.png -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/leaf_rake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/leaf_rake.png -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/saw.png -------------------------------------------------------------------------------- /APM-Start v14/src/assets/images/xbox-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/assets/images/xbox-controller.png -------------------------------------------------------------------------------- /APM-Start v14/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Start v14/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Start v14/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start v14/src/favicon.ico -------------------------------------------------------------------------------- /APM-Start v14/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /APM-Start v14/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-Start v14/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /APM-Start v14/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | @import "~font-awesome/css/font-awesome.min.css"; 4 | 5 | div.card-header { 6 | font-size: large; 7 | } 8 | 9 | div.card { 10 | margin-top: 10px 11 | } 12 | 13 | .table { 14 | margin-top: 10px 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } -------------------------------------------------------------------------------- /APM-Start v14/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /APM-Start v14/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Start v14/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2020", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /APM-Start v14/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /APM-Start/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /APM-Start/.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-Start/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "html.format.wrapAttributes": "force-aligned" 4 | } -------------------------------------------------------------------------------- /APM-Start/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /APM-Start/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to APM!'); 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-Start/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('pm-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /APM-Start/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /APM-Start/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-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-Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve -o", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.2", 15 | "@angular/common": "~10.0.2", 16 | "@angular/compiler": "~10.0.2", 17 | "@angular/core": "~10.0.2", 18 | "@angular/forms": "~10.0.2", 19 | "@angular/platform-browser": "~10.0.2", 20 | "@angular/platform-browser-dynamic": "~10.0.2", 21 | "@angular/router": "~10.0.2", 22 | "bootstrap": "^4.5.0", 23 | "font-awesome": "^4.7.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.3" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1000.1", 30 | "@angular/cli": "~10.0.1", 31 | "@angular/compiler-cli": "~10.0.2", 32 | "@types/node": "^12.11.1", 33 | "@types/jasmine": "~3.5.0", 34 | "@types/jasminewd2": "~2.0.3", 35 | "angular-in-memory-web-api": "^0.11.0", 36 | "codelyzer": "^5.1.2", 37 | "jasmine-core": "~3.5.0", 38 | "jasmine-spec-reporter": "~5.0.0", 39 | "karma": "~5.0.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage-istanbul-reporter": "~3.0.2", 42 | "karma-jasmine": "~3.3.0", 43 | "karma-jasmine-html-reporter": "^1.5.0", 44 | "protractor": "~7.0.0", 45 | "ts-node": "~8.3.0", 46 | "tslint": "~6.1.0", 47 | "typescript": "~3.9.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /APM-Start/readme.md: -------------------------------------------------------------------------------- 1 | # APM 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 4 | 5 | To install it, use `npm install --force`. 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-Start/src/app/app.animation.ts: -------------------------------------------------------------------------------- 1 | import { trigger, animate, transition, style, group, query } from '@angular/animations'; 2 | 3 | export const slideInAnimation = trigger('slideInAnimation', [ 4 | // Transition between any two states 5 | transition('* <=> *', [ 6 | // Events to apply 7 | // Defined style and animation function to apply 8 | // Config object with optional set to true to handle when element not yet added to the DOM 9 | query(':enter, :leave', style({ position: 'fixed', width: '100%', zIndex: 2 }), { optional: true }), 10 | // group block executes in parallel 11 | group([ 12 | query(':enter', [ 13 | style({ transform: 'translateX(100%)' }), 14 | animate('0.5s ease-out', style({ transform: 'translateX(0%)' })) 15 | ], { optional: true }), 16 | query(':leave', [ 17 | style({ transform: 'translateX(0%)' }), 18 | animate('0.5s ease-out', style({ transform: 'translateX(-100%)' })) 19 | ], { optional: true }) 20 | ]) 21 | ]) 22 | ]); 23 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | font-size: large; 3 | cursor: pointer; 4 | } 5 | 6 | .navbar-light .navbar-nav .nav-link.active { 7 | color: #007ACC 8 | } 9 | 10 | /* Spinner */ 11 | .spinner { 12 | font-size:300%; 13 | position:absolute; 14 | top: 50%; 15 | left: 50%; 16 | z-index:10 17 | } 18 | 19 | .fa-spinner { 20 | -webkit-animation: spin 1000ms infinite linear; 21 | animation: spin 1000ms infinite linear; 22 | } 23 | @-webkit-keyframes spin { 24 | 0% { 25 | -webkit-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 100% { 29 | -webkit-transform: rotate(359deg); 30 | transform: rotate(359deg); 31 | } 32 | } 33 | @keyframes spin { 34 | 0% { 35 | -webkit-transform: rotate(0deg); 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | -webkit-transform: rotate(359deg); 40 | transform: rotate(359deg); 41 | } 42 | } -------------------------------------------------------------------------------- /APM-Start/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /APM-Start/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AuthService } from './user/auth.service'; 4 | 5 | @Component({ 6 | selector: 'pm-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | pageTitle = 'Acme Product Management'; 12 | 13 | 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 authService: AuthService) { } 25 | 26 | logOut(): void { 27 | this.authService.logout(); 28 | console.log('Log out'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /APM-Start/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // Imports for loading & configuring the in-memory web api 6 | import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; 7 | import { ProductData } from './products/product-data'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { WelcomeComponent } from './home/welcome.component'; 11 | import { PageNotFoundComponent } from './page-not-found.component'; 12 | 13 | /* Feature Modules */ 14 | import { ProductModule } from './products/product.module'; 15 | import { UserModule } from './user/user.module'; 16 | import { MessageModule } from './messages/message.module'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | BrowserModule, 21 | HttpClientModule, 22 | InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }), 23 | ProductModule, 24 | UserModule, 25 | MessageModule 26 | ], 27 | declarations: [ 28 | AppComponent, 29 | WelcomeComponent, 30 | PageNotFoundComponent 31 | ], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { } 35 | -------------------------------------------------------------------------------- /APM-Start/src/app/home/welcome.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | {{pageTitle}} 4 |
5 |
6 |
7 |
8 | 11 |
12 | 13 |
Developed by:
14 |
15 |

Deborah Kurata

16 |
17 | 18 |
@deborahkurata
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /APM-Start/src/app/home/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-home', 5 | templateUrl: './welcome.component.html' 6 | }) 7 | export class WelcomeComponent { 8 | public pageTitle = 'Welcome'; 9 | } 10 | -------------------------------------------------------------------------------- /APM-Start/src/app/messages/message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Messages 3 | 7 |
8 |
9 |
10 |
12 | {{ message }} 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /APM-Start/src/app/messages/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { MessageService } from './message.service'; 5 | 6 | @Component({ 7 | templateUrl: './message.component.html', 8 | styles: [ 9 | '.message-row { margin-bottom: 10px }' 10 | ] 11 | }) 12 | export class MessageComponent { 13 | get messages(): string[] { 14 | return this.messageService.messages; 15 | } 16 | 17 | constructor(private messageService: MessageService, 18 | private router: Router) { } 19 | 20 | close(): void { 21 | // Close the popup. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /APM-Start/src/app/messages/message.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { MessageComponent } from './message.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | SharedModule 10 | ], 11 | declarations: [ 12 | MessageComponent 13 | ] 14 | }) 15 | export class MessageModule { } 16 | -------------------------------------------------------------------------------- /APM-Start/src/app/messages/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class MessageService { 7 | private _messages: string[] = []; 8 | 9 | get messages(): string[] { 10 | return this._messages; 11 | } 12 | 13 | addMessage(message: string): void { 14 | const currentDate = new Date(); 15 | this.messages.unshift(message + ' at ' + currentDate.toLocaleString()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /APM-Start/src/app/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

This is not the page you were looking for!

6 | ` 7 | }) 8 | export class PageNotFoundComponent { } 9 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-data.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | import { Product } from './product'; 4 | 5 | export class ProductData implements InMemoryDbService { 6 | 7 | createDb(): { products: Product[]} { 8 | const products: Product[] = [ 9 | { 10 | id: 1, 11 | productName: 'Leaf Rake', 12 | productCode: 'GDN-0011', 13 | releaseDate: 'March 19, 2018', 14 | description: 'Leaf rake with 48-inch wooden handle', 15 | price: 19.95, 16 | starRating: 3.2, 17 | imageUrl: 'assets/images/leaf_rake.png', 18 | category: 'Garden', 19 | tags: ['rake', 'leaf', 'yard', 'home'] 20 | }, 21 | { 22 | id: 2, 23 | productName: 'Garden Cart', 24 | productCode: 'GDN-0023', 25 | releaseDate: 'March 18, 2018', 26 | description: '15 gallon capacity rolling garden cart', 27 | price: 32.99, 28 | starRating: 4.2, 29 | imageUrl: 'assets/images/garden_cart.png', 30 | category: 'Garden' 31 | }, 32 | { 33 | id: 5, 34 | productName: 'Hammer', 35 | productCode: 'TBX-0048', 36 | releaseDate: 'May 21, 2018', 37 | description: 'Curved claw steel hammer', 38 | price: 8.9, 39 | starRating: 4.8, 40 | imageUrl: 'assets/images/hammer.png', 41 | category: 'Toolbox', 42 | tags: ['tools', 'hammer', 'construction'] 43 | }, 44 | { 45 | id: 8, 46 | productName: 'Saw', 47 | productCode: 'TBX-0022', 48 | releaseDate: 'May 15, 2018', 49 | description: '15-inch steel blade hand saw', 50 | price: 11.55, 51 | starRating: 3.7, 52 | imageUrl: 'assets/images/saw.png', 53 | category: 'Toolbox' 54 | }, 55 | { 56 | id: 10, 57 | productName: 'Video Game Controller', 58 | productCode: 'GMG-0042', 59 | releaseDate: 'October 15, 2018', 60 | description: 'Standard two-button video game controller', 61 | price: 35.95, 62 | starRating: 4.6, 63 | imageUrl: 'assets/images/xbox-controller.png', 64 | category: 'Gaming' 65 | } 66 | ]; 67 | return { products }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/app/products/product-detail.component.css -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
8 | 9 |
10 | 11 |
12 |
13 |
Name:
14 |
{{product.productName}}
15 |
16 |
17 |
Code:
18 |
{{product.productCode}}
19 |
20 |
21 |
Description:
22 |
{{product.description}}
23 |
24 |
25 |
Availability:
26 |
{{product.releaseDate}}
27 |
28 |
29 |
Price:
30 |
{{product.price|currency:"USD":"symbol"}}
31 |
32 |
33 |
5 Star Rating:
34 |
35 | 36 | 37 |
38 |
39 |
40 |
Category:
41 |
{{product.category}}
42 |
43 |
44 |
Tags:
45 |
{{product.tags}}
46 |
47 |
48 | 49 |
51 | 56 |
57 |
58 | 59 |
60 |
61 | 65 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
{{errorMessage}} 77 |
-------------------------------------------------------------------------------- /APM-Start/src/app/products/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { Product } from './product'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | templateUrl: './product-detail.component.html', 8 | styleUrls: ['./product-detail.component.css'] 9 | }) 10 | export class ProductDetailComponent { 11 | pageTitle = 'Product Detail'; 12 | product: Product; 13 | errorMessage: string; 14 | 15 | constructor(private productService: ProductService) { } 16 | 17 | getProduct(id: number): void { 18 | this.productService.getProduct(id).subscribe({ 19 | next: product => this.onProductRetrieved(product), 20 | error: err => this.errorMessage = err 21 | }); 22 | } 23 | 24 | onProductRetrieved(product: Product): void { 25 | this.product = product; 26 | 27 | if (this.product) { 28 | this.pageTitle = `Product Detail: ${this.product.productName}`; 29 | } else { 30 | this.pageTitle = 'No product found'; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-edit/product-edit-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { NgForm } from '@angular/forms'; 4 | 5 | import { Product } from '../product'; 6 | 7 | @Component({ 8 | templateUrl: './product-edit-info.component.html' 9 | }) 10 | export class ProductEditInfoComponent implements OnInit { 11 | @ViewChild(NgForm, {static: false}) productForm: NgForm; 12 | 13 | errorMessage: string; 14 | product = { id: 1, productName: 'test', productCode: 'test', description: 'test' }; 15 | 16 | constructor(private route: ActivatedRoute) { } 17 | 18 | ngOnInit(): void { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-edit/product-edit-tags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { Product } from '../product'; 5 | 6 | @Component({ 7 | templateUrl: './product-edit-tags.component.html' 8 | }) 9 | export class ProductEditTagsComponent implements OnInit { 10 | errorMessage: string; 11 | newTags = ''; 12 | product = { id: 1, category: 'test', tags: ['test'] }; 13 | 14 | constructor(private route: ActivatedRoute) { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | // Add the defined tags 20 | addTags(): void { 21 | if (!this.newTags) { 22 | this.errorMessage = 'Enter the search keywords separated by commas and then press Add'; 23 | } else { 24 | const tagArray = this.newTags.split(','); 25 | this.product.tags = this.product.tags ? this.product.tags.concat(tagArray) : tagArray; 26 | this.newTags = ''; 27 | this.errorMessage = ''; 28 | } 29 | } 30 | 31 | // Remove the tag from the array of tags. 32 | removeTag(idx: number): void { 33 | this.product.tags.splice(idx, 1); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-edit/product-edit.component.css: -------------------------------------------------------------------------------- 1 | .fa-exclamation { 2 | color: red; 3 | } 4 | 5 | .wizard a { 6 | background: #efefef; 7 | display: inline-block; 8 | margin-right: 5px; 9 | min-width: 150px; 10 | outline: none; 11 | padding: 10px 40px 10px; 12 | position: relative; 13 | text-decoration: none; 14 | } 15 | 16 | .wizard a:hover { 17 | cursor: pointer; 18 | text-decoration: underline; 19 | } 20 | 21 | /* Adds the cut out on the left side of the tab */ 22 | .wizard a:before { 23 | width: 0; 24 | height: 0; 25 | border-top: 20px inset transparent; 26 | border-bottom: 24px inset transparent; 27 | border-left: 20px solid #fff; 28 | position: absolute; 29 | content: ""; 30 | top: 0; 31 | left: 0; 32 | } 33 | 34 | /* Adds the arrow on the right side of the tab */ 35 | .wizard a:after { 36 | width: 0; 37 | height: 0; 38 | border-top: 20px inset transparent; 39 | border-bottom: 24px inset transparent; 40 | border-left: 21px solid #efefef; 41 | position: absolute; 42 | content: ""; 43 | top: 0; 44 | right: -20px; 45 | z-index: 2; 46 | } 47 | 48 | /* Squares the start and end of the tab bar */ 49 | .wizard a:first-child:before, 50 | .wizard a:last-child:after { 51 | border: none; 52 | } 53 | 54 | /* Rounds the corners */ 55 | .wizard a:first-child { 56 | -webkit-border-radius: 8px 0 0 0px; 57 | -moz-border-radius: 8px 0 0 0px; 58 | border-radius: 8px 0 0 0px; 59 | } 60 | 61 | .wizard a:last-child { 62 | -webkit-border-radius: 0 8px 0px 0; 63 | -moz-border-radius: 0 8px 0px 0; 64 | border-radius: 0 8px 0px 0; 65 | } 66 | 67 | .wizard .active { 68 | background: #007ACC; 69 | color: #fff; 70 | } 71 | 72 | /* Adds the right arrow after the tab */ 73 | .wizard .active:after { 74 | border-left-color: #007ACC; 75 | } 76 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.css: -------------------------------------------------------------------------------- 1 | thead { 2 | color: #337AB7; 3 | } -------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{pageTitle}} 4 |
5 | 6 |
7 |
8 |
Filter by:
9 |
10 | 12 |
13 |
14 |
16 |
17 |

Filtered by: {{listFilter}}

18 |
19 |
20 | 21 |
22 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 54 | 55 | 56 | 57 | 61 | 66 | 67 | 68 |
27 | 31 | ProductCodeAvailablePrice5 Star Rating
43 | 48 | 50 | 51 | {{ product.productName }} 52 | 53 | {{ product.productCode }}{{ product.releaseDate }}{{ product.price | currency:"USD":"symbol":"1.2-2" }} 58 | 59 | 60 | 62 | 65 |
69 |
70 | 71 |
72 |
73 | 74 |
76 | Error: {{ errorMessage }} 77 |
-------------------------------------------------------------------------------- /APM-Start/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Product } from './product'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | templateUrl: './product-list.component.html', 8 | styleUrls: ['./product-list.component.css'] 9 | }) 10 | export class ProductListComponent implements OnInit { 11 | pageTitle = 'Product List'; 12 | imageWidth = 50; 13 | imageMargin = 2; 14 | showImage = false; 15 | errorMessage = ''; 16 | 17 | _listFilter = ''; 18 | get listFilter(): string { 19 | return this._listFilter; 20 | } 21 | set listFilter(value: string) { 22 | this._listFilter = value; 23 | this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products; 24 | } 25 | 26 | filteredProducts: Product[] = []; 27 | products: Product[] = []; 28 | 29 | constructor(private productService: ProductService) { } 30 | 31 | ngOnInit(): void { 32 | this.productService.getProducts().subscribe({ 33 | next: products => { 34 | this.products = products; 35 | this.filteredProducts = this.performFilter(this.listFilter); 36 | }, 37 | error: err => this.errorMessage = err 38 | }); 39 | } 40 | 41 | performFilter(filterBy: string): Product[] { 42 | filterBy = filterBy.toLocaleLowerCase(); 43 | return this.products.filter((product: Product) => 44 | product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1); 45 | } 46 | 47 | toggleImage(): void { 48 | this.showImage = !this.showImage; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { ProductListComponent } from './product-list.component'; 4 | import { ProductDetailComponent } from './product-detail.component'; 5 | import { ProductEditComponent } from './product-edit/product-edit.component'; 6 | 7 | import { SharedModule } from '../shared/shared.module'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | SharedModule 12 | ], 13 | declarations: [ 14 | ProductListComponent, 15 | ProductDetailComponent, 16 | ProductEditComponent 17 | ] 18 | }) 19 | export class ProductModule { } 20 | -------------------------------------------------------------------------------- /APM-Start/src/app/products/product.ts: -------------------------------------------------------------------------------- 1 | /* Defines the product entity */ 2 | export interface Product { 3 | id: number; 4 | productName: string; 5 | productCode: string; 6 | category: string; 7 | tags?: string[]; 8 | releaseDate: string; 9 | price: number; 10 | description: string; 11 | starRating: number; 12 | imageUrl: string; 13 | } 14 | 15 | export interface ProductResolved { 16 | product: Product; 17 | error?: any; 18 | } 19 | -------------------------------------------------------------------------------- /APM-Start/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { StarComponent } from './star.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | StarComponent 13 | ], 14 | exports: [ 15 | StarComponent, 16 | CommonModule, 17 | FormsModule 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /APM-Start/src/app/shared/star.component.css: -------------------------------------------------------------------------------- 1 | .crop { 2 | overflow: hidden; 3 | } 4 | div { 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /APM-Start/src/app/shared/star.component.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /APM-Start/src/app/shared/star.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'pm-star', 5 | templateUrl: './star.component.html', 6 | styleUrls: ['./star.component.css'] 7 | }) 8 | export class StarComponent implements OnChanges { 9 | @Input() rating = 0; 10 | starWidth = 0; 11 | @Output() ratingClicked: EventEmitter = 12 | new EventEmitter(); 13 | 14 | ngOnChanges(): void { 15 | this.starWidth = this.rating * 75 / 5; 16 | } 17 | 18 | onClick(): void { 19 | this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /APM-Start/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { User } from './user'; 4 | import { MessageService } from '../messages/message.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | currentUser: User; 11 | 12 | get isLoggedIn(): boolean { 13 | return !!this.currentUser; 14 | } 15 | 16 | constructor(private messageService: MessageService) { } 17 | 18 | login(userName: string, password: string): void { 19 | if (!userName || !password) { 20 | this.messageService.addMessage('Please enter your userName and password'); 21 | return; 22 | } 23 | if (userName === 'admin') { 24 | this.currentUser = { 25 | id: 1, 26 | userName, 27 | isAdmin: true 28 | }; 29 | this.messageService.addMessage('Admin login'); 30 | return; 31 | } 32 | this.currentUser = { 33 | id: 2, 34 | userName, 35 | isAdmin: false 36 | }; 37 | this.messageService.addMessage(`User: ${this.currentUser.userName} logged in`); 38 | } 39 | 40 | logout(): void { 41 | this.currentUser = null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /APM-Start/src/app/user/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Component({ 7 | templateUrl: './login.component.html' 8 | }) 9 | export class LoginComponent { 10 | errorMessage: string; 11 | pageTitle = 'Log In'; 12 | 13 | constructor(private authService: AuthService) { } 14 | 15 | login(loginForm: NgForm): void { 16 | if (loginForm && loginForm.valid) { 17 | const userName = loginForm.form.value.userName; 18 | const password = loginForm.form.value.password; 19 | this.authService.login(userName, password); 20 | 21 | // Navigate to the Product List page after log in. 22 | } else { 23 | this.errorMessage = 'Please enter a user name and password.'; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /APM-Start/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | import { SharedModule } from '../shared/shared.module'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | SharedModule 10 | ], 11 | declarations: [ 12 | LoginComponent 13 | ] 14 | }) 15 | export class UserModule { } 16 | -------------------------------------------------------------------------------- /APM-Start/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-Start/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/.gitkeep -------------------------------------------------------------------------------- /APM-Start/src/assets/images/garden_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/garden_cart.png -------------------------------------------------------------------------------- /APM-Start/src/assets/images/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/hammer.png -------------------------------------------------------------------------------- /APM-Start/src/assets/images/leaf_rake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/leaf_rake.png -------------------------------------------------------------------------------- /APM-Start/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /APM-Start/src/assets/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/saw.png -------------------------------------------------------------------------------- /APM-Start/src/assets/images/xbox-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/assets/images/xbox-controller.png -------------------------------------------------------------------------------- /APM-Start/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /APM-Start/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /APM-Start/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeborahK/Angular-Routing/78c8cbed9d6a90c74bd7599cb1bbf1221a368666/APM-Start/src/favicon.ico -------------------------------------------------------------------------------- /APM-Start/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /APM-Start/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /APM-Start/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~bootstrap/dist/css/bootstrap.min.css"; 3 | @import "~font-awesome/css/font-awesome.min.css"; 4 | 5 | div.card-header { 6 | font-size: large; 7 | } 8 | 9 | div.card { 10 | margin-top: 10px 11 | } 12 | 13 | .table { 14 | margin-top: 10px 15 | } -------------------------------------------------------------------------------- /APM-Start/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APM-Start/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | }, 16 | { 17 | "path": "./e2e/tsconfig.json" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /APM-Start/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changes made to the course since its release: 2 | 3 | - July 7, 2020 - Reviewed the code in the course for any changes required for v10. No code changes required. 4 | 5 | - February 2, 2020 - Reviewed the code in the course for any changes required for v9. No code changes required. 6 | 7 | - August 20, 2019 - The course was updated to Angular v8. For details see: https://docs.google.com/document/d/1l-n3UW1SK3xbeqXGxl34W-FGtm90doHGpfeb-W5Dq2s/edit?usp=sharing 8 | 9 | - December 20, 2018 - The course was updated to Angular v7, Bootstrap 4 with Font Awesome, and RxJS 6. For details on the list of course changes, see: https://blogs.msmvps.com/deborahk/angular-routing-course-update-for-v7/ 10 | 11 | #Changes made to the project files since its release: 12 | 13 | - September 8, 2022 - Updated the code to Angular v14. Angular is now strongly typed by default, so the code using v14 will be slightly different from that shown in the course. Every variable must be initialized and all types appropropriate defined. Minor changes to Bootstrap as well. 14 | 15 | - July 7, 2020 - Updated the code to Angular v10, which modified the packages. Minor changes such as adding function return types and using property shorthand. 16 | 17 | - February 2, 2020 - Updated the code to Angular v9, which modified the packages. The only code change was to undo a change required in Angular v8 regarding the `static` flag on `ViewChild`. 18 | 19 | - August 20, 2019 - The code was replaced with new start and final forlders for Angular v8. 20 | 21 | - December 20, 2018 - The code was replaced with new start and final folders that leverage the Angular CLI and for Angular v7, RxJS 6, Bootstrap 4, and Font Awesome. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Deborah Kurata 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular-Routing 2 | Materials for the ["Angular Routing"](http://bit.ly/Angular-routing) course on Pluralsight. 3 | 4 | `APM-Start`: The starter files. **Use this to code along with the course**. 5 | NOTE: This code is Angular v10. To install it, you'll need to use `npm install --force` and you will see numerous warnings. 6 | 7 | `APM-Start v14`: The starter files updated to Angular v14. **Use this code to code along with the course using v14**. 8 | NOTE: The course was not updated to v14, so if starting with v14 code, some shown code will be slightly different, requiring initialized variables and strong typing. 9 | 10 | `APM-Final`: The completed files. Use this to see the completed solution from the course. 11 | NOTE: This code is Angular v10. To install it, you'll need to use `npm install --force` and you will see numerous warnings. 12 | 13 | `APM-Final v14`: The completed files updated to Angular v14. Use this to see the completed solution for the course in v14. 14 | 15 | See the `README.md` file under each folder for details on installing and running the application. 16 | 17 | Please see the `CHANGELOG.md` for the most recent changes to this repo. 18 | --------------------------------------------------------------------------------