├── .gitignore ├── 00 Boilerplate ├── README.md ├── package.json ├── src │ ├── css │ │ └── site.css │ ├── index.html │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── 01 HelloAngular ├── README.md ├── package.json ├── src │ ├── components │ │ └── common │ │ │ └── header.ts │ ├── css │ │ └── site.css │ ├── index.html │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── 02 Navigation ├── README.md ├── package.json ├── src │ ├── app-routes.ts │ ├── components │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ └── login.ts │ │ ├── patient │ │ │ └── patient.ts │ │ └── patients │ │ │ └── patients.ts │ ├── css │ │ └── site.css │ ├── index.html │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── 03 List Page ├── README.md ├── package.json ├── src │ ├── api │ │ └── patientAPI.ts │ ├── app-routes.ts │ ├── components │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ └── login.ts │ │ ├── patient │ │ │ └── patient.ts │ │ └── patients │ │ │ ├── patients.ts │ │ │ ├── patientsList.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── index.html │ ├── index.ts │ ├── mockData │ │ └── patients.json │ └── model │ │ └── patient.ts ├── tsconfig.json └── webpack.config.js ├── 04 Form Page ├── README.md ├── package.json ├── src │ ├── api │ │ └── patientAPI.ts │ ├── app-routes.ts │ ├── components │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ └── login.ts │ │ ├── patient │ │ │ └── patient.ts │ │ └── patients │ │ │ ├── patients.ts │ │ │ ├── patientsList.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── index.html │ ├── index.ts │ ├── mockData │ │ └── patients.json │ └── model │ │ └── patient.ts ├── tsconfig.json └── webpack.config.js ├── 05 Form Validation ├── README.md ├── package.json ├── src │ ├── api │ │ └── patientAPI.ts │ ├── app-routes.ts │ ├── components │ │ ├── common │ │ │ └── header.ts │ │ ├── login │ │ │ └── login.ts │ │ ├── patient │ │ │ └── patient.ts │ │ └── patients │ │ │ ├── patients.ts │ │ │ ├── patientsList.ts │ │ │ └── searchPatient.ts │ ├── css │ │ └── site.css │ ├── index.html │ ├── index.ts │ ├── mockData │ │ └── patients.json │ ├── model │ │ └── patient.ts │ └── validations │ │ └── validateDni.ts ├── tsconfig.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": "angular1_5-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 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": "https://github.com/Lemoncode/angular1_5-sample-app.git" 13 | }, 14 | "homepage": "https://github.com/Lemoncode/angular1_5-sample-app", 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 1.5 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 HelloAngular/README.md: -------------------------------------------------------------------------------- 1 | # 01 Hello Angular 2 | 3 | In this sample we are going to create an instantiante a minimum angular 1.5 application. 4 | 5 | We are going to take as startup point _00 Boilerplate_ 6 | 7 | # Summary steps: 8 | 9 | - Install Angular libraries. 10 | - Creating the app. 11 | - Instantiating the app from the HTML. 12 | - Creating a component. 13 | - Displaying a component. 14 | 15 | # Steps to build it 16 | 17 | ## Prerequisites 18 | 19 | Prerequisites, you will need to have nodejs installed in your computer. If you want to follow this step guides you will need to take as starting point sample "00 Boilerplate" 20 | 21 | ## Steps 22 | 23 | 24 | Let's start by installing Angular 1.x library 25 | 26 | ``` 27 | npm install angular@1.5.8 --save 28 | ``` 29 | 30 | 31 | Let's install the angularjs types: 32 | 33 | ``` 34 | npm install @Types/angular --save-dev 35 | ``` 36 | 37 | We will need to install JQuery types as well 38 | 39 | ``` 40 | npm install @Types/jquery --save-dev 41 | ``` 42 | 43 | Under _src_ folder let's replace the content of the _index.ts_ file: 44 | 45 | ```javascript 46 | import * as angular from 'angular' 47 | 48 | var app = angular.module('myAppointmentsApp', []); 49 | 50 | // Just to test if the app is instantiated 51 | // check the browser console (developer window) 52 | console.log(app); 53 | ``` 54 | 55 | Now if we open the console we can check that the app has been created successfuly 56 | (in the console window we can expand the dumped app object). 57 | 58 | Let's create our first component. 59 | 60 | First we will indicate in the HTML that we are going to use this application (index.html): 61 | 62 | ```javascript 63 | 64 | ``` 65 | 66 | Under _src_ let's create the following subfolders _components/common_ and 67 | under that subfolder let's create a file called _header.ts_ this file will 68 | contain a simple "header" component: 69 | 70 | ```javascript 71 | import * as angular from 'angular'; 72 | 73 | class HeaderController { 74 | sampleBinding : string; 75 | 76 | constructor() { 77 | this.sampleBinding = "Testing binding header component"; 78 | } 79 | } 80 | 81 | export const header = { 82 | template: '

Header testing bindings: {{$ctrl.sampleBinding}}', 83 | controller: HeaderController 84 | } 85 | ``` 86 | 87 | Let's register this component in the _index.ts_ file 88 | 89 | ```javascript 90 | var app = angular.module('myAppointmentsApp', []); 91 | 92 | app.component('header', header); 93 | ``` 94 | 95 | Let's use this component in our _index.html_ file 96 | 97 | ```html 98 | 99 |
100 |
101 |
102 | 103 | ``` 104 | 105 | Now we can run the sample 106 | 107 | ``` 108 | npm start 109 | ``` 110 | 111 | And we can see how the _header_ component gets instantiated and bindings are 112 | working as expected. 113 | -------------------------------------------------------------------------------- /01 HelloAngular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular1_5-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Lemoncode/angular1_5-sample-app.git" 12 | }, 13 | "homepage": "https://github.com/Lemoncode/angular1_5-sample-app", 14 | "keywords": [ 15 | "angular", 16 | "sample", 17 | "app" 18 | ], 19 | "author": "Lemoncode", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "css-loader": "^0.25.0", 23 | "extract-text-webpack-plugin": "^1.0.1", 24 | "html-webpack-plugin": "^2.28.0", 25 | "style-loader": "^0.13.1", 26 | "ts-loader": "^2.0.1", 27 | "typescript": "^2.2.1", 28 | "webpack": "^1.14.0", 29 | "webpack-dev-server": "^1.16.3" 30 | }, 31 | "dependencies": { 32 | "angular": "^1.5.8" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/Lemoncode/angular1_5-sample-app/issues" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /01 HelloAngular/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class HeaderController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Testing binding header component"; 8 | } 9 | } 10 | 11 | export const header = { 12 | template: '

Header testing bindings: {{$ctrl.sampleBinding}}', 13 | controller: HeaderController 14 | } 15 | -------------------------------------------------------------------------------- /01 HelloAngular/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /01 HelloAngular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 1.5 Sample App 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /01 HelloAngular/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular' 2 | import {header} from './components/common/header'; 3 | 4 | var app = angular.module('myAppointmentsApp', []); 5 | 6 | app.component('header', header); 7 | -------------------------------------------------------------------------------- /01 HelloAngular/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 HelloAngular/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 | -------------------------------------------------------------------------------- /02 Navigation/README.md: -------------------------------------------------------------------------------- 1 | # 02 Navigation 2 | 3 | In this sample we are going to add route navigation support to our sample app. 4 | 5 | We are going to take as startup point _01 HelloAngular_ 6 | 7 | # Summary steps: 8 | 9 | - Install routing libraries. 10 | - Define routes. 11 | - Create empty pages components, including a basic navgation. 12 | 13 | # Steps to build it 14 | 15 | ## Prerequisites 16 | 17 | Prerequisites, you will need to have nodejs installed in your computer. If you want to follow this step guides you will need to take as starting point sample "01 HelloAngular" 18 | 19 | ## Steps 20 | 21 | 22 | Let's start by installing angular-ui-router (angular 1.5 component router is an 23 | abandoned project, [more info](http://stackoverflow.com/questions/33652668/angular-1-5-and-new-component-router)). 24 | 25 | ``` 26 | npm install angular-ui-router --save 27 | ``` 28 | 29 | We need to install types definition for angular-ui-router 30 | 31 | ``` 32 | npm install @Types/angular-ui-router --save-dev 33 | ``` 34 | 35 | We need to install as well es6-promise types 36 | 37 | ``` 38 | npm install @Types/es6-promise --save-dev 39 | ``` 40 | 41 | Now we need to indicate in _index.ts_ that we are going to use this module in our 42 | app: 43 | 44 | ```javascript 45 | import 'angular-ui-router'; 46 | 47 | // (...) 48 | 49 | var app = angular.module('myAppointmentsApp', ['ui.router']); 50 | 51 | app.component('login', login); 52 | ``` 53 | 54 | The next step is to define our routing config, let's create a file called 55 | _app-routes.ts_ there we will setup the ui-router and setup a route to a login pages 56 | 57 | ```javascript 58 | function routing($locationProvider: ng.ILocationProvider, 59 | $stateProvider: angular.ui.IStateProvider, 60 | $urlRouterProvider: angular.ui.IUrlRouterProvider) { 61 | 62 | // html5 removes the need for # in URL 63 | $locationProvider.html5Mode({ 64 | enabled: false 65 | }); 66 | 67 | $stateProvider.state('home', { 68 | url: '/home', 69 | views: { 70 | 'content@': { template: '' } 71 | } 72 | } 73 | ); 74 | 75 | $urlRouterProvider.otherwise('/home'); 76 | } 77 | 78 | export default routing; 79 | ``` 80 | 81 | Now we have to comeback to our index.ts file and register our routing function 82 | 83 | ```javascript 84 | import routing from './app-routes'; 85 | // (...) 86 | 87 | var app = angular.module('myAppointmentsApp', ['ui.router']) 88 | .config(routing);; 89 | ``` 90 | 91 | Under _components_ subfolder let's create a new subfolder called _login_ and 92 | there we are going to create a dummy login component: 93 | 94 | ```javascript 95 | import * as angular from 'angular'; 96 | 97 | class LoginController { 98 | sampleBinding : string; 99 | 100 | constructor() { 101 | this.sampleBinding = "Hello form Login"; 102 | } 103 | } 104 | 105 | export const login = { 106 | template: '

