├── .gitignore ├── 00 Boilerplate ├── README.md ├── package.json ├── src │ ├── css │ │ └── site.css │ ├── index.html │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── 01 Hello Angular ├── README.md ├── package.json ├── src │ ├── components │ │ └── app.ts │ ├── css │ │ └── site.css │ ├── index.html │ └── index.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 02 Navigation ├── README.md ├── package.json ├── src │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── banner.ts │ │ │ ├── loginForm.ts │ │ │ └── loginPage.ts │ │ └── patients │ │ │ └── patientsPage.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ └── routes.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 03 List Page ├── README.md ├── package.json ├── src │ ├── api │ │ ├── mockData.ts │ │ └── patientAPI.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── banner.ts │ │ │ ├── loginForm.ts │ │ │ └── loginPage.ts │ │ ├── patient │ │ │ └── patientPage.ts │ │ └── patients │ │ │ ├── patientList.ts │ │ │ ├── patientsPage.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ ├── model │ │ └── patient.ts │ └── routes.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 04 Form Page ├── README.md ├── package.json ├── src │ ├── api │ │ ├── mockData.ts │ │ └── patientAPI.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── banner.ts │ │ │ ├── loginForm.ts │ │ │ └── loginPage.ts │ │ ├── patient │ │ │ ├── patientForm.ts │ │ │ └── patientPage.ts │ │ └── patients │ │ │ ├── patientList.ts │ │ │ ├── patientsPage.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ ├── model │ │ └── patient.ts │ └── routes.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 05 Form Validation ├── README.md ├── package.json ├── src │ ├── api │ │ ├── mockData.ts │ │ └── patientAPI.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── banner.ts │ │ │ ├── loginForm.ts │ │ │ └── loginPage.ts │ │ ├── patient │ │ │ ├── patientForm.ts │ │ │ └── patientPage.ts │ │ └── patients │ │ │ ├── patientList.ts │ │ │ ├── patientsPage.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ ├── model │ │ └── patient.ts │ ├── routes.ts │ ├── validations │ │ └── dniValidation.ts │ └── validators │ │ ├── dniValidator.ts │ │ └── patientFormValidator.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 06 Refactor ├── README.md ├── package.json ├── src │ ├── api │ │ ├── index.ts │ │ ├── mockData.ts │ │ └── patientAPI.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── components │ │ │ │ ├── banner.ts │ │ │ │ ├── loginButton.ts │ │ │ │ ├── loginField.ts │ │ │ │ └── loginForm.ts │ │ │ ├── index.ts │ │ │ └── loginPage.ts │ │ ├── patient │ │ │ ├── index.ts │ │ │ ├── patientForm.ts │ │ │ └── patientPage.ts │ │ └── patients │ │ │ ├── components │ │ │ ├── patientButtonAdd.ts │ │ │ ├── patientListTable.ts │ │ │ └── patientRow.ts │ │ │ ├── index.ts │ │ │ ├── patientList.ts │ │ │ ├── patientsPage.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ ├── model │ │ └── patient.ts │ ├── routes.ts │ ├── validations │ │ └── dniValidation.ts │ └── validators │ │ ├── dniValidator.ts │ │ └── patientFormValidator.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── 07 Redux ├── README.md ├── package.json ├── src │ ├── actions │ │ ├── doctors │ │ │ ├── assignDoctorsAction.ts │ │ │ └── loadDoctorsAction.ts │ │ ├── patient │ │ │ ├── assignPatientAction.ts │ │ │ ├── loadPatientAction.ts │ │ │ ├── resetPatientFormAction.ts │ │ │ ├── savePatientAction.ts │ │ │ └── updatePatientUIAction.ts │ │ ├── patients │ │ │ ├── assignPatientsAction.ts │ │ │ └── loadPatientsAction.ts │ │ └── specialties │ │ │ ├── assignSpecialtiesAction.ts │ │ │ └── loadSpecialtiesAction.ts │ ├── api │ │ ├── mockData.ts │ │ └── patientAPI.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ ├── banner.ts │ │ │ ├── loginForm.ts │ │ │ └── loginPage.ts │ │ ├── patient │ │ │ ├── patientForm.container.ts │ │ │ └── patientForm.ts │ │ └── patients │ │ │ ├── patientList.container.ts │ │ │ ├── patientList.ts │ │ │ ├── patientsPage.ts │ │ │ ├── searchPatient.container.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── images │ │ └── health.png │ ├── index.html │ ├── index.ts │ ├── model │ │ └── patient.ts │ ├── reducers │ │ ├── doctorsReducer.ts │ │ ├── index.ts │ │ ├── patientReducer.ts │ │ ├── patientsReducer.ts │ │ └── specialtiesReducer.ts │ ├── routes.ts │ ├── states │ │ ├── appState.ts │ │ └── patientFormState.ts │ ├── store.ts │ ├── validations │ │ ├── dniValidation.ts │ │ └── requiredValidation.ts │ └── validators │ │ ├── dniValidator.ts │ │ ├── patientFormValidator.ts │ │ └── requiredValidator.ts ├── tsconfig.json ├── typings.json └── webpack.config.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | typings/ 5 | *.orig 6 | .idea/ 7 | */src/**/*.js 8 | */src/**/*.js.map -------------------------------------------------------------------------------- /00 Boilerplate/README.md: -------------------------------------------------------------------------------- 1 | # 00 Boilerplate 2 | 3 | In this sample we are going to setup the basic plumbing to "build" our project and launch it in a dev server. 4 | 5 | ## We are going to use: 6 | 7 | - [Webpack](https://webpack.github.io/) 8 | - [Typescript](http://www.typescriptlang.org/) 9 | 10 | ## The most interesting parts worth to take a look 11 | 12 | - package.json: check packages installed. 13 | 14 | - webpack.config.js: check the build process and ts-loader to handle typescript. 15 | 16 | - src: javascript using imports. 17 | -------------------------------------------------------------------------------- /00 Boilerplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "html-webpack-plugin": "^2.22.0", 26 | "style-loader": "^0.13.1", 27 | "ts-loader": "^0.8.2", 28 | "typescript": "^1.8.10", 29 | "typings": "^1.3.3", 30 | "webpack": "^1.13.2", 31 | "webpack-dev-server": "^1.15.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /00 Boilerplate/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /00 Boilerplate/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 2 Sample App 6 | 7 | 8 |
9 |

00 Boilerplate

10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /00 Boilerplate/src/index.ts: -------------------------------------------------------------------------------- 1 | var App = console.log('Hello from ts'); 2 | 3 | export default App; 4 | -------------------------------------------------------------------------------- /00 Boilerplate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /00 Boilerplate/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | 20 | ] 21 | }, 22 | 23 | output: { 24 | path: path.join(basePath, "dist"), 25 | filename: '[name].js' 26 | }, 27 | 28 | devServer: { 29 | contentBase: './dist', //Content base 30 | inline: true, //Enable watch and live reload 31 | host: 'localhost', 32 | port: 8080 33 | }, 34 | 35 | devtool: 'source-map', 36 | 37 | module: { 38 | loaders: [ 39 | { 40 | test: /\.ts$/, 41 | exclude: /node_modules/, 42 | loader: 'ts' 43 | }, 44 | { 45 | test: /\.css$/, 46 | exclude: /node_modules/, 47 | loader: ExtractTextPlugin.extract('style','css') 48 | } 49 | ] 50 | }, 51 | 52 | plugins: [ 53 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 54 | new ExtractTextPlugin('[name].css'), 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html' 58 | }) 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /01 Hello Angular/README.md: -------------------------------------------------------------------------------- 1 | # 01 Hello Angular 2 | 3 | In this sample we are going to setup the basic to work with Angular. 4 | 5 | We will start from sample 00 Boilerplate. 6 | 7 | Summary steps: 8 | - Install dependencies 9 | - Create App started component. 10 | 11 | Let's ensure we have installed the previous sample dependencies 12 | 13 | ``` 14 | npm install 15 | ``` 16 | 17 | It's a good idea as well to install globally webpack and 18 | typings: 19 | 20 | ``` 21 | npm install webpack -g 22 | ``` 23 | 24 | ``` 25 | npm install typings -g 26 | ``` 27 | 28 | ## Required dependencies 29 | - *00 Boilerplate* dependencies 30 | - core-js 31 | - reflect-metadata 32 | - zone.js 33 | - @angular/core 34 | - @angular/platform-browser 35 | - @angular/platform-browser-dynamic 36 | 37 | ## Peer dependencies 38 | 39 | - @angular/common 40 | - @angular/compiler 41 | - rxjs 42 | 43 | Let's install all them: 44 | 45 | ``` 46 | npm install @angular/common @angular/compiler 47 | @angular/core @angular/platform-browser @angular/platform-browser-dynamic 48 | core-js reflect-metadata 49 | rxjs zone.js --save 50 | ``` 51 | 52 | And let's install typings for core-js 53 | 54 | ``` 55 | typings install dt~core-js --save --global 56 | ``` 57 | 58 | This will install most of the angular 2 typings. 59 | 60 | And let's update wepback adding to the vendor zone the following 61 | entries 62 | 63 | ```javascript 64 | vendor: [ 65 | "core-js", 66 | "reflect-metadata", 67 | "zone.js", 68 | "@angular/core", 69 | "@angular/platform-browser", 70 | "@angular/platform-browser-dynamic", 71 | "@angular/common", 72 | "@angular/compiler", 73 | "rxjs" 74 | ] 75 | ``` 76 | We need to specify this because some of them are not directly referenced in the application but are needed (indirect references) 77 | 78 | 79 | # App component 80 | 81 | Let's create a subfolder called _components_. 82 | 83 | Under this subfolder we are going to create our main "container" 84 | component, let's create a file called _app.ts_. 85 | 86 | ## Definition 87 | #### src/components/app.ts 88 | 89 | ``` 90 | import { Component } from '@angular/core'; 91 | 92 | @Component( 93 | { 94 | selector: 'app', 95 | template: ` 96 |

01 Hello angular

97 | ` 98 | } 99 | ) 100 | class App { 101 | 102 | } 103 | 104 | export { 105 | App 106 | } 107 | ``` 108 | 109 | - **selector**: This property is used to define how to call this component from HTML. 110 | - **template**: We are defining our *template* string between [backticks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) this is a new feature of ES6 that allow us to do multiline strings (we could as well separate this into a separate HTML template). 111 | 112 | ## Configuration 113 | 114 | In *index.ts* file we are going to create our first NgModule this is needed for booting our application. 115 | 116 | #### src/index.ts 117 | ``` 118 | import { NgModule } from '@angular/core'; 119 | import { BrowserModule } from '@angular/platform-browser'; 120 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 121 | import { App } from './components/app'; 122 | 123 | @NgModule({ 124 | declarations: [App], 125 | imports: [BrowserModule], 126 | bootstrap: [App] 127 | }) 128 | class AppModule { 129 | 130 | } 131 | 132 | platformBrowserDynamic().bootstrapModule(AppModule) 133 | ``` 134 | 135 | - **declarations**: defines which components we are going to use in this module. In this case App component. 136 | - **imports**: describes which *dependencies* this module has. In this case, we are going to create a browser app, 137 | so we have to use BrowserModule. We are going to add here our custom modules that our app needs. 138 | - **bootstrap**: this property tells Angular to load, in this case, App component as the top-level component. 139 | 140 | The last line `platformBrowserDynamic().bootstrapModule(AppModule)` initialize the browser platform to runs AppModule application. 141 | 142 | ## Using App Component 143 | ### src/index.html 144 | 145 | We use *app* selector, that we defined previously. 146 | ``` 147 | 148 | 149 | 150 | 151 | 152 | Angular 2 Sample App 153 | 154 | 155 |
156 | 157 | Loading... 158 | 159 |
160 | 161 | 162 | ``` 163 | 164 | Let's run the sample 165 | 166 | ``` 167 | npm start 168 | ``` 169 | -------------------------------------------------------------------------------- /01 Hello Angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "html-webpack-plugin": "^2.22.0", 26 | "style-loader": "^0.13.1", 27 | "ts-loader": "^0.8.2", 28 | "typescript": "^1.8.10", 29 | "typings": "^1.3.3", 30 | "webpack": "^1.13.2", 31 | "webpack-dev-server": "^1.15.1" 32 | }, 33 | "dependencies": { 34 | "@angular/common": "^2.0.1", 35 | "@angular/compiler": "^2.0.1", 36 | "@angular/core": "^2.0.1", 37 | "@angular/platform-browser": "^2.0.1", 38 | "@angular/platform-browser-dynamic": "^2.0.1", 39 | "core-js": "^2.4.1", 40 | "reflect-metadata": "^0.1.8", 41 | "rxjs": "^5.0.0-beta.12", 42 | "zone.js": "^0.6.21" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /01 Hello Angular/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'app', 6 | template: ` 7 |

01 Hello angular

8 | ` 9 | } 10 | ) 11 | class App { 12 | 13 | } 14 | 15 | export { 16 | App 17 | } 18 | -------------------------------------------------------------------------------- /01 Hello Angular/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /01 Hello Angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /01 Hello Angular/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { App } from './components/app'; 5 | 6 | @NgModule({ 7 | declarations: [App], 8 | imports: [BrowserModule], 9 | bootstrap: [App] 10 | }) 11 | class AppModule { 12 | 13 | } 14 | 15 | platformBrowserDynamic().bootstrapModule(AppModule) 16 | -------------------------------------------------------------------------------- /01 Hello Angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /01 Hello Angular/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /01 Hello Angular/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs" 28 | ] 29 | }, 30 | 31 | output: { 32 | path: path.join(basePath, "dist"), 33 | filename: '[name].js' 34 | }, 35 | 36 | devServer: { 37 | contentBase: './dist', //Content base 38 | inline: true, //Enable watch and live reload 39 | host: 'localhost', 40 | port: 8080 41 | }, 42 | 43 | devtool: 'source-map', 44 | 45 | module: { 46 | loaders: [ 47 | { 48 | test: /\.ts$/, 49 | exclude: /node_modules/, 50 | loader: 'ts' 51 | }, 52 | { 53 | test: /\.css$/, 54 | exclude: /node_modules/, 55 | loader: ExtractTextPlugin.extract('style','css') 56 | } 57 | ] 58 | }, 59 | 60 | plugins: [ 61 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 62 | new ExtractTextPlugin('[name].css'), 63 | new HtmlWebpackPlugin({ 64 | filename: 'index.html', 65 | template: 'index.html' 66 | }) 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /02 Navigation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/platform-browser": "^2.0.1", 40 | "@angular/platform-browser-dynamic": "^2.0.1", 41 | "@angular/router": "^3.0.1", 42 | "bootstrap": "^3.3.7", 43 | "core-js": "^2.4.1", 44 | "jquery": "^3.1.0", 45 | "reflect-metadata": "^0.1.8", 46 | "rxjs": "^5.0.0-beta.12", 47 | "zone.js": "^0.6.21" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /02 Navigation/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'app', 6 | template: ` 7 |
8 |
9 | 10 | 11 |
12 | ` 13 | } 14 | ) 15 | class App { 16 | 17 | } 18 | 19 | export { 20 | App 21 | } 22 | -------------------------------------------------------------------------------- /02 Navigation/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /02 Navigation/src/components/login/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /02 Navigation/src/components/login/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | ` 30 | }) 31 | class LoginForm { 32 | 33 | } 34 | 35 | export { 36 | LoginForm 37 | } 38 | -------------------------------------------------------------------------------- /02 Navigation/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /02 Navigation/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |

Patients list

