├── .gitignore ├── LICENSE ├── LICENSE.CC0 ├── Procfile ├── README.md ├── angular-advanced-topics ├── README.md └── control-css-classes.png ├── angular-basic-training.pdf ├── angular-fundamentals └── README.md ├── angular-other-topics ├── README.md ├── animations │ ├── README.md │ └── web-animations-compatibility.png ├── change-detection │ ├── README.md │ └── change-detection-tree.png └── lazy-loading │ ├── README.md │ ├── build-with-lazy-loading.png │ ├── build-without-lazy-loading.png │ ├── chunk.PNG │ └── main.PNG ├── deck2pdf-0.3.0 ├── bin │ ├── deck2pdf │ └── deck2pdf.bat └── lib │ ├── deck2pdf-0.3.0.jar │ ├── groovy-2.4.4.jar │ └── itextpdf-5.5.1.jar ├── index.html ├── make-pdf.py ├── package-lock.json ├── package.json ├── pdf.html ├── reactive-programming-with-angular ├── EXERCISES.md ├── README.md └── demo │ └── README.md ├── setup └── README.md ├── spas-tooling-and-typescript ├── README.md └── spa-flow.png ├── testing └── README.md └── useful-resources └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Gofore Oy 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /LICENSE.CC0: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular (2+) Basic Training 2 | 3 | ## Training topics 4 | - [SPAs, Tooling and TypeScript](spas-tooling-and-typescript/README.md) 5 | - Tooling 6 | - Node.js and npm 7 | - ES2015 (ES6) 8 | - Webpack 9 | - Using IDEs and Editors for Angular and TypeScript 10 | - Angular CLI 11 | - TypeScript 12 | - Advantages 13 | - Typing 14 | - Interfaces 15 | - [Angular Fundamentals](angular-fundamentals/README.md) 16 | - Background 17 | - Architecture 18 | - NgModules 19 | - Components 20 | - Templates 21 | - Component Lifecycle Hooks 22 | - Two-way Data Binding 23 | - Services 24 | - Asynchronous and Server-side Communication 25 | - [Angular Advanced Topics](angular-advanced-topics/README.md) 26 | - Router 27 | - Pipes 28 | - Forms 29 | - Dependency Injection 30 | - Directives 31 | - [Reactive Programming with Angular](reactive-programming-with-angular/README.md) 32 | - Promises 33 | - Observables 34 | - Reactive Programming with Observables 35 | - RxJS 36 | - [Testing](testing/README.md) 37 | - Unit Testing 38 | - E2E Testing 39 | - [Lazy Loading](angular-other-topics/lazy-loading/README.md) 40 | - [Animations](angular-other-topics/animations/README.md) 41 | 42 | [Useful Resources](useful-resources/README.md) 43 | 44 | ### Open training slides on your own computer 45 | 46 | npm install -g live-server 47 | live-server --cors 48 | -------------------------------------------------------------------------------- /angular-advanced-topics/README.md: -------------------------------------------------------------------------------- 1 | # Angular Advanced Topics 2 | - Router 3 | - Forms 4 | - Pipes 5 | - Directives 6 | 7 | --- 8 | 9 | # Router 10 | - Core responsibilities: 11 | - Map URL into app state 12 | - Provide transitions from one URL to another (state to another) 13 | - Supports lazy loading of certain paths 14 | 15 | --- 16 | 17 | # Router Basics 18 | Route declarations 19 | 20 | _app.module.ts_ 21 | ```typescript 22 | import { NgModule } from '@angular/core'; 23 | *import { RouterModule } from '@angular/router'; 24 | 25 | *const routeConfig = [ 26 | * { 27 | * path: 'todos', 28 | * component: TodosComponent 29 | * } 30 | *]; 31 | 32 | @NgModule({ 33 | declarations: [ 34 | AppComponent, TodosComponent, TodoItemComponent 35 | ], 36 | imports: [ 37 | BrowserModule, 38 | * RouterModule.forRoot(routeConfig), 39 | ], 40 | bootstrap: [AppComponent] 41 | }) 42 | export class AppModule { } 43 | ``` 44 | 45 | --- 46 | 47 | # Router Basics 48 | `` declares the placeholder for routed component tree 49 | 50 | _app.component.html_ 51 | ```html 52 |

App works!