bindings test: {{$ctrl.sampleBinding}}

', 107 | controller: LoginController 108 | } 109 | ``` 110 | 111 | Now we need to setup the placeholder for the views, we will do that in 112 | _index.html_ file: 113 | 114 | 115 | ```html 116 |
117 |
118 |
119 |
120 | ``` 121 | 122 | Let's create a dummy _patients_ component (later on it will display a list of 123 | patients appointments). We will create a subofolder components/patients and under 124 | that subfolder let's create a file called _patients.ts_ 125 | 126 | ```javascript 127 | import * as angular from 'angular'; 128 | 129 | class PatientsController { 130 | sampleBinding : string; 131 | 132 | constructor() { 133 | this.sampleBinding = "Hello from Patients"; 134 | } 135 | } 136 | 137 | export const patients = { 138 | template: '

Bindings test: {{$ctrl.sampleBinding}}

', 139 | controller: PatientsController 140 | } 141 | ``` 142 | 143 | We have to register this component in our index.ts file 144 | 145 | ```javascript 146 | import {patients} from './components/patients/patients'; 147 | // (...) 148 | 149 | app.component('patients', patients); 150 | ``` 151 | 152 | Now we need to register a route to our new patients component: 153 | 154 | 155 | We can run the app and manually navigate to the _patients_ route, but rather 156 | we will just add a link from _login_ route to _patients_ route, let's open 157 | _components/login/login.ts_ and replace the html template by using backticks 158 | and adding a new link: 159 | 160 | ```javascript 161 | export const login = { 162 | template: ` 163 |

bindings test: {{$ctrl.sampleBinding}}

164 | Navigate to patients 165 | `, 166 | controller: LoginController 167 | } 168 | ``` 169 | 170 | Now we can repeat the same steps to create the _patient_ component. 171 | -------------------------------------------------------------------------------- /02 Navigation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular1_5-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Lemoncode/angular1_5-sample-app.git" 12 | }, 13 | "homepage": "https://github.com/Lemoncode/angular1_5-sample-app", 14 | "keywords": [ 15 | "angular", 16 | "sample", 17 | "app" 18 | ], 19 | "author": "Lemoncode", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@types/angular": "^1.6.7", 23 | "@types/jquery": "^2.0.40", 24 | "css-loader": "^0.25.0", 25 | "extract-text-webpack-plugin": "^1.0.1", 26 | "html-webpack-plugin": "^2.22.0", 27 | "style-loader": "^0.13.1", 28 | "ts-loader": "^2.0.1", 29 | "typescript": "^2.2.1", 30 | "webpack": "^1.13.2", 31 | "webpack-dev-server": "^1.15.1" 32 | }, 33 | "dependencies": { 34 | "angular": "^1.5.8", 35 | "angular-ui-router": "^0.4.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /02 Navigation/src/app-routes.ts: -------------------------------------------------------------------------------- 1 | function routing($locationProvider: ng.ILocationProvider, 2 | $stateProvider: angular.ui.IStateProvider, 3 | $urlRouterProvider: angular.ui.IUrlRouterProvider) { 4 | 5 | // html5 removes the need for # in URL 6 | $locationProvider.html5Mode({ 7 | enabled: false 8 | }); 9 | 10 | $stateProvider.state('home', { 11 | url: '/home', 12 | views: { 13 | 'content@': { template: '' } 14 | } 15 | } 16 | ); 17 | 18 | $stateProvider.state('patients', { 19 | url: '/patients', 20 | views: { 21 | 'content@': { template: '' } 22 | } 23 | } 24 | ); 25 | 26 | $stateProvider.state('patient', { 27 | url: '/patient', 28 | views: { 29 | 'content@': { template: '' } 30 | } 31 | } 32 | ); 33 | 34 | $urlRouterProvider.otherwise('/home'); 35 | } 36 | 37 | export default routing; 38 | -------------------------------------------------------------------------------- /02 Navigation/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class HeaderController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Testing binding header component"; 8 | } 9 | } 10 | 11 | export const header = { 12 | template: '

Header testing bindings: {{$ctrl.sampleBinding}}

', 13 | controller: HeaderController 14 | } 15 | -------------------------------------------------------------------------------- /02 Navigation/src/components/login/login.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class LoginController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Login"; 8 | } 9 | } 10 | 11 | export const login = { 12 | template: ` 13 |

bindings test: {{$ctrl.sampleBinding}}

14 | Navigate to patients 15 | `, 16 | controller: LoginController 17 | } 18 | -------------------------------------------------------------------------------- /02 Navigation/src/components/patient/patient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Patient"; 8 | } 9 | } 10 | 11 | export const patient = { 12 | template: ` 13 |

Bindings test: {{$ctrl.sampleBinding}}

14 | `, 15 | controller: PatientController 16 | } 17 | -------------------------------------------------------------------------------- /02 Navigation/src/components/patients/patients.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientsController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Patients"; 8 | } 9 | } 10 | 11 | export const patients = { 12 | template: ` 13 |

Bindings test: {{$ctrl.sampleBinding}}

14 | Navigate to patient 15 | `, 16 | controller: PatientsController 17 | } 18 | -------------------------------------------------------------------------------- /02 Navigation/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /02 Navigation/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 1.5 Sample App 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /02 Navigation/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular' 2 | import 'angular-ui-router'; 3 | import routing from './app-routes'; 4 | import {header} from './components/common/header'; 5 | import {login} from './components/login/login'; 6 | import {patients} from './components/patients/patients'; 7 | import {patient} from './components/patient/patient'; 8 | 9 | 10 | var app = angular.module('myAppointmentsApp', ['ui.router']) 11 | .config(routing); 12 | 13 | app.component('header', header); 14 | app.component('login', login); 15 | app.component('patients', patients); 16 | app.component('patient', patient); 17 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /03 List Page/README.md: -------------------------------------------------------------------------------- 1 | # 03 List Page 2 | 3 | In this sample we are going to build up the appointments page. This will include 4 | creating the entities, creating a fake api (to simulate we are hitting a remote 5 | server), creating the layout, plus the ui interaction. 6 | 7 | We are going to take as startup point _02 Navigation_ 8 | 9 | # Summary steps: 10 | 11 | - Import bootstrap libraries. 12 | - Create the searchPatient component (dummy). 13 | - Create the listComponent. 14 | 15 | - Add mock data plus copy to dev. 16 | - Create entities. 17 | - Create api. 18 | 19 | 20 | # Steps to build it 21 | 22 | ## Prerequisites 23 | 24 | Prerequisites, you will need to have nodejs installed in your computer. If you want to follow this step guides you will need to take as starting point sample "02 Navigation" 25 | 26 | ## Steps 27 | 28 | ### Style 29 | 30 | Before getting started building the app, let's install bootstrap and jquery, we will 31 | use bootstrap as a base to generate the layout. 32 | 33 | ``` 34 | npm install jquery --save 35 | npm install bootstrap --save 36 | ``` 37 | In webpack.config.js: 38 | 39 | 40 | 41 | 42 | We will just use a plugin to expose "$" (jquery) and "JQuery" 43 | as global names. 44 | 45 | ```javascript 46 | plugins: [ 47 | // ... 48 | //Expose jquery used by bootstrap 49 | new webpack.ProvidePlugin({ 50 | $: "jquery", 51 | jQuery: "jquery" 52 | }) 53 | ] 54 | } 55 | ``` 56 | And let's add bootstrap.css to the styles array to be processed by webpack (webpack.config.js) 57 | 58 | ```javascript 59 | entry: { 60 | // (...) 61 | styles: [ 62 | '../node_modules/bootstrap/dist/css/bootstrap.css', 63 | './css/site.css' 64 | ], 65 | // (...) 66 | }, 67 | ``` 68 | 69 | Bootstrap will expose glyphicons and other features, let's expose the right loaders 70 | for this. 71 | 72 | First we will install file-loader package 73 | 74 | ``` 75 | npm install file-loader --save-dev 76 | npm install url-loader --save-dev 77 | ``` 78 | We need to indicate that we will use bootstrap javascript: 79 | 80 | ```javascript 81 | entry: { 82 | // ... 83 | vendor: [ 84 | 'bootstrap' 85 | ] 86 | }, 87 | ``` 88 | 89 | On the CSS loader section, we have to remove the exclude "node_modules" folder 90 | 91 | ```javascript 92 | { 93 | test: /\.css$/, 94 | loader: ExtractTextPlugin.extract('style','css') 95 | }, 96 | ``` 97 | 98 | Then we will configure the loader for fonts / images. 99 | 100 | ```javacript 101 | loaders: [ 102 | // (...) 103 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 104 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 105 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 106 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 107 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 108 | { 109 | test: /\.png$/, 110 | loader: 'file?limit=0&name=[path][name].[hash].', 111 | exclude: /node_modules/ 112 | } 113 | ] 114 | }, 115 | ``` 116 | 117 | ### Layout 118 | 119 | Let's start by creating two separate child components one for 120 | the search panel, and another one for the list panel. 121 | 122 | ```javascript 123 | import * as angular from 'angular'; 124 | 125 | class SearchPatientController { 126 | constructor() { 127 | } 128 | } 129 | 130 | export const searchPatient = { 131 | template: 132 | ` 133 |
134 |
135 |
136 | 137 |

Buscar paciente

138 | 140 | 141 |
142 |
143 |
144 | 145 | 146 |
147 |
148 | 149 | 150 |
151 |
152 | 154 |
155 |
156 | 157 | 158 |
159 |
160 |
161 | 162 |
163 |
164 |
165 |
166 |
167 | `, 168 | controller: SearchPatientController 169 | } 170 | ``` 171 | 172 | We have to register this component at app level _src/index.ts_ 173 | 174 | ```javascript 175 | import {searchPatient} from './components/patients/searchPatient'; 176 | 177 | // (...) 178 | 179 | app.component('searchPatient', searchPatient); 180 | ``` 181 | 182 | Let's use this component in the _patients_ page. 183 | 184 | ```javascript 185 | import * as angular from 'angular'; 186 | 187 | class PatientsController { 188 | constructor() { 189 | 190 | } 191 | } 192 | 193 | export const patients = { 194 | template: ` 195 |
196 |
197 | 198 |
199 |
200 | `, 201 | controller: PatientsController 202 | } 203 | ``` 204 | 205 | Let's do a quick test and check that the component is properly displayed: 206 | 207 | ``` 208 | npm start 209 | ``` 210 | 211 | Great ! we get the panel displayed, let's draw the second component 212 | (list result). 213 | 214 | Let's create a new component called _patientsList_ in the following path 215 | src/components/patients/patientsList.ts 216 | 217 | ```javascript 218 | import * as angular from 'angular'; 219 | 220 | class PatientsListController { 221 | constructor() { 222 | } 223 | } 224 | 225 | export const patientsList = { 226 | template: 227 | ` 228 |
229 |
230 |
231 |
232 | 233 |
234 |
235 |
236 |
237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 258 | 259 | 260 | 266 | 267 | 268 |
PacienteEspecialidad
Sample Name 253 | Sample Specialty 254 | 257 |
269 |
270 |
271 | `, 272 | controller: PatientsListController 273 | } 274 | ``` 275 | 276 | Let's register this component in the index.ts 277 | 278 | ```javascript 279 | import {patientsList} from './components/patients/patientsList'; 280 | 281 | app.component('patientsList', patientsList); 282 | ``` 283 | 284 | 285 | Let's use it in our patients component: 286 | 287 | ```javascript 288 | export const patients = { 289 | template: ` 290 |
291 |
292 | 293 | 294 |
295 |
296 | `, 297 | controller: PatientsController 298 | } 299 | ``` 300 | 301 | ### Data 302 | 303 | Let's create a mock json file under the "MockDataFolder" let's call it _patients.json 304 | 305 | ```json 306 | [ 307 | { 308 | "id": 1, 309 | "dni": "1234567A", 310 | "name": "John Doe", 311 | "specialty": "Traumatología", 312 | "doctor": "Karl J. Linville", 313 | "date": "2017-02-15T00:00:00Z", 314 | "time": "2017-02-15T00:08:30Z" 315 | }, 316 | { 317 | "id": 2, 318 | "dni": "5067254B", 319 | "name": "Anna S. Batiste", 320 | "specialty": "Cirugía", 321 | "doctor": "Gladys C. Horton", 322 | "date": "2017-02-15T00:00:00Z", 323 | "time": "2017-02-15T09:10:00Z" 324 | }, 325 | { 326 | "id": 3, 327 | "dni": "1902045C", 328 | "name": "Octavia L. Hilton", 329 | "specialty": "Traumatología", 330 | "doctor": "Karl J. Linville", 331 | "date": "2017-03-17T00:00:00Z", 332 | "time": "2017-03-17T10:10:00Z" 333 | }, 334 | { 335 | "id": 4, 336 | "dni": "1880514D", 337 | "name": "Tony M. Herrera", 338 | "specialty": "Oftalmología", 339 | "doctor": "Ruthie A. Nemeth", 340 | "date": "2017-03-17T00:00:00Z", 341 | "time": "2017-03-17T11:00:00Z" 342 | }, 343 | { 344 | "id": 5, 345 | "dni": "6810774E", 346 | "name": "Robert J. Macias", 347 | "specialty": "Cirugía", 348 | "doctor": "Gladys C. Horton", 349 | "date": "2017-02-15T00:00:00Z", 350 | "time": "2017-02-15T11:30:00Z" 351 | } 352 | ] 353 | ``` 354 | 355 | We need to copy this mock data to the folder where the dev server is going to run, 356 | in order to do this we are going to use _copy_webpack_plugin, let's install it 357 | 358 | ``` 359 | npm install copy-webpack-plugin --save-dev 360 | ``` 361 | 362 | Then in the _webpack.config.js_ file let's properly configure the plugin: 363 | 364 | ```javascript 365 | 366 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 367 | 368 | ... 369 | 370 | plugins: [ 371 | /// (...) 372 | new CopyWebpackPlugin([ 373 | { from: 'mockData/*'}, 374 | ]) 375 | ] 376 | ``` 377 | 378 | Let's start by creating an entity called _Patient_ that will hold info about the patient and a medical appointment, 379 | we will place this file under _/src/model/patient.ts_ 380 | 381 | ```javascript 382 | export class Patient { 383 | id: number; 384 | dni: string; 385 | name: string; 386 | specialty: string; 387 | doctor: string; 388 | date: Date; 389 | time: Date; 390 | } 391 | ``` 392 | 393 | And to end up with the client data layer, we will create a fake api (promise based) that will expose methods load the list of 394 | appointments plus specialties. 395 | 396 | ```javascript 397 | import { Patient } from '../model/patient'; 398 | 399 | export class PatientAPI { 400 | public static $inject: Array = ["$http"]; 401 | 402 | private baseUrl: string = './mockData/patients.json'; 403 | 404 | constructor(private $http : angular.IHttpService) { 405 | 406 | } 407 | 408 | getAllPatientsAsync(): Promise> { 409 | return this.$http.get(this.baseUrl).then(response => response.data); 410 | }; 411 | } 412 | ``` 413 | 414 | We need to register this service in the main app. 415 | 416 | ```javascript 417 | import {PatientAPI} from './api/patientAPI'; 418 | 419 | app.service('PatientAPI', PatientAPI); 420 | ``` 421 | 422 | ### Interaction 423 | 424 | Now it's time to load the information about the patient's appointments in the 425 | appointments table. 426 | 427 | First of all let's import some needed name space (patients entitiy, plus 428 | patients data api) in our _patientsList.ts 429 | 430 | ```javascript 431 | import {PatientAPI} from "../../api/patientAPI"; 432 | import {Patient} from '../../model/patient'; 433 | ``` 434 | 435 | We are going to define a member variable that will hold a list of Patients, 436 | and request the PatientsAPI service, then we will make the AJAX called 437 | to dynamically load the list of patients (_patientsList.ts_). 438 | 439 | ```javascript 440 | class PatientsListController { 441 | public static $inject: Array = ["PatientAPI"]; 442 | public patients : Array = [] 443 | 444 | constructor(patientAPI : PatientAPI) { 445 | patientAPI.getAllPatientsAsync().then((data) => { 446 | this.patients = data; 447 | } 448 | ); 449 | } 450 | } 451 | ``` 452 | 453 | Finally we are going to bind the list into the layout using an ng-repeat 454 | and binding the fields to the given span. 455 | 456 | ```html 457 | 458 | {{patient.dni}} 459 | {{patient.name}} 460 | 461 | {{patient.specialty}} 462 | 465 | 466 | {{patient.doctor}} 467 | {{patient.date | date:'dd/MM/yyyy'}} 468 | 469 | {{patient.time | date:'hh:mm'}} 470 | 472 | 473 | 474 | 475 | ``` 476 | -------------------------------------------------------------------------------- /03 List Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular1_5-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Lemoncode/angular1_5-sample-app.git" 12 | }, 13 | "homepage": "https://github.com/Lemoncode/angular1_5-sample-app", 14 | "keywords": [ 15 | "angular", 16 | "sample", 17 | "app" 18 | ], 19 | "author": "Lemoncode", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@types/angular": "^1.6.7", 23 | "@types/angular-ui-router": "^1.1.36", 24 | "@types/es6-promise": "0.0.32", 25 | "@types/jquery": "^2.0.40", 26 | "copy-webpack-plugin": "^3.0.1", 27 | "css-loader": "^0.25.0", 28 | "extract-text-webpack-plugin": "^1.0.1", 29 | "file-loader": "^0.9.0", 30 | "html-webpack-plugin": "^2.22.0", 31 | "style-loader": "^0.13.1", 32 | "ts-loader": "^2.0.1", 33 | "typescript": "^2.2.1", 34 | "url-loader": "^0.5.7", 35 | "webpack": "^1.13.2", 36 | "webpack-dev-server": "^1.15.1" 37 | }, 38 | "dependencies": { 39 | "angular": "^1.5.8", 40 | "angular-ui-router": "^0.4.2", 41 | "bootstrap": "^3.3.7", 42 | "jquery": "^3.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /03 List Page/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | 3 | export class PatientAPI { 4 | public static $inject: Array = ["$http"]; 5 | 6 | private baseUrl: string = './mockData/patients.json'; 7 | 8 | constructor(private $http : angular.IHttpService) { 9 | 10 | } 11 | 12 | getAllPatientsAsync(): Promise> { 13 | return this.$http.get(this.baseUrl).then(response => response.data); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /03 List Page/src/app-routes.ts: -------------------------------------------------------------------------------- 1 | function routing($locationProvider: ng.ILocationProvider, 2 | $stateProvider: angular.ui.IStateProvider, 3 | $urlRouterProvider: angular.ui.IUrlRouterProvider) { 4 | 5 | // html5 removes the need for # in URL 6 | $locationProvider.html5Mode({ 7 | enabled: false 8 | }); 9 | 10 | $stateProvider.state('home', { 11 | url: '/home', 12 | views: { 13 | 'content@': { template: '' } 14 | } 15 | } 16 | ); 17 | 18 | $stateProvider.state('patients', { 19 | url: '/patients', 20 | views: { 21 | 'content@': { template: '' } 22 | } 23 | } 24 | ); 25 | 26 | $stateProvider.state('patient', { 27 | url: '/patient', 28 | views: { 29 | 'content@': { template: '' } 30 | } 31 | } 32 | ); 33 | 34 | $urlRouterProvider.otherwise('/home'); 35 | } 36 | 37 | export default routing; 38 | -------------------------------------------------------------------------------- /03 List Page/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class HeaderController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Testing binding header component"; 8 | } 9 | } 10 | 11 | export const header = { 12 | template: '

Header testing bindings: {{$ctrl.sampleBinding}}

', 13 | controller: HeaderController 14 | } 15 | -------------------------------------------------------------------------------- /03 List Page/src/components/login/login.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class LoginController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Login"; 8 | } 9 | } 10 | 11 | export const login = { 12 | template: ` 13 |

bindings test: {{$ctrl.sampleBinding}}

14 | Navigate to patients 15 | `, 16 | controller: LoginController 17 | } 18 | -------------------------------------------------------------------------------- /03 List Page/src/components/patient/patient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Patient"; 8 | } 9 | } 10 | 11 | export const patient = { 12 | template: ` 13 |

Bindings test: {{$ctrl.sampleBinding}}

14 | `, 15 | controller: PatientController 16 | } 17 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/patients.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientsController { 4 | constructor() { 5 | 6 | } 7 | } 8 | 9 | export const patients = { 10 | template: ` 11 |
12 |
13 | 14 | 15 |
16 |
17 | `, 18 | controller: PatientsController 19 | } 20 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/patientsList.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import {PatientAPI} from "../../api/patientAPI"; 3 | import {Patient} from '../../model/patient'; 4 | 5 | class PatientsListController { 6 | public static $inject: Array = ["PatientAPI"]; 7 | public patients : Array = [] 8 | 9 | constructor(patientAPI : PatientAPI) { 10 | patientAPI.getAllPatientsAsync().then((data) => { 11 | this.patients = data; 12 | } 13 | ); 14 | } 15 | } 16 | 17 | export const patientsList = { 18 | template: 19 | ` 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 58 | 59 | 60 |
PacienteEspecialidad
{{patient.name}} 45 | {{patient.specialty}} 46 | 49 |
61 |
62 |
63 | `, 64 | controller: PatientsListController 65 | } 66 | -------------------------------------------------------------------------------- /03 List Page/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class SearchPatientController { 4 | constructor() { 5 | } 6 | } 7 | 8 | export const searchPatient = { 9 | template: 10 | ` 11 |
12 |
13 |
14 | 15 |

Buscar paciente

16 | 18 | 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | `, 46 | controller: SearchPatientController 47 | } 48 | -------------------------------------------------------------------------------- /03 List Page/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /03 List Page/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 1.5 Sample App 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /03 List Page/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular' 2 | import 'angular-ui-router'; 3 | import routing from './app-routes'; 4 | import {header} from './components/common/header'; 5 | import {login} from './components/login/login'; 6 | import {patients} from './components/patients/patients'; 7 | import {patient} from './components/patient/patient'; 8 | import {searchPatient} from './components/patients/searchPatient'; 9 | import {patientsList} from './components/patients/patientsList'; 10 | import {PatientAPI} from './api/patientAPI'; 11 | 12 | var app = angular.module('myAppointmentsApp', ['ui.router']) 13 | .config(routing); 14 | 15 | app.service('PatientAPI', PatientAPI); 16 | 17 | app.component('header', header); 18 | app.component('login', login); 19 | app.component('patients', patients); 20 | app.component('patient', patient); 21 | app.component('searchPatient', searchPatient); 22 | app.component('patientsList', patientsList); 23 | -------------------------------------------------------------------------------- /03 List Page/src/mockData/patients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "dni": "1234567A", 5 | "name": "John Doe", 6 | "specialty": "Traumatology", 7 | "doctor": "Karl J. Linville", 8 | "date": "2017-02-15T00:00:00Z", 9 | "time": "2017-02-15T00:08:30Z" 10 | }, 11 | { 12 | "id": 2, 13 | "dni": "5067254B", 14 | "name": "Anna S. Batiste", 15 | "specialty": "Surgery", 16 | "doctor": "Gladys C. Horton", 17 | "date": "2017-02-15T00:00:00Z", 18 | "time": "2017-02-15T09:10:00Z" 19 | }, 20 | { 21 | "id": 3, 22 | "dni": "1902045C", 23 | "name": "Octavia L. Hilton", 24 | "specialty": "Traumatology", 25 | "doctor": "Karl J. Linville", 26 | "date": "2017-03-17T00:00:00Z", 27 | "time": "2017-03-17T10:10:00Z" 28 | }, 29 | { 30 | "id": 4, 31 | "dni": "1880514D", 32 | "name": "Tony M. Herrera", 33 | "specialty": "Ophthalmology", 34 | "doctor": "Ruthie A. Nemeth", 35 | "date": "2017-03-17T00:00:00Z", 36 | "time": "2017-03-17T11:00:00Z" 37 | }, 38 | { 39 | "id": 5, 40 | "dni": "6810774E", 41 | "name": "Robert J. Macias", 42 | "specialty": "Surgery", 43 | "doctor": "Gladys C. Horton", 44 | "date": "2017-02-15T00:00:00Z", 45 | "time": "2017-02-15T11:30:00Z" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /03 List Page/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Patient { 3 | id: number; 4 | dni: string; 5 | name: string; 6 | specialty: string; 7 | doctor: string; 8 | date: string; 9 | time: string; 10 | } 11 | -------------------------------------------------------------------------------- /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/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 CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['', '.js', '.ts'] 12 | }, 13 | 14 | entry: { 15 | app: './index.ts', 16 | styles: [ 17 | '../node_modules/bootstrap/dist/css/bootstrap.css', 18 | './css/site.css' 19 | ], 20 | vendor: [ 21 | 'bootstrap' 22 | ] 23 | }, 24 | 25 | output: { 26 | path: path.join(basePath, "dist"), 27 | filename: '[name].js' 28 | }, 29 | 30 | devServer: { 31 | contentBase: './dist', //Content base 32 | inline: true, //Enable watch and live reload 33 | host: 'localhost', 34 | port: 8080 35 | }, 36 | 37 | devtool: 'source-map', 38 | 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.ts$/, 43 | exclude: /node_modules/, 44 | loader: 'ts' 45 | }, 46 | { 47 | test: /\.css$/, 48 | loader: ExtractTextPlugin.extract('style','css') 49 | }, 50 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 51 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 52 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 53 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 54 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 55 | { 56 | test: /\.png$/, 57 | loader: 'file?limit=0&name=[path][name].[hash].', 58 | exclude: /node_modules/ 59 | } 60 | 61 | ] 62 | }, 63 | 64 | plugins: [ 65 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 66 | new ExtractTextPlugin('[name].css'), 67 | new HtmlWebpackPlugin({ 68 | filename: 'index.html', 69 | template: 'index.html' 70 | }), 71 | new webpack.ProvidePlugin({ 72 | $: "jquery", 73 | jQuery: "jquery" 74 | }), 75 | new CopyWebpackPlugin([ 76 | { from: 'mockData/*'}, 77 | ]) 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /04 Form Page/README.md: -------------------------------------------------------------------------------- 1 | # 04 Form Page 2 | 3 | In this sample we are going to build up an appoinment edit form. 4 | 5 | We are going to take as startup point _03 List Page 6 | 7 | # Summary steps: 8 | 9 | - Add navigation link. 10 | - Build the component layout. 11 | - Create API plumbing to retrieve data from a given appointment. 12 | - Load the data into the component controller and bind them to the HTML. 13 | - Trigger save on console log. 14 | 15 | # Steps to build it 16 | 17 | ## Prerequisites 18 | 19 | Prerequisites, you will need to have nodejs installed in your computer. If you want to follow this step guides you will need to take as starting point sample "03 List Page". 20 | 21 | ## Steps 22 | 23 | ### Navigation 24 | 25 | Let's start by adding a link navigation, whenever we click on a given appointment 26 | (in the list page) it should jump into the edit appointment entry. 27 | 28 | Since we are going to introduce a new route _patient/id_ let's add it into the 29 | router (_app-routes.ts_): 30 | 31 | ```javascript 32 | $stateProvider.state('patientEdit', { 33 | url: '/patient/{patientId:[0-9]{1,8}}', 34 | views: { 35 | 'content@': { template: '' } 36 | } 37 | } 38 | ); 39 | ``` 40 | 41 | Now in _ListPage.ts_ let's create a link that will point to that route: 42 | 43 | ```html 44 | 45 | {{patient.time}} 46 | 47 | 49 | 50 | 51 | 52 | ``` 53 | 54 | 55 | Let's run _npm start_ from the command line and check that the navigation is being 56 | performed: 57 | 58 | ```javascript 59 | npm start 60 | ``` 61 | 62 | 63 | ### Layout 64 | 65 | It's time to build the appointment edition layout, let's jump into the _patient/patient.ts_ file and replace 66 | the component template with the following one. 67 | 68 | ```html 69 |
70 |
71 |
72 |

Update Appointment

73 |
74 |
75 | 76 |
77 |
78 |
79 | 80 |
81 |
82 | 83 | 84 |
85 |
86 | 87 | 88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 |
96 |
97 | 98 | 99 |
100 |
101 | 102 | 104 |
105 |
106 | 107 | 109 |
110 |
111 |
112 | 113 |
114 |
115 |
116 |
117 |
118 | ``` 119 | 120 | ### Data 121 | 122 | Since we are just mocking data, we are going to add an entry to the _api/patientAPI_ that will load a single 123 | appointment, by passing as entry param it's ID (we will load the whole json file then filter in memory, **remark: 124 | this is just a mock dummy data layer, do not do this in a real project**). 125 | 126 | We will use promises for this, since angular needs it's special tricks to get on the $scope digest cycle, we 127 | cannot directly use ES6 promises, we have to use $q. 128 | 129 | Let's request this service into the patientAPI 130 | 131 | ```javascript 132 | export class PatientAPI { 133 | public static $inject: Array = ['$http', '$q']; 134 | 135 | private baseUrl: string = './mockData/patients.json'; 136 | 137 | constructor(private $http : angular.IHttpService, private $q : angular.IQService) { 138 | ``` 139 | 140 | 141 | ```javascript 142 | getPatientById(id: number) : Promise { 143 | const defer = this.$q.defer(); 144 | 145 | this.getAllPatientsAsync().then((patients) => { 146 | // refine this later one 147 | const nonTypedPatient = patients.filter( 148 | (patient) => { 149 | return (patient.id == id); 150 | } 151 | )[0]; 152 | 153 | const patient : Patient = nonTypedPatient; 154 | 155 | // Mapping should be placed in a separate map 156 | patient.date = new Date(nonTypedPatient.date); 157 | patient.time = new Date(nonTypedPatient.time) 158 | 159 | defer.resolve(patient); 160 | }); 161 | 162 | return defer.promise; 163 | } 164 | ``` 165 | 166 | ## Interaction 167 | 168 | In the _patient/patient.ts_ component we are going to load the appointment information by getting the Id from 169 | the route param, and the call the api _loadPatient_ method. 170 | 171 | ```javascript 172 | import * as angular from 'angular'; 173 | import {PatientAPI} from "../../api/patientAPI"; 174 | import {Patient} from '../../model/patient'; 175 | 176 | class PatientController { 177 | public static $inject: Array = ['PatientAPI', '$stateParams']; 178 | public patient : Patient = null; 179 | 180 | constructor(patientAPI : PatientAPI, $stateParams : angular.ui.IStateParamsService) { 181 | const patientId : number = $stateParams['patientId']; 182 | 183 | patientAPI.getPatientById(patientId).then((data) => { 184 | this.patient = data; 185 | }); 186 | 187 | } 188 | } 189 | ``` 190 | 191 | We have the data loaded in our component let's bind it and display it in our form (let's part of the 192 | _patient/patient.ts_ template content, not down: we are using ng-model directive to bind the forms controls 193 | to the patient/appointment info). 194 | 195 | Let's bind first the straight forward fields (ng-model inputs) 196 | 197 | ```html 198 |
199 | 200 | 205 |
206 |
207 | 208 | 213 |
214 |
215 | 216 |
217 |
218 | 219 | 224 |
225 |
226 | 227 | 232 |
233 | ``` 234 | 235 | Now let's jump into feeding dropdown like entries. 236 | 237 | ```html 238 |
239 | 240 | 246 |
247 |
248 | 249 | 255 |
256 | ``` 257 | 258 | Let's add an implementation for the save button: 259 | 260 | For this sample we will just dump the updated entity into the console log in order 261 | to do that we have to request angular IOC for the $log service 262 | 263 | ``` 264 | public static $inject: Array = ['PatientAPI', '$stateParams', '$log']; 265 | 266 | constructor(patientAPI : PatientAPI, 267 | $stateParams : angular.ui.IStateParamsService, 268 | private $log : angular.ILogService) { 269 | ``` 270 | 271 | 272 | ``` 273 | save() { 274 | this.$log.log(this.patient); 275 | } 276 | ``` 277 | 278 | And let's bind it to the button click event 279 | 280 | ``` 281 | 282 | ``` 283 | -------------------------------------------------------------------------------- /04 Form Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular1_5-sample-app", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 Sample App", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Lemoncode/angular1_5-sample-app.git" 12 | }, 13 | "homepage": "https://github.com/Lemoncode/angular1_5-sample-app", 14 | "keywords": [ 15 | "angular", 16 | "sample", 17 | "app" 18 | ], 19 | "author": "Lemoncode", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@types/angular": "^1.6.7", 23 | "@types/angular-ui-router": "^1.1.36", 24 | "@types/es6-promise": "0.0.32", 25 | "@types/jquery": "^2.0.40", 26 | "copy-webpack-plugin": "^3.0.1", 27 | "css-loader": "^0.25.0", 28 | "extract-text-webpack-plugin": "^1.0.1", 29 | "file-loader": "^0.9.0", 30 | "html-webpack-plugin": "^2.22.0", 31 | "style-loader": "^0.13.1", 32 | "ts-loader": "^2.0.1", 33 | "typescript": "^2.2.1", 34 | "url-loader": "^0.5.7", 35 | "webpack": "^1.13.2", 36 | "webpack-dev-server": "^1.15.1" 37 | }, 38 | "dependencies": { 39 | "angular": "^1.5.8", 40 | "angular-ui-router": "^0.4.2", 41 | "bootstrap": "^3.3.7", 42 | "jquery": "^3.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /04 Form Page/src/api/patientAPI.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../model/patient'; 2 | 3 | export class PatientAPI { 4 | public static $inject: Array = ['$http', '$q']; 5 | 6 | private baseUrl: string = './mockData/patients.json'; 7 | 8 | constructor(private $http : angular.IHttpService, private $q : angular.IQService) { 9 | 10 | } 11 | 12 | getAllPatientsAsync(): Promise> { 13 | return this.$http.get(this.baseUrl).then(response => response.data); 14 | }; 15 | 16 | getPatientById(id: number) : Promise { 17 | const defer = this.$q.defer(); 18 | 19 | this.getAllPatientsAsync().then((patients) => { 20 | // refine this later one 21 | const nonTypedPatient = patients.filter( 22 | (patient) => { 23 | return (patient.id == id); 24 | } 25 | )[0]; 26 | 27 | const patient : Patient = nonTypedPatient; 28 | 29 | // Mapping should be placed in a separate map 30 | patient.date = new Date(nonTypedPatient.date); 31 | patient.time = new Date(nonTypedPatient.time) 32 | 33 | defer.resolve(patient); 34 | }); 35 | 36 | return defer.promise; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /04 Form Page/src/app-routes.ts: -------------------------------------------------------------------------------- 1 | function routing($locationProvider: ng.ILocationProvider, 2 | $stateProvider: angular.ui.IStateProvider, 3 | $urlRouterProvider: angular.ui.IUrlRouterProvider) { 4 | 5 | // html5 removes the need for # in URL 6 | $locationProvider.html5Mode({ 7 | enabled: false 8 | }); 9 | 10 | $stateProvider.state('home', { 11 | url: '/home', 12 | views: { 13 | 'content@': { template: '' } 14 | } 15 | } 16 | ); 17 | 18 | $stateProvider.state('patients', { 19 | url: '/patients', 20 | views: { 21 | 'content@': { template: '' } 22 | } 23 | } 24 | ); 25 | 26 | $stateProvider.state('patient', { 27 | url: '/patient', 28 | views: { 29 | 'content@': { template: '' } 30 | } 31 | } 32 | ); 33 | 34 | $stateProvider.state('patientEdit', { 35 | url: '/patient/{patientId:[0-9]{1,8}}', 36 | views: { 37 | 'content@': { template: '' } 38 | } 39 | } 40 | ); 41 | 42 | 43 | $urlRouterProvider.otherwise('/home'); 44 | } 45 | 46 | export default routing; 47 | -------------------------------------------------------------------------------- /04 Form Page/src/components/common/header.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class HeaderController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Testing binding header component"; 8 | } 9 | } 10 | 11 | export const header = { 12 | template: '

Header testing bindings: {{$ctrl.sampleBinding}}

', 13 | controller: HeaderController 14 | } 15 | -------------------------------------------------------------------------------- /04 Form Page/src/components/login/login.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class LoginController { 4 | sampleBinding : string; 5 | 6 | constructor() { 7 | this.sampleBinding = "Hello from Login"; 8 | } 9 | } 10 | 11 | export const login = { 12 | template: ` 13 |

bindings test: {{$ctrl.sampleBinding}}

14 | Navigate to patients 15 | `, 16 | controller: LoginController 17 | } 18 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patient/patient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import {PatientAPI} from "../../api/patientAPI"; 3 | import {Patient} from '../../model/patient'; 4 | 5 | class PatientController { 6 | public static $inject: Array = ['PatientAPI', '$stateParams', '$log']; 7 | public patient : Patient = null; 8 | public specialties : Array; 9 | public doctors : Array; 10 | 11 | constructor(patientAPI : PatientAPI, 12 | $stateParams : angular.ui.IStateParamsService, 13 | private $log : angular.ILogService) { 14 | const patientId : number = $stateParams['patientId']; 15 | 16 | patientAPI.getPatientById(patientId).then((data) => { 17 | this.patient = data; 18 | }); 19 | 20 | // TODO: We could load this info form a service 21 | // and use id / value 22 | this.specialties = ['Traumatology', 'Surgery', 'Ophthalmology'] 23 | this.doctors = ['Karl J. Linville', 'Gladys C. Horton','Ruthie A. Nemeth'] 24 | // More info about how to bind combo's / lists... 25 | // https://docs.angularjs.org/api/ng/directive/select 26 | // https://docs.angularjs.org/api/ng/directive/ngOptions 27 | } 28 | 29 | save() { 30 | this.$log.log(this.patient); 31 | } 32 | } 33 | 34 | export const patient = { 35 | template: ` 36 |
37 |
38 |
39 |

Update Appointment

40 |
41 |
42 | 43 |
44 |
45 |
46 | 47 |
48 |
49 | 50 | 55 |
56 |
57 | 58 | 63 |
64 |
65 | 66 |
67 |
68 | 69 | 74 |
75 |
76 | 77 | 82 |
83 |
84 | 85 | 91 |
92 |
93 | 94 | 100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 |
109 | `, 110 | controller: PatientController 111 | } 112 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/patients.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientsController { 4 | constructor() { 5 | 6 | } 7 | } 8 | 9 | export const patients = { 10 | template: ` 11 |
12 |
13 | 14 | 15 |
16 |
17 | `, 18 | controller: PatientsController 19 | } 20 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/patientsList.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import {PatientAPI} from "../../api/patientAPI"; 3 | import {Patient} from '../../model/patient'; 4 | 5 | class PatientsListController { 6 | public static $inject: Array = ["PatientAPI"]; 7 | public patients : Array = [] 8 | 9 | constructor(patientAPI : PatientAPI) { 10 | patientAPI.getAllPatientsAsync().then((data) => { 11 | this.patients = data; 12 | } 13 | ); 14 | } 15 | } 16 | 17 | export const patientsList = { 18 | template: 19 | ` 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 60 | 61 | 62 |
PatientSpecialty
{{patient.name}} 45 | {{patient.specialty}} 46 | 49 |
63 |
64 |
65 | `, 66 | controller: PatientsListController 67 | } 68 | -------------------------------------------------------------------------------- /04 Form Page/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class SearchPatientController { 4 | constructor() { 5 | } 6 | } 7 | 8 | export const searchPatient = { 9 | template: 10 | ` 11 |
12 |
13 |
14 | 15 |

Buscar paciente

16 | 18 | 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | `, 46 | controller: SearchPatientController 47 | } 48 | -------------------------------------------------------------------------------- /04 Form Page/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /04 Form Page/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 1.5 Sample App 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /04 Form Page/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular' 2 | import 'angular-ui-router'; 3 | import routing from './app-routes'; 4 | import {header} from './components/common/header'; 5 | import {login} from './components/login/login'; 6 | import {patients} from './components/patients/patients'; 7 | import {patient} from './components/patient/patient'; 8 | import {searchPatient} from './components/patients/searchPatient'; 9 | import {patientsList} from './components/patients/patientsList'; 10 | import {PatientAPI} from './api/patientAPI'; 11 | 12 | var app = angular.module('myAppointmentsApp', ['ui.router']) 13 | .config(routing); 14 | 15 | app.service('PatientAPI', PatientAPI); 16 | 17 | app.component('header', header); 18 | app.component('login', login); 19 | app.component('patients', patients); 20 | app.component('patient', patient); 21 | app.component('searchPatient', searchPatient); 22 | app.component('patientsList', patientsList); 23 | -------------------------------------------------------------------------------- /04 Form Page/src/mockData/patients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "dni": "1234567A", 5 | "name": "John Doe", 6 | "specialty": "Traumatology", 7 | "doctor": "Karl J. Linville", 8 | "date": "2017-02-15T00:00:00Z", 9 | "time": "2017-02-15T00:08:30Z" 10 | }, 11 | { 12 | "id": 2, 13 | "dni": "5067254B", 14 | "name": "Anna S. Batiste", 15 | "specialty": "Surgery", 16 | "doctor": "Gladys C. Horton", 17 | "date": "2017-02-15T00:00:00Z", 18 | "time": "2017-02-15T09:10:00Z" 19 | }, 20 | { 21 | "id": 3, 22 | "dni": "1902045C", 23 | "name": "Octavia L. Hilton", 24 | "specialty": "Traumatology", 25 | "doctor": "Karl J. Linville", 26 | "date": "2017-03-17T00:00:00Z", 27 | "time": "2017-03-17T10:10:00Z" 28 | }, 29 | { 30 | "id": 4, 31 | "dni": "1880514D", 32 | "name": "Tony M. Herrera", 33 | "specialty": "Ophthalmology", 34 | "doctor": "Ruthie A. Nemeth", 35 | "date": "2017-03-17T00:00:00Z", 36 | "time": "2017-03-17T11:00:00Z" 37 | }, 38 | { 39 | "id": 5, 40 | "dni": "6810774E", 41 | "name": "Robert J. Macias", 42 | "specialty": "Surgery", 43 | "doctor": "Gladys C. Horton", 44 | "date": "2017-02-15T00:00:00Z", 45 | "time": "2017-02-15T11:30:00Z" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /04 Form Page/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Patient { 3 | id: number; 4 | dni: string; 5 | name: string; 6 | specialty: string; 7 | doctor: string; 8 | date: Date; 9 | time: Date; 10 | } 11 | -------------------------------------------------------------------------------- /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/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 CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['', '.js', '.ts'] 12 | }, 13 | 14 | entry: { 15 | app: './index.ts', 16 | styles: [ 17 | '../node_modules/bootstrap/dist/css/bootstrap.css', 18 | './css/site.css' 19 | ], 20 | vendor: [ 21 | 'bootstrap' 22 | ] 23 | }, 24 | 25 | output: { 26 | path: path.join(basePath, "dist"), 27 | filename: '[name].js' 28 | }, 29 | 30 | devServer: { 31 | contentBase: './dist', //Content base 32 | inline: true, //Enable watch and live reload 33 | host: 'localhost', 34 | port: 8080 35 | }, 36 | 37 | devtool: 'source-map', 38 | 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.ts$/, 43 | exclude: /node_modules/, 44 | loader: 'ts' 45 | }, 46 | { 47 | test: /\.css$/, 48 | loader: ExtractTextPlugin.extract('style','css') 49 | }, 50 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 51 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 52 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 53 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 54 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 55 | { 56 | test: /\.png$/, 57 | loader: 'file?limit=0&name=[path][name].[hash].', 58 | exclude: /node_modules/ 59 | } 60 | 61 | ] 62 | }, 63 | 64 | plugins: [ 65 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 66 | new ExtractTextPlugin('[name].css'), 67 | new HtmlWebpackPlugin({ 68 | filename: 'index.html', 69 | template: 'index.html' 70 | }), 71 | new webpack.ProvidePlugin({ 72 | $: "jquery", 73 | jQuery: "jquery" 74 | }), 75 | new CopyWebpackPlugin([ 76 | { from: 'mockData/*'}, 77 | ]) 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /05 Form Validation/README.md: -------------------------------------------------------------------------------- 1 | # 05 Form Validation 2 | 3 | In this sample we are going to add validation to the appoint form that we have 4 | previously created. 5 | 6 | We are going to take as startup point _04 Form Page_ 7 | 8 | # Summary steps: 9 | 10 | - Add message validation support. 11 | - Add basic validations. 12 | - Add a custom validation (NIF). 13 | 14 | # Steps to build it 15 | 16 | ## Prerequisites 17 | 18 | Prerequisites, you will need to have nodejs installed in your computer. If you want 19 | to follow this step guides you will need to take as starting point sample "04 From Page". 20 | 21 | ## Steps 22 | 23 | ### Adding libraries 24 | 25 | First we are going to install and add to the project an angular library to 26 | display error messages (angular-messages). In this case we need to make some special 27 | tweaking 28 | 29 | Install packages: 30 | 31 | 32 | ``` 33 | npm install angular-messages --save 34 | ``` 35 | 36 | ``` 37 | npm install @types/node --save-dev 38 | ``` 39 | 40 | 41 | Let's include this dependency in our project (_index.ts_). 42 | 43 | 44 | ```javascript 45 | const angMessages = require('angular-messages'); 46 | var app = angular.module('myAppointmentsApp', ['ui.router',angMessages]).config(routing); 47 | ``` 48 | 49 | 50 | 51 | ### Adding basic validation 52 | 53 | Let's start by making the NIF field required and notify the user in case this 54 | validation fails. 55 | 56 | First we need to add a name to the form element, then add a name as well to 57 | the input that will hold the input tag. 58 | 59 | ```html 60 |
61 | ``` 62 | 63 | ```html 64 | 70 | 71 | ``` 72 | 73 | Now we can add the validation and the error message 74 | 75 | ```html 76 | 83 | 84 |
85 |
You did not enter a field
86 |
87 | ``` 88 | 89 | _Note: we can get this more generic by using templates [documentation](https://docs.angularjs.org/api/ngMessages/directive/ngMessages)_ 90 | 91 | We can disable the save button in case there are errors: 92 | 93 | ```html 94 | 119 | 120 | 121 |
122 | 123 | 124 | `, 125 | controller: PatientController 126 | } 127 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/patients.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class PatientsController { 4 | constructor() { 5 | 6 | } 7 | } 8 | 9 | export const patients = { 10 | template: ` 11 |
12 |
13 | 14 | 15 |
16 |
17 | `, 18 | controller: PatientsController 19 | } 20 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/patientsList.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import {PatientAPI} from "../../api/patientAPI"; 3 | import {Patient} from '../../model/patient'; 4 | 5 | class PatientsListController { 6 | public static $inject: Array = ["PatientAPI"]; 7 | public patients : Array = [] 8 | 9 | constructor(patientAPI : PatientAPI) { 10 | patientAPI.getAllPatientsAsync().then((data) => { 11 | this.patients = data; 12 | } 13 | ); 14 | } 15 | } 16 | 17 | export const patientsList = { 18 | template: 19 | ` 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 60 | 61 | 62 |
PatientSpecialty
{{patient.name}} 45 | {{patient.specialty}} 46 | 49 |
63 |
64 |
65 | `, 66 | controller: PatientsListController 67 | } 68 | -------------------------------------------------------------------------------- /05 Form Validation/src/components/patients/searchPatient.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | class SearchPatientController { 4 | constructor() { 5 | } 6 | } 7 | 8 | export const searchPatient = { 9 | template: 10 | ` 11 |
12 |
13 |
14 | 15 |

Buscar paciente

16 | 18 | 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | `, 46 | controller: SearchPatientController 47 | } 48 | -------------------------------------------------------------------------------- /05 Form Validation/src/css/site.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #ACDF2C 3 | } 4 | -------------------------------------------------------------------------------- /05 Form Validation/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 1.5 Sample App 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /05 Form Validation/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular' 2 | import 'angular-ui-router'; 3 | import routing from './app-routes'; 4 | import {header} from './components/common/header'; 5 | import {login} from './components/login/login'; 6 | import {patients} from './components/patients/patients'; 7 | import {patient} from './components/patient/patient'; 8 | import {searchPatient} from './components/patients/searchPatient'; 9 | import {patientsList} from './components/patients/patientsList'; 10 | import {PatientAPI} from './api/patientAPI'; 11 | import {ValidateDni} from './validations/validateDni'; 12 | 13 | const angMessages = require('angular-messages'); 14 | 15 | 16 | var app = angular.module('myAppointmentsApp', ['ui.router',angMessages]).config(routing); 17 | 18 | 19 | app.service('PatientAPI', PatientAPI); 20 | 21 | app.directive('validateDni', ValidateDni.Factory()); 22 | 23 | app.component('header', header); 24 | app.component('login', login); 25 | app.component('patients', patients); 26 | app.component('patient', patient); 27 | app.component('searchPatient', searchPatient); 28 | app.component('patientsList', patientsList); 29 | -------------------------------------------------------------------------------- /05 Form Validation/src/mockData/patients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "dni": "1234567A", 5 | "name": "John Doe", 6 | "specialty": "Traumatology", 7 | "doctor": "Karl J. Linville", 8 | "date": "2017-02-15T00:00:00Z", 9 | "time": "2017-02-15T00:08:30Z" 10 | }, 11 | { 12 | "id": 2, 13 | "dni": "5067254B", 14 | "name": "Anna S. Batiste", 15 | "specialty": "Surgery", 16 | "doctor": "Gladys C. Horton", 17 | "date": "2017-02-15T00:00:00Z", 18 | "time": "2017-02-15T09:10:00Z" 19 | }, 20 | { 21 | "id": 3, 22 | "dni": "1902045C", 23 | "name": "Octavia L. Hilton", 24 | "specialty": "Traumatology", 25 | "doctor": "Karl J. Linville", 26 | "date": "2017-03-17T00:00:00Z", 27 | "time": "2017-03-17T10:10:00Z" 28 | }, 29 | { 30 | "id": 4, 31 | "dni": "1880514D", 32 | "name": "Tony M. Herrera", 33 | "specialty": "Ophthalmology", 34 | "doctor": "Ruthie A. Nemeth", 35 | "date": "2017-03-17T00:00:00Z", 36 | "time": "2017-03-17T11:00:00Z" 37 | }, 38 | { 39 | "id": 5, 40 | "dni": "6810774E", 41 | "name": "Robert J. Macias", 42 | "specialty": "Surgery", 43 | "doctor": "Gladys C. Horton", 44 | "date": "2017-02-15T00:00:00Z", 45 | "time": "2017-02-15T11:30:00Z" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /05 Form Validation/src/model/patient.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Patient { 3 | id: number; 4 | dni: string; 5 | name: string; 6 | specialty: string; 7 | doctor: string; 8 | date: Date; 9 | time: Date; 10 | } 11 | -------------------------------------------------------------------------------- /05 Form Validation/src/validations/validateDni.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class ValidateDni implements ng.IDirective{ 4 | public link: (scope: angular.IScope , elem: ng.IAugmentedJQuery, attrs: angular.IAttributes, ngModel: angular.INgModelController) => void; 5 | restrict ='A'; 6 | require = 'ngModel'; 7 | 8 | 9 | constructor(scope: angular.IScope, elem:ng.IAugmentedJQuery, attrs: angular.IAttributes, ngModel: angular.INgModelController, $log:angular.ILogService) 10 | { 11 | // It's important to add `link` to the prototype or you will end up with state issues. 12 | // See http://blog.aaronholmes.net/writing-angularjs-directives-as-typescript-classes/#comment-2111298002 for more information. 13 | ValidateDni.prototype.link = (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModel: angular.INgModelController) => 14 | { 15 | 16 | if (!ngModel) { 17 | $log.warn("empty model found"); 18 | return; 19 | } 20 | 21 | // Moving to 1.5 validation: http://codepen.io/transistor1/pen/pgXqNo 22 | ngModel.$validators['validateDni'] = function(dni) { 23 | return validateDNI(dni); 24 | } 25 | 26 | function validateDNI(dni) 27 | { 28 | var lockup = 'TRWAGMYFPDXBNJZSQVHLCKE'; 29 | var valueDni=dni.substr(0,dni.length-1); 30 | var letra=dni.substr(dni.length-1,1).toUpperCase(); 31 | 32 | if(lockup.charAt(valueDni % 23)==letra) 33 | return true; 34 | return false; 35 | } 36 | }; 37 | } 38 | 39 | 40 | public static Factory() 41 | { 42 | var directive = (scope: angular.IScope , elem, attrs: angular.IAttributes, ngModel: angular.INgModelController,$log:angular.ILogService) => 43 | { 44 | return new ValidateDni(scope, elem, attrs, ngModel, $log); 45 | }; 46 | 47 | directive['$inject'] = ['$log']; 48 | return directive; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /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/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 CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['', '.js', '.ts'] 12 | }, 13 | 14 | entry: { 15 | app: './index.ts', 16 | styles: [ 17 | '../node_modules/bootstrap/dist/css/bootstrap.css', 18 | './css/site.css' 19 | ], 20 | vendor: [ 21 | 'bootstrap' 22 | ] 23 | }, 24 | 25 | output: { 26 | path: path.join(basePath, "dist"), 27 | filename: '[name].js' 28 | }, 29 | 30 | devServer: { 31 | contentBase: './dist', //Content base 32 | inline: true, //Enable watch and live reload 33 | host: 'localhost', 34 | port: 8080 35 | }, 36 | 37 | devtool: 'source-map', 38 | 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.ts$/, 43 | exclude: /node_modules/, 44 | loader: 'ts' 45 | }, 46 | { 47 | test: /\.css$/, 48 | loader: ExtractTextPlugin.extract('style','css') 49 | }, 50 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 51 | {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 52 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 53 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 54 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 55 | { 56 | test: /\.png$/, 57 | loader: 'file?limit=0&name=[path][name].[hash].', 58 | exclude: /node_modules/ 59 | } 60 | 61 | ] 62 | }, 63 | 64 | plugins: [ 65 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), 66 | new ExtractTextPlugin('[name].css'), 67 | new HtmlWebpackPlugin({ 68 | filename: 'index.html', 69 | template: 'index.html' 70 | }), 71 | new webpack.ProvidePlugin({ 72 | $: "jquery", 73 | jQuery: "jquery" 74 | }), 75 | new CopyWebpackPlugin([ 76 | { from: 'mockData/*'}, 77 | ]) 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /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 | # angular1_5-sample-app 2 | 3 | Simple LOB app includes login, list, form edit 4 | 5 | Each sample will contains a readme.md file including an step by step guide to recreate it. 6 | 7 | List of samples: 8 | 9 | - 00 Boiler plate 10 | - 00 Hello Angular 11 | - 01 Navigation 12 | - 02 List page. 13 | - 03 Form page 14 | - 04 Form Validation 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 | --------------------------------------------------------------------------------