8 |
9 | ` 10 | }) 11 | class PatientsPage { 12 | 13 | } 14 | 15 | export { 16 | PatientsPage 17 | } 18 | -------------------------------------------------------------------------------- /02 Navigation/src/css/site.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | margin-bottom: 0px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | -------------------------------------------------------------------------------- /02 Navigation/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/02 Navigation/src/images/health.png -------------------------------------------------------------------------------- /02 Navigation/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /02 Navigation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | 8 | import { App } from './components/app'; 9 | import { Header } from './components/common/header'; 10 | import { LoginPage } from './components/login/loginPage'; 11 | import { Banner } from './components/login/banner'; 12 | import { LoginForm } from './components/login/loginForm'; 13 | import { PatientsPage } from './components/patients/patientsPage'; 14 | 15 | @NgModule({ 16 | declarations: [ 17 | App, 18 | Header, 19 | LoginPage, 20 | Banner, 21 | LoginForm, 22 | PatientsPage 23 | ], 24 | imports: [ 25 | BrowserModule, 26 | RouterModule.forRoot(routes) 27 | ], 28 | bootstrap: [App], 29 | providers: [ 30 | { provide: LocationStrategy, useClass: HashLocationStrategy } 31 | ] 32 | }) 33 | class AppModule { 34 | 35 | } 36 | 37 | platformBrowserDynamic().bootstrapModule(AppModule) 38 | -------------------------------------------------------------------------------- /02 Navigation/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | 5 | const routes: Routes = [ 6 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 7 | { path: 'login', component: LoginPage }, 8 | { path: 'patients', component: PatientsPage } 9 | ]; 10 | 11 | export { 12 | routes 13 | } 14 | -------------------------------------------------------------------------------- /02 Navigation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /02 Navigation/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /02 Navigation/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router" 29 | ], 30 | vendorStyles: [ 31 | '../node_modules/bootstrap/dist/css/bootstrap.css' 32 | ] 33 | }, 34 | 35 | output: { 36 | path: path.join(basePath, "dist"), 37 | filename: '[name].js' 38 | }, 39 | 40 | devServer: { 41 | contentBase: './dist', //Content base 42 | inline: true, //Enable watch and live reload 43 | host: 'localhost', 44 | port: 8080 45 | }, 46 | 47 | devtool: 'source-map', 48 | 49 | module: { 50 | loaders: [ 51 | { 52 | test: /\.ts$/, 53 | exclude: /node_modules/, 54 | loader: 'ts' 55 | }, 56 | //Note: Doesn't exclude node_modules to load bootstrap 57 | { 58 | test: /\.css$/, 59 | loader: ExtractTextPlugin.extract('style','css') 60 | }, 61 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 62 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 63 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 64 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 65 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 66 | { 67 | test: /\.png$/, 68 | loader: 'file?limit=0&name=[path][name].[hash].', 69 | exclude: /node_modules/ 70 | } 71 | ] 72 | }, 73 | 74 | plugins: [ 75 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 76 | new ExtractTextPlugin('[name].css'), 77 | new HtmlWebpackPlugin({ 78 | filename: 'index.html', 79 | template: 'index.html' 80 | }), 81 | //Expose jquery used by bootstrap 82 | new webpack.ProvidePlugin({ 83 | $: "jquery", 84 | jQuery: "jquery" 85 | }) 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /03 List Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/platform-browser": "^2.0.1", 40 | "@angular/platform-browser-dynamic": "^2.0.1", 41 | "@angular/router": "^3.0.1", 42 | "bootstrap": "^3.3.7", 43 | "core-js": "^2.4.1", 44 | "jquery": "^3.1.0", 45 | "reflect-metadata": "^0.1.8", 46 | "rxjs": "^5.0.0-beta.12", 47 | "zone.js": "^0.6.21" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /03 List Page/src/api/mockData.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | 3 | const patientsMockData: Array = [ 4 | { id: 1, dni: "71258192Y", name: "John Doe", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "08:30" }, 5 | { id: 2, dni: "98168530E", name: "Anna S. Batiste", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "09:00" }, 6 | { id: 3, dni: "42955917J", name: "Octavia L. Hilton", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "09:30" }, 7 | { id: 4, dni: "00706785H", name: "Tony M. Herrera", specialty: "Ophthalmology", doctor: "Ruthie A. Nemeth", date: "2019-09-19", time: "10:00" }, 8 | { id: 5, dni: "23754350L", name: "Robert J. Macias", specialty: "Traumatology", doctor: "Gladys C. Horton", date: "2019-09-19", time: "10:30" } 9 | ]; 10 | 11 | const specialtiesMockData: Array = [ 12 | "Surgery", 13 | "Traumatology", 14 | "Ophthalmology" 15 | ]; 16 | 17 | export { 18 | patientsMockData, 19 | specialtiesMockData 20 | } 21 | -------------------------------------------------------------------------------- /03 List Page/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Promise } from 'core-js/es6'; 2 | import { Patient } from '../model/patient'; 3 | import { patientsMockData, specialtiesMockData } from './mockData'; 4 | 5 | class PatientAPI { 6 | getAllPatientsAsync(): Promise> { 7 | let patientsPromise = new Promise((resolve, reject) => { 8 | resolve(patientsMockData); 9 | }); 10 | 11 | return patientsPromise; 12 | }; 13 | 14 | getAllSpecialtiesAsync(): Promise> { 15 | let specialtiesPromise = new Promise((resolve, reject) => { 16 | resolve(specialtiesMockData); 17 | }); 18 | 19 | return specialtiesPromise; 20 | } 21 | } 22 | 23 | 24 | export { 25 | PatientAPI 26 | } 27 | -------------------------------------------------------------------------------- /03 List Page/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'app', 6 | template: ` 7 |
8 |
9 | 10 | 11 |
12 | ` 13 | } 14 | ) 15 | class App { 16 | 17 | } 18 | 19 | export { 20 | App 21 | } 22 | -------------------------------------------------------------------------------- /03 List Page/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /03 List Page/src/components/login/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /03 List Page/src/components/login/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | ` 30 | }) 31 | class LoginForm { 32 | 33 | } 34 | 35 | export { 36 | LoginForm 37 | } 38 | -------------------------------------------------------------------------------- /03 List Page/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /03 List Page/src/components/patient/patientPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patient-page', 5 | template: ` 6 |
7 |
8 |

Patient Page

9 |
10 |
11 | ` 12 | }) 13 | class PatientPage { 14 | 15 | } 16 | 17 | export { 18 | PatientPage 19 | } 20 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/patientList.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { Promise } from 'core-js/es6'; 4 | import { PatientAPI } from '../../api/patientAPI'; 5 | 6 | @Component({ 7 | selector: 'patients-list', 8 | template: ` 9 |
10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 47 | 48 | 49 |
PacienteEspecialidad
{{p.name}} 34 | {{p.specialty}} 35 | 38 |
50 |
51 |
52 | ` 53 | }) 54 | class PatientList { 55 | patients: Array; 56 | 57 | constructor(patientAPI : PatientAPI) { 58 | patientAPI.getAllPatientsAsync().then((patients: Array) => { 59 | this.patients = patients; 60 | }); 61 | } 62 | } 63 | 64 | export { 65 | PatientList 66 | } 67 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |
8 | 9 | 10 |
11 |
12 | ` 13 | }) 14 | class PatientsPage { 15 | 16 | } 17 | 18 | export { 19 | PatientsPage 20 | } 21 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Promise } from 'core-js/es6'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | 5 | @Component({ 6 | selector: 'search-patient', 7 | template: ` 8 |
9 |
10 |
11 | 12 |

Buscar paciente

13 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | ` 45 | }) 46 | class SearchPatient { 47 | specialties: Array; 48 | 49 | constructor(patientAPI : PatientAPI) { 50 | patientAPI.getAllSpecialtiesAsync().then((specialties: Array) => { 51 | this.specialties = specialties; 52 | }); 53 | } 54 | 55 | searchPatient(event){ 56 | event.preventDefault(); 57 | } 58 | } 59 | 60 | export { 61 | SearchPatient 62 | } 63 | -------------------------------------------------------------------------------- /03 List Page/src/css/site.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | margin-top: -20px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | 9 | .table { 10 | margin-top: 15px; 11 | } 12 | 13 | .table thead tr { 14 | background-color: white; 15 | } 16 | 17 | .table-striped > tbody > tr:nth-of-type(even) { 18 | background-color: white; 19 | } 20 | 21 | .table-striped > tbody > tr:nth-of-type(odd) { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .collapse { 26 | display: block; 27 | } 28 | 29 | @media (min-width: 992px) { 30 | .collapse-toggle { 31 | display: none; 32 | } 33 | } 34 | 35 | @media (max-width: 992px) { 36 | .collapse { 37 | display: none; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /03 List Page/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/03 List Page/src/images/health.png -------------------------------------------------------------------------------- /03 List Page/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /03 List Page/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | 8 | import { App } from './components/app'; 9 | import { Header } from './components/common/header'; 10 | import { LoginPage } from './components/login/loginPage'; 11 | import { Banner } from './components/login/banner'; 12 | import { LoginForm } from './components/login/loginForm'; 13 | import { PatientsPage } from './components/patients/patientsPage'; 14 | import { SearchPatient } from './components/patients/searchPatient'; 15 | import { PatientList } from './components/patients/patientList'; 16 | import { PatientPage } from './components/patient/patientPage'; 17 | import {PatientAPI} from './api/patientAPI'; 18 | 19 | @NgModule({ 20 | declarations: [ 21 | App, 22 | Header, 23 | LoginPage, 24 | Banner, 25 | LoginForm, 26 | PatientsPage, 27 | SearchPatient, 28 | PatientList, 29 | PatientPage 30 | ], 31 | imports: [ 32 | BrowserModule, 33 | RouterModule.forRoot(routes) 34 | ], 35 | bootstrap: [App], 36 | providers: [ 37 | { provide: LocationStrategy, useClass: HashLocationStrategy }, 38 | PatientAPI 39 | ] 40 | }) 41 | class AppModule { 42 | 43 | } 44 | 45 | platformBrowserDynamic().bootstrapModule(AppModule) 46 | -------------------------------------------------------------------------------- /03 List Page/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | export class Patient { 2 | id: number; 3 | dni: string; 4 | name: string; 5 | specialty: string; 6 | doctor: string; 7 | date: string; 8 | time: string; 9 | } 10 | -------------------------------------------------------------------------------- /03 List Page/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | import { PatientPage } from './components/patient/patientPage'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 8 | { path: 'login', component: LoginPage }, 9 | { path: 'patients', component: PatientsPage }, 10 | { path: 'patient', component: PatientPage } 11 | ]; 12 | 13 | export { 14 | routes 15 | } 16 | -------------------------------------------------------------------------------- /03 List Page/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /03 List Page/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /03 List Page/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router", 29 | "bootstrap" 30 | ], 31 | vendorStyles: [ 32 | '../node_modules/bootstrap/dist/css/bootstrap.css' 33 | ] 34 | }, 35 | 36 | output: { 37 | path: path.join(basePath, "dist"), 38 | filename: '[name].js' 39 | }, 40 | 41 | devServer: { 42 | contentBase: './dist', //Content base 43 | inline: true, //Enable watch and live reload 44 | host: 'localhost', 45 | port: 8080 46 | }, 47 | 48 | devtool: 'source-map', 49 | 50 | module: { 51 | loaders: [ 52 | { 53 | test: /\.ts$/, 54 | exclude: /node_modules/, 55 | loader: 'ts' 56 | }, 57 | //Note: Doesn't exclude node_modules to load bootstrap 58 | { 59 | test: /\.css$/, 60 | loader: ExtractTextPlugin.extract('style','css') 61 | }, 62 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 63 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 64 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 65 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 66 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 67 | { 68 | test: /\.png$/, 69 | loader: 'file?limit=0&name=[path][name].[hash].', 70 | exclude: /node_modules/ 71 | } 72 | ] 73 | }, 74 | 75 | plugins: [ 76 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 77 | new ExtractTextPlugin('[name].css'), 78 | new HtmlWebpackPlugin({ 79 | filename: 'index.html', 80 | template: 'index.html' 81 | }), 82 | //Expose jquery used by bootstrap 83 | new webpack.ProvidePlugin({ 84 | $: "jquery", 85 | jQuery: "jquery" 86 | }) 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /04 Form Page/README.md: -------------------------------------------------------------------------------- 1 | # 04 Form Page 2 | Let's get started working with forms. 3 | 4 | In this demo will create a Patient form page to create and update patient appointments. 5 | 6 | We will start from sample **03 List Page**. 7 | 8 | Summary steps: 9 | - Add new methods to Patient API. 10 | - Install and configure *@angular/forms*. 11 | - Add route with params. 12 | - Create Patient Component. 13 | - Update Model. 14 | 15 | ## Required dependencies 16 | - *03 List Page* dependencies 17 | - @angular/forms 18 | 19 | # API 20 | 21 | We need to add some more mockdata 22 | ## Definition: 23 | ### src/api/mockData.ts 24 | 25 | Add more content to the existing mockData file. 26 | 27 | ```javascript 28 | const doctorsMockData: Array = [ 29 | "Karl J. Linville", 30 | "Gladys C. Horton", 31 | "Ruthie A. Nemeth" 32 | ]; 33 | 34 | ... 35 | 36 | export { 37 | patientsMockData, 38 | specialtiesMockData, 39 | doctorsMockData 40 | } 41 | ``` 42 | 43 | We add new methods: 44 | 45 | - Retrieve doctor collection. 46 | - Retrieve patient by id. 47 | - Save patient. 48 | 49 | ## Definition: 50 | ### src/api/patientAPI.ts 51 | ``` 52 | ... 53 | import { patientsMockData, specialtiesMockData, doctorsMockData } from './mockData'; 54 | ... 55 | 56 | getAllDoctorsAsync(): Promise> { 57 | let doctorsPromise = new Promise((resolve, reject) => { 58 | resolve(doctorsMockData); 59 | }); 60 | 61 | return doctorsPromise; 62 | }; 63 | 64 | getPatientByIdAsync(id: number): Promise { 65 | let patientPromise = new Promise((resolve, reject) => { 66 | let patient = patientsMockData.find((patient: Patient) => { 67 | return patient.id === id; 68 | }); 69 | 70 | resolve(patient); 71 | }); 72 | 73 | return patientPromise; 74 | }; 75 | 76 | savePatient(currentPatient: Patient): void { 77 | let patient = patientsMockData.find((patient: Patient) => { 78 | return patient.id === currentPatient.id; 79 | }); 80 | 81 | if (patient) { 82 | let patientIndex = patientsMockData.indexOf(patient); 83 | patientsMockData.splice(patientIndex, 1, currentPatient); 84 | } else { 85 | let lastId = patientsMockData[patientsMockData.length -1].id; 86 | currentPatient.id = lastId + 1; 87 | 88 | patientsMockData.push(currentPatient); 89 | } 90 | 91 | patient = currentPatient; 92 | } 93 | 94 | ... 95 | ``` 96 | 97 | # Install and configure @angular/forms 98 | 99 | Installing: 100 | 101 | ``` 102 | npm install @angular/forms --save 103 | ``` 104 | 105 | ## Configure 106 | ### webpack.config.js 107 | ``` 108 | ... 109 | 110 | vendor: [ 111 | ... 112 | "@angular/forms" 113 | ], 114 | ... 115 | ``` 116 | 117 | ### src/index.ts 118 | 119 | ``` 120 | ... 121 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 122 | ... 123 | 124 | imports: [ 125 | ... 126 | FormsModule, 127 | ReactiveFormsModule 128 | ], 129 | ... 130 | ``` 131 | 132 | # Routes 133 | 134 | We're going to refactor patient route to allow params 135 | 136 | ## Definition 137 | ### src/routes.ts 138 | 139 | ``` 140 | ... 141 | 142 | const routes: Routes = [ 143 | ... 144 | { path: 'patient/:id', component: PatientPage } 145 | ]; 146 | ``` 147 | 148 | ## Using routerLink with params 149 | ### src/components/patients/patientsList.ts 150 | ``` 151 | ... 152 |
153 |
154 |
155 |
156 | 157 |
158 | ... 159 | 160 | 161 | {{p.time}} 162 | 164 | 165 | 166 | ... 167 | ``` 168 | 169 | 170 | # Model 171 | ### src/model/patient.ts 172 | 173 | We need to initialize properties to default values to avoid form errors like: 174 | 175 | >core.umd.js:3427 EXCEPTION: Uncaught (in promise): Error: Error in ./PatientForm class PatientForm - inline template:16:12 caused by: Cannot find control with unspecified name attribute 176 | 177 | ``` 178 | export class Patient { 179 | id: number; 180 | dni: string; 181 | name: string; 182 | specialty: string; 183 | doctor: string; 184 | date: string; 185 | time: string; 186 | 187 | constructor() { 188 | this.id = 0; 189 | this.dni = ""; 190 | this.name = ""; 191 | this.specialty = ""; 192 | this.doctor = ""; 193 | this.date = ""; 194 | this.time = ""; 195 | } 196 | } 197 | ``` 198 | 199 | # Patient Component 200 | 201 | ### src/components/patient/patientForm.ts 202 | 203 | We're going to create patient form, and update values on changes. 204 | 205 | ``` 206 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 207 | import { Patient } from '../../model/patient'; 208 | import { FormBuilder, FormGroup } from '@angular/forms'; 209 | 210 | @Component({ 211 | selector: 'patient-form', 212 | template: ` 213 |
214 |
215 |
216 |

Editar Cita - Centro de Día

217 |
218 |
219 | 220 |
221 |
222 |
223 | 224 |
225 |
226 | 227 | 229 |
230 |
231 | 232 | 234 |
235 |
236 | 237 |
238 |
239 | 240 | 242 |
243 |
244 | 245 | 247 |
248 |
249 | 250 | 254 |
255 |
256 | 257 | 261 |
262 | 263 |
264 |
265 | 266 |
267 |
268 |
269 |
270 |
271 | ` 272 | }) 273 | class PatientForm implements OnChanges { 274 | @Input() specialties: Array; 275 | @Input() doctors: Array; 276 | @Input() patient: Patient; 277 | @Input() savePatient: (patient: Patient) => void; 278 | patientForm: FormGroup; 279 | 280 | constructor(formBuilder: FormBuilder) { 281 | this.patient = new Patient(); 282 | this.patientForm = formBuilder.group(this.patient); 283 | } 284 | 285 | ngOnChanges(changes) { 286 | let patient: SimpleChange = changes['patient']; 287 | 288 | if(patient && patient.currentValue) { 289 | this.patientForm.setValue(patient.currentValue); 290 | } 291 | } 292 | } 293 | 294 | export { 295 | PatientForm 296 | } 297 | ``` 298 | 299 | This component is a form where we're going to retrieve patient data from server 300 | if it's an existing patient or create new if not, and save changes. 301 | 302 | 303 | ## Configuration: 304 | ### src/index.ts 305 | 306 | Let's register the component 307 | 308 | ```javascript 309 | import { PatientForm } from './components/patient/patientForm'; 310 | ... 311 | declarations: [App, 312 | ... 313 | PatientForm 314 | ], 315 | ``` 316 | 317 | ## Definition: 318 | ### src/components/patient/patientPage.ts 319 | 320 | We're retrieving data from server and inject it child component (patientForm). 321 | 322 | ``` 323 | import { Component } from '@angular/core'; 324 | import { ActivatedRoute, Router } from '@angular/router'; 325 | import { PatientAPI } from '../../api/patientAPI'; 326 | import { Patient } from '../../model/patient'; 327 | 328 | @Component({ 329 | selector: 'patient-page', 330 | template: ` 331 |
332 | 336 | 337 |
338 | ` 339 | }) 340 | class PatientPage { 341 | specialties: Array; 342 | doctors: Array; 343 | patientId: number; 344 | patient: Patient; 345 | 346 | constructor(private route: ActivatedRoute, private router: Router, private patientAPI : PatientAPI) { 347 | this.loadPatientId(); 348 | this.loadPatient(); 349 | this.loadRelatedCollections(); 350 | } 351 | 352 | private loadPatientId() { 353 | this.route.params.subscribe(params => { 354 | this.patientId = parseInt(params['id']); 355 | }); 356 | } 357 | 358 | private loadPatient() { 359 | if (this.patientId > 0) { 360 | this.patientAPI.getPatientByIdAsync(this.patientId) 361 | .then((patient: Patient) => { 362 | this.patient = patient; 363 | }); 364 | } 365 | } 366 | 367 | private loadRelatedCollections() { 368 | Promise.all([ 369 | this.patientAPI.getAllSpecialtiesAsync(), 370 | this.patientAPI.getAllDoctorsAsync() 371 | ]).then((data) => { 372 | this.specialties = data[0]; 373 | this.doctors = data[1]; 374 | }); 375 | } 376 | 377 | savePatient(event: any, patient: Patient){ 378 | event.preventDefault(); 379 | this.patientAPI.savePatient(patient); 380 | this.router.navigate(['/patients']); 381 | } 382 | } 383 | 384 | export { 385 | PatientPage 386 | } 387 | ``` 388 | -------------------------------------------------------------------------------- /04 Form Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/forms": "^2.0.1", 40 | "@angular/platform-browser": "^2.0.1", 41 | "@angular/platform-browser-dynamic": "^2.0.1", 42 | "@angular/router": "^3.0.1", 43 | "bootstrap": "^3.3.7", 44 | "core-js": "^2.4.1", 45 | "jquery": "^3.1.0", 46 | "reflect-metadata": "^0.1.8", 47 | "rxjs": "^5.0.0-beta.12", 48 | "zone.js": "^0.6.21" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /04 Form Page/src/api/mockData.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | const patientsMockData: Array = [ 3 | { id: 1, dni: "1234567A", name: "John Doe", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "08:30" }, 4 | { id: 2, dni: "5067254B", name: "Anna S. Batiste", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "09:00" }, 5 | { id: 3, dni: "1902045C", name: "Octavia L. Hilton", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "09:30" }, 6 | { id: 4, dni: "1880514D", name: "Tony M. Herrera", specialty: "Ophthalmology", doctor: "Ruthie A. Nemeth", date: "2019-09-19", time: "10:00" }, 7 | { id: 5, dni: "6810774E", name: "Robert J. Macias", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "10:30" } 8 | ]; 9 | 10 | const specialtiesMockData: Array = [ 11 | "Surgery", 12 | "Traumatology", 13 | "Ophthalmology" 14 | ]; 15 | 16 | const doctorsMockData: Array = [ 17 | "Karl J. Linville", 18 | "Gladys C. Horton", 19 | "Ruthie A. Nemeth" 20 | ]; 21 | 22 | export { 23 | patientsMockData, 24 | specialtiesMockData, 25 | doctorsMockData 26 | } 27 | -------------------------------------------------------------------------------- /04 Form Page/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Promise } from 'core-js/es6'; 2 | import { Patient } from '../model/patient'; 3 | import { patientsMockData, specialtiesMockData, doctorsMockData } from './mockData'; 4 | 5 | class PatientAPI { 6 | getAllPatientsAsync(): Promise> { 7 | let patientsPromise = new Promise((resolve, reject) => { 8 | resolve(patientsMockData); 9 | }); 10 | 11 | return patientsPromise; 12 | }; 13 | 14 | getAllSpecialtiesAsync(): Promise> { 15 | let specialtiesPromise = new Promise((resolve, reject) => { 16 | resolve(specialtiesMockData); 17 | }); 18 | 19 | return specialtiesPromise; 20 | }; 21 | 22 | getAllDoctorsAsync(): Promise> { 23 | let doctorsPromise = new Promise((resolve, reject) => { 24 | resolve(doctorsMockData); 25 | }); 26 | 27 | return doctorsPromise; 28 | }; 29 | 30 | getPatientByIdAsync(id: number): Promise { 31 | let patientPromise = new Promise((resolve, reject) => { 32 | let patient = patientsMockData.find((patient: Patient) => { 33 | return patient.id === id; 34 | }); 35 | 36 | resolve(patient); 37 | }); 38 | 39 | return patientPromise; 40 | }; 41 | 42 | savePatient(currentPatient: Patient): void { 43 | let patient = patientsMockData.find((patient: Patient) => { 44 | return patient.id === currentPatient.id; 45 | }); 46 | 47 | if (patient) { 48 | let patientIndex = patientsMockData.indexOf(patient); 49 | patientsMockData.splice(patientIndex, 1, currentPatient); 50 | } else { 51 | let lastId = patientsMockData[patientsMockData.length -1].id; 52 | currentPatient.id = lastId + 1; 53 | 54 | patientsMockData.push(currentPatient); 55 | } 56 | 57 | patient = currentPatient; 58 | } 59 | } 60 | 61 | 62 | export { 63 | PatientAPI 64 | } 65 | -------------------------------------------------------------------------------- /04 Form Page/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {PatientAPI} from '../api/patientAPI'; 3 | 4 | @Component( 5 | { 6 | selector: 'app', 7 | template: ` 8 |
9 |
10 | 11 | 12 |
13 | `, 14 | providers: [PatientAPI] 15 | } 16 | ) 17 | class App { 18 | 19 | } 20 | 21 | export { 22 | App 23 | } 24 | -------------------------------------------------------------------------------- /04 Form Page/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /04 Form Page/src/components/login/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /04 Form Page/src/components/login/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | ` 30 | }) 31 | class LoginForm { 32 | 33 | } 34 | 35 | export { 36 | LoginForm 37 | } 38 | -------------------------------------------------------------------------------- /04 Form Page/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patient/patientForm.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { FormBuilder, FormGroup } from '@angular/forms'; 4 | import { PatientAPI } from '../../api/patientAPI'; 5 | 6 | @Component({ 7 | selector: 'patient-form', 8 | template: ` 9 |
10 |
11 |
12 |

Appointment

13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 | 23 | 25 |
26 |
27 | 28 | 30 |
31 |
32 | 33 |
34 |
35 | 36 | 38 |
39 |
40 | 41 | 43 |
44 |
45 | 46 | 50 |
51 |
52 | 53 | 57 |
58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 | ` 68 | }) 69 | class PatientForm implements OnChanges { 70 | @Input() specialties: Array; 71 | @Input() doctors: Array; 72 | @Input() patient: Patient; 73 | @Input() savePatient: (patient: Patient) => void; 74 | patientForm: FormGroup; 75 | 76 | constructor(formBuilder: FormBuilder) { 77 | this.patient = new Patient(); 78 | this.patientForm = formBuilder.group(this.patient); 79 | } 80 | 81 | ngOnChanges(changes) { 82 | let patient: SimpleChange = changes['patient']; 83 | 84 | if(patient && patient.currentValue) { 85 | this.patientForm.setValue(patient.currentValue); 86 | } 87 | } 88 | } 89 | 90 | export { 91 | PatientForm 92 | } 93 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patient/patientPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | import { Patient } from '../../model/patient'; 5 | 6 | @Component({ 7 | selector: 'patient-page', 8 | template: ` 9 |
10 | 14 | 15 |
16 | ` 17 | }) 18 | class PatientPage { 19 | specialties: Array; 20 | doctors: Array; 21 | patientId: number; 22 | patient: Patient; 23 | 24 | constructor(private route: ActivatedRoute, private router: Router, private patientAPI : PatientAPI) { 25 | this.loadPatientId(); 26 | this.loadPatient(); 27 | this.loadRelatedCollections(); 28 | } 29 | 30 | private loadPatientId() { 31 | this.route.params.subscribe(params => { 32 | this.patientId = parseInt(params['id']); 33 | }); 34 | } 35 | 36 | private loadPatient() { 37 | if (this.patientId > 0) { 38 | this.patientAPI.getPatientByIdAsync(this.patientId) 39 | .then((patient: Patient) => { 40 | this.patient = patient; 41 | }); 42 | } 43 | } 44 | 45 | private loadRelatedCollections() { 46 | Promise.all([ 47 | this.patientAPI.getAllSpecialtiesAsync(), 48 | this.patientAPI.getAllDoctorsAsync() 49 | ]).then((data) => { 50 | this.specialties = data[0]; 51 | this.doctors = data[1]; 52 | }); 53 | } 54 | 55 | savePatient(event: any, patient: Patient){ 56 | event.preventDefault(); 57 | this.patientAPI.savePatient(patient); 58 | this.router.navigate(['/patients']); 59 | } 60 | } 61 | 62 | export { 63 | PatientPage 64 | } 65 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/patientList.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { Promise } from 'core-js/es6'; 4 | import { PatientAPI } from '../../api/patientAPI'; 5 | 6 | @Component({ 7 | selector: 'patient-list', 8 | template: ` 9 |
10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 47 | 48 | 49 |
PacienteEspecialidad
{{p.name}} 34 | {{p.specialty}} 35 | 38 |
50 |
51 |
52 | ` 53 | }) 54 | class PatientList { 55 | patients: Array; 56 | 57 | constructor(patientAPI : PatientAPI) { 58 | patientAPI.getAllPatientsAsync().then((patients: Array) => { 59 | this.patients = patients; 60 | }); 61 | } 62 | } 63 | 64 | export { 65 | PatientList 66 | } 67 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |
8 | 9 | 10 |
11 |
12 | ` 13 | }) 14 | class PatientsPage { 15 | 16 | } 17 | 18 | export { 19 | PatientsPage 20 | } 21 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Promise } from 'core-js/es6'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | 5 | @Component({ 6 | selector: 'search-patient', 7 | template: ` 8 |
9 |
10 |
11 | 12 |

