├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── README_starter.md ├── angular.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── scripts ├── backup_branches.sh ├── backup_to_meta.sh ├── delete_backups.sh ├── publish_branches_and_tags.sh ├── publish_jump_start.sh ├── publish_starter.sh ├── rebase.sh ├── reset_local_branches.sh ├── retag.sh ├── rollback_backups.sh ├── safetycheck.js └── the_magic_button.sh ├── server ├── .jshintrc ├── contacts-server.html ├── contacts-server.js ├── data │ └── contacts.js └── voting-server.js ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routes.ts │ ├── app.tokens.ts │ ├── contacts-detail │ │ ├── contacts-detail.component.css │ │ ├── contacts-detail.component.html │ │ └── contacts-detail.component.ts │ ├── contacts-editor │ │ ├── contacts-editor.component.css │ │ ├── contacts-editor.component.html │ │ └── contacts-editor.component.ts │ ├── contacts-list │ │ ├── contacts-list.component.css │ │ ├── contacts-list.component.html │ │ └── contacts-list.component.ts │ ├── contacts-material.module.ts │ ├── contacts.service.ts │ ├── data │ │ ├── contact-data.ts │ │ ├── contact-manager.ts │ │ ├── countries-data.ts │ │ └── gender.ts │ ├── models │ │ └── contact.ts │ └── shared │ │ └── index.ts ├── assets │ ├── .gitkeep │ ├── .npmignore │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ └── images │ │ ├── 0.jpg │ │ ├── 1.jpg │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── 8.jpg │ │ ├── 9.jpg │ │ ├── placeholder.png │ │ └── team.jpg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── testing │ ├── custom-matchers.ts │ └── helpers.ts └── typings.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # e2e 39 | /e2e/*.js 40 | /e2e/*.map 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | package.json 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "semi": true, 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | thoughtram Training Material License (TTML 1.0) 2 | 3 | Copyright (c) 2016 thoughtram GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in the 7 | Software with very limited restrictions. Any person has the right to use, copy, 8 | modify, merge, publish, distribute, sublicense. It’s not permitted to use the 9 | Software to run or support trainings without exclusive allowance from thoughtram GmbH. 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Master Class 2 | 3 | This repository contains all the **Lab Solutions** for the Angular Master Class courseware by thoughtram. 4 | 5 | ## Learning Modules 6 | 7 | Each learning module of the **Angular Master Class** course has 1 or more lab exercises. Each lab exercise adds features and enhancements to the previous lab exercise. 8 | 9 | * The final module solutions are stored in module branches. 10 | * The lab exercise solutions are accessed via git tags; e.g. `jump-start-step-10` or `observables-step-3`. 11 | 12 | Shown below are quick-links and ordering of the Angular Master Class courseware modules: 13 | 14 | | Module | Git Command | 15 | |--------|--------| 16 | | Module 1: **[Jump-Start](https://github.com/thoughtram/angular-master-class-solutions/tree/jump-start/src)** | `git checkout jump-start-step-` | 17 | | Module 2: **[Observables](https://github.com/thoughtram/angular-master-class-solutions/tree/observables/src)** | `git checkout observables-step-` | 18 | | Module 3: **[Architecture](https://github.com/thoughtram/angular-master-class-solutions/tree/architecture/src)** | `git checkout architecture-step-` | 19 | | Module 4: **[Routing](https://github.com/thoughtram/angular-master-class-solutions/tree/routing/src)** | `git checkout routing-step-` | 20 | | Module 5: **[Forms](https://github.com/thoughtram/angular-master-class-solutions/tree/forms/src)** | `git checkout forms-step-` | 21 | | Module 6: **[Redux](https://github.com/thoughtram/angular-master-class-solutions/tree/redux/src)** | `git checkout redux-step-` | 22 | | Module 7: **[ngrx](https://github.com/thoughtram/angular-master-class-solutions/tree/ngrx/src)** | `git checkout ngrx-step-` | 23 | | Module 8: **[Testing](https://github.com/thoughtram/angular-master-class-solutions/tree/testing/src)** | `git checkout testing-step-` | 24 | | Module 9: **[Reusable Libraries](https://github.com/thoughtram/angular-master-class-solutions/tree/reusable-libraries/src)** | `git checkout reusable-libraries-step-` | 25 | | Module 10: **[Progressive Web Apps](https://github.com/thoughtram/angular-master-class-solutions/tree/pwa/src)** | `git checkout pwa-step-` | 26 | | Module 11: **[Universal](https://github.com/thoughtram/angular-master-class-solutions/tree/universal/src)** | `git checkout universal-step-` | 27 | 28 | For each lab exercise, simply use the *git* command to quickly checkout the solution for that lab exercise: 29 | 30 | ``` 31 | git checkout jump-start-step-11 32 | ``` 33 | 34 | To *checkout* the final module solution (after all the lab exercises are completed), either checkout the *branch* or the latest *tag* of that branch. So, for example, to checkout the final solution **Module 1: Jump-Start**, use: 35 | 36 | ``` 37 | git checkout jump-start 38 | ``` 39 | 40 | ## Local Setup 41 | If not done already, clone this repository using: 42 | 43 | ``` 44 | $ git clone https://github.com/thoughtram/angular-master-class-starter.git 45 | ``` 46 | 47 | Done? Great, now install the dependencies (this might take a little while): 48 | 49 | ``` 50 | $ cd angular-master-class-starter 51 | $ npx yarn install 52 | ``` 53 | 54 | > You may also need to globally install the Angular-Cli: `npm i -g @angular/cli` 55 | 56 | ## Web and App Servers 57 | 58 | To launch your web application, use a Terminal session with the command: 59 | 60 | ``` 61 | $ ng serve 62 | ``` 63 | 64 | This starts a web server for the Angular application; open with a browser url `http://localhost:4200`. 65 | 66 | And since your Angular application may request external, remote data [from `http://localhost:4201/api`], you will need a local app server to respond to the REST API calls. We have already configured a server as part of this repository. 67 | 68 | Simply start a second, separate Terminal session with the commend: 69 | 70 | ``` 71 | $ npm run rest-api 72 | ``` 73 | 74 | > While only some of the solutions use the app server, it will not hurt to start it immediately. 75 | 76 | 77 | ## Important 78 | 79 | Have fun and good luck! 80 | 81 | Christoph & Pascal & Dominic 82 | -------------------------------------------------------------------------------- /README_starter.md: -------------------------------------------------------------------------------- 1 | # Angular Master Class by thoughtram 2 | 3 | This is the Angular Master Class exercise repository. Here's where you'll build your Angular application throughout this training. 4 | 5 | Please make sure to follow our [Preparation Guide](http://thoughtram.io/prepare-for-your-training.html) to set up your machine **before** you come to the class. 6 | 7 | If not done already, clone this repository using: 8 | 9 | ``` 10 | $ git clone https://github.com/thoughtram/angular-master-class-starter.git 11 | ``` 12 | 13 | Done? Great, now install the dependencies (this might take a little while): 14 | 15 | ``` 16 | $ cd angular-master-class-starter 17 | $ npx yarn install 18 | ``` 19 | 20 | Then run the application by executing: 21 | 22 | ``` 23 | $ ng serve 24 | ``` 25 | 26 | Then open up your browser at `http://localhost:4200` 27 | 28 | Have fun and good luck! 29 | 30 | Christoph & Pascal & Dominic 31 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "contacts-app": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "trm", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/contacts-app", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "src/styles.scss" 31 | ], 32 | "scripts": [ 33 | "./node_modules/hammerjs/hammer.js" 34 | ] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true 53 | } 54 | } 55 | }, 56 | "serve": { 57 | "builder": "@angular-devkit/build-angular:dev-server", 58 | "options": { 59 | "browserTarget": "contacts-app:build" 60 | }, 61 | "configurations": { 62 | "production": { 63 | "browserTarget": "contacts-app:build:production" 64 | } 65 | } 66 | }, 67 | "extract-i18n": { 68 | "builder": "@angular-devkit/build-angular:extract-i18n", 69 | "options": { 70 | "browserTarget": "contacts-app:build" 71 | } 72 | }, 73 | "test": { 74 | "builder": "@angular-devkit/build-angular:karma", 75 | "options": { 76 | "main": "src/test.ts", 77 | "polyfills": "src/polyfills.ts", 78 | "tsConfig": "tsconfig.spec.json", 79 | "karmaConfig": "karma.conf.js", 80 | "styles": [ 81 | "src/styles.scss" 82 | ], 83 | "scripts": [], 84 | "assets": [ 85 | "src/favicon.ico", 86 | "src/assets" 87 | ] 88 | } 89 | }, 90 | "lint": { 91 | "builder": "@angular-devkit/build-angular:tslint", 92 | "options": { 93 | "tsConfig": [ 94 | "tsconfig.app.json", 95 | "tsconfig.spec.json", 96 | "e2e/tsconfig.json" 97 | ], 98 | "exclude": [ 99 | "**/node_modules/**" 100 | ] 101 | } 102 | } 103 | } 104 | }, 105 | "contacts-app-e2e": { 106 | "root": "e2e/", 107 | "projectType": "application", 108 | "architect": { 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "contacts-app:serve" 114 | } 115 | }, 116 | "lint": { 117 | "builder": "@angular-devkit/build-angular:tslint", 118 | "options": { 119 | "tsConfig": "e2e/tsconfig.e2e.json", 120 | "exclude": [ 121 | "**/node_modules/**" 122 | ] 123 | } 124 | } 125 | } 126 | } 127 | }, 128 | "defaultProject": "contacts-app" 129 | } 130 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('amc-solutions App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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-mocha-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client:{ 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | dir: require('path').join(__dirname, '../coverage'), 21 | reports: [ 'html', 'lcovonly' ], 22 | fixWebpackSourcePaths: true 23 | }, 24 | reporters: ['mocha', 'kjhtml'], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ['Chrome'], 30 | singleRun: false 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-master-class-app", 3 | "version": "5.1.1", 4 | "license": "thoughtram Training Material License (TTML 1.0)", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "serve:ssr": "npm run build:ssr && node dist/server.js", 9 | "build": "ng build --prod", 10 | "build:server": "ng run contacts-app:server -- --output-hashing=none", 11 | "build:ssr": "npm run build && npm run build:server && npm run webpack:server", 12 | "webpack:server": "webpack --config webpack.config.js --progress --colors", 13 | "prettier": "prettier --config .prettierrc --ignore-path .prettierignore", 14 | "format:base": "npm run prettier -- \"{src,e2e}/**/*{.ts,.js,.json,.css,.scss}\"", 15 | "format:check": "npm run format:base -- --list-different", 16 | "format:fix": "npm run format:base -- --write", 17 | "test": "ng test", 18 | "lint": "ng lint", 19 | "e2e": "ng e2e", 20 | "vote-server": "node server/voting-server.js", 21 | "rest-api": "node server/contacts-server.js" 22 | }, 23 | "private": true, 24 | "dependencies": { 25 | "@angular/animations": "~12.1.1", 26 | "@angular/cdk": "~12.1.1", 27 | "@angular/common": "~12.1.1", 28 | "@angular/compiler": "~12.1.1", 29 | "@angular/core": "~12.1.1", 30 | "@angular/flex-layout": "12.0.0-beta.34", 31 | "@angular/forms": "~12.1.1", 32 | "@angular/http": "8.0.0-beta.10", 33 | "@angular/material": "~12.1.1", 34 | "@angular/platform-browser": "~12.1.1", 35 | "@angular/platform-browser-dynamic": "~12.1.1", 36 | "@angular/platform-server": "~12.1.1", 37 | "@angular/router": "~12.1.1", 38 | "@angular/service-worker": "~12.1.1", 39 | "@ngrx/effects": "7.0.0", 40 | "@ngrx/store": "7.0.0", 41 | "@ngrx/store-devtools": "7.0.0", 42 | "@nguniversal/express-engine": "9.1.0", 43 | "@nguniversal/module-map-ngfactory-loader": "7.0.2", 44 | "@thoughtram/amc-demo-lib": "1.0.1", 45 | "ngrx-store-freeze": "0.2.4", 46 | "redux": "4.0.5", 47 | "redux-thunk": "2.3.0", 48 | "rxjs": "6.6.3", 49 | "tslib": "2.1.0", 50 | "zone.js": "~0.11.4" 51 | }, 52 | "devDependencies": { 53 | "@angular-devkit/build-angular": "~12.1.1", 54 | "@angular/cli": "~12.1.1", 55 | "@angular/compiler-cli": "~12.1.1", 56 | "@angular/language-service": "~12.1.1", 57 | "@angular/pwa": "12.1.1", 58 | "@types/jasmine": "~3.6.0", 59 | "@types/jasminewd2": "~2.0.2", 60 | "@types/node": "13.13.5", 61 | "codelyzer": "6.0.1", 62 | "cors": "2.8.0", 63 | "hammerjs": "2.0.8", 64 | "jasmine-core": "~3.7.0", 65 | "jasmine-spec-reporter": "~5.0.0", 66 | "karma": "~6.3.0", 67 | "karma-chrome-launcher": "~3.1.0", 68 | "karma-coverage-istanbul-reporter": "^1.2.1", 69 | "karma-jasmine": "~4.0.0", 70 | "karma-jasmine-html-reporter": "^1.5.0", 71 | "karma-mocha-reporter": "2.2.3", 72 | "ng-packagr": "^2.1.0", 73 | "prettier": "1.14.2", 74 | "protractor": "~7.0.0", 75 | "semver": "^5.3.0", 76 | "ts-loader": "^4.4.1", 77 | "ts-node": "~8.3.0", 78 | "tslint": "~6.1.0", 79 | "typescript": "~4.3.2", 80 | "webpack-cli": "^3.0.4" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /scripts/backup_branches.sh: -------------------------------------------------------------------------------- 1 | git branch -f observables_backup observables 2 | git branch -f architecture_backup architecture 3 | git branch -f forms_backup forms 4 | git branch -f routing_backup routing 5 | git branch -f redux_backup redux 6 | git branch -f ngrx_backup ngrx 7 | git branch -f testing_backup testing 8 | #git branch -f reusable-libraries_backup reusable-libraries 9 | git branch -f pwa_backup pwa 10 | git branch -f universal_backup universal 11 | -------------------------------------------------------------------------------- /scripts/backup_to_meta.sh: -------------------------------------------------------------------------------- 1 | DATE=`date +%d-%m-%Y_%H-%M-%S` 2 | 3 | git push -f meta jump-start:amc_jump-start_$DATE 4 | git push -f meta architecture:amc_architecture_$DATE 5 | git push -f meta observables:amc_observables_$DATE 6 | git push -f meta forms:amc_forms_$DATE 7 | git push -f meta routing:amc_routing_$DATE 8 | git push -f meta redux:amc_redux_$DATE 9 | git push -f meta ngrx:amc_ngrx_$DATE 10 | git push -f meta testing:amc_testing_$DATE 11 | #git push -f meta reusable-libraries:amc_reusable-libraries_$DATE 12 | git push -f meta pwa:amc_pwa_$DATE 13 | git push -f meta universal:amc_universal_$DATE 14 | -------------------------------------------------------------------------------- /scripts/delete_backups.sh: -------------------------------------------------------------------------------- 1 | git branch -D architecture_backup 2 | git branch -D forms_backup 3 | git branch -D observables_backup 4 | git branch -D routing_backup 5 | git branch -D redux_backup 6 | git branch -D ngrx_backup 7 | git branch -D testing_backup 8 | #git branch -D reusable-libraries_backup 9 | git branch -D pwa_backup 10 | git branch -D universal_backup 11 | -------------------------------------------------------------------------------- /scripts/publish_branches_and_tags.sh: -------------------------------------------------------------------------------- 1 | # We need the set -e to abort the script when the safetycheck fails 2 | # http://stackoverflow.com/questions/821396/aborting-a-shell-script-if-any-command-returns-a-non-zero-value/821419#821419 3 | 4 | set -e 5 | ./scripts/safetycheck.js 6 | 7 | git push -f --tags origin 8 | git push -f origin jump-start-step-0:master 9 | git push -f origin jump-start 10 | git push -f origin architecture 11 | git push -f origin observables 12 | git push -f origin forms 13 | git push -f origin routing 14 | git push -f origin redux 15 | git push -f origin ngrx 16 | git push -f origin testing 17 | #git push -f origin reusable-libraries 18 | git push -f origin pwa 19 | git push -f origin universal 20 | -------------------------------------------------------------------------------- /scripts/publish_jump_start.sh: -------------------------------------------------------------------------------- 1 | git checkout jump-start 2 | git push -f jump-start refs/heads/jump-start:refs/heads/master 3 | -------------------------------------------------------------------------------- /scripts/publish_starter.sh: -------------------------------------------------------------------------------- 1 | git checkout $(git rev-list HEAD | tail -n 1) 2 | git branch -D starter 3 | git checkout -b starter 4 | mv README_starter.md README.md 5 | git add -A . 6 | git commit --amend --no-edit 7 | git push -f starter starter:refs/heads/master 8 | git checkout $(git rev-list redux | tail -n 2 | head -1) 9 | git branch -D redux-starter 10 | git checkout -b redux-starter 11 | git push -f starter redux-starter 12 | git checkout jump-start 13 | -------------------------------------------------------------------------------- /scripts/rebase.sh: -------------------------------------------------------------------------------- 1 | # create backups first without branch switching. Much safer. Trust me. 2 | ./scripts/backup_branches.sh 3 | 4 | root_commit="$(git rev-list --max-parents=0 jump-start)" 5 | 6 | git checkout observables 7 | git rebase --onto jump-start HEAD~5 HEAD 8 | git branch -f observables 9 | 10 | git checkout architecture 11 | git rebase --onto jump-start HEAD~5 HEAD 12 | git branch -f architecture 13 | 14 | git checkout forms 15 | git rebase --onto jump-start HEAD~9 HEAD 16 | git branch -f forms 17 | 18 | git checkout routing 19 | git rebase --onto jump-start HEAD~5 HEAD 20 | git branch -f routing 21 | 22 | git checkout ngrx 23 | git rebase --onto jump-start HEAD~8 HEAD 24 | git branch -f ngrx 25 | 26 | git checkout pwa 27 | git rebase --onto jump-start HEAD~4 HEAD 28 | git branch -f pwa 29 | 30 | git checkout universal 31 | git rebase --onto jump-start HEAD~2 HEAD 32 | git branch -f universal 33 | 34 | git checkout testing 35 | git rebase --onto architecture HEAD~10 HEAD 36 | git branch -f testing 37 | 38 | git checkout redux 39 | git rebase --onto "${root_commit}" HEAD~7 HEAD 40 | git branch -f redux 41 | 42 | # git checkout reusable-libraries 43 | # git rebase --onto "${root_commit}" HEAD~5 HEAD 44 | # git branch -f reusable-libraries 45 | 46 | git checkout jump-start 47 | -------------------------------------------------------------------------------- /scripts/reset_local_branches.sh: -------------------------------------------------------------------------------- 1 | resetBranches () { 2 | git fetch --tags origin 3 | echo "checking out origin/jump-start in order be able to reset all local branches" 4 | git checkout origin/jump-start 5 | git branch -f jump-start origin/jump-start 6 | git branch -f observables origin/observables 7 | git branch -f architecture origin/architecture 8 | git branch -f forms origin/forms 9 | git branch -f routing origin/routing 10 | git branch -f redux origin/redux 11 | git branch -f ngrx origin/ngrx 12 | git branch -f testing origin/testing 13 | #git branch -f reusable-libraries origin/reusable-libraries 14 | git branch -f pwa origin/pwa 15 | git branch -f universal origin/universal 16 | } 17 | 18 | echo -n "You are about to *reset* all local branches with latest version from Github. Proceed? (y/n)? " 19 | read answer 20 | if echo "$answer" | grep -iq "^y" ;then 21 | resetBranches 22 | else 23 | echo "kthxbye" 24 | fi 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /scripts/retag.sh: -------------------------------------------------------------------------------- 1 | tagIt () { 2 | git checkout jump-start 3 | gtag root.. jump-start-step-##i $1 4 | git checkout architecture 5 | gtag HEAD~4.. architecture-step-##ii $1 6 | git checkout observables 7 | gtag HEAD~4.. observables-step-##ii $1 8 | git checkout forms 9 | gtag HEAD~8.. forms-step-##ii $1 10 | git checkout routing 11 | gtag HEAD~4.. routing-step-##ii $1 12 | git checkout ngrx 13 | gtag HEAD~7.. ngrx-step-##ii $1 14 | git checkout redux 15 | gtag HEAD~6.. redux-step-##i $1 16 | git checkout testing 17 | gtag HEAD~9.. testing-step-##ii $1 18 | #git checkout reusable-libraries 19 | #gtag HEAD~4.. reusable-libraries-step-##ii $1 20 | git checkout pwa 21 | gtag HEAD~3.. pwa-step-##ii $1 22 | git checkout universal 23 | gtag HEAD~1.. universal-step-##ii $1 24 | 25 | #going back to jump-start as the default branch 26 | git checkout jump-start 27 | } 28 | 29 | echo -n "Wanna do a dry-run first (y/n)? " 30 | read answer 31 | if echo "$answer" | grep -iq "^y" ;then 32 | tagIt --dryrun 33 | else 34 | tagIt 35 | fi 36 | -------------------------------------------------------------------------------- /scripts/rollback_backups.sh: -------------------------------------------------------------------------------- 1 | git branch -f --move observables_backup observables 2 | git branch -f --move forms_backup forms 3 | git branch -f --move routing_backup routing 4 | git branch -f --move architecture_backup architecture 5 | git branch -f --move redux_backup redux 6 | git branch -f --move ngrx_backup ngrx 7 | git branch -f --move testing_backup testing 8 | #git branch -f --move reusable-libraries_backup reusable-libraries 9 | git branch -f --move pwa_backup pwa 10 | git branch -f --move universal_backup universal 11 | -------------------------------------------------------------------------------- /scripts/safetycheck.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | let semver = require('semver'); 4 | let execSync = require('child_process').execSync; 5 | let chalk = require('chalk'); 6 | let spawnOptions = { stdio: 'pipe' }; 7 | 8 | let execute = (cmd, supress) => { 9 | try { 10 | let output = execSync(cmd, spawnOptions).toString(); 11 | if (!supress) { 12 | console.log(output); 13 | } 14 | return output; 15 | } 16 | catch (e) { 17 | //Doesn't feel write to swallow the exceptions but there doesn't seem 18 | //to be a built in way to supress exceptions if git add and git push fail 19 | } 20 | } 21 | 22 | function getVersionFromRef(ref) { 23 | return JSON.parse(execute(`git show ${ref}:package.json`, true)).version; 24 | } 25 | 26 | const branches = [ 27 | 'jump-start', 28 | 'observables', 29 | 'architecture', 30 | 'forms', 31 | 'routing', 32 | 'redux', 33 | 'ngrx', 34 | 'testing', 35 | /*'reusable-libraries',*/ 36 | 'pwa', 37 | 'universal' 38 | ] 39 | 40 | 41 | console.log(chalk.green('Fetching from origin to read upstream version...')); 42 | execute(`git fetch origin`) 43 | 44 | let fail = false; 45 | 46 | branches.forEach(branch => { 47 | console.log(chalk.green.bold(`${branch} versions:`)); 48 | let remoteVersion = getVersionFromRef(`origin/${branch}`); 49 | let localVersion = getVersionFromRef(branch); 50 | 51 | console.log(chalk.green(`Remote version: ${remoteVersion}`)); 52 | console.log(chalk.green(`Local version: ${localVersion}\n`)); 53 | 54 | if (semver.lt(localVersion, remoteVersion)){ 55 | console.log(chalk.red(`It appears that the local version of your courseware is behind upstream. 56 | Please make sure to base your work on top the latest courseware. 57 | An easy way to reset all your local branches would be ./reset_local_branches.sh 58 | But keep in mind that this will reset all courseware branches to the state 59 | that is on latest GitHub.`)); 60 | 61 | fail = true; 62 | } 63 | else if (localVersion === remoteVersion) { 64 | console.log(chalk.green(`The version number of your local courseware matches the one upstream. 65 | This is good! The only thing left to do is to increase the local version number before you deploy.`)); 66 | fail = true; 67 | } 68 | }); 69 | 70 | if (fail){ 71 | process.exit(1); 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /scripts/the_magic_button.sh: -------------------------------------------------------------------------------- 1 | # We need the set -e to abort the script when the safetycheck fails 2 | # http://stackoverflow.com/questions/821396/aborting-a-shell-script-if-any-command-returns-a-non-zero-value/821419#821419 3 | 4 | set -e 5 | 6 | ./scripts/rebase.sh 7 | ./scripts/retag.sh 8 | ./scripts/publish_branches_and_tags.sh 9 | ./scripts/publish_starter.sh 10 | ./scripts/publish_jump_start.sh 11 | ./scripts/backup_to_meta.sh 12 | -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } 4 | -------------------------------------------------------------------------------- /server/contacts-server.html: -------------------------------------------------------------------------------- 1 | 37 | 38 |

