├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── gulpfile.js ├── hero.png ├── package.json ├── src ├── app │ ├── app.module.ts │ ├── counter │ │ ├── debug-panel │ │ │ └── debug-panel.component.ts │ │ ├── order-sheet │ │ │ ├── order-sheet.component.css │ │ │ ├── order-sheet.component.html │ │ │ └── order-sheet.component.ts │ │ └── shared │ │ │ └── custom-validators.ts │ ├── main.ts │ ├── sando-app.component.css │ ├── sando-app.component.html │ └── sando-app.component.ts ├── assets │ ├── GloriaHallelujah.ttf │ ├── GloriaHallelujah.woff2 │ └── OFL.txt ├── favicon.ico ├── icon.png ├── index.html ├── manifest.webapp └── systemjs.config.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | 6 | # dependencies 7 | /node_modules 8 | 9 | # IDEs and editors 10 | /.idea 11 | 12 | # misc 13 | npm-debug.log 14 | /typings 15 | 16 | #System Files 17 | .DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.DS_Store": true, 6 | "dist": true, 7 | "node_modules": true 8 | }, 9 | "typescript.tsdk": "node_modules/typescript/lib" 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Justin Schwartzenberger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 Forms: Data Binding and Validation 2 | 3 | [![Angular 2 Forms: Data Binding and Validation](hero.png)](https://www.linkedin.com/learning/angular-2-forms-data-binding-and-validation) 4 | 5 | This is the repository for my course, [Angular 2 Forms: Data Binding and Validation](https://www.linkedin.com/learning/angular-2-forms-data-binding-and-validation). 6 | The full course is available at [LinkedIn Learning](https://www.linkedin.com/learning/angular-2-forms-data-binding-and-validation). 7 | 8 | ## Course Description 9 | 10 | Building HTML forms in the web world is never as straightforward as expected. Collecting user input and implementing dynamic form validation, 11 | that automatically reacts to input, can be difficult without the right solution. When building client side apps using Angular you get a good 12 | amount of framework code out of the box that makes working with forms a breeze. Angular allows you to easily handle dynamic form validation 13 | by taking advantage of two-way data-binding functionality. 14 | 15 | This course shows how to build HTML web forms with the Angular forms module, using Angular form building blocks and built-in validation 16 | properties and methods. Learn how to use the reactive (model-driven) forms approach to build out a model representation of form data in code and 17 | bind it to native HTML form elements. This course also explores how to implement validation, using JavaScript, in case you need more 18 | fine tuned control of your interactions. 19 | 20 | ## Instructions 21 | 22 | 1. Make sure you have these installed 23 | - [node.js](http://nodejs.org/) 24 | - [git](http://git-scm.com/) 25 | - [gulp](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md) 26 | 2. Clone this repository into your local machine using the terminal (mac) or Gitbash (PC) 27 | 28 | `git clone https://github.com/coursefiles/angular2-reactive-forms-data-and-validation.git` 29 | 30 | 3. CD to the folder 31 | 32 | `cd angular2-reactive-forms-data-and-validation` 33 | 34 | 4. Run the following to install the project dependencies: 35 | 36 | `npm install` 37 | 38 | 5. Run the npm start command to build the code, watch for file changes, and serve up the site locally: 39 | 40 | `npm start` 41 | 42 | 43 | The repository has a branch for each video starting point. For example, the branch **02-01b** is used as the starting code for the video *02-01 Input type text*. You can checkout branches using `git checkout ` and not have to re-run `npm install` each time since you will remain in the same root folder. 44 | 45 | Note that the site will run using `gulp-webserver` and will be served up at the following local address: 46 | http://localhost:8000/ 47 | 48 | *If you use a code editor that launches its own web server please note that it may run on a different port number. 49 | You will want to use `npm start` for this project.* 50 | 51 | ## More Stuff 52 | Check out some of my [other courses on lynda.com](https://lynda.com/justinschwartzenberger). 53 | You can also [follow me on twitter](https://twitter.com/schwarty), or read [my blog](http://schwarty.com). 54 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | sourcemaps = require('gulp-sourcemaps'), 3 | typescript = require('gulp-typescript'), 4 | webserver = require('gulp-webserver'), 5 | tscConfig = require('./tsconfig.json'), 6 | del = require('del'), 7 | runSequence = require('run-sequence'); 8 | 9 | var appSrc = './dist/'; 10 | var tsProject = typescript.createProject('tsconfig.json'); 11 | 12 | gulp.task('build', function (callback) { 13 | runSequence('clean', ['copylibs', 'copystatic', 'typescript'], callback); 14 | }); 15 | 16 | gulp.task('clean', function () { 17 | return del(appSrc); 18 | }); 19 | 20 | gulp.task('copylibs', function () { 21 | return gulp 22 | .src([ 23 | 'node_modules/systemjs/dist/system-polyfills.js', 24 | 'node_modules/systemjs/dist/system.src.js', 25 | 'node_modules/zone.js/dist/*.js', 26 | 'node_modules/core-js/**/*.js', 27 | 'node_modules/reflect-metadata/*.js', 28 | 'node_modules/rxjs/**/*.js', 29 | 'node_modules/@angular/**/*.js' 30 | ], { base: 'node_modules' }) 31 | .pipe(gulp.dest(appSrc + 'vendor')); 32 | }); 33 | 34 | gulp.task('typescript', function () { 35 | return gulp 36 | .src(['src/**/*.ts']) 37 | .pipe(sourcemaps.init()) 38 | .pipe(tsProject()) 39 | .pipe(sourcemaps.write('.')) 40 | .pipe(gulp.dest(appSrc)); 41 | }); 42 | 43 | var staticFiles = [ 44 | 'src/**/*.html', 45 | 'src/**/*.css', 46 | 'src/**/*.ico', 47 | 'src/**/*.png', 48 | 'src/**/*.svg', 49 | 'src/**/*.jpg', 50 | 'src/**/*.ttf', 51 | 'src/**/*.woff2', 52 | 'src/**/*.webapp', 53 | 'src/systemjs.config.js' 54 | ]; 55 | gulp.task('copystatic', function () { 56 | return gulp 57 | .src(staticFiles) 58 | .pipe(gulp.dest(appSrc)); 59 | }); 60 | 61 | gulp.task('watch', function () { 62 | gulp.watch('src/**/*.ts', ['typescript']); 63 | gulp.watch(staticFiles, ['copystatic']); 64 | }); 65 | 66 | gulp.task('webserver', function () { 67 | gulp.src(appSrc) 68 | .pipe(webserver({ 69 | livereload: true, 70 | open: true 71 | })); 72 | }); 73 | 74 | gulp.task('default', function (callback) { 75 | runSequence('clean', 'build', ['watch', 'webserver'], callback); 76 | }); -------------------------------------------------------------------------------- /hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coursefiles/angular2-reactive-forms-data-and-validation/75a143efbca0f98212292ecd681f05bc172a3b18/hero.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-series-app-forms", 3 | "version": "1.0.0", 4 | "author": "Justin Schwartzenberger", 5 | "description": "This project is the repository for my Angular 2 Forms: Data Binding and Validation course on Lynda.com.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/coursefiles/angular2-reactive-forms-data-and-validation.git" 9 | }, 10 | "scripts": { 11 | "start": "gulp" 12 | }, 13 | "dependencies": { 14 | "@angular/common": "2.0.0", 15 | "@angular/compiler": "2.0.0", 16 | "@angular/core": "2.0.0", 17 | "@angular/forms": "2.0.0", 18 | "@angular/http": "2.0.0", 19 | "@angular/platform-browser": "2.0.0", 20 | "@angular/platform-browser-dynamic": "2.0.0", 21 | "systemjs": "0.19.27", 22 | "core-js": "2.4.1", 23 | "reflect-metadata": "0.1.3", 24 | "rxjs": "5.0.0-beta.12", 25 | "zone.js": "0.6.23" 26 | }, 27 | "devDependencies": { 28 | "@types/core-js": "^0.9.34", 29 | "@types/node": "^6.0.41", 30 | "del": "2.2.1", 31 | "gulp": "3.9.1", 32 | "gulp-sourcemaps": "1.6.0", 33 | "gulp-typescript": "3.0.2", 34 | "gulp-webserver": "0.9.1", 35 | "run-sequence": "1.2.1", 36 | "typescript": "2.0.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { SandoAppComponent } from './sando-app.component'; 6 | import { OrderSheetComponent } from './counter/order-sheet/order-sheet.component'; 7 | import { DebugPanelComponent } from './counter/debug-panel/debug-panel.component'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | BrowserModule, 12 | ReactiveFormsModule 13 | ], 14 | declarations: [ 15 | SandoAppComponent, 16 | OrderSheetComponent, 17 | DebugPanelComponent 18 | ], 19 | bootstrap: [ 20 | SandoAppComponent 21 | ] 22 | }) 23 | export class AppModule { } 24 | -------------------------------------------------------------------------------- /src/app/counter/debug-panel/debug-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, HostBinding, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | changeDetection: ChangeDetectionStrategy.OnPush, 5 | selector: 'debug-panel', 6 | template: ` 7 | 8 | 9 |
10 |
{{ data | json }}
11 |
12 | `, 13 | styles: [` 14 | :host { 15 | display: none; 16 | } 17 | :host.has-content { 18 | display: block; 19 | background-color: rgba(237, 119, 119, .9); 20 | position: fixed; 21 | top: 0; 22 | right: 0; 23 | } 24 | :host.is-visible { 25 | bottom: 0; 26 | min-width: 50%; 27 | } 28 | input[type=checkbox] { 29 | display: none; 30 | } 31 | label { 32 | display: block; 33 | text-align: center; 34 | height: 1.6em; 35 | padding: .4em; 36 | line-height: 1.3em; 37 | } 38 | label:before { 39 | content: "show debug"; 40 | width: 100%; 41 | cursor: pointer; 42 | } 43 | input[type=checkbox]:checked+label:before { 44 | content: "hide debug"; 45 | } 46 | :host.is-visible div { 47 | display: block; 48 | height: calc(100% - 1.6em); 49 | } 50 | div { 51 | display: none; 52 | overflow: auto; 53 | } 54 | pre { 55 | font-size: 2em; 56 | padding: 20px; 57 | margin: 0; 58 | } 59 | `] 60 | }) 61 | export class DebugPanelComponent { 62 | @Input() data; 63 | @HostBinding('class.has-content') get content() { return this.hasContent; } 64 | hasContent = false; 65 | @HostBinding('class.is-visible') get visible() { return this.isVisible; } 66 | isVisible = false; 67 | 68 | constructor() { 69 | this.isVisible = localStorage.getItem('debugIsVisible') === 'true'; 70 | } 71 | 72 | ngOnInit() { 73 | this.hasContent = (this.data); 74 | } 75 | 76 | onSaveState(){ 77 | localStorage.setItem('debugIsVisible', this.isVisible.toString()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/counter/order-sheet/order-sheet.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 2em; 4 | margin-bottom: 40px; 5 | } 6 | ul { 7 | list-style: none; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | ul > li { 12 | margin-bottom: 4px; 13 | } 14 | h2 { 15 | font-size: 3em; 16 | padding: 0; 17 | margin: 0; 18 | color: rgb(148, 192, 204); 19 | } 20 | form > section { 21 | margin-bottom: 2em; 22 | } 23 | form > section > h1 { 24 | font-size: 2em; 25 | padding: 10px; 26 | text-transform: uppercase; 27 | border-bottom: 4px solid rgba(244, 244, 240, 1); 28 | position: relative; 29 | margin-bottom: 1em; 30 | } 31 | form > section > h1:before { 32 | content: " "; 33 | position: absolute; 34 | border-bottom: 3px dotted rgba(244, 244, 240, 1); 35 | width: 100%; 36 | bottom: -14px; 37 | left: 0; 38 | } 39 | form > section > section > h1 { 40 | text-transform: uppercase; 41 | font-size: 1.6em; 42 | } 43 | form > section > section { 44 | display: flex; 45 | flex-direction: column; 46 | } 47 | label { 48 | font-size: 1.4em; 49 | cursor: pointer; 50 | } 51 | label:hover, input:checked+label { 52 | color: #fad48b; 53 | } 54 | input[type=text], 55 | textarea, 56 | select, 57 | select option { 58 | font-family: 'Gloria Hallelujah', cursive; 59 | } 60 | input[type=text], 61 | textarea { 62 | border: 0; 63 | font-size: 1.6em; 64 | padding: 4px; 65 | color: #fad48b; 66 | background-color: transparent; 67 | } 68 | input[type=text] { 69 | border-bottom: 2px solid rgba(244, 244, 240, 0.6); 70 | } 71 | input[type=text]:focus, 72 | textarea:focus, 73 | select:focus { 74 | outline: none; 75 | } 76 | textarea { 77 | border: 2px solid rgba(244, 244, 240, 0.6); 78 | width: 98%; 79 | height: 150px; 80 | } 81 | select { 82 | border: 0; 83 | border-bottom: 2px solid rgba(244, 244, 240, 0.6);; 84 | font-size: 1.6em; 85 | padding: 4px; 86 | cursor: pointer; 87 | color: #fad48b; 88 | background-color: transparent; 89 | } 90 | select option { 91 | background-color: #262223; 92 | } 93 | input[type=checkbox], input[type=radio] { 94 | width: 28px; 95 | height: 28px; 96 | position: relative; 97 | margin: 20px auto; 98 | } 99 | button { 100 | font-family: 'Gloria Hallelujah', cursive; 101 | cursor: pointer; 102 | position: relative; 103 | font-size: 2em; 104 | border: 2px solid rgb(250, 212, 139); 105 | border-radius: 8px; 106 | background-color: rgb(250, 212, 139); 107 | color: #262223; 108 | padding: 12px; 109 | } 110 | button:before { 111 | content: ' '; 112 | position: absolute; 113 | border: 4px solid #262223; 114 | border-radius: 8px; 115 | top: 2px; 116 | left: 2px; 117 | right: 2px; 118 | bottom: 2px; 119 | } 120 | button:hover { 121 | background-color: rgb(148, 192, 204); 122 | border-color: rgb(148, 192, 204); 123 | } 124 | button:focus { 125 | outline: none; 126 | } 127 | .customer-name input[type=text] { 128 | width: 100%; 129 | } 130 | .customer-name input[type=text].ng-untouched.ng-pristine { 131 | border-width: 4px; 132 | border-color: rgb(250, 212, 139); 133 | } 134 | .welcome-back { 135 | font-size: 1.6em; 136 | color: #262223; 137 | background-color: rgba(188, 223, 138, 1); 138 | padding: 0 1em; 139 | border-radius: 12px; 140 | position: fixed; 141 | bottom: 14px; 142 | right: 14px; 143 | z-index: 2; 144 | } 145 | .size ul > li { 146 | display: inline-block; 147 | margin-right: 2em; 148 | } 149 | .weird-requests button { 150 | font-size: 1em; 151 | } 152 | .weird-requests input[type=text] { 153 | font-size: 1.6em; 154 | width: 80%; 155 | width: calc(100% - 6em); 156 | } 157 | .specialty-sandwiches label { 158 | font-size: 1.6em; 159 | } 160 | .build-your-own { 161 | display: flex; 162 | flex-flow: row wrap; 163 | justify-content: space-around; 164 | } 165 | .build-your-own > h1, .build-your-own > section { 166 | flex: 1 100%; 167 | } 168 | @media all and (min-width: 949px) { 169 | .build-your-own > section { flex: 1 auto; } 170 | } 171 | @media only screen and (max-width: 948px) { 172 | .build-your-own > section { flex: 1 50%; } 173 | } 174 | @media only screen and (max-width: 565px) { 175 | :host { 176 | font-size: .6em; 177 | } 178 | input[type=checkbox], input[type=radio] { 179 | width: 20px; 180 | height: 20px; 181 | margin: 14px auto; 182 | } 183 | } 184 | @media only screen and (max-width: 425px) { 185 | .build-your-own > section { 186 | flex: 1 100%; 187 | } 188 | .weird-requests input[type=text] { 189 | width: calc(100% - 8em); 190 | } 191 | .weird-requests button { 192 | font-size: 1.6em; 193 | } 194 | button { 195 | padding: 2px; 196 | } 197 | button:before { 198 | border: 0; 199 | font-size: 2em; 200 | } 201 | } 202 | 203 | 204 | .errors { 205 | color: rgba(237, 119, 119, 1); 206 | font-size: 1.6em; 207 | } 208 | input[type=text].ng-invalid, 209 | select.ng-invalid, 210 | textarea.ng-invalid { 211 | border-width: 3px; 212 | border-bottom-color: rgba(237, 119, 119, 1); 213 | } 214 | textarea.ng-invalid { 215 | border-color: rgba(237, 119, 119, 1); 216 | } 217 | .ng-invalid+label { 218 | color: rgba(237, 119, 119, 1); 219 | } 220 | button:disabled { 221 | background-color: rgba(246, 244, 241, 1); 222 | border-color: rgba(246, 244, 241, 1); 223 | cursor: default; 224 | color: rgba(65, 65, 67, .8); 225 | } 226 | button:disabled:before { 227 | border-color: rgba(65, 65, 67, .8); 228 | } -------------------------------------------------------------------------------- /src/app/counter/order-sheet/order-sheet.component.html: -------------------------------------------------------------------------------- 1 |

Order Sheet

2 |
3 |
4 | 5 |
6 |
7 | Required! 8 |
9 |
10 | Must be at least {{customerNameControl.errors.minlength.requiredLength}} letters 11 |
12 |
13 |
14 | Welcome back {{customerNameControl.value}} 15 |
16 |
17 |
18 |

Pick your size

19 |
    20 |
  • 21 | 22 | 23 |
  • 24 |
  • 25 | 26 | 27 |
  • 28 |
29 |
30 |
31 |

Specialty Sandwiches

32 | 33 | 39 |
40 |
41 |

Build Your Own

42 |
43 |

A Bread

44 |
    45 |
  • 46 | 47 | 48 |
  • 49 |
  • 50 | 51 | 52 |
  • 53 |
  • 54 | 55 | 56 |
  • 57 |
58 |
59 |
60 | Let us know what kind of bread you like! 61 |
62 |
63 |
64 |
65 |

The Meats

66 |
    67 |
  • 68 | 69 | 70 |
  • 71 |
  • 72 | 73 | 74 |
  • 75 |
  • 76 | 77 | 78 |
  • 79 |
80 |
81 |
82 |

The Cheeses

83 |
    84 |
  • 85 | 86 | 87 |
  • 88 |
  • 89 | 90 | 91 |
  • 92 |
  • 93 | 94 | 95 |
  • 96 |
97 |
98 |
99 |

Veggies 'n Such

100 |
    101 |
  • 102 | 103 | 104 |
  • 105 |
  • 106 | 107 | 108 |
  • 109 |
  • 110 | 111 | 112 |
  • 113 |
114 |
115 |
116 |
117 |

Weird Requests

118 |
    119 |
  • 120 | 121 | 122 |
  • 123 |
124 | 125 |
126 |
127 |

Other Notes

128 | 129 |
130 | 131 | 132 |
133 | -------------------------------------------------------------------------------- /src/app/counter/order-sheet/order-sheet.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormGroup, FormBuilder, FormControl, FormArray, Validators } from '@angular/forms'; 3 | 4 | import { CustomValidators } from '../shared/custom-validators'; 5 | 6 | @Component({ 7 | selector: 'order-sheet', 8 | templateUrl: 'app/counter/order-sheet/order-sheet.component.html', 9 | styleUrls: ['app/counter/order-sheet/order-sheet.component.css'] 10 | }) 11 | export class OrderSheetComponent { 12 | orderSheetForm: FormGroup; 13 | weirdRequestsControls: FormArray; 14 | showWelcomeMessage = false; 15 | customerNameControl; 16 | 17 | constructor(private formBuilder: FormBuilder) { 18 | this.buildForm(); 19 | } 20 | 21 | private buildForm() { 22 | this.orderSheetForm = this.formBuilder.group({ 23 | customerName: this.formBuilder.control(null, [Validators.required, Validators.minLength(2)]), 24 | size: this.formBuilder.control(null), 25 | bread: this.formBuilder.control(null), 26 | specialtySandwich: this.formBuilder.control(null), 27 | weirdRequests: this.formBuilder.array([ 28 | this.formBuilder.control(null) 29 | ]), 30 | otherNotes: this.formBuilder.control(null), 31 | meats: this.formBuilder.group({ 32 | meatHam: this.formBuilder.control(null), 33 | meatTurkey: this.formBuilder.control(null), 34 | meatRoastBeef: this.formBuilder.control(null) 35 | }), 36 | cheeses: this.formBuilder.group({ 37 | cheeseProvolone: this.formBuilder.control(null), 38 | cheeseCheddar: this.formBuilder.control(null), 39 | cheeseSwiss: this.formBuilder.control(null) 40 | }), 41 | veggiesAndSuch: this.formBuilder.group({ 42 | veggieLettuce: this.formBuilder.control(null), 43 | veggieTomato: this.formBuilder.control(null), 44 | veggieMustard: this.formBuilder.control(null) 45 | }) 46 | }, 47 | { 48 | validator: CustomValidators.requiredWhen('bread', 'specialtySandwich') 49 | }); 50 | this.weirdRequestsControls = this.orderSheetForm.get('weirdRequests') as FormArray; 51 | this.customerNameControl = this.orderSheetForm.get('customerName'); 52 | this.customerNameControl.valueChanges 53 | .subscribe(value => { 54 | this.showWelcomeMessage = value && value.toLowerCase().trim() === 'justin s.'; 55 | }); 56 | } 57 | 58 | onAddWeirdRequest() { 59 | this.weirdRequestsControls.push(this.formBuilder.control(null)); 60 | } 61 | 62 | onRemoveWeirdRequest(index) { 63 | this.weirdRequestsControls.removeAt(index); 64 | } 65 | 66 | onResetForm() { 67 | this.orderSheetForm.reset(); 68 | } 69 | 70 | onSubmitForm() { 71 | console.log(this.orderSheetForm.value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/counter/shared/custom-validators.ts: -------------------------------------------------------------------------------- 1 | export class CustomValidators { 2 | static requiredWhen(requiredControlName, controlToCheckName) { 3 | return (control) => { 4 | let required = control.get(requiredControlName).value; 5 | let toCheck = control.get(controlToCheckName).value; 6 | return (required || (toCheck && !required)) 7 | ? null 8 | : {requiredwhen: true}; 9 | }; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { AppModule } from './app.module'; 3 | 4 | platformBrowserDynamic().bootstrapModule(AppModule); -------------------------------------------------------------------------------- /src/app/sando-app.component.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin: 0; 3 | padding-left: 1em; 4 | font-size: 4em; 5 | } 6 | @media only screen and (max-width: 948px) { 7 | h1 { 8 | font-size: 3em; 9 | padding-left: .6em; 10 | } 11 | } 12 | @media only screen and (max-width: 768px) { 13 | h1 { 14 | font-size: 2.8em; 15 | padding-left: .5em; 16 | } 17 | } 18 | @media only screen and (max-width: 565px) { 19 | h1 { 20 | font-size: 1.8em; 21 | padding-left: .4em; 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/sando-app.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Jackson's Deli and Market

3 |
4 | -------------------------------------------------------------------------------- /src/app/sando-app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'sando-app', 5 | templateUrl: 'app/sando-app.component.html', 6 | styleUrls: ['app/sando-app.component.css'] 7 | }) 8 | export class SandoAppComponent { } 9 | -------------------------------------------------------------------------------- /src/assets/GloriaHallelujah.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coursefiles/angular2-reactive-forms-data-and-validation/75a143efbca0f98212292ecd681f05bc172a3b18/src/assets/GloriaHallelujah.ttf -------------------------------------------------------------------------------- /src/assets/GloriaHallelujah.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coursefiles/angular2-reactive-forms-data-and-validation/75a143efbca0f98212292ecd681f05bc172a3b18/src/assets/GloriaHallelujah.woff2 -------------------------------------------------------------------------------- /src/assets/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Kimberly Geswein (kimberlygeswein.com) 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coursefiles/angular2-reactive-forms-data-and-validation/75a143efbca0f98212292ecd681f05bc172a3b18/src/favicon.ico -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coursefiles/angular2-reactive-forms-data-and-validation/75a143efbca0f98212292ecd681f05bc172a3b18/src/icon.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jackson's Deli and Market 6 | 7 | 8 | 9 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Jackson's Deli and Market", 3 | "icons": [ 4 | { 5 | "src": "icon.png", 6 | "sizes": "96x96", 7 | "type": "image/png" 8 | } 9 | ], 10 | "start_url": "/index.html", 11 | "display": "standalone", 12 | "orientation": "portrait" 13 | } 14 | -------------------------------------------------------------------------------- /src/systemjs.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * System configuration for Angular 2 samples 3 | * Adjust as necessary for your application needs. 4 | */ 5 | (function (global) { 6 | 7 | // map tells the System loader where to look for things 8 | var map = { 9 | 'app': 'app', // 'dist', 10 | 11 | '@angular': 'vendor/@angular', 12 | 'rxjs': 'vendor/rxjs' 13 | }; 14 | 15 | // packages tells the System loader how to load when no filename and/or no extension 16 | var packages = { 17 | 'app': { main: 'main.js', defaultExtension: 'js' }, 18 | 'rxjs': { defaultExtension: 'js' } 19 | }; 20 | 21 | var ngPackageNames = [ 22 | 'common', 23 | 'compiler', 24 | 'core', 25 | 'forms', 26 | 'http', 27 | 'platform-browser', 28 | 'platform-browser-dynamic', 29 | ]; 30 | 31 | // Individual files (~300 requests): 32 | function packIndex(pkgName) { 33 | packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' }; 34 | } 35 | 36 | // Bundled (~40 requests): 37 | function packUmd(pkgName) { 38 | packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; 39 | } 40 | 41 | // Most environments should use UMD; some (Karma) need the individual index files 42 | var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; 43 | 44 | // Add package entries for angular packages 45 | ngPackageNames.forEach(setPackageConfig); 46 | 47 | var config = { 48 | map: map, 49 | packages: packages 50 | }; 51 | 52 | System.config(config); 53 | 54 | })(this); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "system", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": false, 11 | "suppressImplicitAnyIndexErrors": true 12 | } 13 | } --------------------------------------------------------------------------------