Buscar paciente

13 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | ` 45 | }) 46 | class SearchPatient { 47 | specialties: Array; 48 | 49 | constructor(patientAPI : PatientAPI) { 50 | patientAPI.getAllSpecialtiesAsync().then((specialties: Array) => { 51 | this.specialties = specialties; 52 | }); 53 | } 54 | 55 | searchPatient(event){ 56 | event.preventDefault(); 57 | } 58 | } 59 | 60 | export { 61 | SearchPatient 62 | } 63 | -------------------------------------------------------------------------------- /04 Form Page/src/css/site.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | margin-top: -20px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | 9 | .table { 10 | margin-top: 15px; 11 | } 12 | 13 | .table thead tr { 14 | background-color: white; 15 | } 16 | 17 | .table-striped > tbody > tr:nth-of-type(even) { 18 | background-color: white; 19 | } 20 | 21 | .table-striped > tbody > tr:nth-of-type(odd) { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .collapse { 26 | display: block; 27 | } 28 | 29 | @media (min-width: 992px) { 30 | .collapse-toggle { 31 | display: none; 32 | } 33 | } 34 | 35 | @media (max-width: 992px) { 36 | .collapse { 37 | display: none; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /04 Form Page/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/04 Form Page/src/images/health.png -------------------------------------------------------------------------------- /04 Form Page/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /04 Form Page/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | 9 | import { App } from './components/app'; 10 | import { Header } from './components/common/header'; 11 | import { LoginPage } from './components/login/loginPage'; 12 | import { Banner } from './components/login/banner'; 13 | import { LoginForm } from './components/login/loginForm'; 14 | import { PatientsPage } from './components/patients/patientsPage'; 15 | import { SearchPatient } from './components/patients/searchPatient'; 16 | import { PatientList } from './components/patients/patientList'; 17 | import { PatientPage } from './components/patient/patientPage'; 18 | import { PatientForm } from './components/patient/patientForm'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | App, 23 | Header, 24 | LoginPage, 25 | Banner, 26 | LoginForm, 27 | PatientsPage, 28 | SearchPatient, 29 | PatientList, 30 | PatientPage, 31 | PatientForm 32 | ], 33 | imports: [ 34 | BrowserModule, 35 | RouterModule.forRoot(routes), 36 | FormsModule, 37 | ReactiveFormsModule 38 | ], 39 | bootstrap: [App], 40 | providers: [ 41 | { provide: LocationStrategy, useClass: HashLocationStrategy } 42 | ] 43 | }) 44 | class AppModule { 45 | 46 | } 47 | 48 | platformBrowserDynamic().bootstrapModule(AppModule) 49 | -------------------------------------------------------------------------------- /04 Form Page/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | export class Patient { 2 | id: number; 3 | dni: string; 4 | name: string; 5 | specialty: string; 6 | doctor: string; 7 | date: string; 8 | time: string; 9 | 10 | constructor() { 11 | this.id = 0; 12 | this.dni = ""; 13 | this.name = ""; 14 | this.specialty = ""; 15 | this.doctor = ""; 16 | this.date = ""; 17 | this.time = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /04 Form Page/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | import { PatientPage } from './components/patient/patientPage'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 8 | { path: 'login', component: LoginPage }, 9 | { path: 'patients', component: PatientsPage }, 10 | { path: 'patient/:id', component: PatientPage } 11 | ]; 12 | 13 | export { 14 | routes 15 | } 16 | -------------------------------------------------------------------------------- /04 Form Page/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /04 Form Page/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /04 Form Page/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router", 29 | "bootstrap", 30 | "@angular/forms" 31 | ], 32 | vendorStyles: [ 33 | '../node_modules/bootstrap/dist/css/bootstrap.css' 34 | ] 35 | }, 36 | 37 | output: { 38 | path: path.join(basePath, "dist"), 39 | filename: '[name].js' 40 | }, 41 | 42 | devServer: { 43 | contentBase: './dist', //Content base 44 | inline: true, //Enable watch and live reload 45 | host: 'localhost', 46 | port: 8080 47 | }, 48 | 49 | devtool: 'source-map', 50 | 51 | module: { 52 | loaders: [ 53 | { 54 | test: /\.ts$/, 55 | exclude: /node_modules/, 56 | loader: 'ts' 57 | }, 58 | //Note: Doesn't exclude node_modules to load bootstrap 59 | { 60 | test: /\.css$/, 61 | loader: ExtractTextPlugin.extract('style','css') 62 | }, 63 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 64 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 65 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 66 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 67 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 68 | { 69 | test: /\.png$/, 70 | loader: 'file?limit=0&name=[path][name].[hash].', 71 | exclude: /node_modules/ 72 | } 73 | ] 74 | }, 75 | 76 | plugins: [ 77 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 78 | new ExtractTextPlugin('[name].css'), 79 | new HtmlWebpackPlugin({ 80 | filename: 'index.html', 81 | template: 'index.html' 82 | }), 83 | //Expose jquery used by bootstrap 84 | new webpack.ProvidePlugin({ 85 | $: "jquery", 86 | jQuery: "jquery" 87 | }) 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /05 Form Validation/README.md: -------------------------------------------------------------------------------- 1 | # 05 Form Validation 2 | Let's get started working with validations. 3 | 4 | In this demo will use Angular Validators and create custom validations. 5 | 6 | We will start from sample **04 Form Page**. 7 | 8 | Summary steps: 9 | - Create custom DNI validations. 10 | - Create custom DNI Validator. 11 | - Create Patient Form Validator. 12 | - Refactor Patient Form to show error messages. 13 | 14 | ## Required dependencies 15 | - *04 Form Page* dependencies 16 | 17 | # DNI Validations 18 | 19 | We're going to create two custom validations: 20 | - Valid DNI format. 21 | - Valid DNI 22 | 23 | ## Definition: 24 | ### src/validations/dniValidation.ts 25 | 26 | ``` 27 | const hasValidFormat = (value: string): boolean => { 28 | const dniRegex = /^[0-9]{8}[a-z, A-Z]$/; 29 | 30 | return dniRegex.test(value); 31 | } 32 | 33 | const isValidDNI = (value: string): boolean => { 34 | let dniNumber: number = parseInt(value); 35 | let validLetter: string = getValidLetterByDNINumber(dniNumber); 36 | let currentLetter = value.charAt(8).toUpperCase(); 37 | 38 | return currentLetter === validLetter; 39 | }; 40 | 41 | let getValidLetterByDNINumber = (dniNumber: number) : string => { 42 | let letterIndex = dniNumber % 23; 43 | let validLetters = 'TRWAGMYFPDXBNJZSQVHLCKET'; 44 | 45 | return validLetters.charAt(letterIndex) 46 | }; 47 | 48 | export { 49 | hasValidFormat, 50 | isValidDNI 51 | } 52 | ``` 53 | 54 | # DNI Validators 55 | 56 | Angular 2 has default Validators like require, max-length, min-length, etc. 57 | But we want to create a custom Validator associated to a FormControl for DNI 58 | 59 | ## Definition: 60 | ### src/validators/dniValidator.ts 61 | 62 | ``` 63 | import { FormControl } from '@angular/forms'; 64 | import { hasValidFormat, isValidDNI } from '../validations/dniValidation'; 65 | 66 | class DNIValidator { 67 | hasValidFormat(dniFormControl: FormControl): any { 68 | return hasValidFormat(dniFormControl.value) ? 69 | null : 70 | { 71 | "dni.hasValidFormat": { 72 | valid: false 73 | } 74 | } 75 | } 76 | 77 | isValid(dniFormControl: FormControl) { 78 | return isValidDNI(dniFormControl.value) ? 79 | null : 80 | { 81 | "dni.isValid": { 82 | valid: false 83 | } 84 | } 85 | } 86 | } 87 | 88 | const dniValidator = new DNIValidator(); 89 | 90 | export { 91 | dniValidator 92 | } 93 | ``` 94 | 95 | 96 | 97 | # Patient Form Validator 98 | 99 | We're going to set all validations for each patient form fields. 100 | Here we are using Angular validators and custom DNI validators. 101 | 102 | ## Definition: 103 | ### src/Validators/patientFormValidator.ts 104 | 105 | ``` 106 | import { FormGroup, Validators } from '@angular/forms'; 107 | import { dniValidator } from './dniValidator'; 108 | 109 | class PatientFormValidator { 110 | constructor(private patientForm: FormGroup) { 111 | 112 | } 113 | 114 | setValidators() { 115 | this.setDNIValidators(); 116 | this.setNameValidators(); 117 | this.setDateValidators(); 118 | this.setTimeValidators(); 119 | this.setSpecialtyValidators(); 120 | this.setDoctorValidators(); 121 | } 122 | 123 | private setDNIValidators() { 124 | let dniFormControl = this.patientForm.controls['dni']; 125 | 126 | dniFormControl.setValidators([ 127 | Validators.required, 128 | dniValidator.hasValidFormat, 129 | dniValidator.isValid 130 | ]); 131 | } 132 | 133 | private setNameValidators() { 134 | let nameFormControl = this.patientForm.controls['name']; 135 | 136 | nameFormControl.setValidators([ 137 | Validators.required 138 | ]); 139 | } 140 | 141 | private setDateValidators() { 142 | let dateFormControl = this.patientForm.controls['date']; 143 | 144 | dateFormControl.setValidators([ 145 | Validators.required 146 | ]); 147 | } 148 | 149 | private setTimeValidators() { 150 | let timeFormControl = this.patientForm.controls['time']; 151 | 152 | timeFormControl.setValidators([ 153 | Validators.required 154 | ]); 155 | } 156 | 157 | private setSpecialtyValidators() { 158 | let specialtyFormControl = this.patientForm.controls['specialty']; 159 | 160 | specialtyFormControl.setValidators([ 161 | Validators.required 162 | ]); 163 | } 164 | 165 | private setDoctorValidators() { 166 | let doctorFormControl = this.patientForm.controls['doctor']; 167 | 168 | doctorFormControl.setValidators([ 169 | Validators.required 170 | ]); 171 | } 172 | } 173 | 174 | export { 175 | PatientFormValidator 176 | } 177 | ``` 178 | 179 | # Patient Form 180 | 181 | We can access to dirty, invalid, hasError... FormControl and/or FormGroup 182 | properties and NgIf directive to show or hide error validations and disable save 183 | button. 184 | 185 | ## Definition: 186 | ### src/components/patient/patientForm.ts 187 | 188 | ``` 189 | import { PatientFormValidator} from '../../validations/patientFormValidator'; 190 | 191 | ... 192 | 193 |
194 |
195 |
196 | 197 |
198 |
199 |
200 |
201 | 202 | 204 | Campo requerido. 205 | DNI inválido. 206 | Formato incorrecto. 207 |
208 |
209 | 210 | 212 | Campo requerido. 213 |
214 |
215 |
216 |
217 | 218 |
219 |
220 | 221 |
222 |
223 | 224 | 226 | Campo requerido. 227 |
228 |
229 | 230 | 232 | Campo requerido. 233 |
234 | 235 |
236 |
237 | 238 | 243 |
244 |
245 | 246 | 251 |
252 |
253 |
254 | 255 |
256 |
257 |
258 | 262 |
263 |
264 |
265 |
266 | 271 |
272 |
273 |
274 |
275 | ... 276 | 277 | constructor(formBuilder: FormBuilder) { 278 | this.patient = new Patient(); 279 | this.patientForm = formBuilder.group(this.patient); 280 | 281 | let validator = new PatientFormValidator(this.patientForm); 282 | validator.setValidators(); 283 | } 284 | ... 285 | ``` 286 | -------------------------------------------------------------------------------- /05 Form Validation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/forms": "^2.0.1", 40 | "@angular/platform-browser": "^2.0.1", 41 | "@angular/platform-browser-dynamic": "^2.0.1", 42 | "@angular/router": "^3.0.1", 43 | "bootstrap": "^3.3.7", 44 | "core-js": "^2.4.1", 45 | "jquery": "^3.1.0", 46 | "reflect-metadata": "^0.1.8", 47 | "rxjs": "^5.0.0-beta.12", 48 | "zone.js": "^0.6.21" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /05 Form Validation/src/api/mockData.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | const patientsMockData: Array = [ 3 | { id: 1, dni: "1234567A", name: "John Doe", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "08:30" }, 4 | { id: 2, dni: "5067254B", name: "Anna S. Batiste", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "09:00" }, 5 | { id: 3, dni: "1902045C", name: "Octavia L. Hilton", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "09:30" }, 6 | { id: 4, dni: "1880514D", name: "Tony M. Herrera", specialty: "Ophthalmology", doctor: "Ruthie A. Nemeth", date: "2019-09-19", time: "10:00" }, 7 | { id: 5, dni: "6810774E", name: "Robert J. Macias", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "10:30" } 8 | ]; 9 | 10 | const specialtiesMockData: Array = [ 11 | "Surgery", 12 | "Traumatology", 13 | "Ophthalmology" 14 | ]; 15 | 16 | const doctorsMockData: Array = [ 17 | "Karl J. Linville", 18 | "Gladys C. Horton", 19 | "Ruthie A. Nemeth" 20 | ]; 21 | 22 | export { 23 | patientsMockData, 24 | specialtiesMockData, 25 | doctorsMockData 26 | } 27 | -------------------------------------------------------------------------------- /05 Form Validation/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Promise } from 'core-js/es6'; 2 | import { Patient } from '../model/patient'; 3 | import { patientsMockData, specialtiesMockData, doctorsMockData } from './mockData'; 4 | 5 | class PatientAPI { 6 | getAllPatientsAsync(): Promise> { 7 | let patientsPromise = new Promise((resolve, reject) => { 8 | resolve(patientsMockData); 9 | }); 10 | 11 | return patientsPromise; 12 | }; 13 | 14 | getAllSpecialtiesAsync(): Promise> { 15 | let specialtiesPromise = new Promise((resolve, reject) => { 16 | resolve(specialtiesMockData); 17 | }); 18 | 19 | return specialtiesPromise; 20 | }; 21 | 22 | getAllDoctorsAsync(): Promise> { 23 | let doctorsPromise = new Promise((resolve, reject) => { 24 | resolve(doctorsMockData); 25 | }); 26 | 27 | return doctorsPromise; 28 | }; 29 | 30 | getPatientByIdAsync(id: number): Promise { 31 | let patientPromise = new Promise((resolve, reject) => { 32 | let patient = patientsMockData.find((patient: Patient) => { 33 | return patient.id === id; 34 | }); 35 | 36 | resolve(patient); 37 | }); 38 | 39 | return patientPromise; 40 | }; 41 | 42 | savePatient(currentPatient: Patient): void { 43 | let patient = patientsMockData.find((patient: Patient) => { 44 | return patient.id === currentPatient.id; 45 | }); 46 | 47 | if (patient) { 48 | let patientIndex = patientsMockData.indexOf(patient); 49 | patientsMockData.splice(patientIndex, 1, currentPatient); 50 | } else { 51 | let lastId = patientsMockData[patientsMockData.length -1].id; 52 | currentPatient.id = lastId + 1; 53 | 54 | patientsMockData.push(currentPatient); 55 | } 56 | 57 | patient = currentPatient; 58 | } 59 | } 60 | 61 | export { 62 | PatientAPI 63 | } 64 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {PatientAPI} from '../api/patientAPI'; 3 | 4 | @Component( 5 | { 6 | selector: 'app', 7 | template: ` 8 |
9 |
10 | 11 | 12 |
13 | `, 14 | providers: [PatientAPI] 15 | } 16 | ) 17 | class App { 18 | 19 | } 20 | 21 | export { 22 | App 23 | } 24 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/login/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/login/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | ` 30 | }) 31 | class LoginForm { 32 | 33 | } 34 | 35 | export { 36 | LoginForm 37 | } 38 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patient/patientForm.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | import { PatientFormValidator } from '../../validators/patientFormValidator'; 5 | 6 | @Component({ 7 | selector: 'patient-form', 8 | template: ` 9 |
10 |
11 |
12 |

Editar Cita - Centro de Día

13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | 27 | Campo requerido. 28 | DNI inválido. 29 | Formato incorrecto. 30 |
31 |
32 | 33 | 35 | Campo requerido. 36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 | 49 | Campo requerido. 50 |
51 |
52 | 53 | 55 | Campo requerido. 56 |
57 | 58 |
59 |
60 | 61 | 66 |
67 |
68 | 69 | 74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 | 85 |
86 |
87 |
88 |
89 | 94 |
95 |
96 |
97 |
98 |
99 | ` 100 | }) 101 | class PatientForm implements OnChanges { 102 | @Input() specialties: Array; 103 | @Input() doctors: Array; 104 | @Input() patient: Patient; 105 | @Input() savePatient: (event: any, patient: Patient) => void; 106 | @Input() navigateBack: (event: any) => void; 107 | patientForm: FormGroup; 108 | 109 | constructor(formBuilder: FormBuilder) { 110 | this.patient = new Patient(); 111 | this.patientForm = formBuilder.group(this.patient); 112 | 113 | let validator = new PatientFormValidator(this.patientForm); 114 | validator.setValidators(); 115 | } 116 | 117 | ngOnChanges(changes) { 118 | let patient: SimpleChange = changes['patient']; 119 | 120 | if(patient && patient.currentValue) { 121 | this.patientForm.setValue(patient.currentValue); 122 | } 123 | } 124 | } 125 | 126 | export { 127 | PatientForm 128 | } 129 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patient/patientPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | import { Patient } from '../../model/patient'; 5 | 6 | @Component({ 7 | selector: 'patient-page', 8 | template: ` 9 |
10 | 15 | 16 |
17 | ` 18 | }) 19 | class PatientPage { 20 | specialties: Array; 21 | doctors: Array; 22 | patientId: number; 23 | patient: Patient; 24 | 25 | constructor(private route: ActivatedRoute, private router: Router, private patientAPI : PatientAPI) { 26 | this.loadPatientId(); 27 | this.loadPatient(); 28 | this.loadRelatedCollections(); 29 | } 30 | 31 | private loadPatientId() { 32 | this.route.params.subscribe(params => { 33 | this.patientId = parseInt(params['id']); 34 | }); 35 | } 36 | 37 | private loadPatient() { 38 | if (this.patientId > 0) { 39 | this.patientAPI.getPatientByIdAsync(this.patientId) 40 | .then((patient: Patient) => { 41 | this.patient = patient; 42 | }); 43 | } 44 | } 45 | 46 | private loadRelatedCollections() { 47 | Promise.all([ 48 | this.patientAPI.getAllSpecialtiesAsync(), 49 | this.patientAPI.getAllDoctorsAsync() 50 | ]).then((data) => { 51 | this.specialties = data[0]; 52 | this.doctors = data[1]; 53 | }); 54 | } 55 | 56 | savePatient(event: any, patient: Patient){ 57 | this.patientAPI.savePatient(patient); 58 | this.navigateBack(event); 59 | } 60 | 61 | navigateBack(event: any) { 62 | event.preventDefault(); 63 | this.router.navigate(['/patients']); 64 | } 65 | } 66 | 67 | export { 68 | PatientPage 69 | } 70 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/patientList.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { Promise } from 'core-js/es6'; 4 | import { PatientAPI } from '../../api/patientAPI'; 5 | 6 | @Component({ 7 | selector: 'patient-list', 8 | template: ` 9 |
10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 47 | 48 | 49 |
PacienteEspecialidad
{{p.name}} 34 | {{p.specialty}} 35 | 38 |
50 |
51 |
52 | ` 53 | }) 54 | class PatientList { 55 | patients: Array; 56 | 57 | constructor(patientAPI : PatientAPI) { 58 | patientAPI.getAllPatientsAsync().then((patients: Array) => { 59 | this.patients = patients; 60 | }); 61 | } 62 | } 63 | 64 | export { 65 | PatientList 66 | } 67 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |
8 | 9 | 10 |
11 |
12 | ` 13 | }) 14 | class PatientsPage { 15 | 16 | } 17 | 18 | export { 19 | PatientsPage 20 | } 21 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Promise } from 'core-js/es6'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | 5 | @Component({ 6 | selector: 'search-patient', 7 | template: ` 8 |
9 |
10 |
11 | 12 |