Contacts Server

39 |

REST API documentation for the Contacts HTTP server (used in thoughtram AMC).

40 | 94 | 95 | -------------------------------------------------------------------------------- /server/contacts-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * REST Server for Contacts application 3 | * 4 | * getContacts() '/api/contacts' GET 5 | * getContact(id) '/api/contacts/:id' GET 6 | * searchContacts(query) '/api/search' GET 7 | * checkEmail(email) '/api/check-email' GET 8 | * 9 | * createContact(contact) '/api/contacts/:id' PUT 10 | * updateContact(contact) '/api/contacts' POST 11 | * deleteContact(id) '/api/contacts/:id' DELETE 12 | * 13 | */ 14 | 15 | const PATH_API_HTML = './contacts-server.html'; 16 | 17 | const fs = require('fs'); 18 | const url = require('url'); 19 | const db = require('./data/contacts'); 20 | const bodyParser = require('body-parser'); 21 | const express = require('express'); 22 | const cors = require('cors'); 23 | 24 | let unorderedResponse = process.argv.includes('--unordered-response'); 25 | if (unorderedResponse) { 26 | console.log('Serving search results unordered') 27 | } 28 | 29 | let maxId = db.length; 30 | 31 | const multipleResponse = (items) => { return { items: items } }; 32 | const singleResponse = (item) => { return { item: item } }; 33 | const getNextId = () => { return maxId++ }; 34 | const isContact = (contact) => contact.name !== undefined; 35 | const emailIsAvailable = (email) => { 36 | if (email === '') { 37 | return true; 38 | } 39 | let contact = db.find(contact => contact.email == email); 40 | return contact ? false : true; 41 | } 42 | 43 | // ****************************************************************** 44 | // REST Server 45 | // ****************************************************************** 46 | 47 | let app = express(); 48 | app.use(cors()); 49 | app.use(bodyParser.json()); 50 | 51 | 52 | /** 53 | * Redirect 'null' path to get all contacts 54 | */ 55 | app.get('/', (req, res) =>{ 56 | let file = require.resolve(PATH_API_HTML); 57 | res.send( fs.readFileSync(file).toString() ) 58 | }); 59 | 60 | // ****************************************************************** 61 | // REST API 62 | // ****************************************************************** 63 | 64 | /** 65 | * getContacts() RESTful endpoint 66 | */ 67 | app.get('/api/contacts', function (req, res) { 68 | res.json(multipleResponse(db)); 69 | }); 70 | 71 | let delayedRequest = false; 72 | 73 | /** 74 | * searchContacts() RESTful endpoint 75 | */ 76 | app.get('/api/search', function (req, res) { 77 | let text = req.query.text; 78 | let matches = db.filter(contact => contact.name 79 | .toLowerCase().indexOf(text.toLowerCase()) > -1); 80 | 81 | if (unorderedResponse && delayedRequest) { 82 | console.log(`Serving delayed for: ${text}`); 83 | setTimeout(() => res.json(multipleResponse(matches)), 2000) 84 | } else { 85 | console.log(`Serving instantly for: ${text}`); 86 | res.json(multipleResponse(matches)); 87 | } 88 | delayedRequest = !delayedRequest; 89 | }); 90 | 91 | /** 92 | * getContact() RESTful endpoint 93 | */ 94 | app.get('/api/contacts/:id', function (req, res) { 95 | let contact = db.find(contact => contact.id == req.params.id); 96 | contact ? res.json(singleResponse(contact)) : res.status(404).json({ error: 'contact not found' }); 97 | }); 98 | 99 | /** 100 | * updateContact() RESTful endpoint 101 | */ 102 | app.post('/api/contacts', function (req, res) { 103 | if (isContact(req.body)) { 104 | req.body.id = getNextId(); 105 | req.body.image = '/assets/images/placeholder.png'; 106 | db.push(req.body); 107 | res.json(singleResponse(req.body)); 108 | } 109 | else { 110 | res.status(404).json({ error: 'invalid structure' }); 111 | } 112 | }); 113 | 114 | 115 | /** 116 | * createContact() RESTful endpoint 117 | */ 118 | app.put('/api/contacts/:id', function (req, res) { 119 | let contact = db.find(contact => contact.id == req.params.id); 120 | if (contact) { 121 | Object.assign(contact, req.body); 122 | res.json(singleResponse(contact)); 123 | } else { 124 | res.status(404).json({ error: 'contact not found' }); 125 | } 126 | }); 127 | 128 | /** 129 | * deleteContact() RESTful endpoint 130 | */ 131 | app.delete('/api/contacts/:id', function (req, res) { 132 | let contact = db.find(contact => contact.id == req.params.id); 133 | if (contact) { 134 | let index = db.indexOf(contact); 135 | db.splice(index, 1); 136 | res.json(singleResponse(contact)); 137 | } else { 138 | res.status(404).json({ error: 'contact not found' }); 139 | } 140 | }); 141 | 142 | /** 143 | * checkEmail() RESTful endpoint 144 | */ 145 | app.get('/api/check-email', function (req, res) { 146 | if (emailIsAvailable(req.query.email)) { 147 | res.json({ msg: 'AVAILABLE' }); 148 | } else { 149 | res.json({ error: 'NOT_AVAILABLE' }); 150 | } 151 | }); 152 | 153 | app.listen(4201, () => console.log('REST API running on port 4201')); 154 | 155 | 156 | -------------------------------------------------------------------------------- /server/data/contacts.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | id: 0, 4 | name: 'Christoph Burgdorf', 5 | email: 'christoph@thoughtram.io', 6 | phone: '+49 000 1111', 7 | birthday: '1984-01-02', 8 | website: 'thoughtram.io', 9 | image: '/assets/images/0.jpg', 10 | address: { 11 | street: 'thoughtram road 1', 12 | zip: '65222', 13 | city: 'Hanover', 14 | country: 'Germany' 15 | } 16 | }, 17 | { 18 | id: 1, 19 | name: 'Pascal Precht', 20 | email: 'pascal@thoughtram.io', 21 | phone: '+49 000 222', 22 | birthday: '1991-03-31', 23 | website: 'thoughtram.io', 24 | image: '/assets/images/1.jpg', 25 | address: { 26 | street: 'thoughtram road 1', 27 | zip: '65222', 28 | city: 'Hanover', 29 | country: 'Germany' 30 | } 31 | }, 32 | { 33 | id: 2, 34 | name: 'Nicole Hansen', 35 | email: 'who@car.es', 36 | phone: '+49 000 333', 37 | birthday: '1981-03-31', 38 | website: '', 39 | image: '/assets/images/3.jpg', 40 | address: { 41 | street: 'Who Cares Street 42', 42 | zip: '65222', 43 | city: 'Sun Funcisco', 44 | country: 'United States' 45 | } 46 | }, 47 | { 48 | id: 3, 49 | name: 'Zoe Moore', 50 | email: 'zoe@moore.com', 51 | phone: '+49 000 000', 52 | birthday: '1990-02-18', 53 | website: '', 54 | image: '/assets/images/4.jpg', 55 | address: { 56 | street: '3745 denny street', 57 | zip: '86337', 58 | city: 'Ballinasloe', 59 | country: 'United States' 60 | } 61 | }, 62 | { 63 | id: 4, 64 | name: 'Diane Hale', 65 | email: '', 66 | phone: '', 67 | birthday: '', 68 | website: '', 69 | image: '/assets/images/5.jpg', 70 | address: { 71 | street: '1459 tara street', 72 | zip: '18371', 73 | city: 'Bray', 74 | country: 'United States' 75 | } 76 | }, 77 | { 78 | id: 5, 79 | name: 'Barry Ford', 80 | email: '', 81 | phone: '', 82 | birthday: '', 83 | website: '', 84 | image: '/assets/images/6.jpg', 85 | address: { 86 | street: '6503 tara street', 87 | zip: '43378', 88 | city: 'Dungarvan', 89 | country: 'United States' 90 | } 91 | }, 92 | { 93 | id: 6, 94 | name: 'Diana Ellis', 95 | email: '', 96 | phone: '', 97 | birthday: '', 98 | website: '', 99 | image: '/assets/images/7.jpg', 100 | address: { 101 | street: '6554 park lane', 102 | zip: '43378', 103 | city: 'Rush', 104 | country: 'United States' 105 | } 106 | }, 107 | { 108 | id: 7, 109 | name: 'Ella Grant', 110 | email: '', 111 | phone: '', 112 | birthday: '', 113 | website: '', 114 | image: '/assets/images/8.jpg', 115 | address: { 116 | street: '2749 church road', 117 | zip: '87125', 118 | city: 'Clonakilty', 119 | country: 'United States' 120 | } 121 | }, 122 | { 123 | id: 8, 124 | name: 'Brent Mason', 125 | email: '', 126 | phone: '', 127 | birthday: '', 128 | website: '', 129 | image: '/assets/images/9.jpg', 130 | address: { 131 | street: '8436 tara street', 132 | zip: '59949', 133 | city: 'Dundalk', 134 | country: 'United States' 135 | } 136 | }, 137 | { 138 | id: 9, 139 | name: 'Sam Thomas', 140 | email: '', 141 | phone: '', 142 | birthday: '', 143 | website: '', 144 | image: '/assets/images/10.jpg', 145 | address: { 146 | street: '2523 park road', 147 | zip: '59949', 148 | city: 'Drogheda', 149 | country: 'United States' 150 | } 151 | }, 152 | { 153 | id: 10, 154 | name: 'Vicky Roberts', 155 | email: '', 156 | phone: '', 157 | birthday: '', 158 | website: '', 159 | image: '/assets/images/11.jpg', 160 | address: { 161 | street: '9791 grafton street', 162 | zip: '30165', 163 | city: 'Galway', 164 | country: 'London' 165 | } 166 | } 167 | ]; 168 | -------------------------------------------------------------------------------- /server/voting-server.js: -------------------------------------------------------------------------------- 1 | let bodyParser = require('body-parser'); 2 | let express = require('express'); 3 | let cors = require('cors'); 4 | 5 | const delay = 200; 6 | let votes = 0; 7 | let app = express(); 8 | 9 | app.use(cors()); 10 | app.use(bodyParser.json()); 11 | 12 | app.get('/api/votes', getAllVotes); 13 | app.get('/api/votes/increment', addVote); 14 | app.get('/api/votes/decrement', removeVote); 15 | 16 | app.listen(4201, () => console.log('REST API running on port 4201')); 17 | 18 | 19 | // ************************************************* 20 | // Internal REST methods 21 | // ************************************************* 22 | 23 | function getAllVotes(req, res) { 24 | setTimeout(() => res.json({votes}), delay); 25 | } 26 | 27 | function addVote(req, res) { 28 | setTimeout(() => { 29 | votes += 1; 30 | res.json({votes}); 31 | }, delay); 32 | } 33 | 34 | function removeVote(req, res) { 35 | setTimeout(() => { 36 | votes -= 1; 37 | res.json({votes}); 38 | }, delay); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/app/app.component.css -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 |

