├── .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 |
6 | {{pageTitle}}
7 |
25 |
26 |
27 |
54 |
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 |
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 |
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 |
5 |
6 |
8 |
18 |
19 |
20 |
21 |
22 |
23 |
29 | Save
30 |
31 |
36 | Cancel
37 |
38 |
43 | Delete
44 |
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 |
6 | {{pageTitle}}
7 |
25 |
26 |
27 |
54 |
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 |
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 |
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 |
5 |
6 |
8 |
18 |
19 |
20 |
21 |
22 |
23 |
29 | Save
30 |
31 |
36 | Cancel
37 |
38 |
43 | Delete
44 |
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 |
5 |
6 |
7 |
8 |
Filter by:
9 |
10 |
12 |
13 |
14 |
16 |
17 |
Filtered by: {{listFilter}}
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
29 | {{showImage ? "Hide" : "Show"}} Image
30 |
31 |
32 | Product
33 | Code
34 | Available
35 | Price
36 | 5 Star Rating
37 |
38 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
52 | {{ product.productName }}
53 |
54 |
55 | {{ product.productCode }}
56 | {{ product.releaseDate }}
57 | {{ product.price | currency:"USD":"symbol":"1.2-2" }}
58 |
59 |
60 |
61 |
62 |
63 |
65 | Edit
66 |
67 |
68 |
69 |
70 |
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 |
2 | {{pageTitle}}
3 |
14 |
32 |
33 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
63 | Back
64 |
65 |
67 | Edit
68 |
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 |
5 |
6 |
7 |
8 |
Filter by:
9 |
10 |
12 |
13 |
14 |
16 |
17 |
Filtered by: {{listFilter}}
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
29 | {{showImage ? "Hide" : "Show"}} Image
30 |
31 |
32 | Product
33 | Code
34 | Available
35 | Price
36 | 5 Star Rating
37 |
38 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 | {{ product.productName }}
52 |
53 |
54 | {{ product.productCode }}
55 | {{ product.releaseDate }}
56 | {{ product.price | currency:"USD":"symbol":"1.2-2" }}
57 |
58 |
59 |
60 |
61 |
62 |
63 | Edit
64 |
65 |
66 |
67 |
68 |
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 |
2 | {{pageTitle}}
3 |
14 |
32 |
33 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
63 | Back
64 |
65 |
67 | Edit
68 |
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 |
5 |
6 |
7 |
8 |
Filter by:
9 |
10 |
12 |
13 |
14 |
16 |
17 |
Filtered by: {{listFilter}}
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
29 | {{showImage ? "Hide" : "Show"}} Image
30 |
31 |
32 | Product
33 | Code
34 | Available
35 | Price
36 | 5 Star Rating
37 |
38 |
39 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 | {{ product.productName }}
52 |
53 |
54 | {{ product.productCode }}
55 | {{ product.releaseDate }}
56 | {{ product.price | currency:"USD":"symbol":"1.2-2" }}
57 |
58 |
59 |
60 |
61 |
62 |
63 | Edit
64 |
65 |
66 |
67 |
68 |
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 |
--------------------------------------------------------------------------------