Buscar paciente

13 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | ` 45 | }) 46 | class SearchPatient { 47 | specialties: Array; 48 | 49 | constructor(patientAPI : PatientAPI) { 50 | patientAPI.getAllSpecialtiesAsync().then((specialties: Array) => { 51 | this.specialties = specialties; 52 | }); 53 | } 54 | 55 | searchPatient(event){ 56 | event.preventDefault(); 57 | } 58 | } 59 | 60 | export { 61 | SearchPatient 62 | } 63 | -------------------------------------------------------------------------------- /05 Form Validation/src/css/site.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | margin-top: -20px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | 9 | .table { 10 | margin-top: 15px; 11 | } 12 | 13 | .table thead tr { 14 | background-color: white; 15 | } 16 | 17 | .table-striped > tbody > tr:nth-of-type(even) { 18 | background-color: white; 19 | } 20 | 21 | .table-striped > tbody > tr:nth-of-type(odd) { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .collapse { 26 | display: block; 27 | } 28 | 29 | /*>= md resolution*/ 30 | @media (min-width: 992px) { 31 | .collapse-toggle { 32 | display: none; 33 | } 34 | } 35 | 36 | /*md resolution*/ 37 | @media (min-width: 992px) and (max-width: 1200px) { 38 | .row-md { 39 | clear: both; 40 | } 41 | } 42 | 43 | /*<= md resolution*/ 44 | @media (max-width: 992px) { 45 | .collapse { 46 | display: none; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /05 Form Validation/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/05 Form Validation/src/images/health.png -------------------------------------------------------------------------------- /05 Form Validation/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /05 Form Validation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | 9 | import { App } from './components/app'; 10 | import { Header } from './components/common/header'; 11 | import { LoginPage } from './components/login/loginPage'; 12 | import { Banner } from './components/login/banner'; 13 | import { LoginForm } from './components/login/loginForm'; 14 | import { PatientsPage } from './components/patients/patientsPage'; 15 | import { SearchPatient } from './components/patients/searchPatient'; 16 | import { PatientList } from './components/patients/patientList'; 17 | import { PatientPage } from './components/patient/patientPage'; 18 | import { PatientForm } from './components/patient/patientForm'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | App, 23 | Header, 24 | LoginPage, 25 | Banner, 26 | LoginForm, 27 | PatientsPage, 28 | SearchPatient, 29 | PatientList, 30 | PatientPage, 31 | PatientForm 32 | ], 33 | imports: [ 34 | BrowserModule, 35 | RouterModule.forRoot(routes), 36 | FormsModule, 37 | ReactiveFormsModule 38 | ], 39 | bootstrap: [App], 40 | providers: [ 41 | { provide: LocationStrategy, useClass: HashLocationStrategy } 42 | ] 43 | }) 44 | class AppModule { 45 | 46 | } 47 | 48 | platformBrowserDynamic().bootstrapModule(AppModule) 49 | -------------------------------------------------------------------------------- /05 Form Validation/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | export class Patient { 2 | id: number; 3 | dni: string; 4 | name: string; 5 | specialty: string; 6 | doctor: string; 7 | date: string; 8 | time: string; 9 | 10 | constructor() { 11 | this.id = 0; 12 | this.dni = ""; 13 | this.name = ""; 14 | this.specialty = ""; 15 | this.doctor = ""; 16 | this.date = ""; 17 | this.time = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /05 Form Validation/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | import { PatientPage } from './components/patient/patientPage'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 8 | { path: 'login', component: LoginPage }, 9 | { path: 'patients', component: PatientsPage }, 10 | { path: 'patient/:id', component: PatientPage } 11 | ]; 12 | 13 | export { 14 | routes 15 | } 16 | -------------------------------------------------------------------------------- /05 Form Validation/src/validations/dniValidation.ts: -------------------------------------------------------------------------------- 1 | let hasValidFormat = (value: string): boolean => { 2 | const dniRegex = /^[0-9]{8}[a-z, A-Z]$/; 3 | 4 | return dniRegex.test(value); 5 | } 6 | 7 | let isValidDNI = (value: string): boolean => { 8 | let dniNumber: number = parseInt(value); 9 | let validLetter: string = getValidLetterByDNINumber(dniNumber); 10 | let currentLetter = value.charAt(8).toUpperCase(); 11 | 12 | return currentLetter === validLetter; 13 | }; 14 | 15 | let getValidLetterByDNINumber = (dniNumber: number) : string => { 16 | let letterIndex = dniNumber % 23; 17 | let validLetters = 'TRWAGMYFPDXBNJZSQVHLCKET'; 18 | 19 | return validLetters.charAt(letterIndex) 20 | }; 21 | 22 | export { 23 | hasValidFormat, 24 | isValidDNI 25 | } 26 | -------------------------------------------------------------------------------- /05 Form Validation/src/validators/dniValidator.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from '@angular/forms'; 2 | import { hasValidFormat, isValidDNI } from '../validations/dniValidation'; 3 | 4 | class DNIValidator { 5 | hasValidFormat(dniFormControl: FormControl): any { 6 | return hasValidFormat(dniFormControl.value) ? 7 | null : 8 | { 9 | "dni.hasValidFormat": { 10 | valid: false 11 | } 12 | } 13 | } 14 | 15 | isValid(dniFormControl: FormControl) { 16 | return isValidDNI(dniFormControl.value) ? 17 | null : 18 | { 19 | "dni.isValid": { 20 | valid: false 21 | } 22 | } 23 | } 24 | } 25 | 26 | const dniValidator = new DNIValidator(); 27 | 28 | export { 29 | dniValidator 30 | } 31 | -------------------------------------------------------------------------------- /05 Form Validation/src/validators/patientFormValidator.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup, Validators } from '@angular/forms'; 2 | import { dniValidator } from './dniValidator'; 3 | 4 | class PatientFormValidator { 5 | constructor(private patientForm: FormGroup) { 6 | 7 | } 8 | 9 | setValidators() { 10 | this.setDNIValidators(); 11 | this.setNameValidators(); 12 | this.setDateValidators(); 13 | this.setTimeValidators(); 14 | this.setSpecialtyValidators(); 15 | this.setDoctorValidators(); 16 | } 17 | 18 | private setDNIValidators() { 19 | let dniFormControl = this.patientForm.controls['dni']; 20 | 21 | dniFormControl.setValidators([ 22 | Validators.required, 23 | dniValidator.hasValidFormat, 24 | dniValidator.isValid 25 | ]); 26 | } 27 | 28 | private setNameValidators() { 29 | let nameFormControl = this.patientForm.controls['name']; 30 | 31 | nameFormControl.setValidators([ 32 | Validators.required 33 | ]); 34 | } 35 | 36 | private setDateValidators() { 37 | let dateFormControl = this.patientForm.controls['date']; 38 | 39 | dateFormControl.setValidators([ 40 | Validators.required 41 | ]); 42 | } 43 | 44 | private setTimeValidators() { 45 | let timeFormControl = this.patientForm.controls['time']; 46 | 47 | timeFormControl.setValidators([ 48 | Validators.required 49 | ]); 50 | } 51 | 52 | private setSpecialtyValidators() { 53 | let specialtyFormControl = this.patientForm.controls['specialty']; 54 | 55 | specialtyFormControl.setValidators([ 56 | Validators.required 57 | ]); 58 | } 59 | 60 | private setDoctorValidators() { 61 | let doctorFormControl = this.patientForm.controls['doctor']; 62 | 63 | doctorFormControl.setValidators([ 64 | Validators.required 65 | ]); 66 | } 67 | } 68 | 69 | export { 70 | PatientFormValidator 71 | } 72 | -------------------------------------------------------------------------------- /05 Form Validation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /05 Form Validation/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /05 Form Validation/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router", 29 | "bootstrap", 30 | "@angular/forms" 31 | ], 32 | vendorStyles: [ 33 | '../node_modules/bootstrap/dist/css/bootstrap.css' 34 | ] 35 | }, 36 | 37 | output: { 38 | path: path.join(basePath, "dist"), 39 | filename: '[name].js' 40 | }, 41 | 42 | devServer: { 43 | contentBase: './dist', //Content base 44 | inline: true, //Enable watch and live reload 45 | host: 'localhost', 46 | port: 8080 47 | }, 48 | 49 | devtool: 'source-map', 50 | 51 | module: { 52 | loaders: [ 53 | { 54 | test: /\.ts$/, 55 | exclude: /node_modules/, 56 | loader: 'ts' 57 | }, 58 | //Note: Doesn't exclude node_modules to load bootstrap 59 | { 60 | test: /\.css$/, 61 | loader: ExtractTextPlugin.extract('style','css') 62 | }, 63 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 64 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 65 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 66 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 67 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 68 | { 69 | test: /\.png$/, 70 | loader: 'file?limit=0&name=[path][name].[hash].', 71 | exclude: /node_modules/ 72 | } 73 | ] 74 | }, 75 | 76 | plugins: [ 77 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 78 | new ExtractTextPlugin('[name].css'), 79 | new HtmlWebpackPlugin({ 80 | filename: 'index.html', 81 | template: 'index.html' 82 | }), 83 | //Expose jquery used by bootstrap 84 | new webpack.ProvidePlugin({ 85 | $: "jquery", 86 | jQuery: "jquery" 87 | }) 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /06 Refactor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/forms": "^2.0.1", 40 | "@angular/platform-browser": "^2.0.1", 41 | "@angular/platform-browser-dynamic": "^2.0.1", 42 | "@angular/router": "^3.0.1", 43 | "bootstrap": "^3.3.7", 44 | "core-js": "^2.4.1", 45 | "jquery": "^3.1.0", 46 | "reflect-metadata": "^0.1.8", 47 | "rxjs": "^5.0.0-beta.12", 48 | "zone.js": "^0.6.21" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /06 Refactor/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import {PatientAPI} from './patientAPI'; 3 | 4 | @NgModule({ 5 | declarations: [ 6 | ], 7 | imports: [ 8 | ], 9 | providers: [ 10 | PatientAPI 11 | ] 12 | }) 13 | export class APIModule { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /06 Refactor/src/api/mockData.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | const patientsMockData: Array = [ 3 | { id: 1, dni: "1234567A", name: "John Doe", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "08:30" }, 4 | { id: 2, dni: "5067254B", name: "Anna S. Batiste", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "09:00" }, 5 | { id: 3, dni: "1902045C", name: "Octavia L. Hilton", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "09:30" }, 6 | { id: 4, dni: "1880514D", name: "Tony M. Herrera", specialty: "Ophthalmology", doctor: "Ruthie A. Nemeth", date: "2019-09-19", time: "10:00" }, 7 | { id: 5, dni: "6810774E", name: "Robert J. Macias", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "10:30" } 8 | ]; 9 | 10 | const specialtiesMockData: Array = [ 11 | "Surgery", 12 | "Traumatology", 13 | "Ophthalmology" 14 | ]; 15 | 16 | const doctorsMockData: Array = [ 17 | "Karl J. Linville", 18 | "Gladys C. Horton", 19 | "Ruthie A. Nemeth" 20 | ]; 21 | 22 | export { 23 | patientsMockData, 24 | specialtiesMockData, 25 | doctorsMockData 26 | } 27 | -------------------------------------------------------------------------------- /06 Refactor/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Promise } from 'core-js/es6'; 2 | import { Patient } from '../model/patient'; 3 | import { patientsMockData, specialtiesMockData, doctorsMockData } from './mockData'; 4 | 5 | export class PatientAPI { 6 | getAllPatientsAsync(): Promise> { 7 | let patientsPromise = new Promise((resolve, reject) => { 8 | resolve(patientsMockData); 9 | }); 10 | 11 | return patientsPromise; 12 | }; 13 | 14 | getAllSpecialtiesAsync(): Promise> { 15 | let specialtiesPromise = new Promise((resolve, reject) => { 16 | resolve(specialtiesMockData); 17 | }); 18 | 19 | return specialtiesPromise; 20 | }; 21 | 22 | getAllDoctorsAsync(): Promise> { 23 | let doctorsPromise = new Promise((resolve, reject) => { 24 | resolve(doctorsMockData); 25 | }); 26 | 27 | return doctorsPromise; 28 | }; 29 | 30 | getPatientByIdAsync(id: number): Promise { 31 | let patientPromise = new Promise((resolve, reject) => { 32 | let patient = patientsMockData.find((patient: Patient) => { 33 | return patient.id === id; 34 | }); 35 | 36 | resolve(patient); 37 | }); 38 | 39 | return patientPromise; 40 | }; 41 | 42 | savePatient(currentPatient: Patient): void { 43 | let patient = patientsMockData.find((patient: Patient) => { 44 | return patient.id === currentPatient.id; 45 | }); 46 | 47 | if (patient) { 48 | let patientIndex = patientsMockData.indexOf(patient); 49 | patientsMockData.splice(patientIndex, 1, currentPatient); 50 | } else { 51 | let lastId = patientsMockData[patientsMockData.length -1].id; 52 | currentPatient.id = lastId + 1; 53 | 54 | patientsMockData.push(currentPatient); 55 | } 56 | 57 | patient = currentPatient; 58 | } 59 | } 60 | 61 | const patientAPI = new PatientAPI(); 62 | -------------------------------------------------------------------------------- /06 Refactor/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'app', 6 | template: ` 7 |
8 |
9 | 10 | 11 |
12 | ` 13 | } 14 | ) 15 | class App { 16 | 17 | } 18 | 19 | export { 20 | App 21 | } 22 | -------------------------------------------------------------------------------- /06 Refactor/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/components/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/components/loginButton.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'login-button', 6 | 7 | template: ` 8 |
9 |
10 | 11 |
12 |
13 | ` 14 | }) 15 | class LoginButton { 16 | @Input() navigationLink: string; 17 | } 18 | 19 | export { 20 | LoginButton 21 | } 22 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/components/loginField.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'login-field', 6 | 7 | template: ` 8 |
9 | 10 |
11 | 12 |
13 |
14 | ` 15 | }) 16 | class LoginField { 17 | @Input() caption: string; 18 | @Input() fieldId : string; 19 | } 20 | 21 | export { 22 | LoginField 23 | } 24 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/components/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 |
15 | ` 16 | }) 17 | class LoginForm { 18 | navigationLink = "/patients"; 19 | } 20 | 21 | export { 22 | LoginForm 23 | } 24 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import {LoginPage} from './loginPage'; 9 | import {Banner} from './components/banner'; 10 | import {LoginForm} from './components/loginForm'; 11 | import {LoginField} from './components/loginField' 12 | import {LoginButton} from './components/loginButton'; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | LoginPage, 17 | Banner, 18 | LoginForm, 19 | LoginField, 20 | LoginButton 21 | ], 22 | imports: [ 23 | BrowserModule, 24 | RouterModule, 25 | FormsModule, 26 | ReactiveFormsModule 27 | ], 28 | providers: [ 29 | { provide: LocationStrategy, useClass: HashLocationStrategy } 30 | ] 31 | }) 32 | export class LoginModule { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /06 Refactor/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patient/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import {APIModule} from '../../api/'; 9 | 10 | import {PatientForm} from './patientForm'; 11 | import {PatientPage} from './patientPage'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | PatientForm, 16 | PatientPage 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | RouterModule, 21 | FormsModule, 22 | ReactiveFormsModule, 23 | APIModule 24 | ], 25 | providers: [ 26 | { provide: LocationStrategy, useClass: HashLocationStrategy } 27 | ] 28 | }) 29 | export class PatientModule { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patient/patientForm.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | import { PatientFormValidator } from '../../validators/patientFormValidator'; 5 | 6 | @Component({ 7 | selector: 'patient-form', 8 | template: ` 9 |
10 |
11 |
12 |

Editar Cita - Centro de Día

13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | 27 | Campo requerido. 28 | DNI inválido. 29 | Formato incorrecto. 30 |
31 |
32 | 33 | 35 | Campo requerido. 36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 | 49 | Campo requerido. 50 |
51 |
52 | 53 | 55 | Campo requerido. 56 |
57 | 58 |
59 |
60 | 61 | 66 |
67 |
68 | 69 | 74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 | 85 |
86 |
87 |
88 |
89 | 94 |
95 |
96 |
97 |
98 |
99 | ` 100 | }) 101 | class PatientForm implements OnChanges { 102 | @Input() specialties: Array; 103 | @Input() doctors: Array; 104 | @Input() patient: Patient; 105 | @Input() savePatient: (event: any, patient: Patient) => void; 106 | @Input() navigateBack: (event: any) => void; 107 | patientForm: FormGroup; 108 | 109 | constructor(formBuilder: FormBuilder) { 110 | this.patient = new Patient(); 111 | this.patientForm = formBuilder.group(this.patient); 112 | 113 | let validator = new PatientFormValidator(this.patientForm); 114 | validator.setValidators(); 115 | } 116 | 117 | ngOnChanges(changes) { 118 | let patient: SimpleChange = changes['patient']; 119 | 120 | if(patient && patient.currentValue) { 121 | this.patientForm.setValue(patient.currentValue); 122 | } 123 | } 124 | } 125 | 126 | export { 127 | PatientForm 128 | } 129 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patient/patientPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | import { Patient } from '../../model/patient'; 5 | 6 | @Component({ 7 | selector: 'patient-page', 8 | template: ` 9 |
10 | 15 | 16 |
17 | ` 18 | }) 19 | class PatientPage { 20 | specialties: Array; 21 | doctors: Array; 22 | patientId: number; 23 | patient: Patient; 24 | 25 | constructor(private route: ActivatedRoute, private router: Router, private patientAPI : PatientAPI) { 26 | this.loadPatientId(); 27 | this.loadPatient(); 28 | this.loadRelatedCollections(); 29 | } 30 | 31 | private loadPatientId() { 32 | this.route.params.subscribe(params => { 33 | this.patientId = parseInt(params['id']); 34 | }); 35 | } 36 | 37 | private loadPatient() { 38 | if (this.patientId > 0) { 39 | this.patientAPI.getPatientByIdAsync(this.patientId) 40 | .then((patient: Patient) => { 41 | this.patient = patient; 42 | }); 43 | } 44 | } 45 | 46 | private loadRelatedCollections() { 47 | Promise.all([ 48 | this.patientAPI.getAllSpecialtiesAsync(), 49 | this.patientAPI.getAllDoctorsAsync() 50 | ]).then((data) => { 51 | this.specialties = data[0]; 52 | this.doctors = data[1]; 53 | }); 54 | } 55 | 56 | savePatient(event: any, patient: Patient){ 57 | this.patientAPI.savePatient(patient); 58 | this.navigateBack(event); 59 | } 60 | 61 | navigateBack(event: any) { 62 | event.preventDefault(); 63 | this.router.navigate(['/patients']); 64 | } 65 | } 66 | 67 | export { 68 | PatientPage 69 | } 70 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/components/patientButtonAdd.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "patient-button-add", 5 | template: ` 6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 | ` 14 | }) 15 | class PatientButtonAdd { 16 | } 17 | 18 | export { 19 | PatientButtonAdd 20 | } 21 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/components/patientListTable.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { Patient } from "../../../model/patient"; 4 | import { PatientRow } from "./patientRow"; 5 | 6 | @Component({ 7 | selector: "patient-list-table", 8 | template: ` 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
PacienteEspecialidad
25 |
26 | ` 27 | }) 28 | class PatientListTable { 29 | @Input() patients: Array; 30 | } 31 | 32 | export { 33 | PatientListTable 34 | } 35 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/components/patientRow.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { Patient } from "../../../model/patient"; 4 | 5 | @Component({ 6 | selector: "[patient-row]", 7 | template: ` 8 | {{patient.dni}} 9 | {{patient.name}} 10 | 11 | {{patient.specialty}} 12 | 15 | 16 | {{patient.doctor}} 17 | {{patient.date}} 18 | 19 | {{patient.time}} 20 | 22 | 23 | 24 | ` 25 | }) 26 | class PatientRow { 27 | @Input("patient-row") patient: Patient; 28 | } 29 | 30 | export { 31 | PatientRow 32 | } 33 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | 7 | import {APIModule} from '../../api/'; 8 | 9 | import {PatientsPage} from './patientsPage'; 10 | import {PatientList} from './patientList'; 11 | import {SearchPatient} from './searchPatient'; 12 | import {PatientButtonAdd} from "./components/patientButtonAdd"; 13 | import {PatientListTable} from "./components/patientListTable"; 14 | import {PatientRow} from "./components/patientRow"; 15 | 16 | @NgModule({ 17 | declarations: [ 18 | PatientsPage, 19 | PatientList, 20 | SearchPatient, 21 | PatientButtonAdd, 22 | PatientListTable, 23 | PatientRow 24 | ], 25 | imports: [ 26 | BrowserModule, 27 | RouterModule, 28 | APIModule 29 | ], 30 | providers: [ 31 | { provide: LocationStrategy, useClass: HashLocationStrategy } 32 | ] 33 | }) 34 | export class PatientsModule { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/patientList.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Promise } from 'core-js/es6'; 3 | 4 | import { Patient } from '../../model/patient'; 5 | import { PatientAPI } from '../../api/patientAPI'; 6 | import { PatientButtonAdd } from "./components/patientButtonAdd"; 7 | import { PatientListTable } from "./components/patientListTable"; 8 | 9 | @Component({ 10 | selector: 'patient-list', 11 | template: ` 12 |
13 | 14 | 15 |
16 | ` 17 | }) 18 | class PatientList { 19 | patients: Array; 20 | 21 | constructor(patientAPI : PatientAPI) { 22 | patientAPI.getAllPatientsAsync().then((patients: Array) => { 23 | this.patients = patients; 24 | }); 25 | } 26 | } 27 | 28 | export { 29 | PatientList 30 | } 31 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |
8 | 9 | 10 |
11 |
12 | ` 13 | }) 14 | class PatientsPage { 15 | 16 | } 17 | 18 | export { 19 | PatientsPage 20 | } 21 | -------------------------------------------------------------------------------- /06 Refactor/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Promise } from 'core-js/es6'; 3 | import { PatientAPI } from '../../api/patientAPI'; 4 | 5 | @Component({ 6 | selector: 'search-patient', 7 | template: ` 8 |
9 |
10 |
11 | 12 |