Here are some links to help you start:

9 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'trm-contacts-app', 5 | template: ` 6 | Contacts 7 | 8 | `, 9 | styleUrls: ['./app.component.scss'] 10 | }) 11 | export class ContactsAppComponent {} 12 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { NgModule } from '@angular/core'; 4 | import { FlexLayoutModule } from '@angular/flex-layout'; 5 | import { RouterModule } from '@angular/router'; 6 | import { HttpClientModule } from '@angular/common/http'; 7 | import { FormsModule } from '@angular/forms'; 8 | 9 | import { ContactsMaterialModule } from './contacts-material.module'; 10 | 11 | import { ContactsAppComponent } from './app.component'; 12 | import { ContactsListComponent } from './contacts-list/contacts-list.component'; 13 | import { ContactsDetailComponent } from './contacts-detail/contacts-detail.component'; 14 | import { ContactsEditorComponent } from './contacts-editor/contacts-editor.component'; 15 | 16 | import { ContactsService } from './contacts.service'; 17 | 18 | import { APP_ROUTES } from './app.routes'; 19 | import { API_ENDPOINT } from './app.tokens'; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | ContactsAppComponent, 24 | ContactsListComponent, 25 | ContactsDetailComponent, 26 | ContactsEditorComponent 27 | ], 28 | imports: [ 29 | BrowserModule, 30 | BrowserAnimationsModule, 31 | ContactsMaterialModule, 32 | FlexLayoutModule, 33 | RouterModule.forRoot(APP_ROUTES), 34 | HttpClientModule, 35 | FormsModule 36 | ], 37 | providers: [ 38 | ContactsService, 39 | { provide: API_ENDPOINT, useValue: 'http://localhost:4201/api' } 40 | ], 41 | bootstrap: [ContactsAppComponent] 42 | }) 43 | export class ContactsModule { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ContactsListComponent } from './contacts-list/contacts-list.component'; 3 | import { ContactsDetailComponent } from './contacts-detail/contacts-detail.component'; 4 | import { ContactsEditorComponent } from './contacts-editor/contacts-editor.component'; 5 | 6 | export const APP_ROUTES: Routes = [ 7 | { path: '', component: ContactsListComponent }, 8 | { path: 'contact/:id', component: ContactsDetailComponent }, 9 | { path: 'contact/:id/edit', component: ContactsEditorComponent }, 10 | // Wildcard route serves as fallback route and has to be 11 | // the last defined route in this list. 12 | { path: '**', redirectTo: '/' } 13 | ]; 14 | 15 | -------------------------------------------------------------------------------- /src/app/app.tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const API_ENDPOINT = new InjectionToken('apiEndpoint'); 4 | -------------------------------------------------------------------------------- /src/app/contacts-detail/contacts-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/app/contacts-detail/contacts-detail.component.css -------------------------------------------------------------------------------- /src/app/contacts-detail/contacts-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{contact?.name}} 6 | {{contact?.email || 'No email address'}} 7 | 8 | 9 |
10 |
Phone:
11 |
{{contact?.phone || '-'}}
12 |
Website:
13 |
{{contact?.website || '-'}}
14 |
Birthday:
15 |
{{contact?.birthday || '-'}}
16 |
Street:
17 |
{{contact?.address.street || '-'}}
18 |
Zip:
19 |
{{contact?.address.zip || '-'}}
20 |
City:
21 |
{{contact?.address.city || '-'}}
22 |
23 |
24 | 25 | Edit 26 | Go Back 27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/app/contacts-detail/contacts-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { ContactsService } from '../contacts.service'; 4 | import { Contact } from '../models/contact'; 5 | 6 | @Component({ 7 | selector: 'trm-contacts-detail', 8 | templateUrl: './contacts-detail.component.html', 9 | styleUrls: ['./contacts-detail.component.css'] 10 | }) 11 | export class ContactsDetailComponent implements OnInit { 12 | 13 | contact: Contact; 14 | 15 | constructor(private contactsService: ContactsService, private route: ActivatedRoute) {} 16 | 17 | ngOnInit() { 18 | this.contactsService.getContact(this.route.snapshot.paramMap.get('id')) 19 | .subscribe(contact => this.contact = contact); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/contacts-editor/contacts-editor.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/app/contacts-editor/contacts-editor.component.css -------------------------------------------------------------------------------- /src/app/contacts-editor/contacts-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{contact?.name}} 6 | {{contact?.email || 'No email address'}} 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | Address 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 | -------------------------------------------------------------------------------- /src/app/contacts-editor/contacts-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Contact } from '../models/contact'; 4 | import { ContactsService } from '../contacts.service'; 5 | 6 | @Component({ 7 | selector: 'trm-contacts-editor', 8 | templateUrl: './contacts-editor.component.html', 9 | styleUrls: ['./contacts-editor.component.css'] 10 | }) 11 | export class ContactsEditorComponent implements OnInit { 12 | 13 | // we need to initialize since we can't use ?. operator with ngModel 14 | contact: Contact = { address: {}}; 15 | 16 | constructor(private contactsService: ContactsService, 17 | private router: Router, 18 | private route: ActivatedRoute) {} 19 | 20 | ngOnInit() { 21 | this.contactsService.getContact(this.route.snapshot.paramMap.get('id')) 22 | .subscribe(contact => this.contact = contact); 23 | } 24 | 25 | cancel(contact: Contact) { 26 | this.goToDetails(contact); 27 | } 28 | 29 | save(contact: Contact) { 30 | this.contactsService.updateContact(contact) 31 | .subscribe(() => this.goToDetails(contact)); 32 | } 33 | 34 | private goToDetails(contact: Contact) { 35 | this.router.navigate(['/contact', contact.id ]); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/app/contacts-list/contacts-list.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/app/contacts-list/contacts-list.component.css -------------------------------------------------------------------------------- /src/app/contacts-list/contacts-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Picture of {{contact.name}} 8 |

{{contact.name}}

9 |
10 |
11 | -------------------------------------------------------------------------------- /src/app/contacts-list/contacts-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { Contact } from '../models/contact'; 4 | import { ContactsService } from '../contacts.service'; 5 | 6 | @Component({ 7 | selector: 'trm-contacts-list', 8 | templateUrl: './contacts-list.component.html', 9 | styleUrls: ['./contacts-list.component.css'] 10 | }) 11 | export class ContactsListComponent implements OnInit { 12 | 13 | contacts$: Observable>; 14 | 15 | constructor(private contactsService: ContactsService) {} 16 | 17 | ngOnInit () { 18 | this.contacts$ = this.contactsService.getContacts(); 19 | } 20 | 21 | trackByContactId(index, contact) { 22 | return contact.id; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/contacts-material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { MatButtonModule } from '@angular/material/button'; 3 | import { MatCardModule } from '@angular/material/card'; 4 | import { MatCheckboxModule } from '@angular/material/checkbox'; 5 | import { MatDialogModule } from '@angular/material/dialog'; 6 | import { MatInputModule } from '@angular/material/input'; 7 | import { MatIconModule } from '@angular/material/icon'; 8 | import { MatListModule } from '@angular/material/list'; 9 | import { MatSelectModule } from '@angular/material/select'; 10 | import { MatSidenavModule } from '@angular/material/sidenav'; 11 | import { MatRadioModule } from '@angular/material/radio'; 12 | import { MatTabsModule } from '@angular/material/tabs'; 13 | import { MatToolbarModule } from '@angular/material/toolbar'; 14 | 15 | @NgModule({ 16 | exports: [ 17 | MatButtonModule, 18 | MatCardModule, 19 | MatCheckboxModule, 20 | MatDialogModule, 21 | MatInputModule, 22 | MatIconModule, 23 | MatListModule, 24 | MatRadioModule, 25 | MatSelectModule, 26 | MatSidenavModule, 27 | MatTabsModule, 28 | MatToolbarModule 29 | ] 30 | }) 31 | export class ContactsMaterialModule {} 32 | -------------------------------------------------------------------------------- /src/app/contacts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | import { Observable } from 'rxjs'; 5 | import { map } from 'rxjs/operators'; 6 | import { API_ENDPOINT } from './app.tokens'; 7 | 8 | import { Contact } from './models/contact'; 9 | 10 | interface ContactResponse { item : Contact } 11 | interface ContactsResponse { items : Contact[] } 12 | 13 | 14 | @Injectable() 15 | export class ContactsService { 16 | 17 | constructor(private http: HttpClient, @Inject(API_ENDPOINT) private apiEndpoint) {} 18 | 19 | 20 | getContact(id: string): Observable { 21 | return this.http.get(`${this.apiEndpoint}/contacts/${id}`) 22 | .pipe(map(data => data.item)); 23 | } 24 | 25 | getContacts(): Observable> { 26 | return this.http.get(`${this.apiEndpoint}/contacts`) 27 | .pipe(map(data => data.items)); 28 | } 29 | 30 | updateContact(contact: Contact): Observable { 31 | return this.http.put(`${this.apiEndpoint}/contacts/${contact.id}`, contact) 32 | .pipe(map(data => data.item)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/app/data/contact-data.ts: -------------------------------------------------------------------------------- 1 | export const CONTACT_DATA = [ 2 | { 3 | id: 0, 4 | name: 'Christoph Burgdorf', 5 | email: 'christoph@thoughtram.io', 6 | phone: '+49 000 1111', 7 | birthday: '1984-01-02', 8 | website: 'thoughtram.io', 9 | image: '/assets/images/0.jpg', 10 | address: { 11 | street: 'thoughtram road 1', 12 | zip: '65222', 13 | city: 'Hanover', 14 | country: 'Germany' 15 | } 16 | }, 17 | { 18 | id: 1, 19 | name: 'Pascal Precht', 20 | email: 'pascal@thoughtram.io', 21 | phone: '+49 000 222', 22 | birthday: '1991-03-31', 23 | website: 'thoughtram.io', 24 | image: '/assets/images/1.jpg', 25 | address: { 26 | street: 'thoughtram road 1', 27 | zip: '65222', 28 | city: 'Hanover', 29 | country: 'Germany' 30 | } 31 | }, 32 | { 33 | id: 2, 34 | name: 'Nicole Hansen', 35 | email: 'who@car.es', 36 | phone: '+49 000 333', 37 | birthday: '1981-03-31', 38 | website: '', 39 | image: '/assets/images/3.jpg', 40 | address: { 41 | street: 'Who Cares Street 42', 42 | zip: '65222', 43 | city: 'Sun Funcisco', 44 | country: 'United States' 45 | } 46 | }, 47 | { 48 | id: 3, 49 | name: 'Zoe Moore', 50 | email: 'zoe@moore.com', 51 | phone: '+49 000 000', 52 | birthday: '1990-02-18', 53 | website: '', 54 | image: '/assets/images/4.jpg', 55 | address: { 56 | street: '3745 denny street', 57 | zip: '86337', 58 | city: 'Ballinasloe', 59 | country: 'United States' 60 | } 61 | }, 62 | { 63 | id: 4, 64 | name: 'Diane Hale', 65 | email: '', 66 | phone: '', 67 | birthday: '', 68 | website: '', 69 | image: '/assets/images/5.jpg', 70 | address: { 71 | street: '1459 tara street', 72 | zip: '18371', 73 | city: 'Bray', 74 | country: 'United States' 75 | } 76 | }, 77 | { 78 | id: 5, 79 | name: 'Barry Ford', 80 | email: '', 81 | phone: '', 82 | birthday: '', 83 | website: '', 84 | image: '/assets/images/6.jpg', 85 | address: { 86 | street: '6503 tara street', 87 | zip: '43378', 88 | city: 'Dungarvan', 89 | country: 'United States' 90 | } 91 | }, 92 | { 93 | id: 6, 94 | name: 'Diana Ellis', 95 | email: '', 96 | phone: '', 97 | birthday: '', 98 | website: '', 99 | image: '/assets/images/7.jpg', 100 | address: { 101 | street: '6554 park lane', 102 | zip: '43378', 103 | city: 'Rush', 104 | country: 'United States' 105 | } 106 | }, 107 | { 108 | id: 7, 109 | name: 'Ella Grant', 110 | email: '', 111 | phone: '', 112 | birthday: '', 113 | website: '', 114 | image: '/assets/images/8.jpg', 115 | address: { 116 | street: '2749 church road', 117 | zip: '87125', 118 | city: 'Clonakilty', 119 | country: 'United States' 120 | } 121 | }, 122 | { 123 | id: 8, 124 | name: 'Brent Mason', 125 | email: '', 126 | phone: '', 127 | birthday: '', 128 | website: '', 129 | image: '/assets/images/9.jpg', 130 | address: { 131 | street: '8436 tara street', 132 | zip: '59949', 133 | city: 'Dundalk', 134 | country: 'United States' 135 | } 136 | }, 137 | { 138 | id: 9, 139 | name: 'Sam Thomas', 140 | email: '', 141 | phone: '', 142 | birthday: '', 143 | website: '', 144 | image: '/assets/images/10.jpg', 145 | address: { 146 | street: '2523 park road', 147 | zip: '59949', 148 | city: 'Drogheda', 149 | country: 'United States' 150 | } 151 | }, 152 | { 153 | id: 10, 154 | name: 'Vicky Roberts', 155 | email: '', 156 | phone: '', 157 | birthday: '', 158 | website: '', 159 | image: '/assets/images/11.jpg', 160 | address: { 161 | street: '9791 grafton street', 162 | zip: '30165', 163 | city: 'Galway', 164 | country: 'London' 165 | } 166 | } 167 | ]; 168 | -------------------------------------------------------------------------------- /src/app/data/contact-manager.ts: -------------------------------------------------------------------------------- 1 | import { Contact} from '../models/contact'; 2 | 3 | export class ContactManager { 4 | 5 | private _contacts: Array = []; 6 | 7 | get contacts() { 8 | return [...this._contacts]; 9 | } 10 | 11 | constructor(data: Array) { 12 | this._contacts = [...data]; 13 | } 14 | 15 | add(contact: Contact) { 16 | this._contacts.push(contact); 17 | } 18 | 19 | update(contact: Contact) { 20 | let index = this._contacts.findIndex(c => c.id == contact.id); 21 | if (index === -1) { 22 | throw new Error(`Trying to update contact that doesn't exist with ID: ${contact.id}!`); 23 | } 24 | this._contacts[index] = contact; 25 | } 26 | 27 | get(id: number) { 28 | let contact = this.contacts.find(c => c.id === id); 29 | return contact ? contact : null; 30 | } 31 | 32 | getAll() { 33 | return this.contacts; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/app/data/gender.ts: -------------------------------------------------------------------------------- 1 | export const GENDER = [ 2 | 'Female', 3 | 'Male', 4 | 'Transgender Female', 5 | 'Transgender Male', 6 | 'Gender Variant / Non-Conforming', 7 | 'Prefer Not to Answer' 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/models/contact.ts: -------------------------------------------------------------------------------- 1 | export interface Address { 2 | street?: string; 3 | city?: string; 4 | zip?: string; 5 | country?: string; 6 | } 7 | 8 | export interface Contact { 9 | id: number | string; 10 | name?: string; 11 | email?: string; 12 | phone?: string | string[]; 13 | birthday?: string; 14 | website?: string; 15 | image?: string; 16 | address?: Address; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/app/shared/index.ts -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/.npmignore -------------------------------------------------------------------------------- /src/assets/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2b5797 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/favicon.ico -------------------------------------------------------------------------------- /src/assets/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/assets/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/images/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/0.jpg -------------------------------------------------------------------------------- /src/assets/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/1.jpg -------------------------------------------------------------------------------- /src/assets/images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/10.jpg -------------------------------------------------------------------------------- /src/assets/images/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/11.jpg -------------------------------------------------------------------------------- /src/assets/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/2.jpg -------------------------------------------------------------------------------- /src/assets/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/3.jpg -------------------------------------------------------------------------------- /src/assets/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/4.jpg -------------------------------------------------------------------------------- /src/assets/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/5.jpg -------------------------------------------------------------------------------- /src/assets/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/6.jpg -------------------------------------------------------------------------------- /src/assets/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/7.jpg -------------------------------------------------------------------------------- /src/assets/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/8.jpg -------------------------------------------------------------------------------- /src/assets/images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/9.jpg -------------------------------------------------------------------------------- /src/assets/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/placeholder.png -------------------------------------------------------------------------------- /src/assets/images/team.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/assets/images/team.jpg -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtram/angular-master-class-jump-start/1192a60041bd00e12fdf01909c442e842df4aa75/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Contacts 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { ContactsModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(ContactsModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '../node_modules/@angular/material/prebuilt-themes/indigo-pink.css'; 3 | 4 | body, html { 5 | background-color: #f6f6f6; 6 | position: relative; 7 | } 8 | 9 | html, body, trm-contacts-app, mat-drawer-container, .main-content { 10 | margin: 0; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | .mat-form-field { 16 | width: 100%; 17 | } 18 | 19 | .mat-list-base { 20 | background-color: white; 21 | padding-top: 0!important; 22 | } 23 | 24 | .mat-list-base .mat-list-item { 25 | border-bottom: 1px solid #ddd; 26 | text-decoration: none; 27 | 28 | &:hover { 29 | background-color: #f0f0f0; 30 | } 31 | 32 | &.active { 33 | background-color: #e9e9e9; 34 | } 35 | } 36 | 37 | .trm-floating-button { 38 | position: absolute!important; 39 | right: 2em; 40 | bottom: 2em; 41 | color: #fff; 42 | } 43 | 44 | .trm-contacts-detail, 45 | .trm-contacts-creator, 46 | .trm-contacts-editor, 47 | .trm-about { 48 | .mat-card { 49 | max-width: 500px; 50 | margin: 0 auto; 51 | margin-top: 2em; 52 | 53 | img { 54 | border-radius: 50%; 55 | } 56 | } 57 | 58 | .mat-card-title-group.fullBleed { 59 | background-color: #afbafc; 60 | padding: 30px; 61 | margin-top: -24px; 62 | margin-left: -16px; 63 | margin-right: -16px; 64 | margin-bottom: 24px; 65 | 66 | &.editing { 67 | background-color: #fdecbb; 68 | } 69 | } 70 | 71 | .mat-card-content { 72 | dl { 73 | margin-top: 2em; 74 | border-bottom: 1px solid rgba(221, 221, 221, 0.15); 75 | padding-top: 1em; 76 | padding-bottom: 1.5em; 77 | } 78 | 79 | dt, dd { 80 | display: inline-block; 81 | margin-top: 0.5em; 82 | 83 | &:first-child { 84 | margin-top: 0; 85 | } 86 | } 87 | 88 | dt { 89 | width: 25%; 90 | font-weight: bold; 91 | } 92 | 93 | dd { 94 | margin-left: 0; 95 | width: 70%; 96 | } 97 | 98 | dt:first-child + dd { 99 | margin-top: 0; 100 | } 101 | } 102 | 103 | fieldset { 104 | legend { color: #ccc; } 105 | border: 1px solid #ddd; 106 | margin-top: 1em; 107 | padding-left: 1em; 108 | padding-right: 1em; 109 | 110 | mat-input-container, mat-select { 111 | margin-top: 2em; 112 | } 113 | 114 | } 115 | 116 | .mat-form-field { 117 | 118 | &.ng-invalid { 119 | 120 | .mat-hint { 121 | color: rgb(215, 65, 57); 122 | } 123 | 124 | &.ng-touched:not(.ng-pristine) { 125 | .mat-input-underline { 126 | border-color: rgb(215, 65, 57); 127 | } 128 | } 129 | } 130 | } 131 | 132 | mat-radio-button { 133 | display: block; 134 | color: #666; 135 | } 136 | 137 | .gender-label { 138 | color: #ccc; 139 | } 140 | .mat-radio-outer-circle { 141 | border-color: rgba(0,0,0,.12); 142 | } 143 | 144 | .mat-radio-label { 145 | margin-top: 0.5em; 146 | } 147 | } 148 | 149 | .trm-about .mat-card img { 150 | border-radius: 0; 151 | max-height: 400px; 152 | } 153 | 154 | .trm-search-container { 155 | width: 100%; 156 | } 157 | 158 | mat-dialog-container { 159 | font-family: Roboto,"Helvetica Neue",sans-serif; 160 | 161 | [mat-dialog-title] { 162 | margin-left: -24px; 163 | margin-right: -24px; 164 | margin-top: -24px; 165 | padding: 0.5em; 166 | text-align: center; 167 | color: #fff; 168 | background-color: #3f51b5; 169 | font-weight: normal; 170 | } 171 | 172 | [mat-dialog-content] { 173 | margin-bottom: 12px; 174 | margin-top: 36px; 175 | font-size: 14px; 176 | } 177 | } 178 | 179 | mat-dialog-actions { 180 | padding: 8px 0; 181 | [mat-button],[mat-raised-button] { 182 | margin: 0 4px; 183 | } 184 | } 185 | 186 | trm-tab > mat-input-container:first-child { 187 | padding-top: 25px; 188 | } 189 | 190 | -------------------------------------------------------------------------------- /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: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/testing/custom-matchers.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare const global: any; 4 | 5 | const _global = (typeof window === 'undefined' ? global : window); 6 | 7 | export const expect: (actual: any) => NgMatchers = _global.expect; 8 | 9 | export interface NgMatchers extends jasmine.Matchers { 10 | toHaveMap(expected: { [k: string]: string }): boolean; 11 | } 12 | 13 | export const CUSTOM_MATCHERS: jasmine.CustomMatcherFactories = { 14 | toHaveMap: () => { 15 | return { 16 | compare: (actual: { [k: string]: string }, map: { [k: string]: string }) => { 17 | let allPassed = Object.keys(map).length !== 0; 18 | 19 | Object.keys(map).forEach(key => { 20 | allPassed = allPassed && actual[key] === map[key]; 21 | }); 22 | 23 | return { 24 | pass: allPassed, 25 | get message() { 26 | return ` 27 | Expected ${JSON.stringify(actual)} ${!allPassed ? ' ' : 'not '} to contain the 28 | "${JSON.stringify(map)}" 29 | `; 30 | } 31 | }; 32 | } 33 | }; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/testing/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Type, DebugElement } from '@angular/core'; 2 | import { By } from '@angular/platform-browser'; 3 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 4 | 5 | export function createFixture(component: Type, detectChanges = true): ComponentFixture { 6 | const fixture = TestBed.createComponent(component); 7 | 8 | if (detectChanges) { 9 | fixture.detectChanges(); 10 | } 11 | 12 | return fixture; 13 | } 14 | 15 | export function queryFor(fixture: ComponentFixture, selector: string): DebugElement[] { 16 | return fixture.debugElement.queryAll(By.css(selector)); 17 | } 18 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | 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 | "exclude": [ 13 | "src/test.ts", 14 | "src/**/*.spec.ts" 15 | ] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "directive-selector": [ 120 | true, 121 | "attribute", 122 | "trm", 123 | "camelCase" 124 | ], 125 | "component-selector": [ 126 | true, 127 | "element", 128 | "trm", 129 | "kebab-case" 130 | ], 131 | "no-output-on-prefix": true, 132 | "use-input-property-decorator": true, 133 | "use-output-property-decorator": true, 134 | "use-host-property-decorator": true, 135 | "no-input-rename": true, 136 | "no-output-rename": true, 137 | "use-life-cycle-interface": true, 138 | "use-pipe-transform-interface": true, 139 | "component-class-suffix": true, 140 | "directive-class-suffix": true 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { server: path.join(__dirname, 'server', 'server.ts') }, 6 | resolve: { extensions: ['.js', '.ts'] }, 7 | target: 'node', 8 | mode: 'none', 9 | // This makes sure we include node_modules and other 3rd party libraries 10 | externals: [/(node_modules|main\..*\.js)/], 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: '[name].js' 14 | }, 15 | module: { 16 | rules: [{ test: /\.ts$/, loader: 'ts-loader' }] 17 | }, 18 | plugins: [ 19 | // Temporary Fix for issue: https://github.com/angular/angular/issues/11580 20 | // for 'WARNING Critical dependency: the request of a dependency is an expression' 21 | new webpack.ContextReplacementPlugin( 22 | /(.+)?angular(\\|\/)core(.+)?/, 23 | path.join(__dirname, 'src'), // location of your src 24 | {} // a map of your routes 25 | ), 26 | new webpack.ContextReplacementPlugin(/(.+)?express(\\|\/)(.+)?/, path.join(__dirname, 'src'), {}) 27 | ] 28 | }; 29 | --------------------------------------------------------------------------------