53 | 54 | ``` 55 | 56 | --- 57 | 58 | # Router - More advanced routes 59 | 60 | _app.module.ts_ 61 | ```typescript 62 | const routeConfig = [ 63 | { 64 | path: 'todos', 65 | * children: [ 66 | * { 67 | * path: '', 68 | * component: TodosComponent 69 | * }, 70 | * { 71 | * path: ':index', 72 | * component: EditTodoItemComponent 73 | * } 74 | * ] 75 | } 76 | ]; 77 | ``` 78 | 79 | declares routes `todos/` and `todos/:index`. `:index` is named placeholder for path parameter that can be accessed within the component. 80 | 81 | --- 82 | 83 | # Redirects 84 | - By default matching of URLs is done with _startsWith_ algorithm (and everything starts with path `'''`) 85 | - `pathMatch: 'full'` can be used to set the algorithm to full matching 86 | - Redirects can be done by using `redirectTo` instead of specifying `component` 87 | 88 | _app.module.ts_ 89 | ```typescript 90 | const routeConfig = [ 91 | { 92 | * path: '' 93 | * pathMatch: 'full', 94 | * redirectTo: 'todos/' 95 | }, 96 | { 97 | path: 'todos', 98 | component: TodosComponent 99 | } 100 | ]; 101 | ``` 102 | 103 | --- 104 | 105 | # Accessing Route Parameters 106 | - Route parameters can be accessed via `params` field of `ActivatedRoute` as observable 107 | - Once parameters change new event will be sent. 108 | - Makes it possible to reuse the same component instead of instantiating a new one 109 | 110 | ```typescript 111 | @Component({}) 112 | export class EditTodoItemComponent { 113 | constructor(route: ActivatedRoute) { 114 | this.route.params.subscribe(params => { 115 | this.index = params.index; 116 | }); 117 | } 118 | ``` 119 | 120 | --- 121 | 122 | # Fragments & Query Parameters 123 | - Fragments (`example.com#key=value`) and query parameters (`example.com?key=value`) are shared by all routes 124 | - Can be accessed like path parameters: 125 | 126 | ```typescript 127 | @Component({}) 128 | export class MyComponent { 129 | constructor(route: ActivatedRoute) { 130 | this.route.queryParams.subscribe(params => { // or .fragment 131 | this.key = params.key; 132 | }); 133 | } 134 | ``` 135 | 136 | --- 137 | 138 | # Router - Navigating 139 | Two ways to navigate between states: 140 | - Imperatively inside a component: 141 | 142 | ```typescript 143 | this.router.navigateByUrl('todos/1') 144 | ``` 145 | 146 | - Declaratively inside a template: 147 | 148 | ```typescript 149 | 150 | ``` 151 | 152 | --- 153 | 154 | # Navigation syntax 155 | - Parts of paths can be composed easily with the array syntax 156 | - Array syntax concatenates the parts with `/` 157 | - So the below are the same: 158 | 159 | ```typescript 160 | this.router.navigateByUrl('todos/1/tasks'); 161 | this.router.navigate(['todos', 1, 'tasks']); 162 | ``` 163 | 164 | ```html 165 | 166 | 167 | ``` 168 | 169 | --- 170 | 171 | # Absolute vs. Relative Navigation 172 | 173 | - If the path starts with a slash (`/`), it is an absolute navigation. 174 | - Example: 175 | ```typescript 176 | // Assuming we are now in example.com/todos/1 177 | this.router.navigateByUrl('tasks'); // example.com/todos/1/tasks 178 | this.router.navigateByUrl('/tasks'); // example.com/tasks 179 | ``` 180 | - `../` can be used to go one level up 181 | ```typescript 182 | // Assuming we are now in example.com/todos/1/tasks 183 | this.router.navigateByUrl('../'); // example.com/todos/1 184 | ``` 185 | 186 | --- 187 | 188 | # Router Guards 189 | - Guards allow changing the behaviour of routing for certain routes 190 | - Guards can be registered for navigation into and out of route 191 | - Guards can cancel the routing and instead redirect user to somewhere else 192 | - Example use cases: 193 | - Authentication 194 | - Preloading data for component 195 | - Logging 196 | - "You have unsaved changes. Are you sure you want to leave?" 197 | 198 | --- 199 | 200 | # Guard Types 201 | - Multiple guards available for each route: 202 | - `CanActivate` to mediate navigation to a route 203 | - `CanActivateChild` to mediate navigation to a child route 204 | - `CanDeactivate` to mediate navigation away from the current route 205 | - `Resolve` to perform route data retrieval before route activation 206 | - `CanLoad` to mediate navigation to a feature module loaded asynchronously 207 | - The `Can*` guards can return either boolean or promise (resolving to a boolean value) to allow or prevent navigation 208 | 209 | --- 210 | # Guard Example 211 | - Usage: 212 | 213 | _app.module.ts_ 214 | ```typescript 215 | const routeConfig = [ 216 | { 217 | path: 'todos', 218 | * canActivate: [AuthGuard], 219 | component: TodosComponent 220 | } 221 | ]; 222 | ``` 223 | 224 | _auth-guard.ts_ 225 | ```typescript 226 | import { CanActivate, Router, 227 | ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 228 | 229 | @Injectable() 230 | export class AuthGuard implements CanActivate { 231 | constructor(private router: Router, private authService: AuthService) {} 232 | 233 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 234 | return this.authService.checkLogin(state.url); 235 | } 236 | ``` 237 | 238 | --- 239 | 240 | # Router URL Strategies 241 | - Two strategies for URL formation: 242 | - `PathLocationStrategy`: HTML5 _pushState_ style (`example.com/todos/1`) (default) 243 | - `HashLocationStrategy`: Hash URLs (`example.com/#/todos/1`) 244 | - Setting the strategy: 245 | 246 | ```typescript 247 | @NgModule({ 248 | imports: [ 249 | BrowserModule, 250 | RouterModule.forRoot(routeConfig, `{ useHash: true }`) 251 | ], 252 | declarations: [ AppComponent ], 253 | bootstrap: [ AppComponent ] 254 | }) 255 | export class AppModule {} 256 | ``` 257 | 258 | --- 259 | 260 | # Forms 261 | - Angular provides highly advanced form management functionality: 262 | - Two-way data binding 263 | - Change tracking 264 | - Validation 265 | - Error handling 266 | 267 | --- 268 | 269 | # Template-driven vs. Reactive Forms 270 | - Angular forms can be either template-driven or reactive 271 | - Both have their own `NgModule`: `FormsModule` and `ReactiveFormsModule` 272 | - Both modules are in `@angular/forms` npm package 273 | - The underlying principles are the same but the usage is different 274 | - Both can be used within a single application, even on single component 275 | - Only template-driven forms are covered in this training, as they are enough for most use cases 276 | 277 | --- 278 | 279 | # Reactive Forms 280 | - As the name suggests, based on reactive programming (covered tomorrow) 281 | - Form controls defined in component instead of template 282 | - Require more boilerplate code 283 | - For complex use cases 284 | - More easy to test 285 | 286 | --- 287 | 288 | # Reactive Forms Example 289 | 290 | ```typescript 291 | import { Component } from '@angular/core'; 292 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 293 | 294 | export class TodosComponent { 295 | todosForm: FormGroup; 296 | 297 | constructor(private fb: FormBuilder) { 298 | this.todosForm = this.fb.group({ 299 | name: ['', Validators.required], 300 | done: [false], 301 | }); 302 | } 303 | } 304 | ``` 305 | 306 | ```html 307 |
308 | 309 | 310 |
311 | ``` 312 | 313 | --- 314 | 315 | # Template-driven Forms 316 | - Forms declared in templates rather than in component 317 | - Each input inside form element is attached by default 318 | - Each input needs to have (unique) name attribute set 319 | 320 | ```html 321 |
322 | 323 | 324 | 325 | ``` 326 | 327 | --- 328 | 329 | # Using Template-local Variables 330 | Forms export `FormControl` as `ngModel` for each input to be bound to template-local variable 331 | 332 | ```html 333 | 334 | 335 | 336 |
337 | Either name or email is invalid 338 |
339 | 340 | ``` 341 | 342 | --- 343 | 344 | # CSS Classes 345 | - CSS classes are attached automatically by framework 346 | 347 | ![Control CSS Classes](angular-advanced-topics/control-css-classes.png "Control CSS Classes") 348 | 349 | ```css 350 | .ng-invalid[required] { 351 | border: 1px solid red; 352 | } 353 | ``` 354 | 355 | --- 356 | 357 | # Forms - NgForm 358 | - Forms exports `ngForm` which can be bound into template-local variable 359 | - Contains combined information about all the input controls inside the form 360 | 361 | ```html 362 | 363 | 364 | 365 | 366 | 367 | 368 | ``` 369 | 370 | --- 371 | 372 | # Forms - Accessing Form Inside Component 373 | - Template-driven forms can be accessed from component with `@ViewChild('myForm')` annotation: 374 | 375 | ```typescript 376 | import { ViewChild } from '@angular/core'; 377 | import { FormGroup } from '@angular/forms'; 378 | 379 | @Component(..) 380 | export class MyComponent { 381 | @ViewChild('myForm') 382 | private myForm: FormGroup; 383 | } 384 | ``` 385 | 386 | --- 387 | 388 | # Pipes 389 | - Simple display-value transformations 390 | - Similar concept as _filters_ in AngularJS 391 | - Angular provides few commonly needed pipes, e.g.: `uppercase`, `lowecase` and `date` 392 | 393 | ```html 394 | 395 |
{{user.name` | uppercase`}}
396 | ``` 397 | 398 | --- 399 | 400 | # Pipe arguments 401 | - Pipes can take arguments that modify its behavior: 402 | ```html 403 |
{{user.birthDay | date`:'fullDate'`}}
404 |
{{user.birthDay | date`:'yyyy-MM-dd HH:mm a z':'+0900'`}}
405 | ``` 406 | 407 | --- 408 | 409 | # Multiple pipes 410 | - Pipes can be piped (like in UNIX) 411 | 412 | ```html 413 |
{{user.birthDay` | date:'fullDate' | uppercase`}}
414 | 415 | ``` 416 | 417 | --- 418 | 419 | # Custom Pipes 420 | - Declared with `@Pipe` annotation 421 | - `PipeTransform` interface with `transform` method 422 | - `transform` takes the value as first argument and the optional arguments after it 423 | - Must be declared in `NgModule` declaration to be available in templates 424 | 425 | ```typescript 426 | import { Pipe, PipeTransform } from '@angular/core'; 427 | 428 | @Pipe({ name: 'exponential' }) 429 | export class ExponentialPipe implements PipeTransform { 430 | transform(value: number, exponent: number): number { 431 | return Math.pow(value, exponent); 432 | } 433 | } 434 | ``` 435 | 436 | ```html 437 |
{{10 | exponential:3}}
438 | ``` 439 | 440 | --- 441 | 442 | # Generating a Pipe 443 | Browse to root of the project and run: 444 | 445 | ```shell 446 | ng generate pipe capitalize 447 | ``` 448 | 449 | or abbreviated one: 450 | 451 | ```shell 452 | ng g p capitalize 453 | ``` 454 | 455 | This will: 456 | - Create a file called `capitalize.pipe.ts` in the root of `app/` folder along with the test stub 457 | - add it as declaration in `AppModule` so it is available in the templates 458 | 459 | --- 460 | 461 | # Directives 462 | - There are three kinds of directives in Angular: 463 | - Components, which are basically selectors with templates and inputs and outputs 464 | - Structural directives, `ngFor` and `ngIf` are examples of these 465 | - Attribute directives 466 | - Here we talk about attribute directives which can be used to decorate the elements 467 | 468 | --- 469 | 470 | # Directives - Example 471 | - A directive for setting background color to yellow 472 | 473 | ```typescript 474 | import { Directive, ElementRef, Input } from '@angular/core'; 475 | @Directive({ 476 | selector: '[myHighlight]' 477 | }) 478 | export class HighlightDirective { 479 | constructor(el: ElementRef) { 480 | el.nativeElement.style.backgroundColor = 'yellow'; 481 | } 482 | } 483 | ``` 484 | 485 | ```html 486 | 487 | ``` 488 | 489 | --- 490 | 491 | # Directives - Events 492 | - We can use `host` property to define events with their corresponding handlers 493 | 494 | ```typescript 495 | import { Directive, ElementRef, Input } from '@angular/core'; 496 | @Directive({ 497 | selector: '[myHighlight]', 498 | * host: { 499 | * '(mouseenter)': 'onMouseEnter()', 500 | * '(mouseleave)': 'onMouseLeave()' 501 | * } 502 | }) 503 | export class HighlightDirective { 504 | private el:HTMLElement; 505 | constructor(el: ElementRef) { this.el = el.nativeElement; } 506 | * onMouseEnter() { this.highlight("yellow"); } 507 | * onMouseLeave() { this.highlight(null); } 508 | private highlight(color: string) { 509 | this.el.style.backgroundColor = color; 510 | } 511 | } 512 | ``` 513 | 514 | --- 515 | 516 | # Directives - Bind Value from Host Component 517 | - We can bind host components property to our directive by declaring an `@Input` annotation: 518 | 519 | ```typescript 520 | @Input('myHighlight') highlightColor: string; 521 | ``` 522 | - Now we can pass the property like 523 | 524 | ```html 525 | 526 | ``` 527 | -------------------------------------------------------------------------------- /angular-advanced-topics/control-css-classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-advanced-topics/control-css-classes.png -------------------------------------------------------------------------------- /angular-basic-training.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-basic-training.pdf -------------------------------------------------------------------------------- /angular-fundamentals/README.md: -------------------------------------------------------------------------------- 1 | # Angular Fundamentals 2 | - npm Modules 3 | - File Structure 4 | - Architecture 5 | - NgModules 6 | - Components 7 | - Templates 8 | - Component Lifecycle Hooks 9 | - Two-way Data Binding 10 | - Services 11 | - Asynchronous and Server-side Communication 12 | 13 | --- 14 | 15 | # Angular npm Modules 16 | - Framework code is distributed as npm modules: 17 | - `@angular/animations`: Advanced animations functionality 18 | - `@angular/common`: Common utilities (pipes, structural directives etc.) 19 | - `@angular/compiler`: Ahead-of-Time compiler 20 | - `@angular/core`: Core functionality (always needed) 21 | - `@angular/forms`: Form handling 22 | - `@angular/language-service`: Language service for Angular templates for better IDE support 23 | - `@angular/platform-*`: Platform-specific modules (platforms: browser, server, webworker) 24 | - `@angular/router`: Routing functionality 25 | - `@angular/service-worker`: Service worker functionality 26 | - `@angular/upgrade`: NgUpgrade to upgrade from AngularJS -> Angular 27 | 28 | - `@angular/http`: Deprecated HTTP client 29 | 30 | --- 31 | 32 | # Coding Style 33 | - [Angular style guide](https://angular.io/styleguide) declares set of rules 34 | - [Codelyzer](https://github.com/mgechev/codelyzer) (TSLint plugin) checks for 35 | - File naming **_name.type.filetype_**: 36 | - _app.module.ts_ 37 | - _todos.component.ts_ 38 | - _todos.component.html_ 39 | - _todos.component.scss_ 40 | - _user.service.ts_ 41 | - _json.pipe.ts_ 42 | 43 | --- 44 | 45 | # Architecture 46 | - App needs to have at least one **module** 47 | - Module has one root **component** 48 | - Component can have child components 49 | 50 | --- 51 | 52 | # NgModules 53 | - Each application has single root _NgModule_ 54 | - _NgModule_ is a class with `@NgModule` annotation 55 | - Declares collection of related elements (components, services etc.) 56 | 57 | _app.module.ts_ 58 | ```typescript 59 | import { BrowserModule } from '@angular/platform-browser'; 60 | import { NgModule } from '@angular/core'; 61 | import { AppComponent } from './app.component'; 62 | 63 | @NgModule({ 64 | declarations: [AppComponent], 65 | imports: [BrowserModule], 66 | providers: [], 67 | bootstrap: [AppComponent] 68 | }) 69 | export class AppModule { } 70 | ``` 71 | 72 | --- 73 | 74 | # NgModules - Details 75 | - `declarations` contains list of application build blocks, such as components, pipes and directives, with certain selector 76 | - `imports` allows importing of other _NgModules_ 77 | - For example `BrowserModule` imports browser-specific renderers and core directives such as `ngFor` and `ngIf` 78 | - `exports` allows declaring what is exported from this module (covered later) 79 | - `providers` contains list of services for dependency injection 80 | - `bootstrap` contains root element(s) for the application (usually named `AppComponent`) 81 | 82 | --- 83 | 84 | # Booting the application 85 | - We need to tell Angular to start our application 86 | - This is done by providing root module `bootstrapModule` 87 | - This will go through the `bootstrap` array and checks for those selectors in HTML 88 | 89 | _main.ts_ 90 | ```typescript 91 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 92 | import { AppModule } from './app/app.module'; 93 | 94 | platformBrowserDynamic().bootstrapModule(AppModule); 95 | ``` 96 | 97 | _index.html_ 98 | ```html 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 | 106 | --- 107 | 108 | # Modules in Large Applications 109 | - Modules are meant to offer possibility to divide the functionality in logical modules 110 | - For example one could have `CustomerModule`, `AdminModule` and `BillingModule` 111 | - To access `exports` of another module, it needs to be `imported` to the module: 112 | 113 | _app.module.ts_ 114 | ```typescript 115 | import { BrowserModule } from '@angular/platform-browser'; 116 | import { NgModule } from '@angular/core'; 117 | *import { HttpClientModule } from '@angular/common'; 118 | import { AppComponent } from './app.component'; 119 | 120 | @NgModule({ 121 | declarations: [AppComponent], 122 | imports: [BrowserModule, `HttpClientModule`], 123 | providers: [], 124 | bootstrap: [AppComponent] 125 | }) 126 | export class AppModule { } 127 | ``` 128 | 129 | --- 130 | 131 | # Exporting 132 | - Exporting allows declaring the public API provided by the module 133 | - Consider company-specific UI module where `ListComponent` utilizes `ListRowComponent`: 134 | 135 | _ui.module.ts_ 136 | ```typescript 137 | import { NgModule } from '@angular/core'; 138 | import { ListComponent } from './list.component'; 139 | import { ListRowComponent } from './list-row.component'; 140 | 141 | @NgModule({ 142 | declarations: [ListComponent, ListRowComponent], 143 | imports: [], 144 | exports: [ListComponent], 145 | providers: [] 146 | }) 147 | export class UiModule { } 148 | ``` 149 | 150 | --- 151 | 152 | # Modules as Exports 153 | - Directives `ngIf` and `ngFor` are defined actually in `CommonModule` 154 | - But that is not imported anywhere? 155 | - Actually, `BrowserModule` is exporting `CommonModule` as seen in [source](https://github.com/angular/angular/blob/5.2.9/packages/platform-browser/src/browser.ts#L89) 156 | - -> Modules can be exported as part of module 157 | - If `ListModule` and `TableModule` were separate modules: 158 | 159 | _ui.module.ts_ 160 | ```typescript 161 | import { NgModule } from '@angular/core'; 162 | import { ListModule } from './list.module'; 163 | import { TableModule } from './table.module'; 164 | 165 | @NgModule({ 166 | declarations: [], 167 | imports: [], 168 | exports: [ListModule, TableModule], 169 | providers: [] 170 | }) 171 | export class UiModule { } 172 | ``` 173 | 174 | --- 175 | 176 | # Components 177 | - Build blocks of application UI 178 | - Has a template that it binds data to 179 | - Can contain other components 180 | - Class with `@Component` annotation 181 | 182 | _todos.component.ts_ 183 | ```typescript 184 | import { Component } from '@angular/core'; 185 | 186 | @Component({}) 187 | class TodosComponent { } 188 | ``` 189 | 190 | --- 191 | 192 | # Components 193 | - Two parameters are mandatory for `@Component` annotation: 194 | - template with `template` (inline template) or `templateUrl` (separate file) 195 | - selector (should always start with application specific prefix like the default: `app`) 196 | - Components need to be declared in NgModule's declarations to be available in templates 197 | 198 | _todos.component.ts_ 199 | ```typescript 200 | import { Component } from '@angular/core'; 201 | 202 | @Component({ 203 | `templateUrl: 'todos.component.html'`, 204 | `selector: 'app-todos'` 205 | }) 206 | class TodosComponent { } 207 | ``` 208 | 209 | _app.module.ts_ 210 | ```typescript 211 | @NgModule({ 212 | declarations: [AppComponent, `TodosComponent`] 213 | ... 214 | }) 215 | export class AppModule { } 216 | ``` 217 | 218 | --- 219 | 220 | # Generating a Component 221 | Browse to root of the project and run: 222 | 223 | ```shell 224 | ng generate component todos 225 | ``` 226 | 227 | or abbreviated one: 228 | 229 | ```shell 230 | ng g c todos 231 | ``` 232 | 233 | This will: 234 | - Create a folder called `todos` with the component, template, styles and test file 235 | - Add the component to `declarations` of your `AppModule` 236 | 237 | --- 238 | 239 | # Templates 240 | - Plain HTML with few Angular specific additions* 241 | 242 | _todos.component.html_ 243 | ```html 244 |

My todo application

245 | ``` 246 | 247 | - Selectors can be used to add other components 248 | 249 | _app.component.html_ 250 | ```html 251 |

Todos

252 | 253 | ``` 254 | 255 | --- 256 | 257 | # Angular Template Syntax 258 | - **Data binding** with _property name_ inside _double curly braces ({{}})_ 259 | 260 | ```typescript 261 | {{property}} 262 | ``` 263 | 264 | - **Structural directives** with _asterisk ( * ) followed by directive name_ 265 | 266 | ```html 267 |
Item
268 | ``` 269 | 270 | - **Attribute binding** with _attribute name_ inside _square brackets ([])_ 271 | 272 | ```typescript 273 | 274 | ``` 275 | 276 | - **Event binding** with _event name_ inside _parenthesis_ 277 | 278 | ```typescript 279 |
280 | ``` 281 | 282 | - **Template local variables** with _hash (#) followed by name_ 283 | 284 | ```typescript 285 | 286 | ``` 287 | 288 | --- 289 | 290 | # Data Binding 291 | Bind property from the component to the template 292 | 293 | _app.component.ts_ 294 | ```typescript 295 | import { Component } from '@angular/core'; 296 | 297 | @Component({ 298 | selector: 'app-component', 299 | templateUrl: 'app.component.html' 300 | }) 301 | class AppComponent { 302 | `title: string = 'app works!';` 303 | } 304 | ``` 305 | 306 | _app.component.html_ 307 | ```html 308 |

`{{title}}`

309 | ``` 310 | 311 | --- 312 | 313 | # Structural Directives 314 | - Modify the *structure* of the template 315 | - Two most used are `*ngIf` and `*ngFor` 316 | 317 | _todos.component.ts_ 318 | ```typescript 319 | import { Component } from '@angular/core'; 320 | 321 | @Component({ 322 | selector: 'app-todos', 323 | templateUrl: 'todos.component.html' 324 | }) 325 | class TodosComponent { 326 | `todos: any[] = [{name: 'Do the laundry'}, {name: 'Clean my room'}];` 327 | } 328 | ``` 329 | 330 | _todos.component.html_ 331 | ```html 332 |
333 | {{todo.name}} 334 |
335 | ``` 336 | 337 | --- 338 | 339 | # Attribute Binding 340 | Bind value from component into HTML attribute 341 | 342 | _app.component.ts_ 343 | ```typescript 344 | import { Component } from '@angular/core'; 345 | 346 | @Component({ 347 | selector: 'app-component', 348 | templateUrl: 'app.component.html' 349 | }) 350 | class AppComponent { 351 | `isDisabled: boolean = true;` 352 | } 353 | ``` 354 | 355 | _app.component.html_ 356 | ```html 357 | 358 | ``` 359 | 360 | --- 361 | 362 | # Special Attribute Bindings 363 | - Classes and styles have special syntax available: 364 | ```html 365 |
366 |
367 | ``` 368 | 369 | --- 370 | 371 | # Attribute Binding for Components 372 | - Attribute binding only works for native properties of HTML elements by default 373 | - Same concept can also be used to pass data from parent component to child component 374 | 375 | _parent.component.html_ 376 | ```html 377 | 378 | ``` 379 | 380 | _child.component.ts_ 381 | ```typescript 382 | import { Component, Input } from '@angular/core'; 383 | 384 | @Component({ 385 | selector: 'app-child' 386 | }) 387 | class ChildComponent { 388 | * @Input() 389 | * foo: string; 390 | } 391 | ``` 392 | 393 | --- 394 | 395 | # Event Binding 396 | - Register handler code for events 397 | - The actual event can be referenced with `$event` 398 | - The events are basic [DOM events](https://www.w3schools.com/jsref/dom_obj_event.asp) (like `click`, `mouseover`, `change` and `input`) 399 | 400 | _todos.component.html_ 401 | ```html 402 | 403 | ``` 404 | 405 | _todos.component.ts_ 406 | ```typescript 407 | import { Component } from '@angular/core'; 408 | 409 | @Component({..}) 410 | class TodosComponent { 411 | * handleChanged(value: string) { 412 | * // Do something with value 413 | * } 414 | } 415 | ``` 416 | 417 | --- 418 | 419 | # Event Binding for Components 420 | - Event binding only works for native events of HTML elements by default 421 | - Same concept can also be used to pass data from child component to parent component 422 | 423 | _parent.component.html_ 424 | ```html 425 | 426 | ``` 427 | 428 | _child.component.ts_ 429 | ```typescript 430 | import { Component, Output } from '@angular/core'; 431 | 432 | @Component({ 433 | selector: 'app-child' 434 | }) 435 | class ChildComponent { 436 | * @Output() 437 | * change = new EventEmitter(); 438 | } 439 | ``` 440 | 441 | --- 442 | 443 | # Two-way Data Binding 444 | - Two-way data binding with `ngModel` inside _banana-box syntax_: `[(ngModel)]` 445 | - Data flow is still unidirectional, though: 446 | - value from component is updated to input on change 447 | - when user modifies the value, it is updated to component 448 | 449 | ```html 450 | Name: 451 | ``` 452 | 453 | which is just sugar for 454 | 455 | ```html 456 | Name: 457 | ``` 458 | 459 | --- 460 | 461 | # CSS Encapsulation 462 | - Component can have styles applied: 463 | - Inline in annotation (field `styles` in `@Component`) 464 | - In external files (field `styleUrls`) 465 | - These styles are scoped for component -> No other component can get affected by them 466 | 467 | _todos.component.html_ 468 | ```html 469 |
470 | ``` 471 | 472 | _todos.component.css_ 473 | ```css 474 | .todos { 475 | background-color: red; 476 | } 477 | ``` 478 | 479 | Does not affect styles here: 480 | 481 | _clients.component.html _ 482 | ```html 483 |
484 | ``` 485 | 486 | --- 487 | 488 | # Components - Inline Styles 489 | 490 | _todos.component.ts_ 491 | ```typescript 492 | import { Component } from '@angular/core'; 493 | 494 | @Component({ 495 | selector: 'app-todos', 496 | templateUrl: 'todos.component.html', 497 | `styles: ['.active-todo { background-color: yellow; }']` 498 | }) 499 | class TodosComponent { 500 | } 501 | ``` 502 | 503 | --- 504 | 505 | # Components - Styles From File 506 | 507 | _todos.component.ts_ 508 | ```typescript 509 | import { Component } from '@angular/core'; 510 | 511 | @Component({ 512 | selector: 'app-todos', 513 | templateUrl: 'todos.component.html', 514 | `styleUrls: ['todos.component.css']` 515 | }) 516 | class TodosComponent { 517 | } 518 | ``` 519 | 520 | --- 521 | 522 | # Component Lifecycle Hooks 523 | - For hooking into certain lifecycle events 524 | - Interface for each hook 525 | - Example hooks: `ngOnInit` (interface `OnInit`), `ngOnChanges` (interface `OnChanges`) and `ngOnDestroy` (interface `OnDestroy`) 526 | 527 | ```typescript 528 | import { Component, OnInit } from '@angular/core'; 529 | 530 | export class MyComponent `implements OnInit` { 531 | `ngOnInit() { ... }` 532 | } 533 | ``` 534 | 535 | - `ngOnInit` should be used for initialization of data for testability 536 | 537 | --- 538 | 539 | # Services 540 | - Module-wide singletons 541 | - Used to store state, communicate with backends etc. 542 | - Examples: `UserService`, `BackendService` 543 | - Declaring: 544 | 545 | _user.service.ts_ 546 | ```typescript 547 | import { Injectable } from '@angular/core'; 548 | 549 | *@Injectable() 550 | export class UserService { 551 | } 552 | ``` 553 | 554 | - Need to be registered for NgModule 555 | 556 | _app.module.ts_ 557 | ```typescript 558 | @NgModule( 559 | ... 560 | * providers: [UserService] 561 | ) 562 | export class AppModule() {} 563 | ``` 564 | 565 | --- 566 | 567 | # Generating a Service 568 | Browse to root of the project and run: 569 | 570 | ```shell 571 | ng generate service todo 572 | ``` 573 | 574 | or abbreviated one: 575 | 576 | ```shell 577 | ng g s todo 578 | ``` 579 | 580 | This will: 581 | - Create a file called `todos.service.ts` in the root of `app/` folder along with the test stub 582 | - *not* add it as provider in `AppModule` so you must do it yourself! 583 | 584 | --- 585 | 586 | # DI with Constructor Parameters 587 | - Angular implements concept called `Dependency Injection` 588 | - In DI you can just ask for the dependencies as constructor parameters as follows: 589 | 590 | _todos.component.ts_ 591 | ```typescript 592 | import { Component } from '@angular/core'; 593 | *import { BackendService } from './backend.service'; 594 | 595 | @Component({ 596 | selector: 'app-todos', 597 | templateUrl: 'todos.component.html' 598 | }) 599 | class TodosComponent { 600 | * constructor(private backendService: BackendService) { 601 | * } 602 | 603 | initializeData() { 604 | this.backendService.makeRequest(); 605 | } 606 | } 607 | ``` 608 | 609 | Angular will then pass the singleton instance of `BackendService` to the component when creating it. 610 | 611 | --- 612 | 613 | # Asynchronous and Server-side Communication 614 | - Asynchronous things are modeled as Observables (covered later) in Angular 615 | - For now, we only need to know that there is `subscribe` method 616 | - For AJAX requests, use `HttpClient` service found in `@angular/common` 617 | 618 | ```typescript 619 | import { Component } from '@angular/core'; 620 | import { HttpClient } from '@angular/common/http'; 621 | 622 | @Component({...}) 623 | export class MyComponent { 624 | filteredData: any[]; 625 | constructor(private httpClient: HttpClient) { 626 | } 627 | 628 | getData() { 629 | * this.httpClient.get('https://example.com/mydata').subscribe(data => { 630 | // Do stuff with data 631 | this.filteredData = data.filter(item => item.id > 100); 632 | }) 633 | } 634 | } 635 | ``` 636 | -------------------------------------------------------------------------------- /angular-other-topics/README.md: -------------------------------------------------------------------------------- 1 | # Universal Rendering 2 | 3 | --- 4 | 5 | # Problems with SPAs 6 | - Initial load and rendering of data can be slow 7 | - Search engine unfriendliness 8 | 9 | --- 10 | 11 | # Slow Initial Load 12 | - Even minor delay can have high impact on user acquisition ([Google 2009](http://googleresearch.blogspot.fi/2009/06/speed-matters.html)) 13 | - Even worse on mobile devices 14 | - Bad user experience 15 | 16 | --- 17 | 18 | # Search Engine Optimization with SPAs 19 | Two main problems: 20 | - No clear definition of ready 21 | - Deep links are hard to get indexed 22 | 23 | --- 24 | 25 | # No Clear Definition of Ready 26 | - HTML rendered but no data available, yet 27 | - Search engine is forced to guess when page is ready to be read 28 | - Constantly changing content can cause even more difficulties 29 | 30 | --- 31 | 32 | # Deep Links Are Hard to Get Indexed 33 | - Not every browser supports HTML5 History API yet (IE9) 34 | - Thus, HTML bookmark anchor (#) is used: `/home#section1` 35 | 36 | -> Search engines don't detect these as separate pages 37 | 38 | --- 39 | 40 | # Solution? 41 | What if we could combine best of both worlds: *User experience of SPAs* and *SEO friendliness and high performance of vanilla HTML pages*? 42 | 43 | --- 44 | 45 | # Universal Rendering 46 | 1. Render the app as plain HTML on server and return it 47 | 2. When the actual Angular app is ready on a client, change to use it 48 | 49 | -> Best parts of both worlds! 50 | 51 | --- 52 | 53 | # Angular Universal 54 | - Separate project by Angular core team 55 | - Provides functionality to render the app as plain HTML before being replaced by actual JavaScript app once loaded 56 | - Can be used with multiple languages 57 | 58 | --- 59 | 60 | # Preboot 61 | - [Preboot](https://github.com/angular/universal/tree/master/modules/preboot) allows recording events before Angular takes over and replaying them afterwards 62 | - Key features: 63 | - Record and play back events 64 | - Respond immediately to events 65 | - Maintain focus even page is re-rendered 66 | - Buffer client-side re-rendering for smoother transition 67 | - Freeze page until bootstrap complete if user clicks button 68 | -------------------------------------------------------------------------------- /angular-other-topics/animations/README.md: -------------------------------------------------------------------------------- 1 | # Animations 2 | - Built on top of Web Animations specification 3 | - Part of `@angular/core` module 4 | 5 | --- 6 | 7 | # Browser Support 8 | ![Web Animations compatibility table](angular-other-topics/animations/web-animations-compatibility.png "Web Animations compatibility table") 9 | [Polyfill](https://github.com/web-animations/web-animations-js) for older browsers 10 | 11 | --- 12 | 13 | # Adding Polyfill to Angular CLI 14 | 15 | ```shell 16 | npm install --save web-animations-js 17 | ``` 18 | 19 | _angular-cli.json_ 20 | ```json 21 | "scripts": [ 22 | "../node_modules/web-animations-js/web-animations.min.js" 23 | ], 24 | ``` 25 | 26 | --- 27 | 28 | # Animations - Supported Properties 29 | - List available in [the standard](https://www.w3.org/TR/css3-transitions/#animatable-properties) 30 | - Basically every color (background, border, etc.) and dimension (width, height, size, etc.) 31 | - `background-color` 32 | - `border-width` 33 | - `max-height` 34 | 35 | --- 36 | 37 | # Animations API 38 | - Part of `@angular/core`: 39 | - `trigger` 40 | - `state` 41 | - `style` 42 | - `transition` 43 | - `animate` 44 | - Declared within the `@Component` annotation: 45 | 46 | ```typescript 47 | @Component({ 48 | animations: [ 49 | trigger(...) 50 | ] 51 | }) 52 | 53 | ``` 54 | 55 | --- 56 | 57 | # Animation States 58 | - _Trigger_ is bound to the certain element 59 | - _States_ are the possible values for triggered value 60 | - States have _styles_ 61 | - _Transitions_ between states are animated 62 | - _Animations_ have duration and timing functions etc. 63 | 64 | --- 65 | 66 | # Triggers 67 | _Trigger_ is bound to the certain element 68 | 69 | _my.component.ts_ 70 | ```typescript 71 | @Component({ 72 | animations: [ 73 | trigger('myTrigger', [ 74 | ... 75 | ]) 76 | ] 77 | }) 78 | export class MyComponent { 79 | state = 'state1'; 80 | } 81 | ``` 82 | 83 | _my.component.html_ 84 | ```html 85 |
86 | My element 87 |
88 | ``` 89 | 90 | --- 91 | 92 | # States 93 | _States_ are the possible values for triggered value 94 | 95 | _my.component.ts_ 96 | ```typescript 97 | trigger('myTrigger', [ 98 | * state('state1', ...), 99 | * state('state2', ...) 100 | ]) 101 | export class MyComponent { 102 | state = 'state1'; 103 | 104 | changeState() { 105 | state = 'state2'; 106 | } 107 | } 108 | ``` 109 | 110 | --- 111 | 112 | # Styles 113 | States have _styles_ 114 | 115 | _my.component.ts_ 116 | ```typescript 117 | trigger('myTrigger', [ 118 | state('state1', style({ 119 | 'backgroundColor': '#fff' 120 | })), 121 | state('state2', style({ 122 | 'backgroundColor': '#000' 123 | })) 124 | ]) 125 | export class MyComponent { 126 | state = 'state1'; 127 | 128 | changeState() { 129 | state = 'state2'; 130 | } 131 | } 132 | ``` 133 | 134 | --- 135 | 136 | # Transitions 137 | _Transitions_ between states are animated 138 | 139 | _my.component.ts_ 140 | ```typescript 141 | trigger('myTrigger', [ 142 | state(...), 143 | * transition('state1 => state2', ...), 144 | * transition('state2 => state1', ...) 145 | ]) 146 | export class MyComponent { 147 | state = 'state1'; 148 | 149 | changeState() { 150 | state = 'state2'; 151 | } 152 | } 153 | ``` 154 | 155 | --- 156 | 157 | # Animations 158 | _Animations_ have duration and timing functions etc. 159 | 160 | _my.component.ts_ 161 | ```typescript 162 | trigger('myTrigger', [ 163 | state(...), 164 | transition('state1 => state2', `animate('100ms ease-in')`), 165 | transition('state2 => state1', `animate('100ms ease-out')`) 166 | ]) 167 | export class MyComponent { 168 | state = 'state1'; 169 | 170 | changeState() { 171 | state = 'state2'; 172 | } 173 | } 174 | ``` 175 | 176 | --- 177 | 178 | # Special States 179 | - Two special states: 180 | - `void`: element is not attached to a view 181 | - `*`: matches any animation state 182 | - Can be used in transitions: 183 | - `transition('void => *', ...)`: element enters the view 184 | - `transition('* => void', ...)`: element leaves the view 185 | - Entering and leaving also have aliases: 186 | ```typescript 187 | transition(`':enter'`, ...); // void => * 188 | transition(`':leave'`, ...); // * => void 189 | ``` 190 | 191 | --- 192 | 193 | # Animation Events 194 | - Events to be registered 195 | - `(@myTrigger.start)="animationStarted($event)"` 196 | - `(@myTrigger.done)="animationDone($event)"` 197 | 198 | ```html 199 |
203 | My element 204 |
205 | ``` 206 | -------------------------------------------------------------------------------- /angular-other-topics/animations/web-animations-compatibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/animations/web-animations-compatibility.png -------------------------------------------------------------------------------- /angular-other-topics/change-detection/README.md: -------------------------------------------------------------------------------- 1 | # Zone.js 2 | - Implements concept of zones (inspired by Dart) in JavaScript 3 | - "A Zone is an execution context that persists across async tasks" 4 | - In practice makes it possible to track the asyncronous calls made (HTTP, timers and event listeners) within the zone 5 | - Angular uses zones internally to track changes in state 6 | 7 | --- 8 | 9 | # Change Detection - General 10 | - Idea: project the internal state into UI (DOM in web) 11 | - The internal state consists of JavaScript primitives such as objects, arrays, strings etc. 12 | - Change only possible on asynchronous events such as timeouts or HTTP request 13 | 14 | --- 15 | 16 | # Change Detection - Angular 17 | - Zone.js makes it possible to track all possible change sources 18 | - Components change detector checks the bindings (like `{{name}}` and `(click)`) defined in its template on change 19 | - Bindings are propagated from the root to leaves in the depth first order 20 | - Change detection graph is directed tree and can't contain cycles -> improved performance, predictability and debugging 21 | 22 | --- 23 | 24 | ![Change detection](angular-other-topics/change-detection/change-detection-tree.png "Change detection tree") 25 | 26 | --- 27 | # Change Detection - Simplified Implementation 28 | ```typescript 29 | // very simplified version of actual source 30 | class ApplicationRef { 31 | changeDetectorRefs: ChangeDetectorRef[] = []; 32 | 33 | constructor(private zone: NgZone) { 34 | this.zone.onTurnDone 35 | .subscribe(() => this.zone.run(() => this.tick()); 36 | } 37 | 38 | tick() { 39 | this.changeDetectorRefs 40 | .forEach((ref) => ref.detectChanges()); 41 | } 42 | } 43 | ``` 44 | --- 45 | 46 | # Change Detection - Strategies 47 | - Change detection strategy describes which strategy will be used the next time change detection is triggered 48 | - Angular has six change detection strategies: 49 | - **CheckOnce**: After calling _detectChanges_ the mode of the change detector will become _Checked_. 50 | - **Checked**: Change detector should be skipped until its mode changes to _CheckOnce_. 51 | - **CheckAlways**: After calling _detectChanges_ the mode of the change detector will remain _CheckAlways_. 52 | - **Detached**: Change detector sub tree is not a part of the main tree and should be skipped. 53 | - **OnPush**: Change detector's mode will be set to _CheckOnce_ during hydration. 54 | - **Default**: Change detector's mode will be set to _CheckAlways_ during hydration. 55 | - Setting the strategy: 56 | ```typescript 57 | @Component({`changeDetection: ChangeDetectionStrategy.OnPush`}) 58 | ``` 59 | 60 | --- 61 | 62 | # Change Detection - Angular Performance 63 | - Change detection is one of the key functionalities of Angular and thus it is highly optimized 64 | - CDs get VM optimized monomorphic classes generated for them at runtime 65 | - Change detection is always single-pass (stable) because of uni-directional top-to-bottom flow 66 | - More optimizations possible with _immutables_ and _observables_ 67 | -------------------------------------------------------------------------------- /angular-other-topics/change-detection/change-detection-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/change-detection/change-detection-tree.png -------------------------------------------------------------------------------- /angular-other-topics/lazy-loading/README.md: -------------------------------------------------------------------------------- 1 | # Lazy Loading 2 | 3 | --- 4 | # Background 5 | - Web applications are used more and more with mobile devices 6 | - Many countries still have slow internet connections 7 | - Load time has a huge impact on user experience 8 | 9 | 10 | --- 11 | # Lazy Loading 12 | - Traditional apps load everything in the beginning 13 | - Lazy loading means only loading parts after initial page load 14 | 15 | --- 16 | # Default Bundling 17 | 18 | ![Build without lazy loading](angular-other-topics/lazy-loading/build-without-lazy-loading.png) 19 | 20 | Chunks are: 21 | - _polyfills.bundle.js_: Polyfills required to run the app (see _polyfills.ts_) 22 | - _main.bundle.js_: Actual application code along with vendor code (Angular) 23 | - _styles.bundle.css_: Styles for the application 24 | - _inline.bundle.js_: Tiny webpack loader to load the app 25 | 26 | --- 27 | # Custom Chunks 28 | 29 | ![Build with lazy loading](angular-other-topics/lazy-loading/build-with-lazy-loading.png) 30 | 31 | --- 32 | # Angular Module Loading 33 | - By default, modules are loaded eagerly when the application starts 34 | - Modules can loaded lazily by the router with `loadChildren` 35 | 36 | --- 37 | # Angular Module Loading 38 | Main bundle is loaded when application starts 39 | 40 | ![Main Module](angular-other-topics/lazy-loading/main.PNG) 41 | --- 42 | 43 | # Angular Module Loading 44 | A feature module is loaded when it is routed to the first time 45 | ```html 46 | Click me 47 | ``` 48 | ![Lazy Loaded Module](angular-other-topics/lazy-loading/chunk.PNG) 49 | 50 | --- 51 | # Angular CLI 52 | - Angular CLI has built-in support for lazy loading 53 | - Works for both development and production builds 54 | - If `loadChildren` is detected chunks will be generated -> no extra configuration required 55 | 56 | --- 57 | # Router Configuration 58 | 59 | _app.module.ts_ 60 | ```javascript 61 | const routes: Routes = [ 62 | { 63 | path: '', 64 | component: AppComponent 65 | }, 66 | { 67 | path: 'todos', 68 | loadChildren: './todos/todos.module#TodosModule' 69 | }]; 70 | 71 | @NgModule({ 72 | ... 73 | imports: [ 74 | ... 75 | RouterModule.forRoot(routes) 76 | ], 77 | }) 78 | ``` 79 | 80 | --- 81 | # Router Configuration 82 | 83 | _todos.module.ts_ 84 | ```javascript 85 | const routes: Routes = [ 86 | { 87 | path: '', 88 | component: TodosComponent 89 | }, 90 | { 91 | path: ':id', 92 | component: EditTodoComponent 93 | } 94 | ]; 95 | 96 | @NgModule({ 97 | ... 98 | imports: [ 99 | ... 100 | RouterModule.forChild(routes) 101 | ], 102 | }) 103 | export class TodosModule {} 104 | ``` 105 | 106 | --- 107 | # Demo 108 | 109 | [https://github.com/RoopeHakulinen/angular-lazy-loading](https://github.com/RoopeHakulinen/angular-lazy-loading) 110 | 111 | --- 112 | 113 | # Analyzing Bundle Sizes 114 | 115 | ```bash 116 | { 117 | "scripts": { 118 | ... 119 | "analyze": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json" 120 | } 121 | } 122 | ``` 123 | 124 | ```bash 125 | npm install webpack-bundle-analyzer --save-dev 126 | npm run analyze 127 | ``` -------------------------------------------------------------------------------- /angular-other-topics/lazy-loading/build-with-lazy-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/lazy-loading/build-with-lazy-loading.png -------------------------------------------------------------------------------- /angular-other-topics/lazy-loading/build-without-lazy-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/lazy-loading/build-without-lazy-loading.png -------------------------------------------------------------------------------- /angular-other-topics/lazy-loading/chunk.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/lazy-loading/chunk.PNG -------------------------------------------------------------------------------- /angular-other-topics/lazy-loading/main.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/angular-other-topics/lazy-loading/main.PNG -------------------------------------------------------------------------------- /deck2pdf-0.3.0/bin/deck2pdf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## deck2pdf start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and DECK_PDF_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="deck2pdf" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/.." >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/lib/deck2pdf-0.3.0.jar:$APP_HOME/lib/jfxrt.jar:$APP_HOME/lib/itextpdf-5.5.1.jar:$APP_HOME/lib/groovy-2.4.4.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And DECK_PDF_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $DECK_PDF_OPTS 162 | 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" me.champeau.deck2pdf.Main "$@" 165 | -------------------------------------------------------------------------------- /deck2pdf-0.3.0/bin/deck2pdf.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem deck2pdf startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and DECK_PDF_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME%.. 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\lib\deck2pdf-0.3.0.jar;%APP_HOME%\lib\jfxrt.jar;%APP_HOME%\lib\itextpdf-5.5.1.jar;%APP_HOME%\lib\groovy-2.4.4.jar 73 | 74 | @rem Execute deck2pdf 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %DECK_PDF_OPTS% -classpath "%CLASSPATH%" me.champeau.deck2pdf.Main %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable DECK_PDF_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%DECK_PDF_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /deck2pdf-0.3.0/lib/deck2pdf-0.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/deck2pdf-0.3.0/lib/deck2pdf-0.3.0.jar -------------------------------------------------------------------------------- /deck2pdf-0.3.0/lib/groovy-2.4.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/deck2pdf-0.3.0/lib/groovy-2.4.4.jar -------------------------------------------------------------------------------- /deck2pdf-0.3.0/lib/itextpdf-5.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/deck2pdf-0.3.0/lib/itextpdf-5.5.1.jar -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Remark Viewer 5 | 6 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /make-pdf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | sections = [ 4 | 'spas-tooling-and-typescript/README.md', 5 | 'angular-fundamentals/README.md', 6 | 'angular-advanced-topics/README.md', 7 | 'reactive-programming-with-angular/README.md', 8 | 'testing/README.md', 9 | 'angular-other-topics/animations/README.md', 10 | 'angular-other-topics/change-detection/README.md', 11 | 'angular-other-topics/lazy-loading/README.md', 12 | 'useful-resources/README.md' 13 | ] 14 | 15 | for filename in sections: 16 | f = open(os.path.join('./', filename), 'rt') 17 | print(f.read() + '\n\n---') 18 | f.close() 19 | 20 | # Making PDF slides: 21 | # 1. Download deck2pdf 22 | # 2. Run this script and output it to pdf.md (python make-pdf.py > pdf.md) 23 | # 3. Place contents of pdf.md instead pdf.html's textarea section 24 | # 4. Run deck2pdf --profile=remarkjs pdf.html pdf.pdf 25 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-basic-training", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "anymatch": { 17 | "version": "1.3.2", 18 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", 19 | "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", 20 | "requires": { 21 | "micromatch": "2.3.11", 22 | "normalize-path": "2.1.1" 23 | } 24 | }, 25 | "apache-crypt": { 26 | "version": "1.2.1", 27 | "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.1.tgz", 28 | "integrity": "sha1-1vxyqm0n2ZyVqU/RiNcx7v/6Zjw=", 29 | "requires": { 30 | "unix-crypt-td-js": "1.0.0" 31 | } 32 | }, 33 | "apache-md5": { 34 | "version": "1.1.2", 35 | "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.2.tgz", 36 | "integrity": "sha1-7klza2ObTxCLbp5ibG2pkwa0FpI=" 37 | }, 38 | "arr-diff": { 39 | "version": "2.0.0", 40 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", 41 | "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", 42 | "requires": { 43 | "arr-flatten": "1.1.0" 44 | } 45 | }, 46 | "arr-flatten": { 47 | "version": "1.1.0", 48 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", 49 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" 50 | }, 51 | "array-unique": { 52 | "version": "0.2.1", 53 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", 54 | "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" 55 | }, 56 | "async-each": { 57 | "version": "1.0.1", 58 | "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", 59 | "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" 60 | }, 61 | "balanced-match": { 62 | "version": "1.0.0", 63 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 64 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 65 | }, 66 | "basic-auth": { 67 | "version": "2.0.0", 68 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", 69 | "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", 70 | "requires": { 71 | "safe-buffer": "5.1.1" 72 | } 73 | }, 74 | "batch": { 75 | "version": "0.6.1", 76 | "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", 77 | "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" 78 | }, 79 | "bcryptjs": { 80 | "version": "2.4.3", 81 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 82 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 83 | }, 84 | "binary-extensions": { 85 | "version": "1.11.0", 86 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", 87 | "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=" 88 | }, 89 | "brace-expansion": { 90 | "version": "1.1.11", 91 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 92 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 93 | "requires": { 94 | "balanced-match": "1.0.0", 95 | "concat-map": "0.0.1" 96 | } 97 | }, 98 | "braces": { 99 | "version": "1.8.5", 100 | "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", 101 | "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", 102 | "requires": { 103 | "expand-range": "1.8.2", 104 | "preserve": "0.2.0", 105 | "repeat-element": "1.1.2" 106 | } 107 | }, 108 | "chokidar": { 109 | "version": "1.7.0", 110 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", 111 | "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", 112 | "requires": { 113 | "anymatch": "1.3.2", 114 | "async-each": "1.0.1", 115 | "fsevents": "1.1.3", 116 | "glob-parent": "2.0.0", 117 | "inherits": "2.0.3", 118 | "is-binary-path": "1.0.1", 119 | "is-glob": "2.0.1", 120 | "path-is-absolute": "1.0.1", 121 | "readdirp": "2.1.0" 122 | } 123 | }, 124 | "colors": { 125 | "version": "1.2.1", 126 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.1.tgz", 127 | "integrity": "sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg==" 128 | }, 129 | "concat-map": { 130 | "version": "0.0.1", 131 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 132 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 133 | }, 134 | "connect": { 135 | "version": "3.5.1", 136 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.5.1.tgz", 137 | "integrity": "sha1-bTDXpjx/FwhXprOqazY9lz3KWI4=", 138 | "requires": { 139 | "debug": "2.2.0", 140 | "finalhandler": "0.5.1", 141 | "parseurl": "1.3.2", 142 | "utils-merge": "1.0.0" 143 | } 144 | }, 145 | "core-util-is": { 146 | "version": "1.0.2", 147 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 148 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 149 | }, 150 | "cors": { 151 | "version": "2.8.4", 152 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", 153 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", 154 | "requires": { 155 | "object-assign": "4.1.1", 156 | "vary": "1.1.2" 157 | } 158 | }, 159 | "debug": { 160 | "version": "2.2.0", 161 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 162 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 163 | "requires": { 164 | "ms": "0.7.1" 165 | } 166 | }, 167 | "depd": { 168 | "version": "1.1.2", 169 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 170 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 171 | }, 172 | "destroy": { 173 | "version": "1.0.4", 174 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 175 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 176 | }, 177 | "duplexer": { 178 | "version": "0.1.1", 179 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", 180 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" 181 | }, 182 | "ee-first": { 183 | "version": "1.1.1", 184 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 185 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 186 | }, 187 | "encodeurl": { 188 | "version": "1.0.2", 189 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 190 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 191 | }, 192 | "escape-html": { 193 | "version": "1.0.3", 194 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 195 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 196 | }, 197 | "etag": { 198 | "version": "1.8.1", 199 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 200 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 201 | }, 202 | "event-stream": { 203 | "version": "3.3.4", 204 | "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", 205 | "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", 206 | "requires": { 207 | "duplexer": "0.1.1", 208 | "from": "0.1.7", 209 | "map-stream": "0.1.0", 210 | "pause-stream": "0.0.11", 211 | "split": "0.3.3", 212 | "stream-combiner": "0.0.4", 213 | "through": "2.3.8" 214 | } 215 | }, 216 | "expand-brackets": { 217 | "version": "0.1.5", 218 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", 219 | "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", 220 | "requires": { 221 | "is-posix-bracket": "0.1.1" 222 | } 223 | }, 224 | "expand-range": { 225 | "version": "1.8.2", 226 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", 227 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", 228 | "requires": { 229 | "fill-range": "2.2.3" 230 | } 231 | }, 232 | "extglob": { 233 | "version": "0.3.2", 234 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", 235 | "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", 236 | "requires": { 237 | "is-extglob": "1.0.0" 238 | } 239 | }, 240 | "faye-websocket": { 241 | "version": "0.11.1", 242 | "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", 243 | "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", 244 | "requires": { 245 | "websocket-driver": "0.7.0" 246 | } 247 | }, 248 | "filename-regex": { 249 | "version": "2.0.1", 250 | "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", 251 | "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" 252 | }, 253 | "fill-range": { 254 | "version": "2.2.3", 255 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", 256 | "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", 257 | "requires": { 258 | "is-number": "2.1.0", 259 | "isobject": "2.1.0", 260 | "randomatic": "1.1.7", 261 | "repeat-element": "1.1.2", 262 | "repeat-string": "1.6.1" 263 | } 264 | }, 265 | "finalhandler": { 266 | "version": "0.5.1", 267 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.1.tgz", 268 | "integrity": "sha1-LEANjUUwk1vCMlScX6OF7Afeb80=", 269 | "requires": { 270 | "debug": "2.2.0", 271 | "escape-html": "1.0.3", 272 | "on-finished": "2.3.0", 273 | "statuses": "1.3.1", 274 | "unpipe": "1.0.0" 275 | } 276 | }, 277 | "for-in": { 278 | "version": "1.0.2", 279 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 280 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" 281 | }, 282 | "for-own": { 283 | "version": "0.1.5", 284 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 285 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", 286 | "requires": { 287 | "for-in": "1.0.2" 288 | } 289 | }, 290 | "fresh": { 291 | "version": "0.5.2", 292 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 293 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 294 | }, 295 | "from": { 296 | "version": "0.1.7", 297 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 298 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" 299 | }, 300 | "fsevents": { 301 | "version": "1.1.3", 302 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", 303 | "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", 304 | "optional": true, 305 | "requires": { 306 | "nan": "2.10.0", 307 | "node-pre-gyp": "0.6.39" 308 | }, 309 | "dependencies": { 310 | "abbrev": { 311 | "version": "1.1.0", 312 | "bundled": true, 313 | "optional": true 314 | }, 315 | "ajv": { 316 | "version": "4.11.8", 317 | "bundled": true, 318 | "optional": true, 319 | "requires": { 320 | "co": "4.6.0", 321 | "json-stable-stringify": "1.0.1" 322 | } 323 | }, 324 | "ansi-regex": { 325 | "version": "2.1.1", 326 | "bundled": true 327 | }, 328 | "aproba": { 329 | "version": "1.1.1", 330 | "bundled": true, 331 | "optional": true 332 | }, 333 | "are-we-there-yet": { 334 | "version": "1.1.4", 335 | "bundled": true, 336 | "optional": true, 337 | "requires": { 338 | "delegates": "1.0.0", 339 | "readable-stream": "2.2.9" 340 | } 341 | }, 342 | "asn1": { 343 | "version": "0.2.3", 344 | "bundled": true, 345 | "optional": true 346 | }, 347 | "assert-plus": { 348 | "version": "0.2.0", 349 | "bundled": true, 350 | "optional": true 351 | }, 352 | "asynckit": { 353 | "version": "0.4.0", 354 | "bundled": true, 355 | "optional": true 356 | }, 357 | "aws-sign2": { 358 | "version": "0.6.0", 359 | "bundled": true, 360 | "optional": true 361 | }, 362 | "aws4": { 363 | "version": "1.6.0", 364 | "bundled": true, 365 | "optional": true 366 | }, 367 | "balanced-match": { 368 | "version": "0.4.2", 369 | "bundled": true 370 | }, 371 | "bcrypt-pbkdf": { 372 | "version": "1.0.1", 373 | "bundled": true, 374 | "optional": true, 375 | "requires": { 376 | "tweetnacl": "0.14.5" 377 | } 378 | }, 379 | "block-stream": { 380 | "version": "0.0.9", 381 | "bundled": true, 382 | "requires": { 383 | "inherits": "2.0.3" 384 | } 385 | }, 386 | "boom": { 387 | "version": "2.10.1", 388 | "bundled": true, 389 | "requires": { 390 | "hoek": "2.16.3" 391 | } 392 | }, 393 | "brace-expansion": { 394 | "version": "1.1.7", 395 | "bundled": true, 396 | "requires": { 397 | "balanced-match": "0.4.2", 398 | "concat-map": "0.0.1" 399 | } 400 | }, 401 | "buffer-shims": { 402 | "version": "1.0.0", 403 | "bundled": true 404 | }, 405 | "caseless": { 406 | "version": "0.12.0", 407 | "bundled": true, 408 | "optional": true 409 | }, 410 | "co": { 411 | "version": "4.6.0", 412 | "bundled": true, 413 | "optional": true 414 | }, 415 | "code-point-at": { 416 | "version": "1.1.0", 417 | "bundled": true 418 | }, 419 | "combined-stream": { 420 | "version": "1.0.5", 421 | "bundled": true, 422 | "requires": { 423 | "delayed-stream": "1.0.0" 424 | } 425 | }, 426 | "concat-map": { 427 | "version": "0.0.1", 428 | "bundled": true 429 | }, 430 | "console-control-strings": { 431 | "version": "1.1.0", 432 | "bundled": true 433 | }, 434 | "core-util-is": { 435 | "version": "1.0.2", 436 | "bundled": true 437 | }, 438 | "cryptiles": { 439 | "version": "2.0.5", 440 | "bundled": true, 441 | "requires": { 442 | "boom": "2.10.1" 443 | } 444 | }, 445 | "dashdash": { 446 | "version": "1.14.1", 447 | "bundled": true, 448 | "optional": true, 449 | "requires": { 450 | "assert-plus": "1.0.0" 451 | }, 452 | "dependencies": { 453 | "assert-plus": { 454 | "version": "1.0.0", 455 | "bundled": true, 456 | "optional": true 457 | } 458 | } 459 | }, 460 | "debug": { 461 | "version": "2.6.8", 462 | "bundled": true, 463 | "optional": true, 464 | "requires": { 465 | "ms": "2.0.0" 466 | } 467 | }, 468 | "deep-extend": { 469 | "version": "0.4.2", 470 | "bundled": true, 471 | "optional": true 472 | }, 473 | "delayed-stream": { 474 | "version": "1.0.0", 475 | "bundled": true 476 | }, 477 | "delegates": { 478 | "version": "1.0.0", 479 | "bundled": true, 480 | "optional": true 481 | }, 482 | "detect-libc": { 483 | "version": "1.0.2", 484 | "bundled": true, 485 | "optional": true 486 | }, 487 | "ecc-jsbn": { 488 | "version": "0.1.1", 489 | "bundled": true, 490 | "optional": true, 491 | "requires": { 492 | "jsbn": "0.1.1" 493 | } 494 | }, 495 | "extend": { 496 | "version": "3.0.1", 497 | "bundled": true, 498 | "optional": true 499 | }, 500 | "extsprintf": { 501 | "version": "1.0.2", 502 | "bundled": true 503 | }, 504 | "forever-agent": { 505 | "version": "0.6.1", 506 | "bundled": true, 507 | "optional": true 508 | }, 509 | "form-data": { 510 | "version": "2.1.4", 511 | "bundled": true, 512 | "optional": true, 513 | "requires": { 514 | "asynckit": "0.4.0", 515 | "combined-stream": "1.0.5", 516 | "mime-types": "2.1.15" 517 | } 518 | }, 519 | "fs.realpath": { 520 | "version": "1.0.0", 521 | "bundled": true 522 | }, 523 | "fstream": { 524 | "version": "1.0.11", 525 | "bundled": true, 526 | "requires": { 527 | "graceful-fs": "4.1.11", 528 | "inherits": "2.0.3", 529 | "mkdirp": "0.5.1", 530 | "rimraf": "2.6.1" 531 | } 532 | }, 533 | "fstream-ignore": { 534 | "version": "1.0.5", 535 | "bundled": true, 536 | "optional": true, 537 | "requires": { 538 | "fstream": "1.0.11", 539 | "inherits": "2.0.3", 540 | "minimatch": "3.0.4" 541 | } 542 | }, 543 | "gauge": { 544 | "version": "2.7.4", 545 | "bundled": true, 546 | "optional": true, 547 | "requires": { 548 | "aproba": "1.1.1", 549 | "console-control-strings": "1.1.0", 550 | "has-unicode": "2.0.1", 551 | "object-assign": "4.1.1", 552 | "signal-exit": "3.0.2", 553 | "string-width": "1.0.2", 554 | "strip-ansi": "3.0.1", 555 | "wide-align": "1.1.2" 556 | } 557 | }, 558 | "getpass": { 559 | "version": "0.1.7", 560 | "bundled": true, 561 | "optional": true, 562 | "requires": { 563 | "assert-plus": "1.0.0" 564 | }, 565 | "dependencies": { 566 | "assert-plus": { 567 | "version": "1.0.0", 568 | "bundled": true, 569 | "optional": true 570 | } 571 | } 572 | }, 573 | "glob": { 574 | "version": "7.1.2", 575 | "bundled": true, 576 | "requires": { 577 | "fs.realpath": "1.0.0", 578 | "inflight": "1.0.6", 579 | "inherits": "2.0.3", 580 | "minimatch": "3.0.4", 581 | "once": "1.4.0", 582 | "path-is-absolute": "1.0.1" 583 | } 584 | }, 585 | "graceful-fs": { 586 | "version": "4.1.11", 587 | "bundled": true 588 | }, 589 | "har-schema": { 590 | "version": "1.0.5", 591 | "bundled": true, 592 | "optional": true 593 | }, 594 | "har-validator": { 595 | "version": "4.2.1", 596 | "bundled": true, 597 | "optional": true, 598 | "requires": { 599 | "ajv": "4.11.8", 600 | "har-schema": "1.0.5" 601 | } 602 | }, 603 | "has-unicode": { 604 | "version": "2.0.1", 605 | "bundled": true, 606 | "optional": true 607 | }, 608 | "hawk": { 609 | "version": "3.1.3", 610 | "bundled": true, 611 | "requires": { 612 | "boom": "2.10.1", 613 | "cryptiles": "2.0.5", 614 | "hoek": "2.16.3", 615 | "sntp": "1.0.9" 616 | } 617 | }, 618 | "hoek": { 619 | "version": "2.16.3", 620 | "bundled": true 621 | }, 622 | "http-signature": { 623 | "version": "1.1.1", 624 | "bundled": true, 625 | "optional": true, 626 | "requires": { 627 | "assert-plus": "0.2.0", 628 | "jsprim": "1.4.0", 629 | "sshpk": "1.13.0" 630 | } 631 | }, 632 | "inflight": { 633 | "version": "1.0.6", 634 | "bundled": true, 635 | "requires": { 636 | "once": "1.4.0", 637 | "wrappy": "1.0.2" 638 | } 639 | }, 640 | "inherits": { 641 | "version": "2.0.3", 642 | "bundled": true 643 | }, 644 | "ini": { 645 | "version": "1.3.4", 646 | "bundled": true, 647 | "optional": true 648 | }, 649 | "is-fullwidth-code-point": { 650 | "version": "1.0.0", 651 | "bundled": true, 652 | "requires": { 653 | "number-is-nan": "1.0.1" 654 | } 655 | }, 656 | "is-typedarray": { 657 | "version": "1.0.0", 658 | "bundled": true, 659 | "optional": true 660 | }, 661 | "isarray": { 662 | "version": "1.0.0", 663 | "bundled": true 664 | }, 665 | "isstream": { 666 | "version": "0.1.2", 667 | "bundled": true, 668 | "optional": true 669 | }, 670 | "jodid25519": { 671 | "version": "1.0.2", 672 | "bundled": true, 673 | "optional": true, 674 | "requires": { 675 | "jsbn": "0.1.1" 676 | } 677 | }, 678 | "jsbn": { 679 | "version": "0.1.1", 680 | "bundled": true, 681 | "optional": true 682 | }, 683 | "json-schema": { 684 | "version": "0.2.3", 685 | "bundled": true, 686 | "optional": true 687 | }, 688 | "json-stable-stringify": { 689 | "version": "1.0.1", 690 | "bundled": true, 691 | "optional": true, 692 | "requires": { 693 | "jsonify": "0.0.0" 694 | } 695 | }, 696 | "json-stringify-safe": { 697 | "version": "5.0.1", 698 | "bundled": true, 699 | "optional": true 700 | }, 701 | "jsonify": { 702 | "version": "0.0.0", 703 | "bundled": true, 704 | "optional": true 705 | }, 706 | "jsprim": { 707 | "version": "1.4.0", 708 | "bundled": true, 709 | "optional": true, 710 | "requires": { 711 | "assert-plus": "1.0.0", 712 | "extsprintf": "1.0.2", 713 | "json-schema": "0.2.3", 714 | "verror": "1.3.6" 715 | }, 716 | "dependencies": { 717 | "assert-plus": { 718 | "version": "1.0.0", 719 | "bundled": true, 720 | "optional": true 721 | } 722 | } 723 | }, 724 | "mime-db": { 725 | "version": "1.27.0", 726 | "bundled": true 727 | }, 728 | "mime-types": { 729 | "version": "2.1.15", 730 | "bundled": true, 731 | "requires": { 732 | "mime-db": "1.27.0" 733 | } 734 | }, 735 | "minimatch": { 736 | "version": "3.0.4", 737 | "bundled": true, 738 | "requires": { 739 | "brace-expansion": "1.1.7" 740 | } 741 | }, 742 | "minimist": { 743 | "version": "0.0.8", 744 | "bundled": true 745 | }, 746 | "mkdirp": { 747 | "version": "0.5.1", 748 | "bundled": true, 749 | "requires": { 750 | "minimist": "0.0.8" 751 | } 752 | }, 753 | "ms": { 754 | "version": "2.0.0", 755 | "bundled": true, 756 | "optional": true 757 | }, 758 | "node-pre-gyp": { 759 | "version": "0.6.39", 760 | "bundled": true, 761 | "optional": true, 762 | "requires": { 763 | "detect-libc": "1.0.2", 764 | "hawk": "3.1.3", 765 | "mkdirp": "0.5.1", 766 | "nopt": "4.0.1", 767 | "npmlog": "4.1.0", 768 | "rc": "1.2.1", 769 | "request": "2.81.0", 770 | "rimraf": "2.6.1", 771 | "semver": "5.3.0", 772 | "tar": "2.2.1", 773 | "tar-pack": "3.4.0" 774 | } 775 | }, 776 | "nopt": { 777 | "version": "4.0.1", 778 | "bundled": true, 779 | "optional": true, 780 | "requires": { 781 | "abbrev": "1.1.0", 782 | "osenv": "0.1.4" 783 | } 784 | }, 785 | "npmlog": { 786 | "version": "4.1.0", 787 | "bundled": true, 788 | "optional": true, 789 | "requires": { 790 | "are-we-there-yet": "1.1.4", 791 | "console-control-strings": "1.1.0", 792 | "gauge": "2.7.4", 793 | "set-blocking": "2.0.0" 794 | } 795 | }, 796 | "number-is-nan": { 797 | "version": "1.0.1", 798 | "bundled": true 799 | }, 800 | "oauth-sign": { 801 | "version": "0.8.2", 802 | "bundled": true, 803 | "optional": true 804 | }, 805 | "object-assign": { 806 | "version": "4.1.1", 807 | "bundled": true, 808 | "optional": true 809 | }, 810 | "once": { 811 | "version": "1.4.0", 812 | "bundled": true, 813 | "requires": { 814 | "wrappy": "1.0.2" 815 | } 816 | }, 817 | "os-homedir": { 818 | "version": "1.0.2", 819 | "bundled": true, 820 | "optional": true 821 | }, 822 | "os-tmpdir": { 823 | "version": "1.0.2", 824 | "bundled": true, 825 | "optional": true 826 | }, 827 | "osenv": { 828 | "version": "0.1.4", 829 | "bundled": true, 830 | "optional": true, 831 | "requires": { 832 | "os-homedir": "1.0.2", 833 | "os-tmpdir": "1.0.2" 834 | } 835 | }, 836 | "path-is-absolute": { 837 | "version": "1.0.1", 838 | "bundled": true 839 | }, 840 | "performance-now": { 841 | "version": "0.2.0", 842 | "bundled": true, 843 | "optional": true 844 | }, 845 | "process-nextick-args": { 846 | "version": "1.0.7", 847 | "bundled": true 848 | }, 849 | "punycode": { 850 | "version": "1.4.1", 851 | "bundled": true, 852 | "optional": true 853 | }, 854 | "qs": { 855 | "version": "6.4.0", 856 | "bundled": true, 857 | "optional": true 858 | }, 859 | "rc": { 860 | "version": "1.2.1", 861 | "bundled": true, 862 | "optional": true, 863 | "requires": { 864 | "deep-extend": "0.4.2", 865 | "ini": "1.3.4", 866 | "minimist": "1.2.0", 867 | "strip-json-comments": "2.0.1" 868 | }, 869 | "dependencies": { 870 | "minimist": { 871 | "version": "1.2.0", 872 | "bundled": true, 873 | "optional": true 874 | } 875 | } 876 | }, 877 | "readable-stream": { 878 | "version": "2.2.9", 879 | "bundled": true, 880 | "requires": { 881 | "buffer-shims": "1.0.0", 882 | "core-util-is": "1.0.2", 883 | "inherits": "2.0.3", 884 | "isarray": "1.0.0", 885 | "process-nextick-args": "1.0.7", 886 | "string_decoder": "1.0.1", 887 | "util-deprecate": "1.0.2" 888 | } 889 | }, 890 | "request": { 891 | "version": "2.81.0", 892 | "bundled": true, 893 | "optional": true, 894 | "requires": { 895 | "aws-sign2": "0.6.0", 896 | "aws4": "1.6.0", 897 | "caseless": "0.12.0", 898 | "combined-stream": "1.0.5", 899 | "extend": "3.0.1", 900 | "forever-agent": "0.6.1", 901 | "form-data": "2.1.4", 902 | "har-validator": "4.2.1", 903 | "hawk": "3.1.3", 904 | "http-signature": "1.1.1", 905 | "is-typedarray": "1.0.0", 906 | "isstream": "0.1.2", 907 | "json-stringify-safe": "5.0.1", 908 | "mime-types": "2.1.15", 909 | "oauth-sign": "0.8.2", 910 | "performance-now": "0.2.0", 911 | "qs": "6.4.0", 912 | "safe-buffer": "5.0.1", 913 | "stringstream": "0.0.5", 914 | "tough-cookie": "2.3.2", 915 | "tunnel-agent": "0.6.0", 916 | "uuid": "3.0.1" 917 | } 918 | }, 919 | "rimraf": { 920 | "version": "2.6.1", 921 | "bundled": true, 922 | "requires": { 923 | "glob": "7.1.2" 924 | } 925 | }, 926 | "safe-buffer": { 927 | "version": "5.0.1", 928 | "bundled": true 929 | }, 930 | "semver": { 931 | "version": "5.3.0", 932 | "bundled": true, 933 | "optional": true 934 | }, 935 | "set-blocking": { 936 | "version": "2.0.0", 937 | "bundled": true, 938 | "optional": true 939 | }, 940 | "signal-exit": { 941 | "version": "3.0.2", 942 | "bundled": true, 943 | "optional": true 944 | }, 945 | "sntp": { 946 | "version": "1.0.9", 947 | "bundled": true, 948 | "requires": { 949 | "hoek": "2.16.3" 950 | } 951 | }, 952 | "sshpk": { 953 | "version": "1.13.0", 954 | "bundled": true, 955 | "optional": true, 956 | "requires": { 957 | "asn1": "0.2.3", 958 | "assert-plus": "1.0.0", 959 | "bcrypt-pbkdf": "1.0.1", 960 | "dashdash": "1.14.1", 961 | "ecc-jsbn": "0.1.1", 962 | "getpass": "0.1.7", 963 | "jodid25519": "1.0.2", 964 | "jsbn": "0.1.1", 965 | "tweetnacl": "0.14.5" 966 | }, 967 | "dependencies": { 968 | "assert-plus": { 969 | "version": "1.0.0", 970 | "bundled": true, 971 | "optional": true 972 | } 973 | } 974 | }, 975 | "string-width": { 976 | "version": "1.0.2", 977 | "bundled": true, 978 | "requires": { 979 | "code-point-at": "1.1.0", 980 | "is-fullwidth-code-point": "1.0.0", 981 | "strip-ansi": "3.0.1" 982 | } 983 | }, 984 | "string_decoder": { 985 | "version": "1.0.1", 986 | "bundled": true, 987 | "requires": { 988 | "safe-buffer": "5.0.1" 989 | } 990 | }, 991 | "stringstream": { 992 | "version": "0.0.5", 993 | "bundled": true, 994 | "optional": true 995 | }, 996 | "strip-ansi": { 997 | "version": "3.0.1", 998 | "bundled": true, 999 | "requires": { 1000 | "ansi-regex": "2.1.1" 1001 | } 1002 | }, 1003 | "strip-json-comments": { 1004 | "version": "2.0.1", 1005 | "bundled": true, 1006 | "optional": true 1007 | }, 1008 | "tar": { 1009 | "version": "2.2.1", 1010 | "bundled": true, 1011 | "requires": { 1012 | "block-stream": "0.0.9", 1013 | "fstream": "1.0.11", 1014 | "inherits": "2.0.3" 1015 | } 1016 | }, 1017 | "tar-pack": { 1018 | "version": "3.4.0", 1019 | "bundled": true, 1020 | "optional": true, 1021 | "requires": { 1022 | "debug": "2.6.8", 1023 | "fstream": "1.0.11", 1024 | "fstream-ignore": "1.0.5", 1025 | "once": "1.4.0", 1026 | "readable-stream": "2.2.9", 1027 | "rimraf": "2.6.1", 1028 | "tar": "2.2.1", 1029 | "uid-number": "0.0.6" 1030 | } 1031 | }, 1032 | "tough-cookie": { 1033 | "version": "2.3.2", 1034 | "bundled": true, 1035 | "optional": true, 1036 | "requires": { 1037 | "punycode": "1.4.1" 1038 | } 1039 | }, 1040 | "tunnel-agent": { 1041 | "version": "0.6.0", 1042 | "bundled": true, 1043 | "optional": true, 1044 | "requires": { 1045 | "safe-buffer": "5.0.1" 1046 | } 1047 | }, 1048 | "tweetnacl": { 1049 | "version": "0.14.5", 1050 | "bundled": true, 1051 | "optional": true 1052 | }, 1053 | "uid-number": { 1054 | "version": "0.0.6", 1055 | "bundled": true, 1056 | "optional": true 1057 | }, 1058 | "util-deprecate": { 1059 | "version": "1.0.2", 1060 | "bundled": true 1061 | }, 1062 | "uuid": { 1063 | "version": "3.0.1", 1064 | "bundled": true, 1065 | "optional": true 1066 | }, 1067 | "verror": { 1068 | "version": "1.3.6", 1069 | "bundled": true, 1070 | "optional": true, 1071 | "requires": { 1072 | "extsprintf": "1.0.2" 1073 | } 1074 | }, 1075 | "wide-align": { 1076 | "version": "1.1.2", 1077 | "bundled": true, 1078 | "optional": true, 1079 | "requires": { 1080 | "string-width": "1.0.2" 1081 | } 1082 | }, 1083 | "wrappy": { 1084 | "version": "1.0.2", 1085 | "bundled": true 1086 | } 1087 | } 1088 | }, 1089 | "glob-base": { 1090 | "version": "0.3.0", 1091 | "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", 1092 | "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", 1093 | "requires": { 1094 | "glob-parent": "2.0.0", 1095 | "is-glob": "2.0.1" 1096 | } 1097 | }, 1098 | "glob-parent": { 1099 | "version": "2.0.0", 1100 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", 1101 | "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", 1102 | "requires": { 1103 | "is-glob": "2.0.1" 1104 | } 1105 | }, 1106 | "graceful-fs": { 1107 | "version": "4.1.11", 1108 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 1109 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 1110 | }, 1111 | "http-auth": { 1112 | "version": "3.1.3", 1113 | "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", 1114 | "integrity": "sha1-lFz63WZSHq+PfISRPTd9exXyTjE=", 1115 | "requires": { 1116 | "apache-crypt": "1.2.1", 1117 | "apache-md5": "1.1.2", 1118 | "bcryptjs": "2.4.3", 1119 | "uuid": "3.2.1" 1120 | } 1121 | }, 1122 | "http-errors": { 1123 | "version": "1.6.3", 1124 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 1125 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 1126 | "requires": { 1127 | "depd": "1.1.2", 1128 | "inherits": "2.0.3", 1129 | "setprototypeof": "1.1.0", 1130 | "statuses": "1.5.0" 1131 | }, 1132 | "dependencies": { 1133 | "statuses": { 1134 | "version": "1.5.0", 1135 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1136 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1137 | } 1138 | } 1139 | }, 1140 | "http-parser-js": { 1141 | "version": "0.4.11", 1142 | "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.11.tgz", 1143 | "integrity": "sha512-QCR5O2AjjMW8Mo4HyI1ctFcv+O99j/0g367V3YoVnrNw5hkDvAWZD0lWGcc+F4yN3V55USPCVix4efb75HxFfA==" 1144 | }, 1145 | "inherits": { 1146 | "version": "2.0.3", 1147 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1148 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 1149 | }, 1150 | "is-binary-path": { 1151 | "version": "1.0.1", 1152 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", 1153 | "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", 1154 | "requires": { 1155 | "binary-extensions": "1.11.0" 1156 | } 1157 | }, 1158 | "is-buffer": { 1159 | "version": "1.1.6", 1160 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 1161 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 1162 | }, 1163 | "is-dotfile": { 1164 | "version": "1.0.3", 1165 | "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", 1166 | "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" 1167 | }, 1168 | "is-equal-shallow": { 1169 | "version": "0.1.3", 1170 | "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", 1171 | "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", 1172 | "requires": { 1173 | "is-primitive": "2.0.0" 1174 | } 1175 | }, 1176 | "is-extendable": { 1177 | "version": "0.1.1", 1178 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 1179 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" 1180 | }, 1181 | "is-extglob": { 1182 | "version": "1.0.0", 1183 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", 1184 | "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" 1185 | }, 1186 | "is-glob": { 1187 | "version": "2.0.1", 1188 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", 1189 | "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", 1190 | "requires": { 1191 | "is-extglob": "1.0.0" 1192 | } 1193 | }, 1194 | "is-number": { 1195 | "version": "2.1.0", 1196 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", 1197 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", 1198 | "requires": { 1199 | "kind-of": "3.2.2" 1200 | } 1201 | }, 1202 | "is-posix-bracket": { 1203 | "version": "0.1.1", 1204 | "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", 1205 | "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" 1206 | }, 1207 | "is-primitive": { 1208 | "version": "2.0.0", 1209 | "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", 1210 | "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" 1211 | }, 1212 | "is-wsl": { 1213 | "version": "1.1.0", 1214 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", 1215 | "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" 1216 | }, 1217 | "isarray": { 1218 | "version": "1.0.0", 1219 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1220 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 1221 | }, 1222 | "isobject": { 1223 | "version": "2.1.0", 1224 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", 1225 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", 1226 | "requires": { 1227 | "isarray": "1.0.0" 1228 | } 1229 | }, 1230 | "kind-of": { 1231 | "version": "3.2.2", 1232 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1233 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1234 | "requires": { 1235 | "is-buffer": "1.1.6" 1236 | } 1237 | }, 1238 | "live-server": { 1239 | "version": "1.2.0", 1240 | "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.0.tgz", 1241 | "integrity": "sha1-RJhkS7+Bpm8Y3Y3/3vYcTBw3TKM=", 1242 | "requires": { 1243 | "chokidar": "1.7.0", 1244 | "colors": "1.2.1", 1245 | "connect": "3.5.1", 1246 | "cors": "2.8.4", 1247 | "event-stream": "3.3.4", 1248 | "faye-websocket": "0.11.1", 1249 | "http-auth": "3.1.3", 1250 | "morgan": "1.9.0", 1251 | "object-assign": "4.1.1", 1252 | "opn": "5.3.0", 1253 | "proxy-middleware": "0.15.0", 1254 | "send": "0.16.2", 1255 | "serve-index": "1.9.1" 1256 | } 1257 | }, 1258 | "map-stream": { 1259 | "version": "0.1.0", 1260 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 1261 | "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" 1262 | }, 1263 | "micromatch": { 1264 | "version": "2.3.11", 1265 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", 1266 | "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", 1267 | "requires": { 1268 | "arr-diff": "2.0.0", 1269 | "array-unique": "0.2.1", 1270 | "braces": "1.8.5", 1271 | "expand-brackets": "0.1.5", 1272 | "extglob": "0.3.2", 1273 | "filename-regex": "2.0.1", 1274 | "is-extglob": "1.0.0", 1275 | "is-glob": "2.0.1", 1276 | "kind-of": "3.2.2", 1277 | "normalize-path": "2.1.1", 1278 | "object.omit": "2.0.1", 1279 | "parse-glob": "3.0.4", 1280 | "regex-cache": "0.4.4" 1281 | } 1282 | }, 1283 | "mime": { 1284 | "version": "1.4.1", 1285 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 1286 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 1287 | }, 1288 | "mime-db": { 1289 | "version": "1.33.0", 1290 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 1291 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 1292 | }, 1293 | "mime-types": { 1294 | "version": "2.1.18", 1295 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 1296 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 1297 | "requires": { 1298 | "mime-db": "1.33.0" 1299 | } 1300 | }, 1301 | "minimatch": { 1302 | "version": "3.0.4", 1303 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1304 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1305 | "requires": { 1306 | "brace-expansion": "1.1.11" 1307 | } 1308 | }, 1309 | "morgan": { 1310 | "version": "1.9.0", 1311 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", 1312 | "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", 1313 | "requires": { 1314 | "basic-auth": "2.0.0", 1315 | "debug": "2.6.9", 1316 | "depd": "1.1.2", 1317 | "on-finished": "2.3.0", 1318 | "on-headers": "1.0.1" 1319 | }, 1320 | "dependencies": { 1321 | "debug": { 1322 | "version": "2.6.9", 1323 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1324 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1325 | "requires": { 1326 | "ms": "2.0.0" 1327 | } 1328 | }, 1329 | "ms": { 1330 | "version": "2.0.0", 1331 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1332 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1333 | } 1334 | } 1335 | }, 1336 | "ms": { 1337 | "version": "0.7.1", 1338 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 1339 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" 1340 | }, 1341 | "nan": { 1342 | "version": "2.10.0", 1343 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", 1344 | "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", 1345 | "optional": true 1346 | }, 1347 | "negotiator": { 1348 | "version": "0.6.1", 1349 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 1350 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 1351 | }, 1352 | "normalize-path": { 1353 | "version": "2.1.1", 1354 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", 1355 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", 1356 | "requires": { 1357 | "remove-trailing-separator": "1.1.0" 1358 | } 1359 | }, 1360 | "object-assign": { 1361 | "version": "4.1.1", 1362 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1363 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1364 | }, 1365 | "object.omit": { 1366 | "version": "2.0.1", 1367 | "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", 1368 | "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", 1369 | "requires": { 1370 | "for-own": "0.1.5", 1371 | "is-extendable": "0.1.1" 1372 | } 1373 | }, 1374 | "on-finished": { 1375 | "version": "2.3.0", 1376 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1377 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1378 | "requires": { 1379 | "ee-first": "1.1.1" 1380 | } 1381 | }, 1382 | "on-headers": { 1383 | "version": "1.0.1", 1384 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 1385 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 1386 | }, 1387 | "opn": { 1388 | "version": "5.3.0", 1389 | "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", 1390 | "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", 1391 | "requires": { 1392 | "is-wsl": "1.1.0" 1393 | } 1394 | }, 1395 | "parse-glob": { 1396 | "version": "3.0.4", 1397 | "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", 1398 | "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", 1399 | "requires": { 1400 | "glob-base": "0.3.0", 1401 | "is-dotfile": "1.0.3", 1402 | "is-extglob": "1.0.0", 1403 | "is-glob": "2.0.1" 1404 | } 1405 | }, 1406 | "parseurl": { 1407 | "version": "1.3.2", 1408 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 1409 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 1410 | }, 1411 | "path-is-absolute": { 1412 | "version": "1.0.1", 1413 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1414 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1415 | }, 1416 | "pause-stream": { 1417 | "version": "0.0.11", 1418 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 1419 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", 1420 | "requires": { 1421 | "through": "2.3.8" 1422 | } 1423 | }, 1424 | "preserve": { 1425 | "version": "0.2.0", 1426 | "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", 1427 | "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" 1428 | }, 1429 | "process-nextick-args": { 1430 | "version": "2.0.0", 1431 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 1432 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 1433 | }, 1434 | "proxy-middleware": { 1435 | "version": "0.15.0", 1436 | "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", 1437 | "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=" 1438 | }, 1439 | "randomatic": { 1440 | "version": "1.1.7", 1441 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", 1442 | "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", 1443 | "requires": { 1444 | "is-number": "3.0.0", 1445 | "kind-of": "4.0.0" 1446 | }, 1447 | "dependencies": { 1448 | "is-number": { 1449 | "version": "3.0.0", 1450 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", 1451 | "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", 1452 | "requires": { 1453 | "kind-of": "3.2.2" 1454 | }, 1455 | "dependencies": { 1456 | "kind-of": { 1457 | "version": "3.2.2", 1458 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1459 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1460 | "requires": { 1461 | "is-buffer": "1.1.6" 1462 | } 1463 | } 1464 | } 1465 | }, 1466 | "kind-of": { 1467 | "version": "4.0.0", 1468 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", 1469 | "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", 1470 | "requires": { 1471 | "is-buffer": "1.1.6" 1472 | } 1473 | } 1474 | } 1475 | }, 1476 | "range-parser": { 1477 | "version": "1.2.0", 1478 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1479 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1480 | }, 1481 | "readable-stream": { 1482 | "version": "2.3.6", 1483 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1484 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1485 | "requires": { 1486 | "core-util-is": "1.0.2", 1487 | "inherits": "2.0.3", 1488 | "isarray": "1.0.0", 1489 | "process-nextick-args": "2.0.0", 1490 | "safe-buffer": "5.1.1", 1491 | "string_decoder": "1.1.1", 1492 | "util-deprecate": "1.0.2" 1493 | } 1494 | }, 1495 | "readdirp": { 1496 | "version": "2.1.0", 1497 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", 1498 | "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", 1499 | "requires": { 1500 | "graceful-fs": "4.1.11", 1501 | "minimatch": "3.0.4", 1502 | "readable-stream": "2.3.6", 1503 | "set-immediate-shim": "1.0.1" 1504 | } 1505 | }, 1506 | "regex-cache": { 1507 | "version": "0.4.4", 1508 | "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", 1509 | "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", 1510 | "requires": { 1511 | "is-equal-shallow": "0.1.3" 1512 | } 1513 | }, 1514 | "remove-trailing-separator": { 1515 | "version": "1.1.0", 1516 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 1517 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" 1518 | }, 1519 | "repeat-element": { 1520 | "version": "1.1.2", 1521 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", 1522 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" 1523 | }, 1524 | "repeat-string": { 1525 | "version": "1.6.1", 1526 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1527 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 1528 | }, 1529 | "safe-buffer": { 1530 | "version": "5.1.1", 1531 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1532 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1533 | }, 1534 | "send": { 1535 | "version": "0.16.2", 1536 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 1537 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 1538 | "requires": { 1539 | "debug": "2.6.9", 1540 | "depd": "1.1.2", 1541 | "destroy": "1.0.4", 1542 | "encodeurl": "1.0.2", 1543 | "escape-html": "1.0.3", 1544 | "etag": "1.8.1", 1545 | "fresh": "0.5.2", 1546 | "http-errors": "1.6.3", 1547 | "mime": "1.4.1", 1548 | "ms": "2.0.0", 1549 | "on-finished": "2.3.0", 1550 | "range-parser": "1.2.0", 1551 | "statuses": "1.4.0" 1552 | }, 1553 | "dependencies": { 1554 | "debug": { 1555 | "version": "2.6.9", 1556 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1557 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1558 | "requires": { 1559 | "ms": "2.0.0" 1560 | } 1561 | }, 1562 | "ms": { 1563 | "version": "2.0.0", 1564 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1565 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1566 | }, 1567 | "statuses": { 1568 | "version": "1.4.0", 1569 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 1570 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 1571 | } 1572 | } 1573 | }, 1574 | "serve-index": { 1575 | "version": "1.9.1", 1576 | "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", 1577 | "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", 1578 | "requires": { 1579 | "accepts": "1.3.5", 1580 | "batch": "0.6.1", 1581 | "debug": "2.6.9", 1582 | "escape-html": "1.0.3", 1583 | "http-errors": "1.6.3", 1584 | "mime-types": "2.1.18", 1585 | "parseurl": "1.3.2" 1586 | }, 1587 | "dependencies": { 1588 | "debug": { 1589 | "version": "2.6.9", 1590 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1591 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1592 | "requires": { 1593 | "ms": "2.0.0" 1594 | } 1595 | }, 1596 | "ms": { 1597 | "version": "2.0.0", 1598 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1599 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1600 | } 1601 | } 1602 | }, 1603 | "set-immediate-shim": { 1604 | "version": "1.0.1", 1605 | "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", 1606 | "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" 1607 | }, 1608 | "setprototypeof": { 1609 | "version": "1.1.0", 1610 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 1611 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 1612 | }, 1613 | "split": { 1614 | "version": "0.3.3", 1615 | "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", 1616 | "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", 1617 | "requires": { 1618 | "through": "2.3.8" 1619 | } 1620 | }, 1621 | "statuses": { 1622 | "version": "1.3.1", 1623 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1624 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1625 | }, 1626 | "stream-combiner": { 1627 | "version": "0.0.4", 1628 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 1629 | "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", 1630 | "requires": { 1631 | "duplexer": "0.1.1" 1632 | } 1633 | }, 1634 | "string_decoder": { 1635 | "version": "1.1.1", 1636 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1637 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1638 | "requires": { 1639 | "safe-buffer": "5.1.1" 1640 | } 1641 | }, 1642 | "through": { 1643 | "version": "2.3.8", 1644 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1645 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 1646 | }, 1647 | "unix-crypt-td-js": { 1648 | "version": "1.0.0", 1649 | "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz", 1650 | "integrity": "sha1-HAgkFQSBvHoB1J6Y8exmjYJBLzs=" 1651 | }, 1652 | "unpipe": { 1653 | "version": "1.0.0", 1654 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1655 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1656 | }, 1657 | "util-deprecate": { 1658 | "version": "1.0.2", 1659 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1660 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1661 | }, 1662 | "utils-merge": { 1663 | "version": "1.0.0", 1664 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 1665 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 1666 | }, 1667 | "uuid": { 1668 | "version": "3.2.1", 1669 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 1670 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 1671 | }, 1672 | "vary": { 1673 | "version": "1.1.2", 1674 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1675 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1676 | }, 1677 | "websocket-driver": { 1678 | "version": "0.7.0", 1679 | "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", 1680 | "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", 1681 | "requires": { 1682 | "http-parser-js": "0.4.11", 1683 | "websocket-extensions": "0.1.3" 1684 | } 1685 | }, 1686 | "websocket-extensions": { 1687 | "version": "0.1.3", 1688 | "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", 1689 | "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" 1690 | } 1691 | } 1692 | } 1693 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-basic-training", 3 | "version": "1.0.0", 4 | "description": "Angular basic training & TypeScript training material", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "live-server --cors" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gofore/angular-basic-training.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/gofore/angular-basic-training/issues" 17 | }, 18 | "homepage": "https://github.com/gofore/angular-basic-training#readme", 19 | "dependencies": { 20 | "live-server": "^1.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /reactive-programming-with-angular/EXERCISES.md: -------------------------------------------------------------------------------- 1 | # RxJS Exercises 2 | 3 | # Basics 4 | Exercises are done in [JS Bin](http://jsbin.com/negukijoqe/edit?html,js,console) 5 | [RxJS documentation](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html) 6 | 7 | ## Exercise 1 - Map and Filter 8 | Given the following stream of objects: 9 | ```javascript 10 | Rx.Observable.from([ 11 | {name: 'first', id: 1}, 12 | {name: 'second', id: 2}, 13 | {name: 'bob', id: 3}, 14 | {name: 'john', id: 4}, 15 | {name: 'foo', id: 5}, 16 | {name: 'bar', id: 6}, 17 | {name: 'aa', id: 7} 18 | ]); 19 | ``` 20 | 1. Filter items which have the name length shorter than 4 21 | 2. From filtered stream, map only the item id's to a stream and subscribe to it 22 | 23 | ## Exercise 2 - Reduce 24 | Given the following stream of integers: 25 | ```javascript 26 | Rx.Observable.range(0,10); 27 | ``` 28 | Calculate the sum of all items. 29 | 30 | ## Exercise 3 - Scan 31 | Use scan operator to calculate the accumulated value after each integer emitted: 32 | ```javascript 33 | Rx.Observable.interval(500).take(10); 34 | ``` 35 | 36 | ## Exercise 4 - Merge and Concat 37 | Given the following sources of integers: 38 | ```javascript 39 | const source1 = Rx.Observable.interval(500).take(10).map(x => x + 10); 40 | const source2 = Rx.Observable.interval(500).take(10); 41 | ``` 42 | 1. Merge streams 43 | 2. Concatenate streams 44 | 3. What's the difference between combined streams? 45 | 46 | ## Exercise 5 - Buffer 47 | Given the following source of integers: 48 | ```javascript 49 | Rx.Observable.interval(100).take(100); 50 | ``` 51 | Calculate the sum of items emitted every second (Tip: check out bufferTime -operator) 52 | 53 | ## Exercise 6 - Mouse Position 54 | Log mouse position to console on every 500 ms 55 | ```javascript 56 | Rx.Observable.fromEvent(document, 'mousemove'); 57 | ``` 58 | [Mousemove Event Documentation](https://developer.mozilla.org/en-US/docs/Web/Events/mousemove) (Tip: check out throttleTime -operator) 59 | 60 | 61 | 62 | # GitHub Search with Observables 63 | In this exercise series we implement an app that uses GitHub's [search-repositories API](https://developer.github.com/v3/search/#search-repositories). 64 | 65 | Start by cloning the application: 66 | ```bash 67 | git clone https://github.com/gofore/angular-github-search.git 68 | cd angular-github-search 69 | npm install 70 | npm start 71 | ``` 72 | - Open `http://localhost:4200` and check that the search works 73 | - Open the code in your IDE 74 | 75 | *While doing the exercise, try not to unnecessary poll the API because there is a temporary request limit by GitHub.* 76 | 77 | ## Exercise 1 - Improving the Structure 78 | The implementation isn't really utilizing full power of reactive programming. For example you should never subscribe twice. Use [`switchMap`](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap) to remove the another `subscribe`. 79 | 80 | Tip: `switchMap` is like traditional `map` but it will wait for the value from the Observable 81 | Tip: Remember to import the `switchMap`: 82 | ```typescript 83 | import 'rxjs/add/operator/switchMap'; 84 | ``` 85 | 86 | ## Exercise 2 - Async Pipe 87 | Actually, we don't want to subscribe manually at all. Let's let Angular do it instead. Use `async` pipe in the template to show the values of our Observable. 88 | 89 | Tip: [Async Pipe](https://angular.io/api/common/AsyncPipe) 90 | Tip: Once ready, you should not have `.subscribe()` anywhere 91 | Tip: The type for items should be `Observable` 92 | 93 | ## Exercise 3 - Improve the Search 94 | Improve the search: 95 | - Don’t hit the search endpoint on every key stroke but instead only after 300ms delay 96 | - Don’t hit the search endpoint with the same query params for subsequent requests 97 | - Don't crash on errors (empty search will result in error so write something and then delete it to test) 98 | - Don't hit the search endpoint on empty query (implement this after the error handling) 99 | 100 | Tip: See [RxJS documentation](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html) for existing operators. 101 | Tip: Remember to import the operators: 102 | ```typescript 103 | import 'rxjs/add/operator/'; 104 | ``` 105 | 106 | ## Exercise 4 - Use RxJS 5.5 Syntax 107 | Migrate your implementation to use RxJS 5.5 pipeable syntax: 108 | 109 | ```typescript 110 | import { map } from 'rxjs/operators/map'; 111 | import { filter } from 'rxjs/operators/filter'; 112 | 113 | source.pipe( 114 | map(i => i * 2), 115 | filter(i => i < 4) 116 | ) 117 | ``` 118 | 119 | # Todo REST API 120 | Modify your todo service to use the given [Todo REST API](https://github.com/gofore/todo-backend). Use it to: 121 | - List items (GET http://gofore-todo.herokuapp.com/todo-lists/:listId) 122 | - Add items (POST http://gofore-todo.herokuapp.com/todo-lists/:listId) 123 | - Modify items (PUT http://gofore-todo.herokuapp.com/todos/:itemId) 124 | - Bonus: Add removal of items 125 | - Bonus: Sort todo list by item id 126 | -------------------------------------------------------------------------------- /reactive-programming-with-angular/README.md: -------------------------------------------------------------------------------- 1 | # Reactive Programming With Angular 2 | 3 | --- 4 | 5 | # Background 6 | - Reactive programming is programming with asynchronous data streams 7 | - JavaScript is asynchronous by design 8 | - HTTP requests 9 | - Timeouts 10 | - UI events (clicks, key presses, etc.) 11 | - _How to handle all this?_ 12 | 13 | --- 14 | 15 | # Callbacks 16 | - Traditionally solved by registering _callback_ functions to be executed upon completion of task 17 | - E.g. `setTimeout` that calls callback function once given amount of milliseconds has passed 18 | 19 | ```javascript 20 | window.setTimeout(() => { 21 | // Executed after one second 22 | }, 1000); 23 | ``` 24 | 25 | --- 26 | 27 | # Problem: Messy Code 28 | - Using callbacks quickly leads to messy code with multiple nested functions that is hard to follow and rationalize 29 | 30 | ```javascript 31 | getData((x) => { 32 | getMoreData(x, (y) => { 33 | getMoreData(y, (z) => { 34 | ... 35 | }); 36 | }); 37 | }); 38 | ``` 39 | 40 | - For more information google for _callback hell_ 41 | 42 | --- 43 | 44 | # Solution: Promises 45 | - _Promise_ is a promise of providing a value later 46 | - Promise constructor takes single argument that is a function with two parameters: 47 | - `resolve`: function to be called when we want to indicate __success__ 48 | - `reject`: function to be called when we want to indicate __failure__ 49 | 50 | ```javascript 51 | new Promise((resolve, reject) => { 52 | if (...) resolve(x); 53 | else if (...) resolve(y); 54 | else reject(); 55 | }); 56 | ``` 57 | - Both functions allow arguments that are provided for promise consumer 58 | --- 59 | 60 | # Promises are Resolved or Rejected 61 | - Promises are consumed by calling `then` on them. `then` takes two arguments: success and failure handler 62 | 63 | ```javascript 64 | somethingReturningPromise().then( 65 | (value) => { // Resolved 66 | // Handle success case 67 | }, 68 | (value) => { // Rejected 69 | // Handle reject case, e.g. show an error note 70 | }); 71 | ``` 72 | --- 73 | 74 | # Promise Chaining 75 | - Promises can be "chained" by calling then multiple times in a row 76 | - Each `.then()` will change the value of the promise by returning a new value 77 | - If the value returned it is a promise, it will be waited for 78 | 79 | ```typescript 80 | fetch('/users') // Make the HTTP request 81 | .then(response => response.json()) // .json() will return a promise 82 | .then(json => json.users) // Map the result to contain only the "users" field 83 | .then(users => alert('Found ' + users.length + ' users')); // Show an alert with the users 84 | ``` 85 | 86 | --- 87 | 88 | # Problem: Stream Handling and Disposability 89 | - Promises don't work for streams, they are just to subscribe for __single events__ 90 | - Promises can't be __cancelled__ 91 | --- 92 | 93 | # Solution: Observables 94 | - Generalization of promises for streams 95 | - A way for representing __asynchronous event streams__ 96 | - e.g. mouse clicks, WebSocket streams 97 | - Can also be used for single events e.g. HTTP requests 98 | --- 99 | 100 | # RxJS 101 | - _ReactiveX_ is a library for representing __asynchronous event streams__ with __Observables__ and modifying them with various __stream operations__ 102 | - _RxJS_ is a _ReactiveX_ implementation for JavaScript 103 | - Angular integrates with __RxJS 6__ 104 | --- 105 | ### Idea: 106 | _"In ReactiveX an observer subscribes to an Observable."_ 107 | - You subscribe to stream of events so that your handler gets invoked every time there is a new item 108 | ```javascript 109 | observable.subscribe(item => doSomething(item)); 110 | ``` 111 | --- 112 | Streams can be manipulated with traditional array conversion functions such as _map_ and _filter_ 113 | ```javascript 114 | observable 115 | .filter(node => node.children.length > 2) 116 | .map(node => node.name); 117 | ``` 118 | You can _merge_, _concat_ and do other operations on streams to __produce new streams__ from the existing ones 119 | ```javascript 120 | const resultStream = stream1.merge(stream2); 121 | ``` 122 | [ReactiveX operators](http://reactivex.io/documentation/operators.html) 123 | --- 124 | 125 | Observables can also be created from e.g. __objects__, __maps__ and __arrays__ 126 | ```javascript 127 | Rx.Observable.of(42); 128 | Rx.Observable.from([1,2,3,4]); 129 | Rx.Observable.range(1,10); 130 | ``` 131 | --- 132 | 133 | # Subscribing 134 | - Subscribe method takes three functions as arguments: 135 | - __onNext__: called when a new item is emitted 136 | - __onError__: called if observable sequence fails 137 | - __onComplete__: called when sequence is complete 138 | 139 | ```javascript 140 | observable.subscribe( 141 | next => doSomething(next), // onNext 142 | error => handleError(error), // onError 143 | () => done() // onComplete 144 | ); 145 | ``` 146 | --- 147 | 148 | # Unsubscribing (cancelling) 149 | - Observable sequence subscriptions can be unsubscribed 150 | - E.g. observable that produces events that are saved into the memory 151 | ```javascript 152 | const eventSubscription = eventStream.subscribe( 153 | event => this.events.push(event) 154 | ); 155 | ``` 156 | - Sequence will not stop until unsubscribed 157 | ```javascript 158 | eventSubscription.unsubscribe(); 159 | ``` 160 | --- 161 | 162 | # Demo 163 | 164 | --- 165 | 166 | # Catching Errors 167 | - Observables die on errors 168 | - The way to survive from errors is by catching them and returning a new observable sequence 169 | ```javascript 170 | observable.catch((error) => { 171 | console.log(error); 172 | return Rx.Observable.of([1, 2, 3]); 173 | }; 174 | ``` 175 | --- 176 | 177 | # Hot vs. Cold Observables 178 | - Cold observables start running __upon subscription__ 179 | - E.g. http request 180 | - Hot observables are already producing values __before the subscription__ is active 181 | - E.g. mouse move events 182 | --- 183 | 184 | # Observables in Angular 185 | - Observables used exclusively instead of promises 186 | - E.g. HTTP requests only result in single event (one response) but they are modeled as observables 187 | ```typescript 188 | this.httpClient.get('url/restapi/resource') // Returns observable 189 | .subscribe( 190 | data => { this.data = data}, // Success 191 | err => console.error(err), // Failure 192 | () => console.log('done') // Done 193 | ); 194 | ``` 195 | --- 196 | 197 | # Observables in Angular 198 | - Changes in route parameters are propagated through an observable sequence 199 | ```typescript 200 | constructor(route: ActivatedRoute) { 201 | route.params.subscribe(params => this.index = +params['index']); 202 | } 203 | ``` 204 | 205 | --- 206 | 207 | # Exercises 208 | [Open exercise instructions](https://github.com/gofore/angular-basic-training/blob/master/reactive-programming-with-angular/EXERCISES.md) 209 | 210 | --- 211 | # RxJS 5.5 212 | - RxJS 5.5.0 introduced major change to RxJS called _pipeable operators_ 213 | - Importing is a mess with 5.5 but RxJS 6 will fix it 214 | - [Read more](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md) 215 | 216 | --- 217 | # Pipeable Operators Example 218 | 219 | Pre RxJS 5.5 220 | ```typescript 221 | import { Observable } from 'rxjs/Observable'; 222 | import 'rxjs/add/observable/range'; 223 | import 'rxjs/add/operator/filter'; 224 | import 'rxjs/add/operator/map'; 225 | 226 | const source$ = Observable.range(0, 10); 227 | source$ 228 | .filter(x => x % 2 === 0) 229 | .map(x => x + x) 230 | .subscribe(x => console.log(x)) 231 | ``` 232 | 233 | RxJS 5.5 -> 234 | ```typescript 235 | import { range } from 'rxjs/observable/range'; 236 | import { map } from 'rxjs/operators/map'; 237 | import { filter } from 'rxjs/operators/filter'; 238 | 239 | const source$ = range(0, 10); 240 | source$.pipe( 241 | filter(x => x % 2 === 0), 242 | map(x => x + x) 243 | ).subscribe(x => console.log(x)) 244 | ``` 245 | 246 | --- 247 | # RxJS 5.5 Renaming 248 | Some of the operators are reserved words in JavaScript: 249 | - `do` -> `tap` 250 | - `catch` -> `catchError` 251 | - `switch` -> `switchAll` 252 | - `finally` -> `finalize` 253 | 254 | --- 255 | # RxJS 5.5 Pros 256 | - No more "prototype patching" -> Tree-shaking possible -> Smaller bundle sizes 257 | - Custom operators are easier to make 258 | - Better tooling support by linters and compilers 259 | 260 | --- 261 | # RxJS 6 262 | - Released 04/2018 263 | - Major changes: 264 | - Simpler imports (`import { map, filter } from 'rxjs/operators'`) 265 | - Errors thrown asynchronously 266 | - Deprecations 267 | - New operator (`throwIfEmpty`) 268 | - Provides compatibility library (`rxjs-compat`) to support the migration from 5 to 6 269 | - See Ben Lesh's (RxJS 5 and 6 author) presentation in ngConf 2018 for more details ([slides](https://docs.google.com/presentation/d/1h-h4IUgh8mRqItF2F2Ih8g-H9gYjb8MCwHnoIVm-hiU/edit#slide=id.g367dcb4296_0_0), [video](https://www.youtube.com/watch?time_continue=1&v=JCXZhe6KsxQ)) 270 | - [rxjs-dev](https://rxjs-dev.firebaseapp.com/) 271 | -------------------------------------------------------------------------------- /reactive-programming-with-angular/demo/README.md: -------------------------------------------------------------------------------- 1 | # RxJS demo 2 | 3 | Create an observable sequence that produces a value after each period and subscribe to it: 4 | ```javascript 5 | const observable = Rx.Observable.interval(2000).take(20); 6 | const subscription = observable.subscribe( 7 | x => console.log(x), 8 | e => console.log('onError: ' + e.message), 9 | () => console.log('onCompleted')); 10 | 11 | // => 0" 12 | // => 1" 13 | // => 2" 14 | // => 3" 15 | // => 4" 16 | // => "onCompleted" 17 | ``` 18 | Unsubscribe the subscription: 19 | ```javascript 20 | subscription.unsubscribe(); 21 | ``` 22 | 23 | Add filtering for uneven values: 24 | ```javascript 25 | const observable = Rx.Observable.interval(2000).take(20) 26 | .filter(x => x % 2 === 0); 27 | observable.subscribe(x => console.log(x)); 28 | ``` 29 | 30 | Use map to multiply every value by 10: 31 | ```javascript 32 | const observable = Rx.Observable.interval(2000).take(20) 33 | .filter(x => x % 2 === 0) 34 | .map(x => x * 10); 35 | ``` 36 | 37 | Buffer values: 38 | ```javascript 39 | const o = Rx.Observable.interval(500).take(50) 40 | .filter(x => x % 2 === 0) 41 | .map(x => x + 10) 42 | .bufferCount(5); 43 | ``` 44 | 45 | Accumulate the buffered array 46 | ```javascript 47 | const observable = Rx.Observable.interval(500) 48 | .filter(x => x % 2 === 0) 49 | .map(x => x * 10) 50 | .bufferCount(5) 51 | .map(buf => buf.reduce((acc, cur) => acc + cur)); 52 | ``` 53 | 54 | Double Click 55 | ```javascript 56 | const click = Rx.Observable.fromEvent(document, 'click'); 57 | 58 | const clicks = click.buffer(click.debounceTime(500)) 59 | .filter(buf => buf.length >= 2); 60 | 61 | clicks.subscribe(() => console.log('double click')); 62 | ``` 63 | 64 | Flatmap example: 65 | ```javascript 66 | const o = Rx.Observable.range(0, 10) 67 | .flatMap(x => Rx.Observable.of([x, x*2])); 68 | 69 | o.subscribe(x => console.log(x));``` 70 | 71 | Wait for several asynchronous operations to finish: Fork join 72 | ```javascript 73 | const o1 = Rx.Observable.create(observer => { 74 | setTimeout(() => { 75 | observer.next(1); 76 | observer.complete(); 77 | }, 5000); 78 | console.log('o1 started'); 79 | }); 80 | const o2 = Rx.Observable.create(observer => { 81 | setTimeout(() => { 82 | observer.next(2); 83 | observer.complete(); 84 | }, 3000); 85 | console.log('o2 started'); 86 | }); 87 | Rx.Observable.forkJoin([o1, o2]).subscribe(arr => { 88 | console.log(arr[0] + arr[1]); 89 | }); 90 | ``` 91 | -------------------------------------------------------------------------------- /setup/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Gofore's Angular Training! 2 | ## Setup 3 | ### Node.js, npm & git 4 | 5 | Links for installers/installation instruction: 6 | - [Node.js (newer than 10.14) & npm (newer than 6.5)](https://nodejs.org/) 7 | - [git](http://git-scm.com/) 8 | 9 | Please, make sure that the applications are installed correctly by checking their version numbers (which do not need to exactly match). Example of a valid setup: 10 | 11 | ```shell 12 | > node --version 13 | v10.14.1 14 | 15 | > npm --version 16 | 6.5.0 17 | 18 | > git --version 19 | git version 2.6.4 20 | ``` 21 | 22 | ### Editor 23 | You may use any IDE you feel comfortable with but we recommend __IntelliJ IDEA Ultimate__ (or WebStorm Ultimate) or __Visual Studio Code__ to be used since they provide very good support for Angular development. 24 | 25 | If you prefer using Atom or Brackets, please, find instructions below. 26 | 27 | #### IntelliJ IDEA Ultimate (or WebStorm) 28 | - Proprietary (though 30-day trial available) 29 | - Install recommended plugins through IntelliJ IDEA plugin management: 30 | - [_AngularJS_](https://github.com/JetBrains/intellij-plugins/tree/master/AngularJS) 31 | - [_NodeJS_](https://plugins.jetbrains.com/plugin/6098?pr=idea) 32 | 33 | #### Visual Studio Code 34 | - Free & open source 35 | - Install recommended extensions: 36 | - [_TSLint_](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) 37 | - [_Auto Import_](https://marketplace.visualstudio.com/items?itemName=steoates.autoimport) 38 | 39 | ### Google Chrome 40 | [_Google Chrome_](https://www.google.com/chrome/) browser is needed for running and debugging Karma tests. 41 | 42 | ### Project Skeleton for Exercises 43 | Go to your workspace directory (`Documents/GitHub` etc.) and run 44 | 45 | ```shell 46 | npm install -g @angular/cli@7.3.8 47 | ng new angular-training 48 | cd angular-training 49 | ng serve 50 | ``` 51 | 52 | to generate a new project and to start the server. Server is running okay when it says `webpack: bundle is now valid` on the last line. Then, visit `http://localhost:4200/` to check that _app works!_ is printed. Now your environment is ready for training. See you soon! 53 | -------------------------------------------------------------------------------- /spas-tooling-and-typescript/README.md: -------------------------------------------------------------------------------- 1 | # Angular Basic Training - Gofore 2 | 3 | - Open slides: [basic-angular-training.herokuapp.com](http://basic-angular-training.herokuapp.com/) 4 | - Open IDE and start local dev server as instructed in [prerequisites](https://github.com/gofore/angular-basic-training/blob/master/setup/README.md) 5 | - If using IntelliJ IDEA, open "Settings" and go to _TypeScript > Code Style > TypeScript_ and set: 6 | - Spaces: Within > "Object literal braces" & "ES6 import/export braces" 7 | - Punctuation: "Use Single quotes in always" 8 | 9 | --- 10 | 11 | ## Agenda - Day 1 12 | #### Morning 13 | - Introduction to SPAs 14 | - TypeScript & Tooling 15 | - Angular Fundamentals 16 | 17 | #### Afternoon 18 | - Angular Fundamentals (continued) 19 | - Angular Advanced Topics 20 | 21 | --- 22 | 23 | ## Agenda - Day 2 24 | #### Morning 25 | - Reactive Programming with Angular 26 | 27 | #### Afternoon 28 | - Testing 29 | 30 | --- 31 | 32 | # Introduction to SPAs 33 | - What is a SPA? 34 | - Real-life examples 35 | - Technical overview 36 | 37 | --- 38 | 39 | # What Is a SPA? 40 | - "A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience similar to a desktop application." - Wikipedia 41 | - Browser fetches executable code that makes asynchronous calls for actual data to be shown 42 | - Data is visualized and/or manipulated and stored back on server asynchronously 43 | 44 | --- 45 | 46 | ![SPA flow](spas-tooling-and-typescript/spa-flow.png "SPA flow") 47 | 48 | --- 49 | 50 | # Real-life Examples 51 | - [Google search](http://www.google.com) 52 | - [Facebook](http://facebook.com) 53 | - [Twitter](http://twitter.com) 54 | 55 | 56 | --- 57 | 58 | # SPA Frameworks/Libraries 59 | - Backbone.js 60 | - Ember 61 | - Meteor 62 | - AngularJS 63 | - Aurelia 64 | - React 65 | - Vue.js 66 | - Angular 67 | - And so many more... 68 | 69 | --- 70 | 71 | # AngularJS 72 | - Published 2010 73 | - MVC (Model-View-Controller) framework with dependency injection 74 | - Revolutionary on its own time 75 | - Two-way data binding 76 | - Emphasis on testability (decouples DOM manipulation from app logic) 77 | 78 | --- 79 | 80 | # Angular 81 | - Built by around 20 Google developers & lots of open source devs 82 | - Built with TypeScript (ES2015 and Dart versions available) 83 | - Complete rewrite of AngularJS 84 | - Not just another web framework, complete platform 85 | - Also for desktop and mobile development 86 | - [Documentation](https://angular.io/docs/ts/latest/api/) 87 | 88 | --- 89 | 90 | # Angular Release 91 | - Major version every 6 months (April and October) 92 | - Two version deprecation policy 93 | - Even numbers (4, 6, ..) offer LTS (Long-Term Support) 94 | - Releases: 95 | - 2.0.0-beta.0 1/2016 96 | - 2.0.0-rc.0 5/2016 97 | - 2.0.0 9/2016 98 | - 4.0.0 3/2017 99 | - 5.0.0 11/2017 100 | - 6.0.0 4/2018 101 | - 7.0.0 10/2018 102 | - 8.0.0 5/2019 103 | - 9.0.0 10-11/2019 104 | - More info in [Angular GitHub](https://github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md) 105 | - Releases [Angular Github Releases](https://github.com/angular/angular/releases) 106 | 107 | --- 108 | 109 | # Tooling 110 | 111 | - Traditionally web pages have been just static HTML, CSS and maybe some simple JS for dropdowns etc. 112 | - Nowadays massive SPAs require something more advanced and thus the need for tooling 113 | - Some basic needs for tooling: 114 | - Compiling ES2015/TypeScript -> ES5 and LESS/SASS -> CSS 115 | - Combining multiple source files into single bundle file for faster loading 116 | - Running test suites 117 | - Optimizations (minification, Dead code elimination, tree shaking) 118 | 119 | --- 120 | 121 | # JSON 122 | - JavaScript Object Notation (JSON) is a lightweight data-interchange format 123 | - Meant to be easy for both, humans and machines 124 | - Key-value pairs, where values can be any JavaScript primitives (except functions) 125 | 126 | ```json 127 | { 128 | "name": "John Doe", 129 | "email": "john.doe@example.com", 130 | "friends": [ 131 | {"name": "Jane Doe"}, 132 | {"name": "John Doe Jr."} 133 | ] 134 | } 135 | ``` 136 | 137 | --- 138 | 139 | # Node.js & npm 140 | 141 | - Node.js is JavaScript interpreter built on top of Chrome's V8 JavaScript engine 142 | - Used for running development server, to run tests, to build production-optimized bundle, etc. 143 | - npm (node package manager) is the package manager for Node 144 | - ~~More packages than on any other package manager for any other language: over 270k (May 2016)~~ ([modulecounts.com](http://www.modulecounts.com/)) 145 | - More packages than on any other package manager for any other language: over 816k (May 2019) ([modulecounts.com](http://www.modulecounts.com/)) 146 | 147 | --- 148 | 149 | # package.json - project configuration 150 | - Declares dependencies, development-time dependencies and commands available 151 | - Can be generated with `npm init` 152 | 153 | ```json 154 | { 155 | "name": "Angular basic training", 156 | "author": "Gofore", 157 | "scripts": { 158 | "build": "my-build.sh", 159 | "start": "my-webserver.sh" 160 | }, 161 | "dependencies": { 162 | "@angular/core": "2.0.0", 163 | "@angular/forms": "2.0.0", 164 | "@angular/http": "2.0.0" 165 | }, 166 | "devDependencies": { 167 | "typescript": "^2.0.0", 168 | "jasmine": "^2.5.0" 169 | } 170 | } 171 | ``` 172 | 173 | --- 174 | 175 | # Angular CLI 176 | 177 | - Command-line interface for Angular development 178 | - Recommended by the core team 179 | - One of the core modules: `@angular/cli` 180 | - Follows Angular core versioning as of _7.0.0_ 181 | - Abstracts away the bundling 182 | - Uses Webpack internally 183 | 184 | --- 185 | 186 | # Angular CLI Features 187 | - Generate the project initially 188 | - Run dev server 189 | - Generate modules, components, services, tests, directives 190 | - sub-commands 191 | - appShell (skeleton view) 192 | - application 193 | - class 194 | - component 195 | - directive 196 | - enum 197 | - guard 198 | - interface 199 | - library [Docs](https://angular.io/guide/creating-libraries) 200 | - module 201 | - pipe 202 | - service 203 | - serviceWorker [Getting started](https://angular.io/guide/service-worker-getting-started) 204 | - universal [Guide](https://angular.io/guide/universal) 205 | - Generate production build 206 | - Tests 207 | - Update your dependencies 208 | - Supports CSS preprocessors (SASS and LESS) 209 | - Allows third-party generators for Angular CLI projects 210 | - More information [Angular Cli](https://angular.io/cli) 211 | 212 | --- 213 | 214 | # Angular CLI Usage 215 | - Generating an app 216 | ```shell 217 | ng new PROJECT_NAME 218 | ``` 219 | 220 | - Run development server 221 | ```shell 222 | ng serve # Available in localhost:4200 223 | ``` 224 | 225 | - Run tests 226 | ```shell 227 | ng test 228 | ``` 229 | 230 | - Generate a component 231 | ```shell 232 | ng generate component todos 233 | ``` 234 | 235 | --- 236 | 237 | # ES2015 (ES6) 238 | - EcmaScript (ES) is the standard for JavaScript 239 | - One of the newer EcmaScript standards 240 | - Published 2015 241 | - Also known as ES6 because last version was ES5 242 | - Provides a lot of improvements for writing JavaScript in scale 243 | - Not supported by older browsers (namely IE11) 244 | 245 | --- 246 | 247 | # ES2015 - Key Features 248 | - `let` and `const` to replace `var` 249 | - Arrow functions 250 | - Multiline strings 251 | - Modules 252 | - Enhancements on basic types such as `includes()` for string and `find()` for array 253 | 254 | --- 255 | 256 | # Let and const 257 | - `const` declares constant **reference** (not constant value) 258 | - `let` declares variable (eg. `let myVar = 'asd';`) 259 | - **Rule of thumb: Always use `const` if possible, `let` otherwise.** 260 | 261 | ```javascript 262 | const input = [0, 1, 2, 3, 4]; 263 | input = []; // Uncaught TypeError: Assignment to constant variable. 264 | input.push(5); // Works, as input is just the reference 265 | ``` 266 | 267 | For immutable objects & arrays there are libraries such as [_Immutable.js_](https://facebook.github.io/immutable-js/). 268 | 269 | --- 270 | 271 | # Arrow functions 272 | - Lexical `this` and more concise syntax 273 | 274 | ```javascript 275 | // Traditional function 276 | const increase = function (value) { 277 | return value + 1; 278 | }; 279 | 280 | // Arrow function 281 | const increase = (value) => { 282 | return value + 1; 283 | }; 284 | 285 | // Parenthesis omitted 286 | const increase = value => { 287 | return value + 1; 288 | }; 289 | 290 | // Return value without curly braces 291 | const increase = value => value + 1; 292 | ``` 293 | 294 | --- 295 | 296 | # String Literals 297 | ES5 string: 298 | ```javascript 299 | const str = firstName + ' ' + secondName.charAt(0) + '. ' + lastName; 300 | ``` 301 | 302 | ES2015 multiline string with backticks: 303 | ```typescript 304 | const str = ´${firstName} ${secondName.charAt(0)}. ${lastName}`; 305 | ``` 306 | 307 | --- 308 | 309 | # Modules 310 | Allows `import`ing and `export`ing code between files (modules) 311 | 312 | _lib.js_ 313 | ```javascript 314 | export function square(x) { 315 | return x * x; 316 | } 317 | export function squareSum(x, y) { 318 | return Math.sqrt(square(x) + square(y)); 319 | } 320 | ``` 321 | 322 | _main.js_ 323 | ```javascript 324 | import { square, squareSum } from './lib'; 325 | console.log(square(11)); // 121 326 | console.log(squareSum(4, 3)); // 5 327 | ``` 328 | 329 | - Importing from npm modules 330 | 331 | ```javascript 332 | import { Component, Input } from '@angular/core'; 333 | ``` 334 | 335 | --- 336 | 337 | # Array Functions 338 | - `map`, `filter` and `reduce` operate over the array 339 | - `map` creates a new array with the results of calling a provided function on every element in the calling array. 340 | ```typescript 341 | const arr = [0, 1, 2, 3]; 342 | const result = arr.map(item => item * 2); 343 | // result is [0, 2, 4, 6] 344 | ``` 345 | 346 | - `filter` creates a new array with all elements that pass the test implemented by the provided function. 347 | ```typescript 348 | const arr = [0, 1, 2, 3]; 349 | const result = arr.filter(item => item < 2); 350 | // result is [0, 1] 351 | ``` 352 | 353 | - `reduce` applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. 354 | ```typescript 355 | const arr = [0, 1, 2, 3]; 356 | const result = arr.reduce((acc, item) => acc + item , 0); 357 | // result is 6 358 | ``` 359 | Group By -method 360 | ```typescript 361 | const persons = [ 362 | {name: 'John', city: 'Helsinki'}, 363 | {name: 'Marie', city: 'Helsinki'}, 364 | {name: 'Jane', city: 'Oulu'}, 365 | {name: 'Lily', city: 'Turku'}, 366 | {name: 'Barney', city: 'Helsinki'}, 367 | {name: 'Ted', city: 'Tampere'}, 368 | {name: 'Robin', city: 'Tampere'}, 369 | {name: 'Marshall', city: 'Oulu'} 370 | ]; 371 | const groupedByCity = persons.reduce((acc, person) => { 372 | (acc[person.city] = acc[person.city] || []).push( person ) 373 | return acc; 374 | } , {}); 375 | // result 376 | { 377 | Helsinki: 378 | 0: "John" 379 | 1: "Marie" 380 | 2: "Barney" 381 | Oulu: 382 | 0: "Jane" 383 | 1: "Marshall" 384 | Tampere: 385 | 0: "Ted" 386 | 1: "Robin" 387 | Turku: 388 | 0: "Lily" 389 | } 390 | ``` 391 | 392 | --- 393 | 394 | # TypeScript 395 | - General-purpose programming language 396 | - Built by Microsoft to build JavaScript in scale 397 | - Initial release in October 2012 398 | - Typed superset of JavaScript -> Any valid JS is valid TypeScript 399 | - Advantages: 400 | - Static type system on top of JavaScript to catch errors already on compile-time 401 | - Angular & RxJS are written in TypeScript 402 | - Typescript roadmap [Github](https://github.com/Microsoft/TypeScript/wiki/Roadmap) 403 | 404 | --- 405 | 406 | # Typing 407 | - Provides the same types as in JavaScript: `number`, `string`, `boolean`, `null`, `undefined` and `object` 408 | - Arrays like `number[]`, `string[]` and `any[]` 409 | - Also some "extra" types such as `any`, `void` and `enum` 410 | - `any` is basically anything like `number`, `string` or `any[]` 411 | - Types are marked after the name 412 | - Type declaration can be omitted when assigning 413 | - Examples: 414 | 415 | ```typescript 416 | let luckyNumber: number = 50; 417 | luckyNumber = 'nine'; // TypeError: Can't assign string to number 418 | 419 | function increase(value: number): number { 420 | return value + 1; 421 | } 422 | ``` 423 | 424 | --- 425 | 426 | # Interfaces 427 | - Interfaces to declare the acceptable object structures 428 | - Can have optional properties (declared with `?` before `:`) 429 | - _Structural typing_ instead of _Nominal typing_ 430 | - Nominal: checks class matches 431 | - Structural: check members matching 432 | 433 | ```typescript 434 | interface Person { 435 | firstName: string; 436 | middleName?: string; 437 | lastName: string; 438 | } 439 | 440 | const greeter = (person: Person): string => "Hello, " + person.firstName + " " + person.lastName; 441 | 442 | greeter({ firstName: "John", lastName: "Doe" }); // Hello, John Doe 443 | ``` 444 | 445 | --- 446 | 447 | # Classes 448 | - Like in many other programming languages 449 | - Member fields can be declared in constructor with visibility modifier (`private`, `protected`, `public`) 450 | 451 | ```typescript 452 | class Student { 453 | fullName: string; 454 | constructor(private firstName: string, private lastName: string) { 455 | this.fullName = this.firstName + " " + this.lastName; 456 | } 457 | 458 | getFirstName() { 459 | return this.firstName; 460 | } 461 | } 462 | 463 | const student = new Student("John", "Doe"); 464 | console.log(student.getFirstName()); 465 | ``` 466 | 467 | --- 468 | 469 | # Annotations (Decorators) 470 | - Used to "decorate" classes and properties with additional functionality 471 | - Like Java annotations 472 | - Apply to next entity (class, field, method) after them 473 | 474 | ```typescript 475 | @Component({ 476 | template: 'my template' 477 | }) 478 | class MyClass { 479 | @Input() myProperty: string; 480 | } 481 | ``` 482 | -------------------------------------------------------------------------------- /spas-tooling-and-typescript/spa-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofore/angular-basic-training/a446c7e32e29196044865a7aa4ca14b04319e4e7/spas-tooling-and-typescript/spa-flow.png -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | --- 4 | 5 | # Unit testing 6 | - Testing of components in isolation from other components 7 | - Guard against changes that break existing code 8 | - Specify and clarify what the code does 9 | 10 | --- 11 | 12 | # Jasmine 13 | - Unit testing framework for JavaScript 14 | - Simple basic syntax: 15 | - `describe(string, function)` to define suite of test cases 16 | - `it(string, function)` to declare single test case 17 | - `expect(a).toBe(b)` to make assertions 18 | 19 | --- 20 | 21 | # First Jasmine Test 22 | - Class under test 23 | ```typescript 24 | export class Person { 25 | constructor(private name: string) { 26 | } 27 | 28 | getName() { 29 | return this.name; 30 | } 31 | } 32 | ``` 33 | 34 | --- 35 | 36 | # First Jasmine Test 37 | - Test case 38 | ```typescript 39 | describe('Person', () => { 40 | let person; 41 | 42 | beforeEach(() => { 43 | person = new Person('John'); 44 | }); 45 | 46 | it('should return name', () => { 47 | expect(person.getName()).toBe('John'); 48 | }); 49 | }); 50 | ``` 51 | 52 | --- 53 | 54 | # Spies 55 | - Test double functions AKA spies let you stub any function and track calls to it 56 | 57 | ```typescript 58 | it('tracks that the spy was called', () => { 59 | spyOn(person, 'getName'); 60 | 61 | person.getName(); 62 | expect(person.getName).toHaveBeenCalled(); 63 | }); 64 | ``` 65 | 66 | --- 67 | 68 | # Spies 69 | ```typescript 70 | it('stubs the function return value', () => { 71 | spyOn(person, 'getName').and.returnValue('Jane'); 72 | 73 | expect(person.getName()).toBe('Jane'); 74 | }); 75 | ``` 76 | 77 | --- 78 | 79 | # Angular Testing Platform (ATP) 80 | - TestBed for wiring angular components for testing 81 | - Inject mock dependencies 82 | - Testing components with async behavior 83 | 84 | --- 85 | 86 | # Component Under Test 87 | - Suppose we had the following component: 88 | ```typescript 89 | @Component({ 90 | selector: 'videos', 91 | templateUrl: 'videos.component.html' 92 | }) 93 | class VideosComponent { 94 | videos: Video[]; 95 | 96 | constructor(videoService: VideoService) { 97 | this.videos = videoService.getList(); 98 | } 99 | } 100 | ``` 101 | 102 | --- 103 | 104 | # Wiring Up 105 | - We can setup the test environment with mocks using TestBed: 106 | ```typescript 107 | beforeEach(() => { 108 | TestBed.configureTestingModule({ 109 | declarations: [ VideosComponent ], 110 | providers: [{provide: VideoService, useClass: FakeVideoService}] 111 | }); 112 | }); 113 | ``` 114 | 115 | --- 116 | 117 | # Access to Tested Component 118 | ```typescript 119 | fixture = TestBed.createComponent(VideosComponent); 120 | 121 | comp = fixture.componentInstance; 122 | 123 | debugElement = fixture.debugElement; 124 | 125 | nativeElement = fixture.nativeElement; 126 | 127 | ``` 128 | 129 | --- 130 | 131 | # Async with ATP 132 | - Let's change the implementation of the VideoService to return a promise: 133 | ```typescript 134 | ngOnInit() { 135 | this.videoService.getList().then(videos => this.videos = videos); 136 | } 137 | ``` 138 | - This means that we'll have to deal with asynchronous behavior 139 | 140 | --- 141 | 142 | # async() 143 | - We can run test code within asynchronous zone 144 | ```typescript 145 | it('should show videos', async(() => { 146 | fixture.detectChanges(); // trigger data binding 147 | fixture.whenStable().then(() => { // wait for async getVideos 148 | fixture.detectChanges(); // update view with videos 149 | expect(getVideos()).toBe(testVideos); 150 | }); 151 | })); 152 | ``` 153 | 154 | --- 155 | 156 | # fakeAsync() 157 | - Or within fake asynchronous zone 158 | ```typescript 159 | it('should show videos', fakeAsync(() => { 160 | fixture.detectChanges(); // trigger data binding 161 | tick(); // wait for async getVideos 162 | fixture.detectChanges(); // update view with videos 163 | expect(getVideos()).toBe(testVideos); 164 | })); 165 | ``` 166 | 167 | --- 168 | 169 | # Karma 170 | - Karma is a test runner with support e.g. for coverage reports and test results exports 171 | - Angular CLI comes with Karma installed 172 | - To run tests, type: 173 | ```shell 174 | npm run test 175 | ``` 176 | 177 | --- 178 | 179 | # E2E testing 180 | - End-to-End (E2E) tests test flow of the application 181 | - Ensures that the components of the application function together as expected 182 | - E2E tests often define use cases of the application 183 | 184 | --- 185 | 186 | # Protractor 187 | - E2E test framework for Angular applications 188 | - Runs tests against your application running in a real browser, interacting with it as a user would 189 | - Angular CLI comes with Protractor installed 190 | - To run E2E tests, type: 191 | ```shell 192 | npm run e2e 193 | ``` 194 | 195 | --- 196 | 197 | # Example spec 198 | ```javascript 199 | describe('Protractor Demo App', () => { 200 | it('should have a title', () => { 201 | browser.get('http://demo.com/protractor-demo/'); 202 | 203 | expect(browser.getTitle()).toEqual('Demo Application'); 204 | expect(element(by.css('h1')).toEqual('Header'); 205 | }); 206 | }); 207 | ``` 208 | -------------------------------------------------------------------------------- /useful-resources/README.md: -------------------------------------------------------------------------------- 1 | # Useful Resources 2 | 3 | - [Thoughtram](https://blog.thoughtram.io/): Highly advanced articles about Angular, RxJS and other related libraries. 4 | - [Style guide](https://angular.io/guide/styleguide): Angular's official style guide. Worth skimming through. 5 | - [ngrev](https://github.com/mgechev/ngrev): Graphical tool for reverse engineering of Angular projects. 6 | - [Ahead-of-Time compilation](https://angular.io/docs/ts/latest/cookbook/aot-compiler.html) - Boost build times by compiling offline (not in browser) 7 | - [Release schedule](https://github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md) - Angular's release schedule and versioning info 8 | - [Web workers support](https://angular.io/docs/ts/latest/api/#!?apiFilter=worker) - Boost up performance with threading 9 | - [Progressive Web Apps (PWA)](https://angular.io/guide/service-worker-getting-started) - Offline-capable, app-like web sites supporting push notification built with Angular 10 | - [Lazy loading](https://angular.io/guide/lazy-loading-ngmodules) - Only load essentials parts initially 11 | - [Universal apps](https://angular.io/guide/universal) - render Angular app as HTML on server 12 | - [Migration from AngularJS](https://angular.io/docs/ts/latest/guide/upgrade.html) - Migrating from AngularJS to Angular is possible 13 | - Frameworks built on top of Angular for different platforms: 14 | - [Electron](http://electron.atom.io/) for desktop apps 15 | - [Ionic](https://ionicframework.com/docs/) (works on top of [Apache Cordova](https://cordova.apache.org/)) for hybrid mobile apps 16 | - [NativeScript](https://www.nativescript.org/) for native UI mobile apps --------------------------------------------------------------------------------