Buscar paciente

13 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | ` 45 | }) 46 | class SearchPatient { 47 | specialties: Array; 48 | 49 | constructor(patientAPI : PatientAPI) { 50 | patientAPI.getAllSpecialtiesAsync().then((specialties: Array) => { 51 | this.specialties = specialties; 52 | }); 53 | } 54 | 55 | searchPatient(event){ 56 | event.preventDefault(); 57 | } 58 | } 59 | 60 | export { 61 | SearchPatient 62 | } 63 | -------------------------------------------------------------------------------- /06 Refactor/src/css/site.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | margin-top: -20px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | 9 | .table { 10 | margin-top: 15px; 11 | } 12 | 13 | .table thead tr { 14 | background-color: white; 15 | } 16 | 17 | .table-striped > tbody > tr:nth-of-type(even) { 18 | background-color: white; 19 | } 20 | 21 | .table-striped > tbody > tr:nth-of-type(odd) { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .collapse { 26 | display: block; 27 | } 28 | 29 | /*>= md resolution*/ 30 | @media (min-width: 992px) { 31 | .collapse-toggle { 32 | display: none; 33 | } 34 | } 35 | 36 | /*md resolution*/ 37 | @media (min-width: 992px) and (max-width: 1200px) { 38 | .row-md { 39 | clear: both; 40 | } 41 | } 42 | 43 | /*<= md resolution*/ 44 | @media (max-width: 992px) { 45 | .collapse { 46 | display: none; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /06 Refactor/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/06 Refactor/src/images/health.png -------------------------------------------------------------------------------- /06 Refactor/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /06 Refactor/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | 9 | import { App } from './components/app'; 10 | import { Header } from './components/common/header'; 11 | 12 | import { LoginModule } from './components/login/'; 13 | import { PatientsModule } from './components/patients/'; 14 | import { PatientModule } from './components/patient/'; 15 | 16 | @NgModule({ 17 | declarations: [ 18 | App, 19 | Header, 20 | ], 21 | imports: [ 22 | BrowserModule, 23 | RouterModule.forRoot(routes), 24 | FormsModule, 25 | ReactiveFormsModule, 26 | LoginModule, 27 | PatientsModule, 28 | PatientModule 29 | ], 30 | bootstrap: [App], 31 | providers: [ 32 | { provide: LocationStrategy, useClass: HashLocationStrategy } 33 | ] 34 | }) 35 | class AppModule { 36 | 37 | } 38 | 39 | platformBrowserDynamic().bootstrapModule(AppModule) 40 | -------------------------------------------------------------------------------- /06 Refactor/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | export class Patient { 2 | id: number; 3 | dni: string; 4 | name: string; 5 | specialty: string; 6 | doctor: string; 7 | date: string; 8 | time: string; 9 | 10 | constructor() { 11 | this.id = 0; 12 | this.dni = ""; 13 | this.name = ""; 14 | this.specialty = ""; 15 | this.doctor = ""; 16 | this.date = ""; 17 | this.time = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /06 Refactor/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | import { PatientPage } from './components/patient/patientPage'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 8 | { path: 'login', component: LoginPage }, 9 | { path: 'patients', component: PatientsPage }, 10 | { path: 'patient/:id', component: PatientPage } 11 | ]; 12 | 13 | export { 14 | routes 15 | } 16 | -------------------------------------------------------------------------------- /06 Refactor/src/validations/dniValidation.ts: -------------------------------------------------------------------------------- 1 | let hasValidFormat = (value: string): boolean => { 2 | const dniRegex = /^[0-9]{8}[a-z, A-Z]$/; 3 | 4 | return dniRegex.test(value); 5 | } 6 | 7 | let isValidDNI = (value: string): boolean => { 8 | let dniNumber: number = parseInt(value); 9 | let validLetter: string = getValidLetterByDNINumber(dniNumber); 10 | let currentLetter = value.charAt(8).toUpperCase(); 11 | 12 | return currentLetter === validLetter; 13 | }; 14 | 15 | let getValidLetterByDNINumber = (dniNumber: number) : string => { 16 | let letterIndex = dniNumber % 23; 17 | let validLetters = 'TRWAGMYFPDXBNJZSQVHLCKET'; 18 | 19 | return validLetters.charAt(letterIndex) 20 | }; 21 | 22 | export { 23 | hasValidFormat, 24 | isValidDNI 25 | } 26 | -------------------------------------------------------------------------------- /06 Refactor/src/validators/dniValidator.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from '@angular/forms'; 2 | import { hasValidFormat, isValidDNI } from '../validations/dniValidation'; 3 | 4 | class DNIValidator { 5 | hasValidFormat(dniFormControl: FormControl): any { 6 | return hasValidFormat(dniFormControl.value) ? 7 | null : 8 | { 9 | "dni.hasValidFormat": { 10 | valid: false 11 | } 12 | } 13 | } 14 | 15 | isValid(dniFormControl: FormControl) { 16 | return isValidDNI(dniFormControl.value) ? 17 | null : 18 | { 19 | "dni.isValid": { 20 | valid: false 21 | } 22 | } 23 | } 24 | } 25 | 26 | const dniValidator = new DNIValidator(); 27 | 28 | export { 29 | dniValidator 30 | } 31 | -------------------------------------------------------------------------------- /06 Refactor/src/validators/patientFormValidator.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup, Validators } from '@angular/forms'; 2 | import { dniValidator } from './dniValidator'; 3 | 4 | class PatientFormValidator { 5 | constructor(private patientForm: FormGroup) { 6 | 7 | } 8 | 9 | setValidators() { 10 | this.setDNIValidators(); 11 | this.setNameValidators(); 12 | this.setDateValidators(); 13 | this.setTimeValidators(); 14 | this.setSpecialtyValidators(); 15 | this.setDoctorValidators(); 16 | } 17 | 18 | private setDNIValidators() { 19 | let dniFormControl = this.patientForm.controls['dni']; 20 | 21 | dniFormControl.setValidators([ 22 | Validators.required, 23 | dniValidator.hasValidFormat, 24 | dniValidator.isValid 25 | ]); 26 | } 27 | 28 | private setNameValidators() { 29 | let nameFormControl = this.patientForm.controls['name']; 30 | 31 | nameFormControl.setValidators([ 32 | Validators.required 33 | ]); 34 | } 35 | 36 | private setDateValidators() { 37 | let dateFormControl = this.patientForm.controls['date']; 38 | 39 | dateFormControl.setValidators([ 40 | Validators.required 41 | ]); 42 | } 43 | 44 | private setTimeValidators() { 45 | let timeFormControl = this.patientForm.controls['time']; 46 | 47 | timeFormControl.setValidators([ 48 | Validators.required 49 | ]); 50 | } 51 | 52 | private setSpecialtyValidators() { 53 | let specialtyFormControl = this.patientForm.controls['specialty']; 54 | 55 | specialtyFormControl.setValidators([ 56 | Validators.required 57 | ]); 58 | } 59 | 60 | private setDoctorValidators() { 61 | let doctorFormControl = this.patientForm.controls['doctor']; 62 | 63 | doctorFormControl.setValidators([ 64 | Validators.required 65 | ]); 66 | } 67 | } 68 | 69 | export { 70 | PatientFormValidator 71 | } 72 | -------------------------------------------------------------------------------- /06 Refactor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /06 Refactor/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /06 Refactor/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router", 29 | "bootstrap", 30 | "@angular/forms" 31 | ], 32 | vendorStyles: [ 33 | '../node_modules/bootstrap/dist/css/bootstrap.css' 34 | ] 35 | }, 36 | 37 | output: { 38 | path: path.join(basePath, "dist"), 39 | filename: '[name].js' 40 | }, 41 | 42 | devServer: { 43 | contentBase: './dist', //Content base 44 | inline: true, //Enable watch and live reload 45 | host: 'localhost', 46 | port: 8080 47 | }, 48 | 49 | devtool: 'source-map', 50 | 51 | module: { 52 | loaders: [ 53 | { 54 | test: /\.ts$/, 55 | exclude: /node_modules/, 56 | loader: 'ts' 57 | }, 58 | //Note: Doesn't exclude node_modules to load bootstrap 59 | { 60 | test: /\.css$/, 61 | loader: ExtractTextPlugin.extract('style','css') 62 | }, 63 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 64 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 65 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 66 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 67 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 68 | { 69 | test: /\.png$/, 70 | loader: 'file?limit=0&name=[path][name].[hash].', 71 | exclude: /node_modules/ 72 | } 73 | ] 74 | }, 75 | 76 | plugins: [ 77 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 78 | new ExtractTextPlugin('[name].css'), 79 | new HtmlWebpackPlugin({ 80 | filename: 'index.html', 81 | template: 'index.html' 82 | }), 83 | //Expose jquery used by bootstrap 84 | new webpack.ProvidePlugin({ 85 | $: "jquery", 86 | jQuery: "jquery" 87 | }) 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /07 Redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 2 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/angular2-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular2-sample-app/blob/master/README.md", 15 | "keywords": [ 16 | "angular", 17 | "sample", 18 | "app" 19 | ], 20 | "author": "Lemoncode", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "css-loader": "^0.25.0", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "file-loader": "^0.9.0", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^0.8.2", 29 | "typescript": "^1.8.10", 30 | "typings": "^1.3.3", 31 | "url-loader": "^0.5.7", 32 | "webpack": "^1.13.2", 33 | "webpack-dev-server": "^1.15.1" 34 | }, 35 | "dependencies": { 36 | "@angular/common": "^2.0.1", 37 | "@angular/compiler": "^2.0.1", 38 | "@angular/core": "^2.0.1", 39 | "@angular/platform-browser": "^2.0.1", 40 | "@angular/platform-browser-dynamic": "^2.0.1", 41 | "@angular/router": "^3.0.1", 42 | "bootstrap": "^3.3.7", 43 | "core-js": "^2.4.1", 44 | "jquery": "^3.1.0", 45 | "redux": "^3.6.0", 46 | "redux-thunk": "^2.1.0", 47 | "reflect-metadata": "^0.1.8", 48 | "rxjs": "^5.0.0-beta.12", 49 | "zone.js": "^0.6.21" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /07 Redux/src/actions/doctors/assignDoctorsAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | 3 | export const ASSIGN_DOCTORS: string = "ASSIGN_DOCTORS"; 4 | export interface AssignDoctorsAction extends Action { 5 | doctors: Array; 6 | } 7 | 8 | export const assignDoctors: ActionCreator = 9 | (doctors: Array) => ({ 10 | type: ASSIGN_DOCTORS, 11 | doctors 12 | }); 13 | -------------------------------------------------------------------------------- /07 Redux/src/actions/doctors/loadDoctorsAction.ts: -------------------------------------------------------------------------------- 1 | import { patientAPI } from '../../api/patientAPI'; 2 | import { assignDoctors } from './assignDoctorsAction'; 3 | 4 | export const loadDoctors = () => { 5 | return dispatcher => { 6 | patientAPI.getAllDoctorsAsync().then((doctors: Array) => { 7 | dispatcher(assignDoctors(doctors)); 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patient/assignPatientAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | import { Patient } from '../../model/patient'; 3 | 4 | export const ASSIGN_PATIENT: string = "ASSIGN_PATIENT"; 5 | export interface AssignPatientAction extends Action { 6 | patient: Patient; 7 | } 8 | 9 | export const assignPatient: ActionCreator = 10 | (patient: Patient) => ({ 11 | type: ASSIGN_PATIENT, 12 | patient 13 | }); 14 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patient/loadPatientAction.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../../model/patient'; 2 | import { patientAPI } from '../../api/patientAPI'; 3 | import { assignPatient } from './assignPatientAction'; 4 | 5 | export const loadPatientById = (id: number) => { 6 | return dispatcher => { 7 | if (id > 0) { 8 | patientAPI.getPatientByIdAsync(id).then((patient: Patient) => { 9 | dispatcher(assignPatient(patient)); 10 | }); 11 | } else { 12 | const patient = new Patient(); 13 | dispatcher(assignPatient(patient)); 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patient/resetPatientFormAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | 3 | export const RESET_PATIENT_FORM: string = "RESET_PATIENT_FORM"; 4 | 5 | export const resetPatientForm: ActionCreator = () => ({ 6 | type: RESET_PATIENT_FORM 7 | }); 8 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patient/savePatientAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | import { Patient } from '../../model/patient'; 3 | import { PatientFormState } from '../../states/patientFormState'; 4 | import { patientAPI } from '../../api/patientAPI'; 5 | import { patientFormValidator } from '../../validators/patientFormValidator'; 6 | 7 | export const SAVE_PATIENT: string = "SAVE_PATIENT"; 8 | export interface SavePatientAction extends Action { 9 | patientFormState: PatientFormState; 10 | } 11 | export const savePatient: ActionCreator = 12 | (patient: Patient) => { 13 | let patientFormState = patientFormValidator.validatePatient(patient); 14 | 15 | if (patientFormState.isValid) { 16 | patientAPI.savePatient(patient); 17 | patientFormState.isSaveCompleted = true; 18 | } 19 | 20 | return { 21 | type: SAVE_PATIENT, 22 | patientFormState 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patient/updatePatientUIAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | import { FormError } from '../../states/patientFormState'; 3 | import { patientFormValidator } from '../../validators/patientFormValidator'; 4 | 5 | export const PATIENT_UI_INPUT: string = "PATIENT_UI_INPUT"; 6 | export interface PatientUIInputAction extends Action { 7 | fieldName: string; 8 | value: any; 9 | formError: FormError; 10 | } 11 | 12 | export const updatePatientUI: ActionCreator = 13 | (fieldName: string, value: any) => { 14 | let formError: FormError = patientFormValidator.validateField(fieldName, value); 15 | 16 | return { 17 | type: PATIENT_UI_INPUT, 18 | fieldName, 19 | value, 20 | formError 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patients/assignPatientsAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | import { Patient } from '../../model/patient'; 3 | 4 | export const ASSIGN_PATIENTS: string = "ASSIGN_PATIENTS"; 5 | export interface AssignPatientsAction extends Action { 6 | patients: Array; 7 | } 8 | 9 | export const assignPatients: ActionCreator = 10 | (patients: Array) => ({ 11 | type: ASSIGN_PATIENTS, 12 | patients 13 | }); 14 | -------------------------------------------------------------------------------- /07 Redux/src/actions/patients/loadPatientsAction.ts: -------------------------------------------------------------------------------- 1 | import { patientAPI } from '../../api/patientAPI'; 2 | import { Patient } from '../../model/patient'; 3 | import { assignPatients } from './assignPatientsAction'; 4 | 5 | export const loadPatients = () => { 6 | return dispatcher => { 7 | patientAPI.getAllPatientsAsync().then((patients: Array) => { 8 | dispatcher(assignPatients(patients)); 9 | }); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /07 Redux/src/actions/specialties/assignSpecialtiesAction.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionCreator } from 'redux'; 2 | 3 | export const ASSIGN_SPECIALTIES: string = "ASSIGN_SPECIALTIES"; 4 | export interface AssignSpecialtiesAction extends Action { 5 | specialties: Array; 6 | } 7 | 8 | export const assignSpecialties: ActionCreator = 9 | (specialties: Array) => ({ 10 | type: ASSIGN_SPECIALTIES, 11 | specialties 12 | }); 13 | -------------------------------------------------------------------------------- /07 Redux/src/actions/specialties/loadSpecialtiesAction.ts: -------------------------------------------------------------------------------- 1 | import { patientAPI } from '../../api/patientAPI'; 2 | import { assignSpecialties } from './assignSpecialtiesAction'; 3 | 4 | export const loadSpecialties = () => { 5 | return dispatcher => { 6 | patientAPI.getAllSpecialtiesAsync().then((specialties: Array) => { 7 | dispatcher(assignSpecialties(specialties)); 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /07 Redux/src/api/mockData.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | const patientsMockData: Array = [ 3 | { id: 1, dni: "71258192Y", name: "John Doe", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "08:30" }, 4 | { id: 2, dni: "98168530E", name: "Anna S. Batiste", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "09:00" }, 5 | { id: 3, dni: "42955917J", name: "Octavia L. Hilton", specialty: "Traumatology", doctor: "Karl J. Linville", date: "2019-09-19", time: "09:30" }, 6 | { id: 4, dni: "00706785H", name: "Tony M. Herrera", specialty: "Ophthalmology", doctor: "Ruthie A. Nemeth", date: "2019-09-19", time: "10:00" }, 7 | { id: 5, dni: "23754350L", name: "Robert J. Macias", specialty: "Surgery", doctor: "Gladys C. Horton", date: "2019-09-19", time: "10:30" } 8 | ]; 9 | 10 | const specialtiesMockData: Array = [ 11 | "Surgery", 12 | "Traumatology", 13 | "Ophthalmology" 14 | ]; 15 | 16 | const doctorsMockData: Array = [ 17 | "Karl J. Linville", 18 | "Gladys C. Horton", 19 | "Ruthie A. Nemeth" 20 | ]; 21 | 22 | export { 23 | patientsMockData, 24 | specialtiesMockData, 25 | doctorsMockData 26 | } 27 | -------------------------------------------------------------------------------- /07 Redux/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Promise } from 'core-js/es6'; 2 | import { Patient } from '../model/patient'; 3 | import { patientsMockData, specialtiesMockData, doctorsMockData } from './mockData'; 4 | 5 | class PatientAPI { 6 | getAllPatientsAsync(): Promise> { 7 | let patientsPromise = new Promise((resolve, reject) => { 8 | resolve(patientsMockData); 9 | }); 10 | 11 | return patientsPromise; 12 | }; 13 | 14 | getAllSpecialtiesAsync(): Promise> { 15 | let specialtiesPromise = new Promise((resolve, reject) => { 16 | resolve(specialtiesMockData); 17 | }); 18 | 19 | return specialtiesPromise; 20 | }; 21 | 22 | getAllDoctorsAsync(): Promise> { 23 | let doctorsPromise = new Promise((resolve, reject) => { 24 | resolve(doctorsMockData); 25 | }); 26 | 27 | return doctorsPromise; 28 | }; 29 | 30 | getPatientByIdAsync(id: number): Promise { 31 | let patientPromise = new Promise((resolve, reject) => { 32 | let patient = patientsMockData.find((patient: Patient) => { 33 | return patient.id === id; 34 | }); 35 | 36 | resolve(patient); 37 | }); 38 | 39 | return patientPromise; 40 | }; 41 | 42 | savePatient(currentPatient: Patient): void { 43 | let patient = patientsMockData.find((patient: Patient) => { 44 | return patient.id === currentPatient.id; 45 | }); 46 | 47 | if (patient) { 48 | let patientIndex = patientsMockData.indexOf(patient); 49 | patientsMockData.splice(patientIndex, 1, currentPatient); 50 | } else { 51 | let lastId = patientsMockData[patientsMockData.length -1].id; 52 | currentPatient.id = lastId + 1; 53 | 54 | patientsMockData.push(currentPatient); 55 | } 56 | 57 | patient = currentPatient; 58 | } 59 | } 60 | 61 | let patientAPI: PatientAPI = new PatientAPI(); 62 | 63 | export { 64 | patientAPI 65 | } 66 | -------------------------------------------------------------------------------- /07 Redux/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'app', 6 | template: ` 7 |
8 |
9 | 10 | 11 |
12 | ` 13 | } 14 | ) 15 | class App { 16 | 17 | } 18 | 19 | export { 20 | App 21 | } 22 | -------------------------------------------------------------------------------- /07 Redux/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component( 4 | { 5 | selector: 'header', 6 | template: ` 7 |
8 | 15 |
16 | ` 17 | } 18 | ) 19 | class Header { 20 | 21 | } 22 | 23 | export { 24 | Header 25 | } 26 | -------------------------------------------------------------------------------- /07 Redux/src/components/login/banner.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | const imageSrc = require('../../images/health.png'); 3 | 4 | @Component({ 5 | selector: 'banner', 6 | template: ` 7 | 10 | ` 11 | }) 12 | class Banner { 13 | imageSrc: any; 14 | 15 | constructor() { 16 | this.imageSrc = imageSrc; 17 | } 18 | } 19 | 20 | export { 21 | Banner 22 | } 23 | -------------------------------------------------------------------------------- /07 Redux/src/components/login/loginForm.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-form', 5 | template: ` 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | ` 30 | }) 31 | class LoginForm { 32 | 33 | } 34 | 35 | export { 36 | LoginForm 37 | } 38 | -------------------------------------------------------------------------------- /07 Redux/src/components/login/loginPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'login-page', 5 | template: ` 6 |
7 | 8 | 9 |
10 | ` 11 | }) 12 | class LoginPage { 13 | 14 | } 15 | 16 | export { 17 | LoginPage 18 | } 19 | -------------------------------------------------------------------------------- /07 Redux/src/components/patient/patientForm.container.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { Patient } from '../../model/patient'; 4 | import { Store } from 'redux'; 5 | import { AppStore } from '../../store'; 6 | import { AppState } from '../../states/appState'; 7 | import { PatientFormState } from '../../states/patientFormState'; 8 | import { loadSpecialties } from '../../actions/specialties/loadSpecialtiesAction'; 9 | import { loadDoctors } from '../../actions/doctors/loadDoctorsAction'; 10 | import { loadPatientById } from '../../actions/patient/loadPatientAction'; 11 | import { savePatient } from '../../actions/patient/savePatientAction'; 12 | import { updatePatientUI } from '../../actions/patient/updatePatientUIAction'; 13 | import { resetPatientForm } from '../../actions/patient/resetPatientFormAction'; 14 | 15 | @Component({ 16 | selector: 'patient-form-container', 17 | template: ` 18 |
19 | 26 | 27 |
28 | ` 29 | }) 30 | class PatientFormContainer { 31 | specialties: Array; 32 | doctors: Array; 33 | patientForm: PatientFormState; 34 | private patientId: number; 35 | 36 | constructor(private route: ActivatedRoute, private router: Router, 37 | @Inject(AppStore) private store: Store) { 38 | this.loadPatientId(); 39 | 40 | store.subscribe(() => this.updateState()); 41 | store.dispatch(loadSpecialties()); 42 | store.dispatch(loadDoctors()); 43 | store.dispatch(loadPatientById(this.patientId)); 44 | } 45 | 46 | private loadPatientId() { 47 | this.route.params.subscribe(params => { 48 | this.patientId = parseInt(params['id']); 49 | }); 50 | } 51 | 52 | savePatient(patient: Patient) { 53 | this.store.dispatch(savePatient(patient)); 54 | } 55 | 56 | navigateBack(event?: any) { 57 | if (event) { 58 | event.preventDefault(); 59 | } 60 | this.store.dispatch(resetPatientForm()); 61 | this.router.navigate(['/patients']); 62 | } 63 | 64 | updateState() { 65 | let state: AppState = this.store.getState(); 66 | this.specialties = state.specialties; 67 | this.doctors = state.doctors; 68 | this.patientForm = state.patientForm; 69 | 70 | if (state.patientForm.isSaveCompleted) { 71 | this.navigateBack(); 72 | } 73 | } 74 | 75 | updatePatientFormUI(event: any) { 76 | var field = event.target.id; 77 | var value = event.target.value; 78 | 79 | this.store.dispatch(updatePatientUI(field, value)); 80 | } 81 | } 82 | 83 | export { 84 | PatientFormContainer 85 | } 86 | -------------------------------------------------------------------------------- /07 Redux/src/components/patient/patientForm.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { PatientFormState } from '../../states/patientFormState'; 4 | 5 | @Component({ 6 | selector: 'patient-form', 7 | template: ` 8 |
9 |
10 |
11 |

