├── .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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
17 |
18 |
19 |
20 |
21 | DNI
22 | Paciente
23 | Especialidad
24 | Doctor
25 | Cita
26 | Hora
27 |
28 |
29 |
30 |
31 | {{p.dni}}
32 | {{p.name}}
33 |
34 | {{p.specialty}}
35 |
37 |
38 |
39 | {{p.doctor}}
40 | {{p.date}}
41 |
42 | {{p.time}}
43 |
45 |
46 |
47 |
48 |
49 |
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 |
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 |
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 |
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 |
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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
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 |
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 |
17 |
18 |
19 |
20 |
21 | DNI
22 | Paciente
23 | Especialidad
24 | Doctor
25 | Cita
26 | Hora
27 |
28 |
29 |
30 |
31 | {{p.dni}}
32 | {{p.name}}
33 |
34 | {{p.specialty}}
35 |
37 |
38 |
39 | {{p.doctor}}
40 | {{p.date}}
41 |
42 | {{p.time}}
43 |
45 |
46 |
47 |
48 |
49 |
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 |
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 |
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 |
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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
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 |
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 |
17 |
18 |
19 |
20 |
21 | DNI
22 | Paciente
23 | Especialidad
24 | Doctor
25 | Cita
26 | Hora
27 |
28 |
29 |
30 |
31 | {{p.dni}}
32 | {{p.name}}
33 |
34 | {{p.specialty}}
35 |
37 |
38 |
39 | {{p.doctor}}
40 | {{p.date}}
41 |
42 | {{p.time}}
43 |
45 |
46 |
47 |
48 |
49 |
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 |
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 |
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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | DNI
14 | Paciente
15 | Especialidad
16 | Doctor
17 | Cita
18 | Hora
19 |
20 |
21 |
22 |
23 |
24 |
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 |
14 |
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 |
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 |
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 |
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 |
9 |
10 |
13 |
14 |
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 |
8 |
9 |
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 |
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 |
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 |
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 |
15 |
16 |
17 |
18 |
19 | DNI
20 | Patient
21 | Specialty
22 | Doctor
23 | Date
24 | Time
25 |
26 |
27 |
28 |
29 | {{p.dni}}
30 | {{p.name}}
31 |
32 | {{p.specialty}}
33 |
35 |
36 |
37 | {{p.doctor}}
38 | {{p.date}}
39 |
40 | {{p.time}}
41 |
43 |
44 |
45 |
46 |
47 |
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 |
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 |
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 |
--------------------------------------------------------------------------------