Appointment

12 |
13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 | 27 | {{patientForm.errors.dni.errorMessage}} 28 |
29 |
30 | 31 | 34 | {{patientForm.errors.name.errorMessage}} 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 | 50 | {{patientForm.errors.date.errorMessage}} 51 |
52 |
53 | 54 | 57 | {{patientForm.errors.time.errorMessage}} 58 |
59 |
60 |
61 | 62 | 67 | {{patientForm.errors.specialty.errorMessage}} 68 |
69 |
70 | 71 | 76 | {{patientForm.errors.doctor.errorMessage}} 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 | 88 |
89 |
90 |
91 |
92 | 97 |
98 |
99 |
100 |
101 |
102 | ` 103 | }) 104 | class PatientForm implements OnInit { 105 | @Input() specialties: Array; 106 | @Input() doctors: Array; 107 | @Input() patientForm: PatientFormState; 108 | @Input() onSave: (patient: Patient) => void; 109 | @Input() navigateBack: (event: any) => void; 110 | @Input() onChange: (event: any) => void; 111 | 112 | ngOnInit() { 113 | this.patientForm = new PatientFormState(); 114 | } 115 | } 116 | 117 | export { 118 | PatientForm 119 | } 120 | -------------------------------------------------------------------------------- /07 Redux/src/components/patients/patientList.container.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | import { Store } from 'redux'; 4 | import { AppStore } from '../../store'; 5 | import { AppState } from '../../states/appState'; 6 | import { loadPatients } from '../../actions/patients/loadPatientsAction'; 7 | 8 | @Component({ 9 | selector: 'patient-list-container', 10 | template: ` 11 | 12 | 13 | ` 14 | }) 15 | class PatientListContainer { 16 | patients: Array; 17 | 18 | constructor(@Inject(AppStore) private store: Store) { 19 | store.subscribe(() => this.updateState()); 20 | store.dispatch(loadPatients()); 21 | } 22 | 23 | updateState() { 24 | let state: AppState = this.store.getState(); 25 | this.patients = state.patients; 26 | } 27 | } 28 | 29 | export { 30 | PatientListContainer 31 | } 32 | -------------------------------------------------------------------------------- /07 Redux/src/components/patients/patientList.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Patient } from '../../model/patient'; 3 | 4 | @Component({ 5 | selector: 'patient-list', 6 | template: ` 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 45 | 46 | 47 |
PatientSpecialty
{{p.name}} 32 | {{p.specialty}} 33 | 36 |
48 |
49 |
50 | ` 51 | }) 52 | class PatientList { 53 | @Input() patients: Array; 54 | } 55 | 56 | export { 57 | PatientList 58 | } 59 | -------------------------------------------------------------------------------- /07 Redux/src/components/patients/patientsPage.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'patients-page', 5 | template: ` 6 |
7 |
8 | 9 | 10 |
11 |
12 | ` 13 | }) 14 | class PatientsPage { 15 | 16 | } 17 | 18 | export { 19 | PatientsPage 20 | } 21 | -------------------------------------------------------------------------------- /07 Redux/src/components/patients/searchPatient.container.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { Store } from 'redux'; 3 | import { AppStore } from '../../store'; 4 | import { AppState } from '../../states/appState'; 5 | import { loadSpecialties } from '../../actions/specialties/loadSpecialtiesAction'; 6 | 7 | @Component({ 8 | selector: 'search-patient-container', 9 | template: ` 10 |
11 | 13 | 14 |
15 | ` 16 | }) 17 | class SearchPatientContainer { 18 | specialties: Array; 19 | 20 | constructor(@Inject(AppStore) private store: Store) { 21 | store.subscribe(() => this.updateState()); 22 | store.dispatch(loadSpecialties()); 23 | } 24 | 25 | searchPatient(event){ 26 | event.preventDefault(); 27 | } 28 | 29 | updateState() { 30 | let state: AppState = this.store.getState(); 31 | this.specialties = state.specialties; 32 | } 33 | } 34 | 35 | export { 36 | SearchPatientContainer 37 | } 38 | -------------------------------------------------------------------------------- /07 Redux/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'search-patient', 5 | template: ` 6 |
7 |
8 |
9 | 10 |

Search patient

11 | 13 | 14 |
15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 | ` 43 | }) 44 | class SearchPatient { 45 | @Input() specialties: Array; 46 | @Input() searchPatient: (event: any) => void; 47 | } 48 | 49 | export { 50 | SearchPatient 51 | } 52 | -------------------------------------------------------------------------------- /07 Redux/src/css/site.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | margin-top: -20px; 3 | } 4 | 5 | .form-horizontal { 6 | margin-top: 20px; 7 | } 8 | 9 | .table { 10 | margin-top: 15px; 11 | } 12 | 13 | .table thead tr { 14 | background-color: white; 15 | } 16 | 17 | .table-striped > tbody > tr:nth-of-type(even) { 18 | background-color: white; 19 | } 20 | 21 | .table-striped > tbody > tr:nth-of-type(odd) { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .collapse { 26 | display: block; 27 | } 28 | 29 | /*>= md resolution*/ 30 | @media (min-width: 992px) { 31 | .collapse-toggle { 32 | display: none; 33 | } 34 | } 35 | 36 | /*md resolution*/ 37 | @media (min-width: 992px) and (max-width: 1200px) { 38 | .row-md { 39 | clear: both; 40 | } 41 | } 42 | 43 | /*<= md resolution*/ 44 | @media (max-width: 992px) { 45 | .collapse { 46 | display: none; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /07 Redux/src/images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/angular2-sample-app/b6588c51263437793ae132489fa1d80904343157/07 Redux/src/images/health.png -------------------------------------------------------------------------------- /07 Redux/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 2 Sample App 7 | 8 | 9 |
10 | 11 | Loading... 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /07 Redux/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { routes } from './routes'; 7 | import { AppStore, store } from './store'; 8 | 9 | import { App } from './components/app'; 10 | import { Header } from './components/common/header'; 11 | import { LoginPage } from './components/login/loginPage'; 12 | import { Banner } from './components/login/banner'; 13 | import { LoginForm } from './components/login/loginForm'; 14 | import { PatientsPage } from './components/patients/patientsPage'; 15 | import { SearchPatientContainer } from './components/patients/searchPatient.container'; 16 | import { SearchPatient } from './components/patients/searchPatient'; 17 | import { PatientListContainer } from './components/patients/patientList.container'; 18 | import { PatientList } from './components/patients/patientList'; 19 | import { PatientFormContainer } from './components/patient/patientForm.container'; 20 | import { PatientForm } from './components/patient/patientForm'; 21 | 22 | @NgModule({ 23 | declarations: [ 24 | App, 25 | Header, 26 | LoginPage, 27 | Banner, 28 | LoginForm, 29 | PatientsPage, 30 | SearchPatientContainer, 31 | SearchPatient, 32 | PatientListContainer, 33 | PatientList, 34 | PatientFormContainer, 35 | PatientForm 36 | ], 37 | imports: [ 38 | BrowserModule, 39 | RouterModule.forRoot(routes) 40 | ], 41 | bootstrap: [App], 42 | providers: [ 43 | { provide: LocationStrategy, useClass: HashLocationStrategy }, 44 | { provide: AppStore, useValue: store } 45 | ] 46 | }) 47 | class AppModule { 48 | 49 | } 50 | 51 | platformBrowserDynamic().bootstrapModule(AppModule) 52 | -------------------------------------------------------------------------------- /07 Redux/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | export class Patient { 2 | id: number; 3 | dni: string; 4 | name: string; 5 | specialty: string; 6 | doctor: string; 7 | date: string; 8 | time: string; 9 | 10 | constructor() { 11 | this.id = 0; 12 | this.dni = ""; 13 | this.name = ""; 14 | this.specialty = ""; 15 | this.doctor = ""; 16 | this.date = ""; 17 | this.time = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /07 Redux/src/reducers/doctorsReducer.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux'; 2 | import { ASSIGN_DOCTORS, AssignDoctorsAction } from '../actions/doctors/assignDoctorsAction'; 3 | 4 | export const doctorsReducer: Reducer> = 5 | (state: Array = [], action: AssignDoctorsAction): Array => { 6 | switch(action.type) { 7 | case ASSIGN_DOCTORS: 8 | return [...action.doctors]; 9 | 10 | default: 11 | return state; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /07 Redux/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { Reducer, combineReducers } from 'redux'; 2 | import { AppState } from '../states/appState'; 3 | import { patientsReducer } from './patientsReducer'; 4 | import { specialtiesReducer } from './specialtiesReducer'; 5 | import { doctorsReducer } from './doctorsReducer'; 6 | import { patientReducer } from './patientReducer'; 7 | 8 | export const reducers: Reducer = combineReducers({ 9 | patients: patientsReducer, 10 | specialties: specialtiesReducer, 11 | doctors: doctorsReducer, 12 | patientForm: patientReducer 13 | }); 14 | -------------------------------------------------------------------------------- /07 Redux/src/reducers/patientReducer.ts: -------------------------------------------------------------------------------- 1 | import { Reducer, Action } from 'redux'; 2 | import { Patient } from '../model/patient'; 3 | import { PatientFormState, PatientFormErrors } from '../states/patientFormState'; 4 | import { ASSIGN_PATIENT, AssignPatientAction } from '../actions/patient/assignPatientAction'; 5 | import { PATIENT_UI_INPUT, PatientUIInputAction } from '../actions/patient/updatePatientUIAction'; 6 | import { SAVE_PATIENT, SavePatientAction } from '../actions/patient/savePatientAction'; 7 | import { RESET_PATIENT_FORM } from '../actions/patient/resetPatientFormAction'; 8 | 9 | export const patientReducer: Reducer = 10 | (state: PatientFormState = new PatientFormState(), action: Action): PatientFormState => { 11 | switch(action.type) { 12 | case ASSIGN_PATIENT: 13 | return assignPatient(state, action as AssignPatientAction); 14 | 15 | case PATIENT_UI_INPUT: 16 | return patientUIInput(state, action as PatientUIInputAction); 17 | 18 | case SAVE_PATIENT: 19 | return savePatient(state, action as SavePatientAction); 20 | 21 | case RESET_PATIENT_FORM: 22 | return Object.assign({}, state, new PatientFormState()); 23 | 24 | default: 25 | return state; 26 | } 27 | }; 28 | 29 | const assignPatient = (state: PatientFormState, action: AssignPatientAction) => { 30 | return Object.assign({}, state, { 31 | patient: action.patient 32 | }); 33 | } 34 | 35 | const patientUIInput = (state: PatientFormState, action: PatientUIInputAction) => { 36 | let patient = Object.assign({}, state.patient, { 37 | [action.fieldName]: action.value 38 | }); 39 | 40 | let errors = Object.assign({}, state.errors, { 41 | [action.fieldName]: action.formError 42 | }); 43 | 44 | return Object.assign({}, state, { 45 | patient, 46 | errors, 47 | isValid: isFormValid(errors) 48 | }); 49 | } 50 | 51 | const isFormValid = (errors: PatientFormErrors): boolean => { 52 | return Object.keys(errors).every((key) => { 53 | return errors[key].isValid; 54 | }); 55 | } 56 | 57 | const savePatient = (state: PatientFormState, action: SavePatientAction) => { 58 | return Object.assign({}, state, action.patientFormState); 59 | } 60 | -------------------------------------------------------------------------------- /07 Redux/src/reducers/patientsReducer.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux'; 2 | import { Patient } from '../model/patient'; 3 | import { ASSIGN_PATIENTS, AssignPatientsAction } from '../actions/patients/assignPatientsAction'; 4 | 5 | export const patientsReducer: Reducer> = 6 | (state: Array = [], action: AssignPatientsAction): Array => { 7 | switch(action.type) { 8 | case ASSIGN_PATIENTS: 9 | return [...action.patients]; 10 | 11 | default: 12 | return state; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /07 Redux/src/reducers/specialtiesReducer.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux'; 2 | import { ASSIGN_SPECIALTIES, AssignSpecialtiesAction } from '../actions/specialties/assignSpecialtiesAction'; 3 | 4 | export const specialtiesReducer: Reducer> = 5 | (state: Array = [], action: AssignSpecialtiesAction): Array => { 6 | switch(action.type) { 7 | case ASSIGN_SPECIALTIES: 8 | return [...action.specialties]; 9 | 10 | default: 11 | return state; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /07 Redux/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './components/login/loginPage'; 3 | import { PatientsPage } from './components/patients/patientsPage'; 4 | import { PatientFormContainer } from './components/patient/patientForm.container'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 8 | { path: 'login', component: LoginPage }, 9 | { path: 'patients', component: PatientsPage }, 10 | { path: 'patient/:id', component: PatientFormContainer } 11 | ]; 12 | 13 | export { 14 | routes 15 | } 16 | -------------------------------------------------------------------------------- /07 Redux/src/states/appState.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | import { PatientFormState } from './patientFormState'; 3 | 4 | export interface AppState { 5 | patients: Array; 6 | specialties: Array; 7 | doctors: Array; 8 | patientForm: PatientFormState; 9 | } 10 | -------------------------------------------------------------------------------- /07 Redux/src/states/patientFormState.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | 3 | export class PatientFormState { 4 | patient: Patient; 5 | isValid: boolean; 6 | errors: PatientFormErrors; 7 | isSaveCompleted: boolean; 8 | 9 | constructor() { 10 | this.patient = new Patient(); 11 | this.isValid = true; 12 | this.errors = new PatientFormErrors(); 13 | this.isSaveCompleted = false; 14 | } 15 | } 16 | 17 | export class PatientFormErrors { 18 | dni: FormError; 19 | name: FormError; 20 | date: FormError; 21 | time: FormError; 22 | specialty: FormError; 23 | doctor: FormError; 24 | 25 | constructor () { 26 | this.dni = new FormError(); 27 | this.name = new FormError(); 28 | this.date = new FormError(); 29 | this.time = new FormError(); 30 | this.specialty = new FormError(); 31 | this.doctor = new FormError(); 32 | } 33 | } 34 | 35 | export class FormError { 36 | isValid: boolean; 37 | errorMessage: string; 38 | 39 | constructor() { 40 | this.isValid = true; 41 | this.errorMessage = ""; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /07 Redux/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, StoreEnhancer, createStore, compose, applyMiddleware } from 'redux'; 2 | import { reducers } from './reducers'; 3 | import { AppState } from './states/appState'; 4 | import { OpaqueToken } from '@angular/core'; 5 | import ReduxThunk from 'redux-thunk'; 6 | 7 | let devTools: StoreEnhancer = 8 | window['devToolsExtension'] ? 9 | window['devToolsExtension']() : 10 | f => f; 11 | 12 | export let store: Store = createStore( 13 | reducers, 14 | compose( 15 | applyMiddleware(ReduxThunk), 16 | devTools 17 | ) 18 | ); 19 | 20 | //Due to Store from Redux is an interface and we can't use interfaces as a 21 | //dependency injection key, we need to define something to be injectable. 22 | //Angular provides OpaqueToken that it's a better choice thant injecting 23 | //a string directly because it helps us avoid collisions. 24 | export const AppStore = new OpaqueToken('App.store'); 25 | -------------------------------------------------------------------------------- /07 Redux/src/validations/dniValidation.ts: -------------------------------------------------------------------------------- 1 | class DNIValidation { 2 | hasValidFormat = (value: string): boolean => { 3 | const dniRegex = /^[0-9]{8}[a-z, A-Z]$/; 4 | 5 | return dniRegex.test(value); 6 | } 7 | 8 | isValid = (value: string): boolean => { 9 | let dniNumber: number = parseInt(value); 10 | let validLetter: string = this.getValidLetterByDNINumber(dniNumber); 11 | let currentLetter = value.charAt(8).toUpperCase(); 12 | 13 | return currentLetter === validLetter; 14 | }; 15 | 16 | private getValidLetterByDNINumber = (dniNumber: number) : string => { 17 | let letterIndex = dniNumber % 23; 18 | let validLetters = 'TRWAGMYFPDXBNJZSQVHLCKET'; 19 | 20 | return validLetters.charAt(letterIndex) 21 | }; 22 | } 23 | 24 | const dniValidation = new DNIValidation(); 25 | 26 | export { 27 | dniValidation 28 | } 29 | -------------------------------------------------------------------------------- /07 Redux/src/validations/requiredValidation.ts: -------------------------------------------------------------------------------- 1 | class RequiredValidation { 2 | isValid(value): boolean { 3 | return value !== null && 4 | value !== undefined && 5 | value !== "" 6 | } 7 | } 8 | 9 | const requiredValidation = new RequiredValidation(); 10 | 11 | export { 12 | requiredValidation 13 | } 14 | -------------------------------------------------------------------------------- /07 Redux/src/validators/dniValidator.ts: -------------------------------------------------------------------------------- 1 | import {FormError } from '../states/patientFormState'; 2 | import { dniValidation } from '../validations/dniValidation'; 3 | import { requiredValidation } from '../validations/requiredValidation'; 4 | 5 | class DNIValidator { 6 | validateDNI(dni: string): FormError { 7 | let formError = new FormError(); 8 | formError.isValid = true; 9 | 10 | switch (false) { 11 | case requiredValidation.isValid(dni): 12 | formError.isValid = false; 13 | formError.errorMessage = "Mandatory field"; 14 | break; 15 | 16 | case dniValidation.hasValidFormat(dni): 17 | formError.isValid = false; 18 | formError.errorMessage = "Invalid format"; 19 | break; 20 | 21 | case dniValidation.isValid(dni): 22 | formError.isValid = false; 23 | formError.errorMessage = "Invalid DNI"; 24 | break; 25 | } 26 | 27 | return formError; 28 | } 29 | } 30 | 31 | const dniValidator = new DNIValidator(); 32 | 33 | export { 34 | dniValidator 35 | } 36 | -------------------------------------------------------------------------------- /07 Redux/src/validators/patientFormValidator.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | import { PatientFormState, PatientFormErrors, FormError } from '../states/patientFormState'; 3 | import { dniValidator } from './dniValidator'; 4 | import { requiredValidator } from './requiredValidator'; 5 | 6 | class PatientFormValidator { 7 | 8 | validateField(field: string, value: any): FormError { 9 | switch(field) { 10 | case "dni": 11 | return dniValidator.validateDNI(value); 12 | 13 | default: 14 | return requiredValidator.validateRequiredField(value); 15 | } 16 | } 17 | 18 | validatePatient(patient: Patient): PatientFormState { 19 | let patientFormState = new PatientFormState(); 20 | patientFormState.patient = patient; 21 | 22 | patientFormState.errors.dni = dniValidator.validateDNI(patient.dni); 23 | patientFormState.errors.name = requiredValidator.validateRequiredField(patient.name); 24 | patientFormState.errors.date = requiredValidator.validateRequiredField(patient.date); 25 | patientFormState.errors.time = requiredValidator.validateRequiredField(patient.time); 26 | patientFormState.errors.specialty = requiredValidator.validateRequiredField(patient.specialty); 27 | patientFormState.errors.doctor = requiredValidator.validateRequiredField(patient.doctor); 28 | 29 | patientFormState.isValid = this.isFormValid(patientFormState.errors); 30 | 31 | return patientFormState; 32 | } 33 | 34 | private isFormValid = (errors: PatientFormErrors): boolean => { 35 | return Object.keys(errors).every((key) => { 36 | return errors[key].isValid; 37 | }); 38 | } 39 | } 40 | 41 | const patientFormValidator = new PatientFormValidator(); 42 | 43 | export { 44 | patientFormValidator 45 | } 46 | -------------------------------------------------------------------------------- /07 Redux/src/validators/requiredValidator.ts: -------------------------------------------------------------------------------- 1 | import { FormError } from '../states/patientFormState'; 2 | import { requiredValidation } from '../validations/requiredValidation'; 3 | 4 | class RequiredValidator { 5 | validateRequiredField(value: any): FormError { 6 | let formError = new FormError(); 7 | formError.isValid = requiredValidation.isValid(value); 8 | 9 | if (!formError.isValid) { 10 | formError.errorMessage = "Mandatory field"; 11 | } 12 | 13 | return formError; 14 | } 15 | } 16 | 17 | const requiredValidator = new RequiredValidator(); 18 | 19 | export { 20 | requiredValidator 21 | } 22 | -------------------------------------------------------------------------------- /07 Redux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noLib": false, 13 | "preserveConstEnums": true, 14 | "suppressImplicitAnyIndexErrors": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /07 Redux/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "redux": "registry:dt/redux#3.5.2+20160703092728", 5 | "redux-thunk": "registry:dt/redux-thunk#2.1.0+20160703120921", 6 | "webpack-env": "registry:dt/webpack-env#1.12.2+20160316155526" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /07 Redux/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var basePath = __dirname; 6 | 7 | module.exports = { 8 | context: path.join(basePath, "src"), 9 | resolve: { 10 | extensions: ['', '.js', '.ts'] 11 | }, 12 | 13 | entry: { 14 | app: './index.ts', 15 | styles: [ 16 | './css/site.css' 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "reflect-metadata", 21 | "zone.js", 22 | "@angular/core", 23 | "@angular/platform-browser", 24 | "@angular/platform-browser-dynamic", 25 | "@angular/common", 26 | "@angular/compiler", 27 | "rxjs", 28 | "@angular/router", 29 | "bootstrap", 30 | "redux", 31 | "redux-thunk" 32 | ], 33 | vendorStyles: [ 34 | '../node_modules/bootstrap/dist/css/bootstrap.css' 35 | ] 36 | }, 37 | 38 | output: { 39 | path: path.join(basePath, "dist"), 40 | filename: '[name].js' 41 | }, 42 | 43 | devServer: { 44 | contentBase: './dist', //Content base 45 | inline: true, //Enable watch and live reload 46 | host: 'localhost', 47 | port: 8080 48 | }, 49 | 50 | devtool: 'source-map', 51 | 52 | module: { 53 | loaders: [ 54 | { 55 | test: /\.ts$/, 56 | exclude: /node_modules/, 57 | loader: 'ts' 58 | }, 59 | //Note: Doesn't exclude node_modules to load bootstrap 60 | { 61 | test: /\.css$/, 62 | loader: ExtractTextPlugin.extract('style','css') 63 | }, 64 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 65 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 66 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 67 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 68 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 69 | { 70 | test: /\.png$/, 71 | loader: 'file?limit=0&name=[path][name].[hash].', 72 | exclude: /node_modules/ 73 | } 74 | ] 75 | }, 76 | 77 | plugins: [ 78 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 79 | new ExtractTextPlugin('[name].css'), 80 | new HtmlWebpackPlugin({ 81 | filename: 'index.html', 82 | template: 'index.html' 83 | }), 84 | //Expose jquery used by bootstrap 85 | new webpack.ProvidePlugin({ 86 | $: "jquery", 87 | jQuery: "jquery" 88 | }) 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Lemoncode 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 | # angular2-sample-app 2 | 3 | Simple LOB app includes login, list, form edit 4 | 5 | Each sample contains a readme.md file including an step by step guide to recreate it. 6 | 7 | List of samples: 8 | 9 | - [00 Boilerplate](https://github.com/Lemoncode/angular2-sample-app/tree/master/00%20Boilerplate) 10 | - [01 Hello Angular](https://github.com/Lemoncode/angular2-sample-app/tree/master/01%20Hello%20Angular) 11 | - [02 Navigation](https://github.com/Lemoncode/angular2-sample-app/tree/master/02%20Navigation) 12 | - [03 List Page](https://github.com/Lemoncode/angular2-sample-app/tree/master/03%20List%20Page) 13 | - [04 Form Page](https://github.com/Lemoncode/angular2-sample-app/tree/master/04%20Form%20Page) 14 | - [05 Form Validation](https://github.com/Lemoncode/angular2-sample-app/tree/master/05%20Form%20Validation) 15 | 16 | # About Basefactor + Lemoncode 17 | 18 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 19 | 20 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 21 | 22 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 23 | 24 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------