Developing With Angular
Developing With Angular
Denys Vuika
This book is for sale at http://leanpub.com/developing-with-angular
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Book Revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Book progress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Code examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Feedback, Bug Reports and Suggestions . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Other publications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Testimonials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Visual Studio Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Recommended extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Hiding files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
ES6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Arrow Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Expression Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Statement Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Lexical this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Template Literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Extended Parameter Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Default Parameter Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Rest Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Spread Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Destructuring assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Basic example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Array destructuring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Value assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Default values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Swapping values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Skipping values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
CONTENTS
TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Getting Started with TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Installing TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Using tsc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Typings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Linting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
tslint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
TypeScript Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Basic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Any . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Setters and Getters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Return values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Method parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Optional parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Default parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Rest Parameters and Spread Operator . . . . . . . . . . . . . . . . . 43
Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Access Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Public . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Private . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
CONTENTS
Protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Module Loaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Running at server side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Running in browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Class Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Decorators with parameters . . . . . . . . . . . . . . . . . . . . . . 62
Multiple decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Method Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Accessor Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Property Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Parameter Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Angular CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Your first application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Running application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Code Linting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Unit tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Code coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Development and Production builds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Using blueprints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Creating modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Assigning components to modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Routing support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Generating standalone scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Creating a simple component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Generating components with Angular CLI . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Component metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Inline templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
External templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Inline styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
External styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Input properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Output events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Raising events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
CONTENTS
Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Component events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Bubbling up child events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
DOM events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Service events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Attribute Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Handling host events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Host members binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Binding element attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Binding element properties . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Binding style attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Binding class names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Built-in attribute directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
NgStyle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
NgClass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Binding to String Expression (single): . . . . . . . . . . . . . . . . . 224
Binding to String Expression (space delimited) . . . . . . . . . . . . 225
Binding to Object Literal . . . . . . . . . . . . . . . . . . . . . . . . 225
Binding to Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
NgNonBindable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
Structural Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Built-in structural directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
NgIf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
NgFor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
CONTENTS
Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
Pipes with Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Chaining Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Built-in Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
UpperCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
LowerCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
TitleCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Custom formatting rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Predefined formatting rules . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Decimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Percent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Json . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Slice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Using with Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Using with Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
I18nSelect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
I18nPlural . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
Async . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Using with NgFor directive . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Using with Date object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
Custom Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
Implementing Custom Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Pure And Impure Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
Preparing Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
Pure Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Impure Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Webpack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
Initial project structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
Typescript configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
Basic webpack setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
Enabling source maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Simple Angular application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
Generating index page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Development server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Start command with NPM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Fixing Angular warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Testing source maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
Advanced webpack setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
External Html templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
External CSS styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Loading images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
TSLint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Unit testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
Code coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436
Code splitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Multiple configurations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Development mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Production mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
CSS splitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
Vendor code splitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 15 (2018-06-03) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 14 (2018-04-08) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 13 (2018-02-12) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 12 (2017-12-17) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 11 (2017-12-03) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 10 (2017-11-12) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Revision 9 (2017-10-15) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 8 (2017-09-17) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 7 (2017-09-14) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 6 (2017-08-24) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 5 (2017-08-13) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 4 (2017-07-22) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 3 (2017-07-09) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 2 (2017-07-02) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Revision 1 (2017-06-09) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Introduction
This author of this book follows Lean Publishing1 principles, and the book evolves with readers’
feedback.
In the first few chapters, we are going to dwell on the basics you may need to understand Angular
development better later on.
We are going to start with the main ES6 (ECMAScript 2015) features you should come across
frequently when using TypeScript.
Next, the most remarkable TypeScript features you should get familiar before starting the Angular
development.
After that, we are covering the Angular framework and supplemental toolings, like Angular CLI
(Command Line Interface) and Webpack.
Finally, you should expect many practical topics that are addressing specific application require-
ments and scenarios, including those coming from the community requests.
Book Revision
Revision 15 (2018-06-03)
• Updated: ngOnDestroy
• New Listening for View and Content changes
Book progress
You can see the progress of the writing on this GitHub board2 . Be sure to check the board if you
want to see what’s coming next or what is in progress right now.
Code examples
You can find all code examples in this GitHub repository: developing-with-angular3 . The source
code gets frequently revisited and updated.
1
https://leanpub.com/manifesto
2
https://github.com/DenisVuyka/developing-with-angular/projects/1
3
https://github.com/DenisVuyka/developing-with-angular
Introduction 2
Other publications
You can find many other interesting publications at my Medium channel6 .
Testimonials
Your feedback on the book content is very appreciated. I would love to publish your photo and
testimonial on the web page of the book. Email me at: [email protected] .
4
mailto:[email protected]?subject=developing-with-angular%20feedback
5
https://github.com/DenisVuyka/developing-with-angular/issues
6
https://medium.com/@DenysVuika/
7
mailto:[email protected]?subject=developing-with-angular%20testimonial
Prerequisites
Node
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine8 . Node.js uses an
event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’
package ecosystem, npm9 , is the largest ecosystem of open source libraries in the world.
Navigate to the main page10 , download installer for your platform and follow setup instructions.
You can use the following commands to test currnet versions of the Node and NPM on your machine:
node -v
# v8.4.0
npm -v
# 5.0.4
vs code
Recommended extensions
TSLint11
Integrates the tslint linter for the TypeScript language into VS Code.
Launch VS Code Quick Open (⌘+P), paste the following command, and press enter.
ESLint12
Integrates ESLint into VS Code. See project page13 for more details.
EditorConfig14
EditorConfig Support for Visual Studio Code
11
https://marketplace.visualstudio.com/items?itemName=eg2.tslint
12
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
13
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
14
https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig
Prerequisites 5
Hiding files
This step is optional. In the code go to the Preferences -> Workspace Settings and paste the
following settings into the opened file:
1 {
2 "files.exclude": {
3 "**/.git": true,
4 "**/.DS_Store": true
5 }
6 }
You can extend the list with the files, extensions or directories based on your preferences.
ES6
ECMAScript 2015 (also known as ES6 and often referred to as Harmony) is the 6th major release of
the ECMAScript language specification. I am going to cover the most important features here to get
you started with ES6 and then be able moving to TypeScript and Angular faster.
Classes
The class syntax in JavaScript is not a new object-oriented inheritance model but simply a
syntactical sugar on top of the existing prototype-based inheritance. Traditionally we have been
using standard Objects and Prototypes like shown below:
With class syntax developers get more natural and boilerplate-free result:
1 class Widget {
2 constructor(id, x, y) {
3 this.id = id;
4 this.setPosition(x, y);
5 }
6
7 setPosition(x, y) {
8 this.x = x;
9 this.y = y;
10 }
11 }
The constructor function is automatically called when you create a new instance of Widget:
ES6 7
Inheritance
The extends keyword is used to define a class as a child of another class. The following example
demonstrates inheritance in practice:
We created a new TextBox class that is based on the Widget and adds additional text property. Note
that a base Widget constructor must also be called when a child class instantiated. It must be the
very first line of the child constructor implementation.
Here’s another example:
ImageBox also inherits Widget class and adds size-related information alongside position. Access
to both classes is demonstrated with the reset function that calls Widget.setPosition and
ImageBox.setSize functions.
ES6 8
Arrow Functions
ES6 offers a shorter syntax for a function expression called arrow function, also known as fat
arrow function. Arrow functions provide more expressive closure syntax, simplify function scoping
and change the way this is handled.
Expression Bodies
When used as expressions bodies arrow functions work much like anonymous one-line lambdas
that you can meet in many programming languages. Let’s filter a book collection to find something
to read using both ES5 and ES6 to see the difference:
1 var books = [
2 { name: 'Book 1', read: true },
3 { name: 'Book 2' , read: false },
4 { name: 'Book 3', read: true }
5 ];
6
7 // ES5
8 var booksToRead = books.filter(function (b) { return !b.read });
9
10 // ES6
11 var booksToRead = books.filter(b => !b.read);
Curly brackets and return statement are not required if only one expression is present. You could
write the same example like following:
1 // ES6
2 let booksToRead = books.filter(b => { return !b.read; });
Statement Bodies
Arrow functions provide more expressive closure syntax.
ES6 9
1 // ES6
2 // list the books I've read
3 books.forEach(b => {
4 if (book.read) {
5 console.log(b.name);
6 }
7 });
1 // ES6
2 let button = document.getElementById('submit-button');
3 button.addEventListener('click' () => {
4 this.onButtonClicked();
5 });
1 // ES6
2 setTimeout(_ => {
3 console.log('First callback');
4 setTimeout(_ => {
5 console.log('Second callback');
6 }, 1);
7 }, 1);
Lexical this
One of the best features of arrow functions in ES6 is the more intuitive handling of current object
context. These function expressions do not bind their variables:
• arguments
• super
• this
• new.target
ES6 10
1 // ES6
2 this.books.forEach(b => {
3 if (!b.read) {
4 this.booksToRead.push(b);
5 }
6 });
There are multiple ways of doing the same with ECMAScript 5, and all of them involve manual
context management
As arrow functions do not create and bind their own this context the following code is concise and
works as expected:
ES6 11
1 // ES6
2 function ProgressBar() {
3 this.progress = 0;
4
5 setInterval(() => {
6 this.progress++;
7 }, 1000);
8 }
9
10 let p = new ProgressBar();
In the example above this properly refers to the ProgressBar object. Before ES6 you would most
probably additional variables like self, that, and other.
1 // ES5
2 function ProgressBar() {
3 var self = this;
4 self.progress = 0;
5
6 setInterval(function () {
7 self.progress++;
8 }, 1000);
9 }
Template Literals
Template Literals (formerly called “template strings” in prior drafts of the ECMAScript 6 language
specification) are string literals providing intuitive expression interpolation for single-line and
multiline strings.
You use backticks to enclose a string literal and ${} to interpolate JavaScript variables or arbitrary
expressions
1 // ES6
2 let point = { x: 10, y: 20 };
3 console.log(`Position is ${point.x}:${point.y}`);
4 // output: Position is 10:10
With ES5 you have to concatenate strings when dealing with multiple lines:
ES6 12
1 // ES5
2 var title = 'Title'
3 var component = {
4 template: '' +
5 '<h1>' + title + '<h1>\n' +
6 '<div class="grid">\n' +
7 ' <div class="col-6"></div>\n' +
8 ' <div class="col-6"></div>\n' +
9 '</div>'
10 }
Multi-line string creation with template literals becomes very clean and readable:
1 // ES6
2 let title = 'Title';
3 let component = {
4 template: `
5 <h1>${title}</h1>
6 <div class="grid">
7 <div class="col-6"></div>
8 <div class="col-6></div>
9 </div>
10 `
11 }
1 // ES6
2 function playSound(file, volume = 50) {
3 console.log(`Playing '${file}' with volume ${volume}.`);
4 }
5 playSound('test.mp3');
6 // Playing 'test.mp3' with volume 50.
7
8 playSound('test.mp3', 70);
9 // Playing 'test.mp3' with volume 70.
With ES5 you have to check every parameter to be undefined and setting defaults manually if
needed.
1 // ES5
2 function playSound(file, volume) {
3 if (volume === undefined) {
4 volume = 50;
5 }
6 console.log("Playing '" + file + "' with volume " + volume);
7 }
8 playSound('test.mp3');
9 // Playing 'test.mp3' with volume 50.
10
11 playSound('test.mp3', 70);
12 // Playing 'test.mp3' with volume 70.
So support for default parameter values is a huge step forward and real time saver.
Rest Parameter
In ES5, if you want your function to handle an indefinite or an arbitrary number of arguments, you
must use special arguments variable:
ES6 14
1 // ES5
2 function logMessages() {
3 for (var i = 0; i < arguments.length; i++) {
4 console.log(arguments[i]);
5 }
6 }
7
8 logMessages('Hello,', 'world!');
Which produces:
Hello,
world!
In ES6, you can aggregate all remaining arguments into a single function parameter
1 // ES6
2 function logMessages(...messages) {
3 for (const message of messages) {
4 console.log(message);
5 }
6 }
7
8 logMessages('Hello,', 'world!');
Hello,
world!
Rest parameters become even more valuable when you need collecting arguments starting from a
different position. In the next example, the rest parameter is used to collect arguments from the
second one to the end of the array.
ES6 15
1 // ES6
2 function greet(message, ...friends) {
3 for (const friend of friends) {
4 console.log(`${message}, ${friend}!`);
5 }
6 }
7
8 greet('Hello', 'John', 'Joan', 'Bob')
The function above allows you to set the greeting message as the first parameter and array of friend
names to generate messages. The console output, in this case, should be:
Hello, John!
Hello, Joan!
Hello, Bob!
Spread Operator
Spread operator is used to expand an iterable collection into multiple arguments.
1 // ES6
2 let positive = [ 1, 2, 3 ];
3 let negative = [ -1, -2, -3 ]
4
5 let numbers = [...negative, 0, ...positive];
6
7 console.log(numbers);
8 // [-1, -2, -3, 0, 1, 2, 3]
1 // ES6
2 let message = 'Hello, world';
3 let chars = [...message];
4
5 console.log(chars);
6 // ["H", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d"]
Spread operator easily becomes an alternative to the Array.prototype.concat() method. With ES5
the example above will look like the following:
ES6 16
1 // ES5
2 var positive = [ 1, 2, 3 ];
3 var negative = [ -1, -2, -3 ];
4 var zero = [0];
5
6 var numbers = negative.concat(zero, positive);
7
8 console.log(numbers);
9 // [-1, -2, -3, 0, 1, 2, 3]
Destructuring assignment
ES6 provides a way to extract values out of the objects or collections into the separate variables to
access them easier in the code. That is often called “value unpacking” or “destructuring”.
Basic example
As an example, you can extract a subset of values from the collection using the following format:
Let’s create an array of words and extract the first couple of them into separate variables “first” and
“second” like in the code below:
1 // ES6
2
3 let words = [ 'this', 'is', 'hello', 'world', 'example' ];
4 let [ first, second ] = words;
5
6 console.log(first); // 'this'
7 console.log(second); // 'is'
As you can see from the example above, you can extract a subset of an array and split it into multiple
variables. Without destructuring your code might look like the following:
ES6 17
1 // ES5
2
3 var words = [ 'this', 'is', 'hello', 'world', 'example' ];
4 var first = words[0];
5 var second = words[1];
6
7 console.log(first); // 'this'
8 console.log(second); // 'is'
Array destructuring
You have already seen some of the array destructuring examples earlier in the section. We enclose
variables in square brackets using the following syntax:
Please note that you can also apply the same destructuring technique to the function call results:
1 // ES6
2
3 function getWords() {
4 return [ 'this', 'is', 'hello', 'world', 'example' ];
5 }
6
7 let [ first, second ] = getWords();
8 console.log(`${first} ${second}`); // 'this is'
In addition to basic unpacking and variable assignment, several other things bring much value and
reduce the code.
Value assignment
The destructuring syntax can be used to assign values to variables instead of extracting them. Take
a look at the following example:
ES6 18
1 // ES6
2
3 let first, second;
4
5 [ first, second ] = [ 'hello', 'world' ];
6
7 console.log(first); // 'hello'
8 console.log(second); // 'world'
Default values
Another great feature of the array destructuring is default values. There might be cases when the
array has no values, and you want to provide some reasonable defaults.
The format of the syntax, in this case, is as follows:
1 // ES6
2
3 let words = [ 'hello' ];
4 let [ first = 'hey', second = 'there' ] = words;
5
6 console.log(first); // 'hello'
7 console.log(second); // 'there'
The array we got initially does not contain two words. We are trying to extract first two variables
from it, and set ‘hey’ as the default value for the first word, and ‘there’ as a default for the second
one. At the runtime however only second variable stays with the default value.
Default value assignment is a compelling feature that helps you reduce the code for variable
initialization and safety checks. Below is how the same code could look like in ES5:
ES6 19
1 // ES5
2
3 var words = ['hello'];
4
5 var first = words[0];
6 if (!first) {
7 first = 'hey';
8 }
9
10 var second = words[1];
11 if (!second) {
12 second = 'there'
13 }
14
15 console.log(first); // 'hello'
16 console.log(second); // 'there'
Swapping values
Traditionally to swap two variables, developers need a third temporary one to hold the value of
either first or second variable.
1 // ES5
2
3 var first = 'world';
4 var second = 'hello';
5
6 var temp = first;
7 first = second;
8 second = temp;
9
10 console.log(first + ' ' + second); // 'hello world'
With ES6 you can now reduce the code by using destructuring assignment syntax to swap variables
in a single line like in the next example:
ES6 20
1 // ES6
2
3 let first = 'world';
4 let second = 'hello';
5
6 [ first, second ] = [ second, first ];
7
8 console.log(`${first} ${second}`); // 'hello world'
This feature may be a great time saver when it comes to sorting functions.
Skipping values
We have been using examples that take the beginning of the array so far.
The ES6 does not restrict you to that only scenario; it is also possible skipping values when unpacking
or destructuring arrays.
You can just put the commas instead of variables like in the example below:
As you see, the ES6 allows you to unpack the head of the array into separate variables. Sometimes
you may want to access the tail of the array as a single variable as well.
For this particular case, there’s a special syntax that utilises ES6 “rest” parameters.
We use “rest parameter” to define a variable to hold the tail of the array and below is an example of
how to achieve this behaviour:
ES6 21
Object destructuring
Besides arrays and collections, you can use destructuring assignment syntax with the object
instances as well.
We enclose variables in curly brackets using the following syntax:
Unpacking properties
ES6 allows you to extract properties by their names similar to how to unpack arrays.
Let’s try to unpack a couple of properties from a user object:
1 let obj = {
2 id: 1,
3 username: 'jdoe',
4 firstName: 'John',
5 lastName: 'Doe'
6 };
7
8 let { id, username } = obj;
9
10 console.log(id); // '1'
11 console.log(username); // 'jdoe'
Renaming properties
You can also give destructured property an alias if you want to use it as a variable with a different
name.
The syntax, in this case, is going to be as follows:
Let’s now rewrite our previous example to use custom property names.
ES6 22
1 let obj = {
2 id: 1,
3 username: 'jdoe',
4 firstName: 'John',
5 lastName: 'Doe'
6 };
7
8 let { id: uid, username: login } = obj;
9
10 console.log(uid); // '1'
11 console.log(login); // 'jdoe'
We are using “uid” and “login” instead of “id” and “username” properties this time.
Default values
When applying property destructuring to the object properties, you can provide default values for
missing properties. That saves time for property checks and reduces coding efforts.
For example, let’s provide a default value for the “id” property and also unpack the property “role”
that that does not exist for the given object, and set it to be “guest” by default.
1 let obj = {
2 id: 1,
3 username: 'jdoe',
4 firstName: 'John',
5 lastName: 'Doe'
6 };
7
8 let { id = 0, role = 'guest' } = obj;
9
10 console.log(id); // '1'
11 console.log(role); // 'guest'
Unpacking methods
You can extract object methods into separate variables and use them as shortcuts:
ES6 23
The example above demonstrates a “console.log” method being extracted into the “log” variable and
used separately.
We utilise the following syntax:
1 // ES6
2
3 class MyClass {
4
5 sayHello(message) {
6 console.log(`Hello, ${message}`);
7 }
8
9 sayBye(message) {
10 console.log(`Bye, ${message}`);
11 }
12
13 }
14
15 let myClass = new MyClass();
16 let { sayHello, sayBye } = myClass;
17
18 sayHello('how are you?'); // 'Hello, how are you?'
19 sayBye('see you soon.'); // 'Bye, see you soon'
Renaming methods
You can also rename destructured methods if needed. The following syntax should be used to give
the unpacked method a custom name:
Let’s update the “MyClass” we used earlier and rename “sayHello” and “sayBye” methods to just
“hello” and “bye”:
ES6 24
1 // ES5
2
3 function showDialog(options) {
4 options = options || {};
5 var message = options.message || 'Unknown message';
6 var size = options.size || { width: 400, height: 400 };
7 var position = options.position || { x: 200, y: 300 };
8
9 console.log('message: ' + message);
10 console.log('size: ' + size.width + ':' + size.height);
11 console.log('position: ' + position.x + ':' + position.y);
12 }
Above is the simplified version of the custom options management that has been very popular for
years. We provide a JavaScript object as an “options” parameter, and function does parsing and
detecting missing properties to initialize default values if needed.
Depending on the size of the options object there might be many checks just to set the default values
for them. Especially if there are nested objects with own properties, like “size” and “position” in our
case.
Now, if you call the “showDialog” function with no parameters except the “message” value, the
output should be similar to the following one:
ES6 25
showDialog({
message: 'hello world'
});
Next, try to call the same function with a partial set of options, for instance, the “size” settings:
showDialog({
message: 'hey there',
size: { width: 200, height: 100 }
});
Now you can rewrite the “showDialog” implementation to use destructuring with default values
like in the next example:
1 // ES6
2
3 function showDialog({
4 message = 'Message',
5 size = { width: 400, height: 400 },
6 position = { x: 200, y: 300 } }) {
7
8 console.log(`message: ${message}`);
9 console.log(`size: ${size.width}:${size.height}`);
10 console.log(`position: ${position.x}:${position.y}`);
11 }
Notice how we use the destructuring assignment syntax to declare a function parameter.
ES6 26
1 showDialog({
2 message: 'hey there',
3 size: { width: 200, height: 100 }
4 });
5
6 // message: hey there
7 // size: 200:100
8 // position: 200:300
IDE support
Many modern IDEs already provide support for destructuring syntax within function or method
parameters. VS Code15 , for instance, provides auto-completion both for function calls and for nested
properties.
Modules
Before ES6 developers traditionally were using Revealing Module pattern to emulate modules
in JavaScript. The basic concept of a Revealing Module is that you use closures (self-invoking
functions) with an Object which encapsulates its data and behaviour.
1 // ES5
2 var Module = (function() {
3 var privateMethod = function() {
4 // do something
5 console.log('private method called');
6 };
7
8 return {
9 x: 10,
10 name: 'some name',
11 publicMethod: function() {
15
https://code.visualstudio.com/
ES6 27
12 // do something
13 console.log('public method called');
14 privateMethod();
15 }
16 };
17 })();
18
19 Module.publicMethod()
I recommend also reading an excellent article “Mastering the Module Pattern16 ” by Todd Motto to
get deep coverage of Revealing Module pattern in JavaScript.
The rise of module systems based on either AMD or CommonJS syntax has mostly replaced revealing
modules and other hand-written solutions in ES5.
1 // ES6
2
3 // module lib/logger.js
4 export function log (message) { console.log(message); };
5 export var defaultErrorMessage = 'Aw, Snap!';
6
7 // myApp.js
8 import * as logger from "lib/logger";
9 logger.log(logger.defaultErrorMessage);
10
11 // anotherApp.js
12 import { log, defaultErrorMessage } from "lib/logger";
13 log(defaultErrorMessage);
Here’s how the same approach would look like if written with ECMAScript 5:
16
https://toddmotto.com/mastering-the-module-pattern/
ES6 28
1 // ES5
2
3 // lib/logger.js
4 LoggerLib = {};
5 LoggerLib.log = function(message) { console.log(message); };
6 LoggerLib.defaultErrorMessage = 'Aw, Snap!';
7
8 // myApp.js
9 var logger = LoggerLib;
10 logger.log(logger.defaultErrorMessage);
11
12 // anotherApp.js
13 var log = LoggerLib.log;
14 var defaultErrorMessage = LoggerLib.defaultErrorMessage;
15 log(defaultErrorMessage);
Default Values
You can make your ES6 module exporting some value as default one.
1 // ES6
2
3 // lib/logger.js
4 export default (message) => console.log(message);
5
6 // app.js
7 import output from 'lib/logger';
8 output('hello world');
Wildcard Export
Another great feature of ES6 modules is support for wildcard-based export of values. That becomes
handy if you are creating a composite module that re-exports values from other modules.
ES6 29
1 // ES6
2
3 // lib/complex-module.js
4 export * from 'lib/logger';
5 export * from 'lib/http';
6 export * from 'lib/utils';
7
8 // app.js
9 import { logger, httpClient, stringUtils } from 'lib/complex-module';
10 logger.log('hello from logger');
See also
• ECMAScript 6 — New Features: Overview & Comparison17
17
http://es6-features.org/
TypeScript
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
Installing TypeScript
npm install -g typescript
Using tsc
In your code editor create a file logger.ts with the following content:
1 function log(message) {
2 console.log(message);
3 }
4
5 log('Hello, world!');
Now you can use a command line to compile your source code to ES5 with tsc tool and run it with
node.js:
tsc logger.ts
node logger.js
TypeScript compiler takes logger.ts file, processes it and produces a JavaScript output to logger.js.
At this point, the .js file is ready to be used with an HTML page or executed by node.js.
You should see the following output in the command line:
Hello, world!
Now let’s see how type validation works. Add string type annotation for the log function and call
it with a number.
TypeScript 31
tsc logger.ts
> logger.ts(5,5): error TS2345: Argument of type '0' is not assignable to parame\
ter
of type 'string'.
Typings
TBD
Linting
TBD
tslint
TSLint checks your TypeScript code for readability, maintainability, and functionality
errors.
TBD
TypeScript Features
TBD
TypeScript 32
Types
TypeScript supports all the types used in JavaScript:
• boolean
• number
• string
• arrays
• enum
• any
• void
Basic Types
Boolean
The most basic datatype is the simple true/false value, which JavaScript and TypeScript
call a boolean value.
1 isEnabled = 'YES';
2 // logger.ts(2,1): error TS2322: Type '"YES"' is not assignable to type 'boolean\
3 '.
Number
TypeScript maps all JavaScript numbers to the number type:
Here’s an example:
TypeScript 33
String
Typescript supports ES6 template literals (formerly known as template strings). As in ECMAScript
6, you use backticks (‘) to enclose a string literal and ${} to interpolate JavaScript variables or arbitrary
expressions. Either double quotes (“) or single quotes (‘) can be used to surround string data.
Arrays
There are two main ways you can provide type definition for arrays of values in TypeScript:
As in JavaScript arrays, you can push elements and access them by index
The sample above demonstrates array element access together with string interpolation. When
executed it should produce:
TypeScript 34
Enum
TypeScript provides support for an enumerated type known in many languages (Swift, C#, Java, C,
and others). This data type consists of a set of named values mapped to numbers.
By default numbering of enum members starts with 0 and increments by one. You have full control
of the values if needed.
It must be noted however that you access names by the numeric values, not by an array index as it
may seem.
Any
A special any type is used to opt-out of the TypeScript type-checking process and addresses the
following cases:
Please note that by opting-out of the type-checking process you take full responsibility for safety
checks, as now TypeScript compiler is not able to verify the code at compile time. The following
example shows valid TypeScript code:
1 obj.log('hello world');
2 obj.helloWorld('log');
hello world
TypeError: obj.helloWorld is not a function
Void
The void type is used to declare a function does not return any value explicitly.
1 class Logger {
2
3 log(message: string): void {
4 console.log(message);
5 return true;
6 }
7
8 }
If you try compiling the code above you should get an error:
You can fix the type-check error by removing return statement from the log method:
TypeScript 36
1 class Logger {
2
3 log(message: string): void {
4 console.log(message);
5 }
6
7 }
You might also be using void types as function parameters or with Interfaces:
Classes
TypeScript provides support for classes introduced with ES6 (ECMAScript 2015) and adds a set of
features to improve object-oriented development.
1 class Widget {
2
3 id: string;
4
5 constructor(id: string) {
6 this.id = id;
7 }
8
9 render() {
10 console.log(`Rendering widget "${this.id}"`);
11 }
12
TypeScript 37
13 }
14
15 let widget = new Widget('text1');
16 widget.render();
Properties
With ES6 you define class properties from with the class constructor:
1 // ES6
2 class Widget {
3
4 constructor(id) {
5 this.id = id;
6 this.x = 0;
7 this.y = 0;
8 }
9
10 }
If you try compiling example above with tsc utility (TypeScript compiler) you should get the
following errors:
The errors are raised because TypeScript requires you to define properties separately. It is needed to
enable many other features TypeScript provides.
TypeScript 38
1 class Widget {
2
3 id: string;
4 x: number;
5 x: number;
6
7 constructor(id: string) {
8 this.id = id;
9 this.x = 0;
10 this.y = 0;
11 }
12 }
1 class Widget {
2
3 id: string;
4 x: number = 0;
5 x: number = 0;
6
7 constructor(id: string) {
8 this.id = id;
9 }
10 }
TypeScript supports computed properties, which do not store a value. Instead, they provide getters
and setters to retrieve and assign values in a controlled way.
TBD: describe get/set format
One of the common cases for a getter is computing a return value based on other property values:
TypeScript 39
1 class User {
2
3 firstName: string;
4 lastName: string;
5
6 get fullName(): string {
7 return `${this.firstName} ${this.lastName}`.trim();
8 }
9
10 constructor(firstName: string, lastName: string) {
11 this.firstName = firstName;
12 this.lastName = lastName;
13 }
14
15 }
16
17 let user = new User('Joan', 'Doe');
18 console.log(`User full name is: ${user.fullName}`);
If you save this example to file script.ts, compile it and run like shown below
You should see the output with the full username as expected:
Now let’s introduce a simple setter for the firstName property. Every time a new property value is
set we are going to remove leading and trailing white space. Such values as “ Joan” and “Joan “ are
automatically converted to “Joan”.
1 class User {
2
3 private _firstName: string;
4
5 get firstName(): string {
6 return this._firstName;
7 }
8
9 set firstName(value: string) {
TypeScript 40
10 if (value) {
11 this._firstName = value.trim();
12 }
13 }
14 }
15
16 let user = new User();
17 user.firstName = ' Joan ';
18 console.log(`The first name is "${user.firstName}".`);
Methods
Methods are functions that operate on a class object and are bound to an instance of that object. You
can use this keyword to access properties and call other methods like in the example below:
1 class Sprite {
2
3 x: number;
4 y: number;
5
6 render() {
7 console.log(`rendering widget at ${this.x}:${this.y}`);
8 }
9
10 moveTo(x: number, y: number) {
11 this.x = x;
12 this.y = y;
13 this.render();
14 }
15
16 }
17
18 let sprite = new Sprite();
19 sprite.moveTo(5, 10);
20 // rendering widget at 5:10
Return values
TypeScript 41
1 class NumberWidget {
2
3 getId(): string {
4 return 'number1';
5 }
6
7 getValue(): number {
8 return 10;
9 }
10
11 }
You can use a void type if the method does not return any value.
1 class TextWidget {
2
3 text: string;
4
5 reset(): void {
6 this.text = '';
7 }
8
9 }
Method parameters
1 class Logger {
2
3 log(message: string, level: number) {
4 console.log(`(${level}): ${message}`);
5 }
6
7 }
TypeScript will automatically perform type checking at compile time. Let’s try providing a string
value for the level parameter:
TypeScript 42
(2): test
Optional parameters
1 class Logger {
2
3 log(message: string, level?: number) {
4 if (level === undefined) {
5 level = 1;
6 }
7 console.log(`(${level}): ${message}`);
8 }
9
10 }
11
12 let logger = new Logger();
13 logger.log('Application error');
Please note that optional parameters must always follow required ones.
Default parameters
TypeScript also supports default values for parameters. Instead of checking every parameter for
undefined value you can provide defaults directly within the method declaration:
1 class Logger {
2
3 log(message: string = 'Unknown error', level: number = 1) {
4 console.log(`(${level}): ${message}`);
5 }
6
7 }
In TypeScript, you can gather multiple arguments into a single variable known as rest parameter.
Rest parameters were introduced as part of ES6, and TypesScripts extends them with type checking
support.
TypeScript 44
1 class Logger {
2
3 showErrors(...errors: string[]) {
4 for (let err of errors) {
5 console.error(err);
6 }
7 }
8
9 }
Now you can provide an arbitrary number of arguments for showErrors method:
Something
went
wrong
Rest parameters in TypeScript work great with Spread Operator allowing you to expand a collection
into multiple arguments. It is also possible mixing regular parameters with spread ones:
In the example above we compose a collection of arguments from arbitrary parameters and content
of the messages array in the middle. The showErrors method should handle all entries correctly and
produce the following output:
Error
something
went
wrong
!
Constructors
Constructors in TypeScript got same features as methods. You can have default and optional
parameters, use rest parameters and spread operators with class constructor functions.
Besides, TypeScript provides support for automatic property creation based on constructor param-
eters. Let’s create a typical User class implementation:
TypeScript 45
1 class User {
2
3 firstName: string;
4 lastName: string;
5
6 get fullName(): string {
7 return `${this.firstName} ${this.lastName}`.trim();
8 }
9
10 constructor(firstName: string, lastName: string) {
11 this.firstName = firstName;
12 this.lastName = lastName;
13 }
14
15 }
Instead of assigning parameter values to the corresponding properties we can instruct TypeScript to
perform an automatic assignment instead. You can do that by putting one of the access modifiers
public, private or protected before the parameter name.
You are going to get more details on access modifiers later in this book. For now, let’s see the updated
User class using automatic property assignment:
1 class User {
2
3 get fullName(): string {
4 return `${this.firstName} ${this.lastName}`.trim();
5 }
6
7 constructor(public firstName: string, public lastName: string) {
8 }
9
10 }
11
12 let user = new User('Joan', 'Doe');
13 console.log(`Full name is: ${user.fullName}`);
TypeScript creates firstName and lastName properties when generating JavaScript output. You need
targeting at least ES5 to use this feature. Save example above to file script.ts then compile and
run with node:
TypeScript 46
You have not defined properties explicitly, but fullName getter was still able accessing them via
this. If you take a look at the emitted JavaScript you should see the properties are defined there as
expected:
1 // ES5
2 var User = (function () {
3 function User(firstName, lastName) {
4 this.firstName = firstName;
5 this.lastName = lastName;
6 }
7 Object.defineProperty(User.prototype, "fullName", {
8 get: function () {
9 return (this.firstName + " " + this.lastName).trim();
10 },
11 enumerable: true,
12 configurable: true
13 });
14 return User;
15 }());
16 var user = new User('Joan', 'Doe');
17 console.log("Full name is: " + user.fullName);
Now you can also switch to ES6 target to see how TypeScript assigns properties:
1 // ES6
2 class User {
3 constructor(firstName, lastName) {
4 this.firstName = firstName;
5 this.lastName = lastName;
6 }
7 get fullName() {
8 return `${this.firstName} ${this.lastName}`.trim();
9 }
10 }
11 let user = new User('Joan', 'Doe');
12 console.log(`Full name is: ${user.fullName}`);
Inheritance
One of the important TypeScript features is the class inheritance that enables OOP patterns for
developers. Under the hood TypeScript is using the same extends syntactic sugar when targeting
ES6 JavaScript, and prototypical inheritance wrappers when generating output in ES5.
We can refer to animals as a classic example of class-based programming and inheritance.
1 class Animal {
2 name: string;
3 constructor(name: string) {
4 this.name = name;
5 }
6 makeSound() {
7 console.log('Unknown sound');
8 }
9 }
You have created a basic Animal class that contains a name property and makeSound method. That
translates to ES5 as following:
TypeScript 48
1 // ES5
2 var Animal = (function () {
3 function Animal(name) {
4 this.name = name;
5 }
6 Animal.prototype.makeSound = function () {
7 console.log('Unknown sound');
8 };
9 return Animal;
10 }());
Now you can create a Dog implementation that provides a right sound:
Please note that if you have a constructor in the base class, then you must call it from all derived
classes. Otherwise, TypeScript should raise a compile-time error:
error TS2377: Constructors for derived classes must contain a 'super' call.
Now let’s add a Cat implementation with its sound and test both classes:
TypeScript 49
Once the code compiles and executes you should get the following output:
Woof-woof
Meow
Access Modifiers
TypeScript supports public, private and protected modifiers for defining accessibility of the class
members.
Public
By default, each member of the class is public so that you can omit it. However, nothing stops you
from declaring public modifier explicitly if needed:
1 class User {
2 public firstName: string;
3 public lastName: string;
4
5 public speak() {
6 console.log('Hello');
7 }
8
9 constructor(firstName: string, lastName: string) {
10 this.firstName = firstName;
11 this.lastName = lastName;
12 }
13 }
Now if you compile example above to JavaScript you should see the following:
TypeScript 50
Private
You mark a member as private when it should never be accessed from outside of its containing
class. One of the most common scenarios is creating private fields to hold values for properties. For
example:
1 class User {
2 private _firstName: string;
3 private _lastName: string;
4
5 get firstName() {
6 return this._firstName;
7 }
8
9 get lastName() {
10 return this._lastName;
11 }
12
13 constructor(firstName: string, lastName: string) {
14 this._firstName = firstName;
15 this._lastName = lastName;
16 }
17 }
The class we have created above allows setting user’s first and last name only from within the
constructor.
If you try changing name properties from outside the class, TypeScript will raise an error at compile
time:
TypeScript 51
Protected
The protected modifier restricts member visibility from outside of the containing class but provides
access from the derived classes.
Let’s start with base Page class implementation:
1 class Page {
2
3 protected renderHeader() { /* ... */ }
4 protected renderContent() { /* ... */ }
5 protected renderFooter() { /* ... */ }
6
7 render() {
8 this.renderHeader();
9 this.renderContent();
10 this.renderFooter();
11 }
12 }
We created a Page class that has public method render. Internally render calls three separate
methods to render header, content and footer of the page. These methods are not available from
the outside the the class.
Now we are going to create a simple derived AboutPage class:
As you can see the AboutPage defines its render method that calls renderHeader and renderFooter
in parent class but puts custom content in the middle.
You can also use protected modifier with class constructors. In this case, the class can be instantiated
only by the derived classes that extend it. That becomes handy when you want to have properties
and methods available for multiple classes as a base implementation, but don’t want a base class to
be instantiated outside its containing class.
For example
1 class Page {
2 protected constructor(id: string) {
3 // ...
4 }
5
6 render() { /* base render */ }
7 }
8
9 class MainPage extends Page {
10 constructor(id: string) {
11 super(id);
12 }
13
14 render() { /* render main page */ }
15 }
16
17 class AboutPage extends Page {
18 constructor(id: string) {
19 super(id);
20 }
21
22 render() { /* render about page */ }
23 }
24
25 let main = new MainPage('main');
26 let about = new AboutPage('about');
You can create instances of MainPage and AboutPage both having access to protected members of
the Page class. However, you are not able creating an instance of the Page class directly.
TypeScript 53
Readonly modifier
One of the common ways to create a read-only property in many object-oriented programming
languages is by having a private local variable with a getter only.
1 class Widget {
2
3 private _id: string;
4
5 get id(): string {
6 return this._id;
7 }
8
9 constructor(id: string) {
10 this._id = id;
11 }
12 }
13
14 let widget = new Widget('textBox');
15 console.log(`Widget id: ${widget.id}`);
16 // Widget id: textBox
You can also make properties read-only by using the readonly keyword. That reduces repetitive
typing when dealing with many read-only properties, and greatly improves overall code readability.
Let’s update the previous example to use readonly:
1 class Widget {
2 readonly id: string;
3
4 constructor(id: string) {
5 this.id = id;
6 }
7 }
If you try changing the value of the property outside of the constructor TypeScript will raise an
error:
TypeScript 54
You can provide default values for read-only properties only in two places: property declaration and
constructor.
1 class Widget {
2 readonly id: string;
3 readonly minWidth: number = 200;
4 readonly minHeight: number = 100;
5
6 constructor(id: string) {
7 this.id = id;
8 }
9 }
10
11 let widget = new Widget('text');
12 widget.minWidth = 1000;
13 // error TS2540: Cannot assign to 'minWidth' because it is a constant or a read-\
14 only property.
Interfaces
An interface is a description of the actions that an object can do.
You might already be familiar with interfaces in other programming languages like C# and Java, or
contracts in Swift.
Interfaces are not part of the ECMAScript. It is a level of abstraction supported by TypeScript to
improve the type-checking process, and not converted to JavaScript code.
Here’s an example of an interface describing generic Text component:
1 interface TextComponent {
2
3 text: string;
4 render(): void;
5
6 }
Now you can use the interface above to describe the requirement of having the text property that
is a string and a render method:
TypeScript 55
We are using implements keyword to wire class with a particular interface. It is not important in
what order class members are defined as long as all properties and methods the interface requires
are present and have required types.
Let’s create another class that implements TextComponent interface partially:
The example above shows a class that must implement three different interfaces to compile.
Abstract Classes
Interfaces describe only requirements for classes; you cannot create an instance of the interface. You
need abstract classes un order to provide implementation details.
TypeScript 56
Same as with interfaces you cannot create instances of abstract classes directly, only other classes
derived from an abstract one. Also, it is possible marking class methods as abstract. Abstract
methods do not contain implementation, and similar to interface methods provide requirements
for derived classes.
Note how HomePageComponent implements abstract renderContent that has access to renderHeader
and renderFooter methods carried out in the parent class.
You can also use access modifiers with abstract methods. The most frequent scenario is when
methods need to be accessible only from within the child classes, and invisible from the outside:
For example:
TypeScript 57
Any additional class that inherits (extends) HomePageComponent will still be able calling or redefining
renderContent method. But if you try accessing renderContent from outside the TypeScript should
raise the following error:
Modules
TypeScript supports the concept of modules introduced in ES6. Modules allow isolating code and
data and help splitting functionality into logical groups.
One of the major features of ES6 (and TypeScript) modules is their file scope. The code inside the
module (classes, variables, functions, and other) does not pollute global scope and is not accessible
from the outside unless exported explicitly.
To share the code of the module with the outside world, you use export keyword:
1 // module1.ts
2 export class TextBoxComponent {
3 constructor(public text: string) {}
4 render() {
5 console.log(`Rendering '${this.text}' value.`);
6 }
7 }
To use this code in your main application file or another module, you must import it first. You import
the TextBoxComponent class using import keyword:
1 // app.ts
2 import { TextBoxComponent } from './module1'
3
4 let textBox = new TextBoxComponent('hello world');
5 textBox.render();
Module Loaders
ES6 and TypeScript rely on module loaders to locate files, resolve external dependencies and
execute module files.
The most popular module loaders are:
• Server side
– CommonJs (used by node.js)
• Client side
– SystemJS
– RequireJS
– Webpack
TypeScript supports different formats of generated JavaScript output. You can instruct compiler to
generate code adopted for multiple module loading systems using formats such as
TypeScript 59
You can test TextBoxComponent we have created earlier with node.js using commonjs module target:
TypeScript automatically compiles referenced modules. It starts with app.ts, resolves and compiles
module1 as module1.ts file, and produces two JavaScript files app.js and module.js that can be
executed by node.js.
Here’s an example of app.js file content:
1 "use strict";
2 // app.ts
3 var module1_1 = require("./module1");
4 var textBox = new module1_1.TextBoxComponent('hello world');
5 textBox.render();
Running in browser
In order to run module-based application in browser you can take SystemJS loader:
1 <script src="systemjs/dist/system.js"></script>
2 <script>
3 SystemJS.import('/app/app.js');
4 </script>
Let’s take a look at a simple TypeScript application that references an external module.
TypeScript 60
1 // logger.ts
2 export class Logger {
3
4 output: any;
5
6 constructor(outputId: string) {
7 this.output = document.getElementById(outputId);
8 }
9
10 info(message: string) {
11 this.output.innerText = `INFO: ${message}`;
12 }
13
14 }
Our simple logger is going to put a message as a content of the document element provided from
the outside.
1 // app.ts
2 import { Logger } from './logger';
3
4 let logger = new Logger('content');
5 logger.info('hello world');
The application needs to be compiled with SystemJS support to load correctly. You can configure
TypeScript to generate compatible JavaScript code by setting module code generation setting to
system:
Source code
You can find source code for the examples above in the “typescript/systemjs-example18 ”
folder.
To install dependencies, compile and run the demo use the following commands:
18
https://github.com/DenisVuyka/developing-with-angular/tree/master/typescript/systemjs-example
TypeScript 61
npm install
npm start
Your default browser should run example page automatically. Once the page gets loaded you should
see an expected message:
Decorators
TypeScript introduces decorators feature, metadata expressions similar to Java annotation tags or
C# and Swift attributes. ECMAScript does not yet have native support for annotating classes and
class members (the feature is in the proposal state), so decorators is an experimental TypeScript
feature.
Decorators have a traditional notation of @expression where expression is the name of the function
that should be invoked at runtime. This function receives decorated target as a parameter and can
be attached to:
• class declaration
• method
• accessor
• property
• parameter
Class Decorators
Class decorators are attached to class declarations. At runtime, the function that backs the decorator
gets applied to the class constructor. That allows decorators inspecting, modifying or even replacing
class instances if needed.
Here’s a simple example of the LogClass decorator that outputs some log information every time
being invoked:
Now you can use newly created decorator with different classes:
TypeScript 62
1 @LogClass
2 class TextWidget {
3 text: string;
4
5 constructor(text: string = 'default text') {
6 this.text = text;
7 }
8
9 render() {
10 console.log(`Rendering text: ${this.text}`);
11 }
12 }
When a new instance of TextWidget class is created, the @LogClass attribute will be automatically
invoked:
It is also possible passing values to decorators. You can achieve this with a feature known as
decorator factories. A decorator factory is a function returning an expression that is called at
runtime:
Let’s create another simple decorator with log output that accepts additional prefix and suffix
settings:
TypeScript 63
1 @LogClassWithParams('BEGIN:', ':END')
2 class TextWidget {
3 text: string;
4
5 constructor(text: string = 'default text') {
6 this.text = text;
7 }
8
9 render() {
10 console.log(`Rendering text: ${this.text}`);
11 }
12 }
13
14 let widget = new TextWidget();
15 widget.render();
You have marked TextWidget class with the LogClassWithParams decorator having a prefix and
suffix properties set to BEGIN: and :END values. The console output, in this case, should be:
BEGIN:
LogClassWithParams decorator called for:
function TextWidget(text) {
if (text === void 0) { text = 'default text'; }
this.text = text;
}
}
:END
TypeScript 64
Multiple decorators
You are not limited to a single decorator per class. TypeScript allows declaring as much class and
member decorators as needed:
1 @LogClass
2 @LogClassWithParams('BEGIN:', ':END')
3 @LogClassWithParams('[', ']')
4 class TextWidget {
5 // ...
6 }
Note that decorators are called from right to left, or in this case from bottom to top. It means that
first decorator that gets executed is:
1 @LogClassWithParams('[', ']')
1 @LogClass
Method Decorators
Method decorators are attached to class methods and can be used to inspect, modify or completely
replace method definition of the class. At runtime, these decorators receive following values as
parameters: target instance, member name and member descriptor.
Let’s create a decorator to inspect those parameters:
1 class TextWidget {
2 text: string;
3
4 constructor(text: string = 'default text') {
5 this.text = text;
6 }
7
8 @LogMethod
9 render() {
10 console.log(`Rendering text: ${this.text}`);
11 }
12 }
13
14 let widget = new TextWidget();
15 widget.render();
You can use decorator factories also with method decorators to support additional parameters.
This decorator can now be applied to methods. You can attach multiple decorators to a single method:
TypeScript 66
1 class TextWidget {
2 text: string;
3
4 constructor(text: string = 'default text') {
5 this.text = text;
6 }
7
8 @LogMethodWithParams('hello')
9 @LogMethodWithParams('world')
10 render() {
11 console.log(`Rendering text: ${this.text}`);
12 }
13 }
14
15 let widget = new TextWidget();
16 widget.render();
Note that decorators are called from right to left, or in this case from bottom to top. If you run the
code the output should be as follows:
render: world
render: hello
Rendering text: default text
Accessor Decorators
Accessor decorators are attached to property getters or setters and can be used to inspect, modify
or completely replace accessor definition of the property. At runtime, these decorators receive
following values as parameters: target instance, member name and member descriptor.
Note that you can attach accessor decorator to either getter or setter but not both. This restriction
exists because on the low level decorators deal with Property Descriptors19 that contain both get
and set accessors.
Let’s create a decorator to inspect parameters:
19
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
TypeScript 67
1 class TextWidget {
2 private _text: string;
3
4 @LogAccessor
5 get text(): string {
6 return this._text;
7 }
8
9 set text(value: string) {
10 this._text = value;
11 }
12
13 constructor(text: string = 'default text') {
14 this._text = text;
15 }
16 }
17
18 let widget = new TextWidget();
Same as with class and method decorators you can use decorator factories feature to pass parameters
to your accessor decorator.
TypeScript 68
TypeScript allows using more than one decorator given you attach it to the same property accessor:
1 class TextWidget {
2 private _text: string;
3
4 @LogAccessorWithParams('hello')
5 @LogAccessorWithParams('world')
6 get text(): string {
7 return this._text;
8 }
9
10 set text(value: string) {
11 this._text = value;
12 }
13
14 constructor(text: string = 'default text') {
15 this._text = text;
16 }
17 }
18
19 let widget = new TextWidget();
The console output should be as shown below, note the right-to-left execution order:
In case you declare decorator for both accessors TypeScript generates an error at compile time:
TypeScript 69
1 class TextWidget {
2 private _text: string;
3
4 @LogAccessorWithParams('hello')
5 get text(): string {
6 return this._text;
7 }
8
9 @LogAccessorWithParams('world')
10 set text(value: string) {
11 this._text = value;
12 }
13 }
Property Decorators
Property decorators are attached to class properties. At runtime, property decorator receives the
following arguments:
• target object
• property name
Due to technical limitations, it is not currently possible observing or modifying property initializers.
That is why property decorators do not get Property Descriptor value at runtime and can be used
mainly to observe a property with a particular name has been defined for a class.
Here’s a simple property decorator to display parameters it gets at runtime:
1 class TextWidget {
2
3 @LogProperty
4 id: string;
5
6 constructor(id: string) {
7 this.id = id;
8 }
9
10 render() {
11 // ...
12 }
13 }
14
15 let widget = new TextWidget('text1');
Parameter Decorators
Parameter decorators are attached to function parameters. At runtime, every parameter decorator
function is called with the following arguments:
• target
• parameter name
• parameter position index
Due to technical limitations, it is possible only detecting that a particular parameter has been
declared on a function.
Let’s inspect runtime arguments with this simple parameter decorator:
TypeScript 71
You can now use this decorator with a class constructor and method parameters:
1 class TextWidget {
2
3 render(@LogParameter positionX: number,
4 @LogParameter positionY: number) {
5 // ...
6 }
7
8 }
Parameter decorators are also executed in right-to-left order. So you should see console outputs for
positionY and then positionX:
Angular CLI
As you might have noticed from the previous chapters, creating a project structure for a new web
app may be a non-trivial task. Working with multiple projects or frequently creating new ones may
become extremely time-consuming as you need configuring project structure again and again.
The Angular CLI makes it easy to create an application that already works, right out of
the box. It already follows our best practices!
This command line tool automates and simplifies many common scenarios, including:
• create new project structure from scratch with most optimal configuration out of the box
• scaffold common Angular building blocks using one of the various blueprints (components,
directives, pipes, services and other)
• serving, watching and live reload
• code linting
• unit testing, code coverage reports, and end-to-end testing
• development and production builds
20
https://cli.angular.io
Angular CLI 73
Installing
npm install -g @angular/cli
The tool installs globally and is available via ng command. Angular CLI supports lots of features;
you can view details on available commands with the help command:
ng help
ng new my-first-app
The ng tool should produce console output similar to the following one:
installing ng
create .editorconfig
create README.md
create src/app/app.component.css
create src/app/app.component.html
create src/app/app.component.spec.ts
create src/app/app.component.ts
create src/app/app.module.ts
create src/assets/.gitkeep
create src/environments/environment.prod.ts
create src/environments/environment.ts
create src/favicon.ico
create src/index.html
create src/main.ts
create src/polyfills.ts
create src/styles.css
create src/test.ts
create src/tsconfig.app.json
create src/tsconfig.spec.json
create src/typings.d.ts
create .angular-cli.json
create e2e/app.e2e-spec.ts
create e2e/app.po.ts
Angular CLI 74
create e2e/tsconfig.e2e.json
create .gitignore
create karma.conf.js
create package.json
create protractor.conf.js
create tsconfig.json
create tslint.json
Successfully initialized git.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
You can `ng set --global packageManager=yarn`.
Project 'my-first-app' successfully created.
The scripts section of the package.json file should point to ng tool for all the actions:
package.json
1 {
2 ...
3 "scripts": {
4 "ng": "ng",
5 "start": "ng serve",
6 "build": "ng build",
7 "test": "ng test",
8 "lint": "ng lint",
9 "e2e": "ng e2e"
10 },
11 ...
12 }
Running application
Now switch to the newly generated my-first-app folder and launch the app:
cd my-first-app/
ng serve
The ng serve command compiles and serves entire project using webpack bundler with an output
similar to following:
Angular CLI 75
It is important to note that with ng serve you are going to run your project with live development
server. The server is going to watch for code changes, rebuild all affected bundles and reload the
browser.
Now if you navigate to http://localhost:4200 you should see the following default text:
app works!
Alternatively, you can run serve command with the --open switch to automatically open system
browser with the application once compilation is complete:
ng serve --open
The command above allows accessing your application from the local machine and local network
via port 3000.
There are plenty of options and switches that can be used with ng serve command; you can refer
to full details by calling ng help.
Code Linting
Checking code is one of the essential steps. Angular CLI ships with the TSLint support and predefined
set of rules in the tsconfig.json file.
Angular CLI 76
ng lint
Default auto-generated project should contain no errors. You should see the following result in the
console:
Let’s try to ensure TSLint works as expected by modifying the /src/app/app.component.ts file.
Just change single quotes with double quotes like below:
src/app/app.component.ts
1 export class AppComponent {
2 title = "app works!";
3 }
Unit tests
You get a predefined unit testing configuration with every generated project. By default, you are
going to use Karma runner with the Jasmine test framework.
ng test
Unit tests
Tests run in watch mode, meaning they automatically re-run upon code changes.
Please note that out-of-box configuration requires Google Chrome browser to run tests, via the karma-
chrome-launcher plugin.
As per Angular code style guides, all the unit test files reside next to the components tested. The
Angular CLI generates three dummy tests in the src/app/app.component.spec.ts:
Angular CLI 77
src/app/app.component.spec.ts
Let’s check what happens when a test fails. Append the following code to the end of the
app.component.spec.ts:
src/app/app.component.spec.ts
Now if you run ng test once again you should see the following report:
Angular CLI 78
Failed test
There are two tabs on the page: Spec List and Failures. You should see Failures by default but if
there’s more than one unit test failed you can check them on the Spec List page:
Spec List
If you do not plan to debug tests and just want to see a quick result of the test run just add --single-
run switch to the test command:
ng test --single-run
Developers typically use single run configurations for continuous integration (CI) scenarios.
Code coverage
You can generate a coverage report for your unit tests by adding --code-coverage switch to the
test command:
• compile the project with webpack, including TypeScript transpilation with source maps
• use Karma runner and Jasmine to execute unit tests
• remap code coverage report for JavaScript back to TypeScript
• generate HTML report within coverage folder
After testing finishes you can either open coverage/index.html with your favourite browser. For
example:
open ./coverage/index.html
The command above should serve your coverage report and automatically launches default browser
with the main page.
Code Coverage
ng build
ng build <options...>
By default it is running in development mode (an equivalent of ng build -dev) and produces output
to the dist/ folder. You will get bundles together with source maps for better debugging:
File Size
favicon.ico 5430
index.html 613
inline.bundle.js 5764
inline.bundle.js.map 5824
main.bundle.js 6539
main.bundle.js.map 3817
polyfills.bundle.js 169209
polyfills.bundle.js.map 204535
styles.bundle.js 10039
styles.bundle.js.map 13372
vendor.bundle.js 2884505
vendor.bundle.js.map 3081499
For production purposes you will want using the following command:
ng build -prod
ng build --target=production
File Size
favicon.ico 5430
inline.d72284a6a83444350a39.bundle.js 1460
main.e088c8ce83e51568eb21.bundle.js 12163
polyfills.f52c146b4f7d1751829e.bundle.js 58138
styles.d41d8cd98f00b204e980.bundle.css 0
vendor.a2da17b9c49cdce7678a.bundle.js 362975
Please note that styles bundle will be empty because by default newly generated app has
src/styles.css file empty.
The ng tool removes dist folder between the builds so you should not worry about files left from
previous builds and modes.
Angular CLI 81
The content of the dist folder is everything you need to deploy your application to the remote
server. You can also use any web server of your choice to run the application in production.
For example:
• Nginx server
• Tomcat server
• IIS server
• and many others
In addition, you can deploy your application to any static pages host, like:
• GitHub pages21
• Netlify22
• and many others
It is still possible to use Angular CLI and embedded development server to test production builds.
You can use the following command to build the app in production mode and then run and open
default browser to check it:
Using blueprints
Besides generating new application project structure, the ng tool supports creating core Angular
building blocks be means of generate (or g) command and several blueprints.
For the time being Angular CLI supports the following set of blueprints out-of-box:
21
https://pages.github.com/
22
https://www.netlify.com/
Angular CLI 82
Let’s assume you have generated a new my-first-app like suggested below:
ng new my-first-app
cd my-first-app
Now to create a new component, you should be using the following command:
ng g component my-first-component
The ng tool takes your current directory and creates all component related files:
installing component
create src/app/my-first-component/my-first-component.component.css
create src/app/my-first-component/my-first-component.component.html
create src/app/my-first-component/my-first-component.component.spec.ts
create src/app/my-first-component/my-first-component.component.ts
update src/app/app.module.ts
If you are running ng generate command (or ng g by alias) from the root of your project, the CLI
should automatically put content to src/app/<feature> folder like shown above.
You can also specify additional folder structure that should become relative to src/app during
generation. All missing directories get created automatically. Run the following command from
the root project folder:
ng g service services/simple-service
The ng tool creates src/app/services path and puts simple-service implementation there:
Angular CLI 83
installing service
create src/app/services/simple-service.service.spec.ts
create src/app/services/simple-service.service.ts
WARNING Service is generated but not provided, it must be provided to be used
Finally, you can change current directory and generate Angular artefacts there:
mkdir src/app/directives
cd src/app/directives/
ng g directive my-first-directive
installing directive
create src/app/directives/my-first-directive.directive.spec.ts
create src/app/directives/my-first-directive.directive.ts
update src/app/app.module.ts
All blueprints follow common Angular code style guides. You get a separate folder and all files one
should expect when starting with a new Angular component:
One of the best features of Angular CLI is that you do not get just placeholder files but a valid ready
to use artefact and unit tests. Let’s peek inside MyFirstComponent component we have created earlier,
and try integrating into the application.
my-first-component.component.ts
10 constructor() { }
11
12 ngOnInit() {
13 }
14
15 }
You get app-my-first-component selector, external template and style, together with a constructor
and OnInit placeholder to save your time.
The stylesheet file (my-first-component.component.css) is empty by default, and template file
(my-first-component.component.html) contains the following simple layout:
my-first-component.component.html
1 <p>
2 my-first-component works!
3 </p>
The CLI will even update src/app/app.module.ts to include your newly created component into
the application module:
src/app/app.module.ts
20 providers: [],
21 bootstrap: [AppComponent]
22 })
23 export class AppModule { }
To test the generated component you can use main application template:
src/app/app.component.html
1 <h1>
2 {{title}}
3 </h1>
4
5 <app-my-first-component>
6 </app-my-first-component>
ng serve
As a starting point you also get a simple ready-to-run unit test for your component:
Angular CLI 86
my-first-component.component.spec.ts
As mentioned earlier you can run unit tests in watch mode with the help of ng test command:
Angular CLI 87
Component Test
If you have added your component to the app.component.html template, some unit tests may fail.
To fix them you should update test configuration in app.component.spec.ts and include your
component into the test module:
app.component.spec.ts
Creating modules
The Angular CLI tool also provides support for multiple modules and generating entities that belong
to the particular module.
Let’s start by generating a new module using the next command:
ng g module my-components
And the content of the module contains a basic implementation like in the example below:
src/app/my-components/my-components.module.ts
Note that by default Angular creates a folder for your module, similar to what it does for components.
This is handy once you create components, services, directives and pipes that need to belong to
this module. But if you want to put the resulting module in a single file next to the “app.module.ts”
use the “–flat” switch.
You can check more details on the available switches by running the “ng g module –help” command.
Angular CLI 89
ng g component my-button-1
To include your new component into a particular module use the “–module” switch. If you are
building a shared module, you might also use the “–export” switch, so that module exports your
component besides declaration.
And the module content now looks like in the code example below:
src/app/my-components.module.ts
1 import { NgModule } from '@angular/core';
2 import { CommonModule } from '@angular/common';
3 import { MyButton1Component } from './my-button-1/my-button-1.component';
4
5 @NgModule({
6 imports: [
7 CommonModule
8 ],
9 declarations: [MyButton1Component],
10 exports: [MyButton1Component]
11 })
12 export class MyComponentsModule { }
Angular CLI 90
Do not forget to check the “ng g component –help” to see all available options.
You can also include your new module into some other existing module from the command line.
src/app/my-components.module.ts
1 @NgModule({
2 imports: [
3 CommonModule,
4 MyFeatureModule
5 ],
6 declarations: [MyButton1Component],
7 exports: [MyButton1Component]
8 })
9 export class MyComponentsModule { }
Routing support
If you plan working with Angular Router or want to experiment with routing capabilities, the
Angular CLI can generate an application for you with initial Router support.
Use the “–routing” switch if you want to generate a routing module scaffold with your application.
src/app/app-routing.module.ts
1 import { NgModule } from '@angular/core';
2 import { Routes, RouterModule } from '@angular/router';
3
4 const routes: Routes = [];
5
6 @NgModule({
7 imports: [RouterModule.forRoot(routes)],
8 exports: [RouterModule]
9 })
10 export class AppRoutingModule { }
In addition, the main application component is going to contain the router outlet component:
Angular CLI 91
src/app/app.component.html
1 ...
2 <router-outlet></router-outlet>
ng eject
• update package.json with all dependencies needed to compile project without extra tools
• generate and output the proper webpack configuration (webpack.config.js) and scripts for
testing
The tool might provide additional notes in the console output like below:
==============================================================
Ejection was successful.
==============================================================
Some packages were added. Please run "npm install".
Now scripts section in your package.json file should link to local content for a start, build and
various test scripts:
Angular CLI 92
package.json
1 {
2 ...
3 "scripts": {
4 "ng": "ng",
5 "start": "webpack-dev-server --port=4200",
6 "build": "webpack",
7 "test": "karma start ./karma.conf.js",
8 "lint": "ng lint",
9 "e2e": "protractor ./protractor.conf.js",
10 "prepree2e": "npm start",
11 "pree2e": "webdriver-manager update --standalone false --gecko false --q\
12 uiet"
13 },
14 ...
15 }
Components
Components are the main building blocks in Angular.
A typical Angular application is represented by a tree of elements starting with a single root one.
1 <app-root>
2
3 <app-header title="My header">
4 ...
5 </app-header>
6
7 <app-layout type="horizontal">
8
9 <app-sidebar>
10 ...
11 </app-sidebar>
12
13 <app-content>
14 ...
15 </app-content>
16
17 </app-layout>
18
19 <app-footer>
20 ...
21 </app-footer>
22
23 </app-root>
As you can see from the markup above, our main application template consists at least of the
following six components:
• app-root
• app-header
• app-layout
• app-sidebar
• app-content
Components 94
• app-footer
Angular CLI
You can find detailed information on setting up project scaffolds in the “Angular CLI”
chapter.
You start creating a component with importing the @Component decorator from the @angular/core:
The @Component decorator supports multiple properties, and we are going review them in the
Component metadata section later in this chapter. For the bare minimum, you need to set the
“selector” and “template” values to create a basic reusable component.
Our minimal “Header” component implementation can look in practice like in the following
example.
src/app/components/header.component.ts
You set the selector value to ‘app-header’. That means you are registering a new HTML element
called “<app-header>”.
You also set a “template” property that holds the inline HTML template string. At run time the
Header element is to be rendered as a <div> element with its inner text bound to the “title” property
of the “HeaderComponent” class.
Note that before using Header component within an application, we need to register it within the
main application module.
Application module
You are going to get more detailed information on Angular modules in a separate Modules
chapter.
For now, it is good for you to have just a basic understanding of how components get
registered within modules.
Below you can see an example of how typically register a component. For the sake of simplicity, we
are going to see only newly added content.
Components 96
src/app/app.component.ts
1 ...
2 import { HeaderComponent } from './components/header.component';
3
4 @NgModule({
5 declarations: [
6 ...
7 HeaderComponent
8 ],
9 ...
10 })
11 export class AppModule { }
Finally, to test your component just put the following content into the main application template
HTML file.
src/app/app.component.template
1 <app-header></app-header>
Once you compile and run your web application, you should see the following content on the main
page.
Header
Congratulations, you have just created a basic Angular component that you can now reuse across
all your web application.
ng g component components/footer
You should instantly notice how Angular CLI saves your time. It creates you a full set of files for
your Footer component and event modifies the main application module file for you.
You can check the console output below:
Components 97
installing component
create src/app/components/footer/footer.component.css
create src/app/components/footer/footer.component.html
create src/app/components/footer/footer.component.spec.ts
create src/app/components/footer/footer.component.ts
update src/app/app.module.ts
Angular CLI
You can find detailed information on blueprints and content generation in the “Angular
CLI” chapter.
As a result, you get an initial component implementation with an external HTML and CSS templates
and even a unit test scaffold.
src/app/components/footer/footer.component.ts
Finally, you can update your main application template to see both header and footer elements in
action:
src/app/app.component.html
1 <app-header></app-header>
2 <app-footer></app-footer>
Upon compiling the application and reloading the page, you should see the following output.
Components 98
Header
footer works!
Note how Angular CLI provides a content of the automatically generated Footer element.
So as you can from the examples above, you save an enormous amount of time when using Angular
CLI when it comes to scaffold generation.
Component metadata
According to the official documentation, you can use the following set of properties with a
@Component decorator:
The @Component decorator extends the @Directive one, so it also inherits the following set of
properties you can use as well:
Components 99
Templates
There are two ways to assign a component view template: inline-defined and external file.
Inline templates
You specify inline template by setting the “template” field value of the @Component decorator. To get
better formatting and multi-line support, you can use template literals feature introduced in ES6 and
backed by TypeScript out of the box.
Template literals
Template literals are string literals allowing embedded expressions. You can use multi-line
strings and string interpolation features with them.
For more detailed information on this ES6 feature, please refer to the following Template
literals23 article.
Most of the modern IDEs already have support for mixed content, including TypeScript files. If
you are using Visual Studio Code24 for development, then you already have syntax highlighting for
HTML and CSS.
Let’s edit the Header component template to take multiple lines like in the example below:
23
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals
24
https://code.visualstudio.com/
Components 100
src/app/components/header.component.ts
1 @Component({
2 selector: 'app-header',
3 template: `
4 <div>
5 <div>{{ title }}</div>
6 </div>
7 `
8 })
9 export class HeaderComponent {
10 title: string = 'Header';
11 }
Using backtick characters also allows you to have single and double quotes in HTML without any
additional escaping or string concatenation. You are using the same HTML content inlined as you
would have in separate files.
Typically you may want to use inline templates only when your component view is small.
External templates
The HTML code in the templates usually grows over time and becomes less maintainable. That is
why storing HTML in the separate files may be more practical and productive.
The @Component decorator provides support for external templates through the “templateUrl” option.
Please note that you should set only “template” or “templateUrl”, you cannot define both of them at
the same time.
Let’s now update our Header component we created earlier and move its HTML template to a
separate file.
src/app/components/header.component.html
1 <div>
2 <div>{{ title }}</div>
3 </div>
The “templateUrl” property should always point to a path relative to the component class file. In our
case, we put both files together in the same directory and update decorator declaration accordingly.
Components 101
src/app/components/header.component.ts
1 @Component({
2 selector: 'app-header',
3 templateUrl: './header.component.html'
4 })
5 export class HeaderComponent {
6 title: string = 'Header';
7 }
Typically developers put them next to the component class implementation and give the same name
for the file as the component:
• header.component.html
• header.component.ts
External files
External files are the most convenient and recommended way of storing your components’
HTML templates.
Styles
Similar to the HTML templates, the @Component decorator also provides support for CSS styles in
either inline or external form.
Inline styles
If your component is small and you decided to use inline HTML templates then high chances you
may want to inline CSS styles as well.
The “styles” property of the @Component decorator is used to store an array of CSS styles that Angular
applies to the component at runtime.
To test that in practice let’s add a couple of class names to the HTM elements inside the component’s
template.
Components 102
src/app/components/header.component.ts
1 @Component({
2 selector: 'app-header',
3 template: `
4 <div class="app-header">
5 <div class="app-header-title">
6 {{ title }}
7 </div>
8 </div>
9 `
10 })
11 export class HeaderComponent { ... }
We just added “app-header” and “app-header-title” classes, and it is time to provide some CSS for
those elements.
src/app/components/header.component.ts
1 @Component({
2 selector: 'app-header',
3 template: `
4 <div class="app-header">
5 <div class="app-header-title">
6 {{ title }}
7 </div>
8 </div>
9 `,
10 styles: [`
11 .app-header {
12 border: 1px solid gray;
13 padding: 4px;
14 }
15 .app-header-title {
16 font-weight: bold;
17 }
18 `]
19 })
20 export class HeaderComponent { ... }
Now if you compile and run your web application you should see a nice grey border around your
Header component. Also, the “Header” title should have the bold font style.
Components 103
As with the inline templates, you may want to inline your style files only for the small components.
External styles
The CSS styles tend to grow over time even faster than HTML content. So it is recommended to
move them to the separate files at early stages.
Similar to external HTML templates Angular provides support for external CSS. You can use
“styleUrls” property to provide an array of the URLs with relative paths to corresponding files.
You already know how to use external HTML templates with your components. Let’s now extract
the CSS into a separate file as well. Typically the main style file is called after the parent component
class, in our case, it becomes “header.component.css”.
src/app/components/header.component.css
1 .app-header {
2 border: 1px solid gray;
3 padding: 4px;
4 }
5
6 .app-header-title {
7 font-weight: bold;
8 }
You should now have “Header” component split into the three separate files:
• header.component.css
• header.component.html
• header.component.ts
With the changes to @Component decorator properties, the component implementation should now
look like the following:
Components 104
src/app/components/header.component.ts
1 @Component({
2 selector: 'app-header',
3 templateUrl: './header.component.html',
4 styleUrls: [
5 './header.component.css'
6 ]
7 })
8 export class HeaderComponent { ... }
Visually the component should look the same as with the inline CSS styles. Once you compile and
run your web application, the main page looks like on the screenshot below.
External files
External files are the most convenient and recommended way of storing your components’
CSS stylesheets.
Input properties
The @Input decorator is used to mark a class property that binds to incoming data.
Let’s take a closer look at our Header component created earlier in this chapter.
The component exposes a “title” property of the “string” type and with the default value set to
“Header”.
1 @Component({...})
2 export class HeaderComponent {
3 title: string = 'Header';
4 }
We have also provided an HTML template that binds to the underlying “title” property.
Components 105
1 <div class="app-header">
2 <div class="app-header-title">{{ title }}</div>
3 </div>
As you can notice from the example above the major limitation of the Header component is that we
cannot change the header title text from the outside.
The only way we can use it so far is by utilising plain selector:
{{linenos=off}} html <app-header></app-header> <app-footer></app-footer>
Next, let’s provide data-binding support for the “title” property using @Input decorator as following:
src/app/components/header.component.ts
You can now use Header component with custom title values. Set the value to “My Header” to test
that in action.
This time once your web application compiles and starts the Header should look like on the picture
below:
You can also bind “title” property value to another property. For example, imagine a scenario when
main application component maintains global settings and initializes Header and Footer content.
Components 106
src/app/app.component.ts
1 @Component({ ... })
2 export class AppComponent {
3 title = 'app';
4 }
In this case, you can bind the Header title like following:
<app-header [title]="title"></app-header>
<app-footer></app-footer>
Reload the page, and you are going to see the header having now the “app” title as per main
application component implementation.
Binding to expressions
Keep in mind that you are binding to JavaScript expressions when using square brackets
with element attributes.
The example above can also be changed to bind to a string or component class method. For
example <app-header [title]="getHeaderTitle()"> or <app-header [title]="'Hello
' + 'world'">.
By default, the @Input takes the name of the decorated property when setting up the bindings. You
can, however, change the name of the resulting HTML attribute.
@Input decorator accepts additional optional property alias “bindingPropertyName” to redefine the
name to use in the template. Let’s change the binding name of the “title” property to the “title-text”
value.
From now on you should be settings “title-text” attribute in HTML templates when using Header
component with the custom title.
Components 107
Please note that you are going to deal with two different property names at the same time when
using input aliases. The component template still references property by the original name “title”
while externally this property is known as “title-text”:
<div class="app-header">
<div class="app-header-title">{{ title }}</div>
</div>
Output events
You use @Output decorator in combination with the EventEmitter type to create component events.
To better understand how events are used and work let’s introduce and see a component event in
action.
Raising events
We are going to extend our Header component with a “click” event. Every time the header title gets
clicked the component is going to raise a “titleClick” event.
25
https://angular.io/guide/styleguide#style-05-13
Components 108
src/app/components/header.component.ts
1 import { ..., Output, EventEmitter } from '@angular/core';
2
3 @Component({...})
4 export class HeaderComponent {
5 ...
6
7 @Output()
8 titleClick = new EventEmitter();
9 }
Now to raise the newly introduced event from the component, we call its “next” method:
this.titleClick.next();
Next, we need to wire component template with the underlying method “handleTitleClick”. The
latter is going to be responsible for raising the corresponding “titleClick” event.
src/app/components/header.component.html
1 <div class="app-header">
2 <div class="app-header-title" (click)="handleTitleClick()">{{ title }}</div>
3 </div>
src/app/components/header.component.ts
1 @Component({...})
2 export class HeaderComponent {
3 ...
4
5 @Output()
6 titleClick = new EventEmitter();
7
8 handleTitleClick() {
9 this.titleClick.next();
10 }
11 }
Handling events
We have extended our Header component to raise an event once a user clicks the title. Now let’s
make our main application handle this event and display click counter on the page.
Components 109
src/app/app.component.html
1 <app-header
2 [title]="title"
3 (titleClick)="onTitleClicked()">
4 </app-header>
5 <div>
6 Header clicks: {{ headerClicks }}
7 </div>
8 <app-footer></app-footer>
As you can see from the code above the main application component subscribes to the “titleClick”
event and uses “onTitleClicked” method as an event handler.
The component also displays the “headerClicks” property value for us to see the event handler works.
The final thing we need right now is incrementing the counter property like in the code below:
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3
4 headerClicks = 0;
5
6 onTitleClicked() {
7 this.headerClicks += 1;
8 }
9
10 }
Now if you run the web application and click several times on the header text, you should see the
clicks counter increase in real time.
Typed events
The EventEmitter type we used for earlier is a generic type, and by default takes the type variable
of “any”.
Components 110
EventEmitter<any>
In many cases, you may want to provide the additional event arguments to enable better handling
of your component events. For example, a “click” event may expose details on the mouse cursor
position or a “textChanged” event that exposes old and new text values.
In the previous section, we have already created a Header component that raises generic “titleClick”
event with no arguments. To compare both approaches let’s now update Footer component with the
similar event but of a specific type.
The Footer is going to count the number of clicks itself and provide the value as part of the event.
The main application is no longer required to keep track on clicks as it is going to get exact values
from the corresponding event arguments.
If you remember, we created a Footer component using the following Angular CLI command:
ng g component components/footer
First, create a “FooterClickedEvent” class to hold the clicks-related information for our “titleClicked”
event:
src/app/components/footer/footer-clicked.event.ts
For the sake of simplicity, we are going to create a class with a single read-only property “totalClicks”
we assign in the constructor, and that defaults to zero if not provided.
Next, edit your footer component and update its code with the following pieces that add a “title”
input property and “titleClicked” output event:
Components 111
src/app/components/footer/footer.component.ts
As you can see above, we also declare a private property “totalClicks” to hold the overall clicks
count.
Note how we use EventEmitter type this time. Using “FooterClickedEvent” as an EventEmitter’s
type variable allows us now to create an instance of the given type and emit as an event.
src/app/components/footer/footer.component.ts
1 @Component({...})
2 export class FooterComponent {
3 ...
4
5 private totalClicks = 0;
6
7 handleTitleClick() {
8 const event = new FooterClickedEvent(++this.totalClicks)
9 this.titleClick.next(event);
10 }
11
12 }
Now we can update the component template to display the title and handle mouse clicks:
Components 112
src/app/components/footer/footer.component.html
1 <p>
2 <span (click)="handleTitleClick()">{{ title }}</span>
3 </p>
Every time user clicks the “title” element of the Footer, the component is going to increment clicks
counter and raise an event with its actual value.
<app-footer
title="My footer"
(titleClick)="onHeaderClicked($event)">
</app-footer>
In our current case, we handle “titleClick” event and pass original “FooterClickedEvent” to the
“onHeaderClicked” handler inside application controller. That provides access to “totalClicks”
property we created earlier.
DOM events
Please keep in mind that “$event” usage applies to all events, either custom or standard
DOM ones. For instance, you can inspect “click”, “hover”, “input” and many other DOM
events from within your class methods.
Let’s now update our main application component to display the number of times the user clicked
the Footer.
Components 113
src/app/app.component.ts
1 ...
2 import { FooterClickedEvent } from './components/footer/footer-clicked.event';
3
4 @Component({...})
5 export class AppComponent {
6 ...
7 footerClicks = 0;
8
9 onHeaderClicked(event: FooterClickedEvent) {
10 this.footerClicks = event.totalClicks;
11 }
12 }
As you can see in the example above, we now can import “FooterClickedEvent” type and use with
the event handler parameters to get type checking and code completion support in your IDE.
Finally, let’s update the main component template to display click counters for the Footer alongside
the Header.
src/app/app.component.html
1 <app-header
2 [title]="title"
3 (titleClick)="onTitleClicked()">
4 </app-header>
5
6 <div>Header clicks: {{ headerClicks }}</div>
7 <div>Footer clicks: {{ footerClicks }}</div>
8
9 <app-footer
10 title="My footer"
11 (titleClick)="onHeaderClicked($event)">
12 </app-footer>
You can now run your web application and make several clicks on Header and Footer components
to see all event handlers in action. You should see results similar to the following:
Components 114
Aliased outputs
Similar to the @Input decorator the @Output one also supports custom aliases for event names and
takes the name of the decorated property as the default value.
In the previous examples, we used “titleClick” for the output property name:
src/app/components/footer/footer.component.ts
1 @Component({...})
2 export class FooterComponent {
3 ...
4
5 @Output()
6 titleClick = new EventEmitter<FooterClickedEvent>();
7 }
You could also provide the “title-click” alias for the event like below:
src/app/components/footer/footer.component.ts
1 @Component({...})
2 export class FooterComponent {
3 ...
4
5 @Output('title-click')
6 titleClick = new EventEmitter<FooterClickedEvent>();
7 }
In this case the “official” (or public) event name for the Footer’s “titleClick” component would be
“title-click”, and not “titleClick”:
<app-footer
title="My footer"
(title-click)="onHeaderClicked($event)">
</app-footer>
26
https://angular.io/guide/styleguide#style-05-13
Components 115
Providers
Every Angular component can declare its own set of providers. The use of local providers allows
developers to replace global instances of services, and register and use a new copy of the service for
the given component and all child components.
Let’s see how local component providers work in practice. We are going to need a new service
‘ClickCounterService’ that you can generate with the following Angular CLI command:
ng g service click-counter
The service is going to keep track of the user clicks on the elements. Our components should notify
the service upon every click, and also subscribe to the service events to get notifications on clicks
from other components.
Add the “clicks” property to the service to hold total amount of clicks happened across the
application. Then add the “clicked” event to allow components to subscribe and perform custom
actions if needed. Finally, implement the “click” method that increments the click counter and emits
the corresponding event at the same time.
Let’s register the newly created “ClickCounterService” service as part of the global providers, in the
main application module.
Registering service
Given that developers can register services in different places, the Angular CLI does not
perform default registration and does not modify “app.module.ts” file like it does for other
Angular entities.
Components 116
src/app/app.module.ts
1 ...
2 import { ClickCounterService } from './click-counter.service';
3
4 @NgModule({
5 ...
6 providers: [
7 ClickCounterService
8 ],
9 ...
10 })
11 export class AppModule { }
For the next step, we are going to need three simple components. You can quickly generate them
using the following Angular CLI commands:
ng g component componentA
ng g component componentB
ng g component componentC
Now replace the content of the main application component template with the following code:
src/app/app.component.html
1 <app-component-a></app-component-a>
2 <app-component-b></app-component-b>
3 <app-component-c></app-component-c>
Once you build and run the application, you should see the following content on the main page:
Now, let’s integrate one of the components with the “ClickCounterService” service we introduced
earlier. Our component is going to have an HTML button that invokes “onClick” method upon every
click. The component also subscribes to the service’s “clicked” event to update the local “totalClicks”
property and display it to the user.
Components 117
src/app/component-a/component-a.component.ts
Also, replace the component template content with the following markup:
src/app/component-a/component-a.component.html
1 Component A <br>
2 Clicks: {{ totalClicks }} <br>
3 <button (click)="onClick()">Click</button>
Run the application or switch to the running one. Click the component button multiple times to see
the counter updates.
Components 118
Repeat the same procedure for other two components we got. All three components should display
the total number of clicks fetched from the server, and have a button for the user to click.
Also, let’s slightly improve the main application template and add dividers for better visibility:
src/app/app.component.html
1 <app-component-a></app-component-a>
2 <hr>
3 <app-component-b></app-component-b>
4 <hr>
5 <app-component-c></app-component-c>
Switch to your running application and try clicking one of the buttons several times. You should see
that all click counters get updated automatically with the same value. That is an expected behaviour
because all we got three components powered by the same instance of the “ClickCounterService”
service. Every time we click a button, the service notifies other components that update local counter
properties and display labels.
Now, let’s see what happens if one of the components, let it be Component B, declares its own
“providers” collection. Import the “ClickCounterService” and declare it as in the example below:
Components 119
src/app/component-b.component.ts
1 ...
2 import { ClickCounterService } from '../click-counter.service';
3
4 @Component({
5 selector: 'app-component-b',
6 templateUrl: './component-b.component.html',
7 styleUrls: ['./component-b.component.css'],
8 providers: [
9 ClickCounterService
10 ]
11 })
12 export class ComponentBComponent implements OnInit {
13 ...
14 }
If now you start clicking on the first component, only “Component A” and “Component C” are going
to update the labels. The “Component B” should remain with the zero value.
As you can see, the “Component B” declares its local instance of the service, so it does not react
to the events raised by the global one. Now if you click the “Component B” button several times,
its counter label should update separately from other components. Moreover, other components are
not going to update on “Component B” clicks, as they are listening to the global service events.
Components 120
The component-level provider is a great feature. It allows you to have more than one instance of the
service or to have custom service implementation or replacement for a particular component and
all child components that you use in its template.
Advanced feature
You should be very careful and use this feature only when it is necessary as it is quite
easy to introduce an issue when creating multiple instances of the same service type. For
example the Authentication Service. Typically you may want always to have only one
instance, as the service might keep the authentication state or some other critical data, and
having more than one service leads to application issues.
Source code
You can find the source code in the “angular/components/component-providers27 ” folder.
Host
The host property is an object of a Map type and specifies the events, actions, properties and
attributes related to the resulting element.
Use the following Angular CLI command to generate a new “host-events” component for the tests:
ng g component host-events
Also, replace the content of the main application component template with your component
declaration:
27
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/component-providers
Components 121
src/app/app.component.html
1 <h2>Host events:</h2>
2 <app-host-events></app-host-events>
Run the application and ensure the main page looks similar to the following:
CSS class
Your component or directive can assign a class name to the corresponding DOM element that serves
the root of the component. For example setting the class name to the “host-events” value looks like
the following:
src/app/host-events/host-events.component.ts
1 @Component({
2 selector: 'app-host-events',
3 templateUrl: './host-events.component.html',
4 styleUrls: ['./host-events.component.css'],
5 host: {
6 class: 'host-events'
7 }
8 })
9 export class HostEventsComponent implements OnInit {
10 ...
11 }
At runtime, if you use the “Inspect element” tool to view the compiled HTML output, it should look
similar to the following:
Components 122
Note that the main “app-host-events” element has now the “host-events” class name associated with
it.
You can also set multiple classes based on property values using the following format:
host: {
'[class.<className1>]': '<statement1>',
'[class.<classNameN>]': '<statementN>'
}
Let’s bind a couple of CSS class names to the component class properties:
src/app/host-events/host-events.component.ts
1 @Component({
2 selector: 'app-host-events',
3 templateUrl: './host-events.component.html',
4 styleUrls: ['./host-events.component.css'],
5 host: {
6 '[class.is-invalid]': 'isInvalid',
7 '[class.is-readonly]': 'isReadonly'
8 }
9 })
10 export class HostEventsComponent implements OnInit {
11 ...
12
13 isInvalid: boolean = false;
14 isReadonly: boolean = false;
15 }
The class values are toggled based on the corresponding property values.
For example, the “is-invalid” is appended to the class list of the DOM element as soon as ‘isInvalid’
property turns to “true”, also, is automatically removed from the class list if it has the value of “false”.
Host events
The component or directive you create can also use “host” metadata property to bind element events
to the class methods. The usage format, in this case, is as follows:
Components 123
host: {
'(<event>)': '<statement>'
}
To test the event bindings in action let’s wire the “mouseenter” and “mouseleave” DOM events with
the “onMouseEnter” and “onMouseLeave” methods that are going to change the “color” property
value:
src/app/host-events/host-events.component.ts
Finally, update the component template to use the “color” property like in the example below:
Components 124
src/app/host-events/host-events.component.html
1 <p [style.color]="color">
2 host-events works!
3 </p>
Now switch to the running application and try moving the mouse cursor over the component text.
You should see the colour of the text changing automatically upon mouse events.
Your component can also listen to global events using the following format:
host: {
'(<target>:<event>)': '<statement>'
}
• window
• document
• body
For example, the component can listen to ‘window.resize’ events and adapt the layout accordingly:
src/app/host-events/host-events.component.ts
1 ...
2
3 @Component({
4 ...
5 host: {
6 class: 'host-events',
7 '(mouseenter)': 'onMouseEnter()',
8 '(mouseleave)': 'onMouseLeave()',
9 '(window:resize)': 'onWindowResize()'
10 }
11 })
Components 125
If you run the developer tools in your browser and try resizing the browser window, you should
notice the component reacts on that and writes corresponding messages to the console log.
Host attributes
The Angular framework allows you to map multiple HTML attributes to the class properties or static
string values. Let’s set the “role” and “aria-label” attributes from within the host metadata:
src/app/host-events/host-events.component.ts
1 ...
2
3 @Component({
4 ...
5 host: {
6 class: 'host-events',
7 '(mouseenter)': 'onMouseEnter()',
8 '(mouseleave)': 'onMouseLeave()',
9 '(window:resize)': 'onWindowResize()',
10 'role': 'button',
11 'aria-label': 'Demo button'
12 }
13 })
14 export class HostEventsComponent {
15 ...
16 }
As soon as the page gets rendered, you can inspect the compiled HTML layout to check the element
attributes. The “app-host-events” element now contains the expected attributes:
Components 126
Host properties
Finally, you can bind component properties to element properties. That might be useful when
developing Angular directives.
For example, a directive that automatically changes the button value based on one of its properties
can look like the following:
src/app/host-properties.directive.ts
Now, let’s define a simple button and see the directive in action:
src/app/app.component.html
1 <h2>Host events:</h2>
2 <app-host-events></app-host-events>
3
4 <hr>
5 <button appHostProperties>Click me</button>
Note that we provided the button text in the HTML template. Switch back to the browser window
and ensure the button value got changed by the directive as per the host metadata settings.
Components 127
Similar to the CSS classes, you can provide values for multiple attributes of the decorated DOM
element using the following syntax:
host: {
'[attr.<name1>]': '<statement1>',
'[attr.<nameN>]': '<statementN>',
}
Let’s bind a “custom1” attribute to the “attrValue” property of the class to check how that feature
works.
If you remember, we got a button element declared in the main application template like below:
At the runtime, the element gets a custom attribute from the directive:
Components 128
Source code
You can find the source code in the “angular/components/component-host28 ” folder.
Queries
There may be scenarios when you need accessing child components from your current component
that contains them. That becomes useful when you need calling public methods or change properties
of the children.
Source code
You can find the source code in the “angular/components/component-queries29 ” folder.
ng g component list
ng g component list-item
Extend the generated List component with an extra property “title” marked with the @Input
decorator.
28
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/component-host
29
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/component-queries
Components 129
src/app/list/list.component.ts
Next, update the component HTML template to display the “title” value, and also the “ng-content”
component to render any other components or HTML elements the end developers provide:
src/app/list/list.component.html
Now you can declare a List element in the main application component template, and also put several
ListItem components inside its tags:
src/app/app.component.html
1 <app-list>
2 <app-list-item></app-list-item>
3 <app-list-item></app-list-item>
4 <app-list-item></app-list-item>
5 </app-list>
At runtime, the code above should give you the following output on the main page:
Components 130
@ViewChild
The @ViewChild decorator allows you to retrieve and reference the component or directive from the
current component View.
For example, the main application component can gain access to the List component we have defined
in its HTML template, and modify properties of the component instance from the code. To do that
we need to use a @ViewChild decorator with the target type. You can access the property decorated
with the @ViewChild only after component’s View initialises. The “AfterViewInit” interface and
corresponding method is the most appropriate place for that.
src/app/app.component.ts
In the code snippet above, we change the title of the child List component from code. Switch to the
browser running your application, and you should see the following:
The component query is not limited to the target type reference. You can also use local references
and use string identifiers, for example, mark the List with the “myList” id:
Components 131
src/app/app.component.html
1 <app-list #myList>
2 ...
3 </app-list>
Now you can use this id with the “@ViewChild” decorator if needed:
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent implements AfterViewInit {
3
4 @ViewChild('myList')
5 list: ListComponent;
6
7 ...
8 }
@ViewChildren
If you declare more than one List component, you should notice that every time only the first
instance is fetched. To get a reference to all the child instances of the particular component type
you need to use a @ViewChildren decorator.
Let’s have two List components separated by a horizontal line like in the example below:
src/app/app.component.html
1 <app-list>
2 <app-list-item></app-list-item>
3 <app-list-item></app-list-item>
4 <app-list-item></app-list-item>
5 </app-list>
6
7 <hr>
8
9 <app-list>
10 <app-list-item></app-list-item>
11 <app-list-item></app-list-item>
12 <app-list-item></app-list-item>
13 </app-list>
Now create a “lists” property in the underlying component class to reference all “ListComponent”
instances after View gets initialised:
Components 132
src/app/app.component.ts
For the next step, we are going to update “title” property of every List component in the View:
src/app/app.component.ts
Switch back to the browser and once the application compiles and restarts you should see the
following:
Components 133
ViewChildren Queries
@ContentChild
The @ViewChild provides access only to components and directives that are part of the view but not
inside the “ng-content” tags. You need to use @ContentChild decorator to work with the elements
inside “ng-content” container.
If you remember, the List component template already features the “ng-template”:
src/app/list/list.component.html
1 <div>{{ title }} </div>
2 <ng-content></ng-content>
Update the ListItemComponent component class with the “title” property like in the following
example:
src/app/list-item/list-item.component.ts
1 ...
2
3 @Component({...})
4 export class ListItemComponent {
5
6 title = 'list-item works!';
7
8 }
For the sake of simplicity just replace the content of the ListItem component template with the next
block of HTML:
Components 134
src/app/list-item/list-item.component.html
1 <p>
2 {{ title }}
3 </p>
For a start, let’s access the very first entry of the List content collection by introducing a
“firstListItem” property decorated with the @ContentChild. As soon as component content gets
initialised, we are going to update the title of the referenced item.
src/app/list/list.component.ts
Note that your component now needs to implement the “AfterContentInit” interface and have the
corresponding “ngAfterContentInit” method implementation. That is the most recommended place
to work with the elements provided using the @ContentChild decorator.
Switch to the browser, and you should now look the following on the main page:
Components 135
ContentChild Query
@ContentChildren
Similar to the @ViewChild, the @ContentChild decorator always returns the first found element if
there are more than one declared in the Component View. You are going to need a @ContentChildren
decorator if you intend working with all the instances.
src/app/list/list.component.ts
The example above should already be familiar to you. We have just updated every item in the list
by changing its title. The main page in the browser should be looking similar to the following one:
Components 136
ContentChildren Query
1 <app-list>
2 <app-list-item *ngIf="showFirstItem"></app-list-item>
3 <app-list-item></app-list-item>
4 <app-list-item></app-list-item>
5 </app-list>
As we already know, based on the “ngIf state”, the Angular will remove a corresponding element
from the DOM, or add it back. There are many scenarios, however, when your component controller
needs to know about such changes. For example, imagine a “DataTable” component that uses child
components to define column structure, but then turns developer-defined view or content elements
to some internal representation. The component must always know what is the “visible” part of the
layout to work with.
Let’s now extend our previous “ViewChildren” example with an extra flag to toggle visibility of the
first list entry. We will add a “showFirstItem” property, and a button that changes the property value
on each click.
Components 137
src/app/app.component.ts
src/app/app.component.html
1 <hr>
2
3 <button (click)="showFirstItem = !showFirstItem">
4 Toggle first item
5 </button>
We have declared two List components in the previous examples. Let’s now wire the first entry of
each of the components with the “showFirstItem” condition like in the example below:
src/app/app.component.html
1 <app-list>
2 <app-list-item *ngIf="showFirstItem"></app-list-item>
3 <app-list-item></app-list-item>
4 <app-list-item></app-list-item>
5 </app-list>
6
7 <hr>
8
9 <app-list>
10 <app-list-item *ngIf="showFirstItem"></app-list-item>
11 <app-list-item></app-list-item>
12 <app-list-item></app-list-item>
13 </app-list>
14
15 <hr>
16
17 <button (click)="showFirstItem = !showFirstItem">
18 Toggle first item
19 </button>
Components 138
If you run the application now, you will see every first item appear and disappear from the list each
time you click the “Toggle first item” buttons. We have just emulated the situation when layout
changes based on the external criteria. But how does the component now react on those changes?
The “QueryList” class exposes a special “changes” property of the “Observable<any>” type that can
help us watch for the layout changes and achieve the desired behaviour.
Now you can update the ListComponent implementation and add a simple change tracking code
like in the following example:
src/app/list/list.component.ts
1 @Component({...})
2 export class ListComponent implements AfterContentInit {
3
4 ...
5
6 ngAfterContentInit() {
7 ...
8
9 this.listItems.changes.subscribe(() => {
10 console.log(
11 `List content changed and has now ${this.listItems.length} items.`
12 );
13 });
14 }
15 }
Please run the application once again and click the “Toggle first item” button a few times. Alongside
the element being added to and removed from the page, you will see the following console output:
We have two instances of the ListComponent declared in the application component template. And
both lists have its first element wired with conditional visibility expression. That is why you will
see messages from both components in the browser console output window.
As you can see, subscribing and listening to “QueryList<T>.change” events gives you an ability to
react on layout changes and perform extra behaviours at the component level.
Components 139
Component Lifecycle
Angular supports multiple lifecycle events for components, directives and services.
For the Components, the Angular invokes the following methods at runtime, in the order of
execution:
• ngOnChanges
• ngOnInit
• ngDoCheck
• ngAfterContentInit
• ngAfterContentChecked
• ngAfterViewInit
• ngAfterViewChecked
• ngOnDestroy
As Directives do not have UI templates, they get the following set of lifecycle methods supported
out of the box:
• ngOnChanges
• ngOnInit
• ngDoCheck
• ngOnDestroy
• ngOnDestroy
Every method represents a separate interface in the @angular/core namespace and contains a “ng”
prefix appended to the interface name. For example, the “OnInit” interface declares a “ngOnInit”
method, and so on. We are going to dive deeper into details on each method and interface below.
Technically, the interfaces for the lifecycle events are optional. The Angular is going to call those
events in any case, and if the corresponding methods are present, they get automatically invoked. It
is a good practice, however, to still expose those interfaces in your classes to enable static checks by
TypeScript.
You can find additional information on the lifecycle events in the following official docs: Lifecycle
Hooks30
Source code
You can find the source code in the “angular/components/lifecycle31 ” folder.
30
https://angular.io/guide/lifecycle-hooks#component-lifecycle-hooks-overview
31
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/lifecycle
Components 140
ngOnChanges
The “ngOnChanges” method is related to the “OnChanges” hook. Angular calls it every time an
input property of the component or directive gets changed.
You can find the code of the interface below:
interface OnChanges {
ngOnChanges(changes: SimpleChanges): void;
}
Let’s create a new Angular CLI project and generate an “on-changes-demo” component to see the
“OnChanges” hook in action.
ng g component on-changes-demo
To visualise the property change, we need two properties to store the current and old values. The
“text” property stores current string value and is decorated with the @Input to enable two-way
bindings with template components, or external input. The “previous” property provides the one-
way binding support to display the old string.
src/app/on-changes-demo/on-changes-demo.component.ts
Now that we have a code defined, the minimal component implementation can look like in the
following example:
Components 141
src/app/on-changes-demo/on-changes-demo.component.html
1 <p>
2 Text: {{ text }}
3 <br>
4 Previous: {{ previous }}
5 </p>
At this point, we are ready to implement “OnChanges” interface from the “@angular/core” package.
Your “ngOnChanges” method can look like the next one:
src/app/on-changes-demo/on-changes-demo.component.ts
As you already know, the Angular keeps track of all property changes. In our current case, the
framework collects the changes made to the “OnChangesDemoComponent” component properties
and, wraps into a special “SimpleChanges” map and passes to the corresponding hook method.
interface SimpleChanges {
[propName: string]: SimpleChange;
}
Every value of the “SimpleChanges” map implements a “SimpleChange” interface with at least the
following properties and methods:
Components 142
• previousValue: any;
• currentValue: any;
• firstChange: boolean;
• isFirstChange(): boolean;
As you can see the “SimpleChange” API allows you to inspect the current and previous values, as
well as to check whether this is the first time the particular property gets changed. The “firstChange”
property allows you to distinguish between setting default property value within the class initialiser
as opposed to the changes caused by user interaction or runtime changes.
Now let’s use our newly created component with the main application one. The simple way to test
value changes is to bind an input element to the “text” property like in the example below:
src/app/app.component.html
1 <input [(ngModel)]="text">
2 <app-on-changes-demo [text]="text"></app-on-changes-demo>
Note that you are going to need an extra “text” property for the application component controller
class:
src/app/app.component.ts
Run the application with the “ng serve –open” command and try typing text in the main page. Notice
that our component displays both old and new values as you type them into the input element.
Components 143
OnChanges example
ngOnInit
The “OnInit” is the most common lifecycle hook. You are probably going to use it a lot in your
custom components.
It is a good practice to perform component initialisation in the “ngOnInit” method and not in the
constructor. Angular invokes the “ngOnInit” after the construction and once all input properties set,
so you can setup your component based on the property values from the outside.
You can find the code of the interface below:
interface OnInit {
ngOnInit(): void;
}
In the same project, we created earlier, generate a new “on-init-demo” component with the following
command:
ng g component on-init-demo
Next, define a property “currentDate” of the “Date” type, and set its value in the “ngOnInit” method
like in the following example:
src/app/on-init-demo/on-init-demo.component.ts
12 constructor() { }
13
14 ngOnInit() {
15 this.currentDate = new Date();
16 }
17
18 }
src/app/on-init-demo/on-init-demo.component.html
1 <p>
2 Date: {{ currentDate }}
3 </p>
Given that we do not have any input properties, the main application component template can
contain just the empty tag. Append the following code to the existing template HTML:
src/app/app.component.html
1 ...
2
3 <hr>
4 <h2>ngOnInit</h2>
5
6 <app-on-init-demo></app-on-init-demo>
If you run the application right now, you should see current date and time with default string
formatting:
Let’s now try creating the component multiple times on the fly to see the “ngOnInit” method calls
in practice.
We can wrap our custom “OnInitDemoComponent” with the “ng-container” element decorated by
the “ngIf” directive. Add a new “showNgOnInit” property of the “boolean” type to the component
controller class, and bind the checkbox to toggle component at runtime:
Components 145
src/app/app.component.ts
1 @Component({...)
2 export class AppComponent {
3
4 ...
5
6 showNgOnInit = true;
7
8 }
Update the application controller template to look like the following code:
src/app/app.component.html
1 ...
2
3 <hr>
4 <h2>ngOnInit</h2>
5
6 <label>
7 <input type="checkbox" [(ngModel)]="showNgOnInit">
8 Toggle ngOnInit demo
9 </label>
10
11 <ng-container *ngIf="showNgOnInit">
12 <app-on-init-demo></app-on-init-demo>
13 </ng-container>
ngModel
Don’t forget that you need to import “FormsModule” for your root application module to
be able to use “ngModel” with components.
Now, every time you tick the checkbox element a new “OnInitDemoComponent” is created and
displayed, and a new date value assigned by the “ngOnInit” method.
Components 146
As you can imagine, we used a pretty basic scenario for component setup. In real life, your “ngOnInit”
content might be more complicated.
ngDoCheck
The “DoCheck” hook allows you to integrate into the change detection cycle and find changes within
the objects references or any areas where Angular did not detect changes automatically.
You can find the code of the interface below:
interface DoCheck {
ngDoCheck(): void;
}
Performance Penalty
Please keep in mind that, depending on the component tree size and complexity, the
“ngDoCheck” method is going to execute enormous amount of times and may become
a performance bottleneck if you poorly implement the code. Avoid using “ngDoCheck”
method unless necessary.
When using properties of the object type, the Angular is going to watch the changes by value
reference, meaning detects the change of the entire value, but not the changes in the child properties.
That is the case where we are going to use “DoCheck” lifecycle hook and detect changes in the object.
For the next exercise, we need a new component called “DoCheckDemoComponent” that you can
generate using the following command:
ng g component do-check-demo
Let’s integrate it into the main application template now to save the time later. Append the next
HTML snippet to the content of the component template:
src/app/app.component.html
1 ...
2 <hr>
3 <h2>ngDoCheck</h2>
4 <app-do-check-demo></app-do-check-demo>
Next, we need an object value and custom properties. For the sake of simplicity let’s create a “User”
object featuring first and last names as separate properties. The component should have “current”
and “previous” values to facilitate property checks.
Components 147
src/app/do-check-demo/do-check-demo.component.ts
For the first iteration leave the “ngDoCheck” method empty and switch to the component template.
Our component needs to display values for both current and previous property values.
src/app/do-check-demo/do-check-demo.component.html
1 <p>
2 Current user: {{ currentUser.firstName + ' ' + currentUser.lastName }}
3 <br>
4 Previous user: {{ previousUser.firstName + ' ' + previousUser.lastName }}
5 </p>
You can run the application and check that component works as expected. The main page should
contain the full name of the user stored in the “currentUser” property. The text for the “previousUser”
property should be empty because we have not yet changed anything.
Components 148
As mentioned earlier, Angular calls “DoCheck” many times based on the change detection cycle. To
see that in practice, let’s introduce a new field “checks” that should hold a count of checks performed
for our component. We are going to increment this field every time the “ngDoCheck” get called.
Next, we create a field “updates” to hold a count of “currentUser” updates detected by our custom
“ngDoCheck” implementation. The method checks both “firstName” and “lastName” property values
and updates the counter, in addition to setting a new instance of the “previousUser” value.
Finally, let’s introduce an “Update user” button that changes the value of the current user and so
triggers the change detection cycle and our custom checks. The button calls “updateUser” method
upon every click.
Below is the implementation of all the mentioned class members and behaviour:
src/app/do-check-demo/do-check-demo.component.ts
1 @Component({...})
2 export class DoCheckDemoComponent implements DoCheck {
3
4 currentUser = {
5 firstName: 'John',
6 lastName: 'Doe'
7 };
8
9 previousUser = {
10 firstName: '',
11 lastName: ''
12 };
13
14 checks = 0;
15 updates = 0;
16
17 updateUser() {
18 this.currentUser = {
19 firstName: 'James',
20 lastName: 'Bond'
21 };
22 }
23
24 ngDoCheck() {
Components 149
25 this.checks ++;
26 if (this.previousUser.firstName !== this.currentUser.firstName
27 || this.previousUser.lastName !== this.currentUser.lastName) {
28 this.updates ++;
29 this.previousUser = Object.assign({}, this.currentUser);
30 }
31 }
32
33 }
Now, edit the component HTML template to add necessary labels and a button to update the
underlying user.
src/app/do-check-demo/do-check-demo.component.html
1 <p>
2 Current user: {{ currentUser.firstName + ' ' + currentUser.lastName }}
3 <br>
4 Previous user: {{ previousUser.firstName + ' ' + previousUser.lastName }}
5 </p>
6
7 <p>
8 Checks: {{ checks }} <br>
9 Updates: {{ updates }} <br>
10 <button (click)="updateUser()">Update user</button>
11 </p>
Run the application or switch to the corresponding browser tab if it is already running. You should
see something like the following:
Now click the “Update user” button one time and check that both counters got incremented.
Components 150
If you continue clicking the button, you should notice that “checked” counter keeps incrementing.
Note that it increments even if you click some other buttons or clickable Angular components. That
demonstrates that our custom component reacts on change detection cycle managed by Angular,
and increments the counter as designed.
The “update” counter, however, should remain the same. That is obvious as we change the values of
the user only once.
ngAfterContentInit
The “ngAfterContentInit” method belongs to the “AfterContentInit” hook in Angular, and is part of
the “Content Projection” feature.
Angular raises “AfterContentInit” every time an injected child component gets initialised and is
ready for use and access from the code if needed.
Content Projection
Content projection is the process of injection of the external HTML content and other
Angular components into the body of your component template. You can get more
information in the Content Projection section later in this chapter.
interface AfterContentInit {
ngAfterContentInit(): void;
}
As all in all previous cases, let’s start by generating a separate component to experiment with the
hook:
ng g component after-content-init-demo
For our exercise, we do not need “constructor” and “OnInit” implementation. You can clean the
component class and replace with the “AfterContentInit” interface implementation.
src/app/after-content-init-demo/after-content-init-demo.component.ts
The “ng-content” element is going to be an injection point for external content. Update the
component template to look like the following markup:
src/app/after-content-init-demo/after-content-init-demo.component.html
We now need to generate a second component to test the Angular lifecycle hook.
ng g component after-content-init-child
You can clean the component class code as we need just a default implementation.
Components 152
src/app/after-content-init-child/after-content-init-child.component.ts
Also, the default component template should work fine as well. The Angular CLI usually generates
a “<component> works!” label out of the box.
src/app/after-content-init-child/after-content-init-child.component.html
1 <p>
2 after-content-init-child works!
3 </p>
Finally, let’s extend the main application template with the testing parent-child hierarchy with
our newly introduced components. Just append the following HTML snippet to existing template
markup.
src/app/app.component.html
1 ...
2 <hr>
3 <h2>ngAfterContentInit</h2>
4
5 <app-after-content-init-demo>
6
7 <app-after-content-init-child>
8 </app-after-content-init-child>
9
10 </app-after-content-init-demo>
Serve the application with “ng serve –open” command or switch to a running one in the browser.
You should see the next layout at the bottom of your page:
Components 153
Typically you are going to use “AfterContentInit” hook with the @ContentChild decorator. While
we use the “ngAfterContentInit” to detect when an injected component finished initialising, the
“ContentChild” allows getting references to the component instance.
Please refer to the example below to get a better understanding of how both APIs work together.
src/app/after-content-init-demo/after-content-init-demo.component.ts
1 import {
2 Component, OnInit,
3 AfterContentInit, ContentChild } from '@angular/core';
4 import {
5 AfterContentInitChildComponent
6 } from '../after-content-init-child/after-content-init-child.component';
7
8 @Component({...})
9 export class AfterContentInitDemoComponent implements AfterContentInit {
10
11 @ContentChild(AfterContentInitChildComponent)
12 child: AfterContentInitChildComponent;
13
14 ngAfterContentInit() {
15 console.log('AfterContentInit:', this.child);
16 }
17
18 }
Once you run your application and open the developer tools, you should see the following content
in the browser console output:
At this point, your parent component can perform additional setup for the child one, like changing
properties, subscribing to events or calling methods.
Components 154
ngAfterContentChecked
The “ngAfterContentChecked” method belongs to the “AfterContentChecked” interface in Angular,
and is part of the “Content Projection” feature.
The “AfterContentChecked” lifecycle hook allows you to provide a custom mechanism for checking
changes in the projected components. The behaviour is similar to the “DoCheck” but applied to the
components that are part of the “ng-content” container.
Content Projection
Content projection is the process of injection of the external HTML content and other
Angular components into the body of your component template. You can get more
information in the Content Projection section later in this chapter.
interface AfterContentChecked {
ngAfterContentChecked(): void;
}
Let’s use the same “AfterContentInitDemoComponent” we created earlier. For the sake of simplicity,
we are going to add the “checked” counter field and update it on every change detection cycle.
src/app/after-content-init-demo/after-content-init-demo.component.ts
1 import { ..., AfterContentChecked } from '@angular/core';
2 ...
3
4 @Component({...})
5 export class AfterContentInitDemoComponent implements AfterContentInit, AfterCon\
6 tentChecked {
7
8 checked = 0;
9
10 ...
11
12 ngAfterContentChecked() {
13 this.checked ++;
14 }
15
16 }
Update the component template to also display the “checked” counter after the projected content.
Components 155
src/app/after-content-init-demo/after-content-init-demo.component.html
Once your application starts or reloads, the bottom of the page should look similar to the following:
Please note that the counter is going to update multiple times even if you interact with some other
components or elements on the page. You should keep the code inside “ngAfterContentChecked”
method body fast and small not to introduce a performance bottleneck for the entire application.
ngAfterViewInit
Angular invokes the “ngAfterViewInit” hook once the view of the component is ready. That also
includes the child components that may be part of the template. You can access child instance
members and perform additional tasks in the code, for example changing properties or subscribing
to events.
You can find the code of the interface below:
interface AfterViewInit {
ngAfterViewInit(): void;
}
Let’s introduce a basic scenario that requires us to access view elements and update their properties.
We are going to have two buttons in the component template that have no text, and once the view
gets initialised our component should update the text of each button.
We need a separate “AfterViewInitDemoComponent” component that gets generated with the next
Angular CLI command:
Components 156
ng g component after-view-init-demo
Import and implement the “AfterViewInit” interface from the “@angular/core” package. The
“ngAfterViewInit” method can be blank for now, and we are going to implement it shortly.
src/app/after-view-init-demo/after-view-init-demo.component.ts
Next, add two buttons to the template. Each of the button needs to have a template reference id so
that our component class can access each button using “prevPageButton” and “nextPageButton” id
values.
src/app/after-view-init-demo/after-view-init-demo.component.html
1 <div>
2 after-view-init-demo works!
3 <div>
4 <button #prevPageButton></button>
5 <button #nextPageButton></button>
6 </div>
7 </div>
As a next step, declare the newly generated component within the main application template by
appending the following code to the existing markup:
Components 157
src/app/app.component.html
1 ...
2 <hr>
3 <h2>ngAfterViewInit</h2>
4 <app-after-view-init-demo></app-after-view-init-demo>
Serve the application right now and ensure the component renders its template with a default label
and two buttons with missing labels as in the picture below:
You also need importing the ViewChild decorator type. This decorator is used to get a reference to
the native element of the component template.
Also, create two separate properties “prevButton” and “nextButton” or the “ElementRef” type. Every
“ElementRef” instance exposes a “nativeElement” property that you can use to access the DOM
element, in our case HTML button inputs. The component sets the “innerText” values for both
buttons inside the “ngAfterViewInit” method body like in the next example:
src/app/after-view-init-demo/after-view-init-demo.component.ts
This time, when you run the application, both buttons should have correct labels:
The “AfterViewInit” is usually used to modify the behaviour of the view elements once the
corresponding component is ready.
ngAfterViewChecked
The “ngAfterViewChecked” method represents the “AfterViewChecked” lifecycle hook and inter-
face. It allows you to provide custom change tracking that is not handled by Angular due to some
reason. The behaviour is similar to the AfterContentChecked hook but applies to the view template
children rather than projected content.
You can find the code of the interface below:
interface AfterViewChecked {
ngAfterViewChecked(): void;
}
As with previous examples, let’s introduce a “checked” field to hold the number of checks, and use
the previous component with two buttons to demonstrate the lifecycle hook in practice.
src/app/after-view-init-demo/after-view-init-demo.component.ts
1 import { ..., AfterViewChecked } from '@angular/core';
2
3 @Component({...})
4 export class AfterViewInitDemoComponent implements AfterViewInit, AfterViewCheck\
5 ed {
6
7 checked = 0;
8
9 ...
10
11 ngAfterViewChecked() {
12 this.checked ++;
13 }
14
15 }
Components 159
Don’t forget to update the component template by appending the label like in the next snippet:
src/app/after-view-init-demo/after-view-init-demo.component.html
1 ...
2 <div>
3 Checked (times): {{ checked }}
4 </div>
Now run the application and try clicking the buttons. You should notice that the counter updates
every time you interact with the button inside the component template, or any other Angular
component on the page.
Needless to say that your custom change tracking code should be highly optimised and run fast.
Otherwise, you risk getting a performance bottleneck.
ngOnDestroy
The “OnDestroy” lifecycle hook provides you with a way to run cleanup operations for your
component, directive or service to reduce resources and avoid potential memory leaks.
If your component controller subscribes to various event handlers, the “ngOnDestroy” method is
the most appropriate place to tear down all event subscriptions.
You can find the code of the interface below:
interface OnDestroy {
ngOnDestroy(): void;
}
Let’s generate a new component to see how “OnDestroy” hook works in practice. We are going to
produce two log entries for the browser console log, one during the “OnInit” call and one for the
“OnDestroy”.
1 ng g component on-destroy-demo
Components 160
src/app/on-destroy-demo/on-destroy-demo.component.ts
Next, declare a “showNgOnDestroy” property for the main application component class to control
the visibility of our generated component.
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3 ...
4
5 showNgOnDestroy = true;
6
7 }
For the last step, declare a checkbox with the label to control the value of the “showNgOnDestroy”
property, and a “ng-container” element that wraps the “app-on-destroy-demo”.
Components 161
src/app/app.component.html
1 ...
2 <hr>
3 <h2>ngOnDestroy</h2>
4 <label>
5 <input type="checkbox" [(ngModel)]="showNgOnDestroy">
6 Toggle ngOnDestroy demo
7 </label>
8
9 <ng-container *ngIf="showNgOnDestroy">
10 <app-on-destroy-demo></app-on-destroy-demo>
11 </ng-container>
At runtime, the component should look like the following once the application starts:
As soon as you untick the checkbox, the Angular is going to tear down the component that should
no longer be on the page and call the “ngOnDestroy” method.
Open the development tools for your current browser and try clicking the checkbox multiple times.
The console output should look like the one below:
Cleaning up subscriptions
Also, the OnDestroy event is also used to cleanup subscriptions to external service or component
events. It is very important to unsubscribe from all the events to prevent memory leaks and
performance degradation.
Let’s take an example of a service that exposes multiple events.
Components 162
src/app/on-destroy-demo/simple.service.ts
We use dependency injection to get an instance of this service injected into the component
constructor. And we use the “OnInit” hook to set up the event handlers.
src/app/on-destroy-demo/on-destroy-demo.component.ts
1 @Component({...})
2 export class OnDestroyDemoComponent implements OnInit, OnDestroy {
3
4 constructor(private service: SimpleService) {}
5
6 ngOnInit() {
7 this.service.loaded.subscribe(() => {
8 // handle event
9 });
10 this.service.changed.subscribe(() => {
11 // handle event
12 });
13 }
14
15 ngOnDestroy() {
16 // ...
17 }
18 }
It is sometimes difficult to spot the problem in such an approach, but we may get a memory leak
with the code above. The issue is that the service we inject is a singleton one, and Angular keeps its
instance somewhere. It also means that now service instance will keep a reference to our component
alive due to the event handler and subscription. That is why we need to clean all subscriptions during
destroy phase.
Components 163
When using Observables, you get an instance of the Subscription each time you call “subscribe”
method of the “Observable” or “Subject”.
Subscription
Represents a disposable resource, such as the execution of an Observable. Subscription has
one important method, unsubscribe, that takes no argument and just disposes the resource
held by the subscription.
The easiest way to process multiple subscriptions is to use them in bulk, wrapping into a private array
variable. That saves a lot of time and prevents issues related to missed variables or “unsubscribe”
calls.
First, let’s create a private property “subscriptions” of the array type that will hold all our
subscriptions to external events.
1 @Component({...})
2 export class OnDestroyDemoComponent implements OnInit, OnDestroy {
3
4 private subscriptions: Subscription[] = [];
5
6 }
Now you can push multiple subscriptions into the array like in the next example:
1 @Component({...})
2 export class OnDestroyDemoComponent implements OnInit, OnDestroy {
3
4 private subscriptions: Subscription[] = [];
5
6 constructor(private service: SimpleService) {}
7
8 ngOnInit() {
9 this.subscriptions.push(
10 this.service.loaded.subscribe(() => {
11 // handle event
12 }),
13 this.service.changed.subscribe(() => {
14 // handle event
15 }),
16 this.service.somethingElse.subscribe(() => {
17 // handle event
Components 164
18 })
19 );
20 }
21
22 ngOnDestroy() {
23 // ...
24 }
25
26 }
Keeping all subscriptions in one place makes it easy to cleanup them in bulk when Angular invokes
“ngOnDestroy” life-cycle hook.
1 @Component({...})
2 export class OnDestroyDemoComponent implements OnInit, OnDestroy {
3
4 private subscriptions: Subscription[] = [];
5
6 constructor(private service: SimpleService) {}
7
8 ngOnInit() {...}
9
10 ngOnDestroy() {
11 this.subscriptions.forEach(s => s.unsubscribe());
12 this.subscriptions = [];
13 }
14
15 }
Do not forget about the technique above when dealing with subscriptions. It should prevent memory
leaks and performance issues when the component is re-created or gets destroyed.
Content Projection
The process of adding custom content into the existing components without rebuilding them is often
called content projection.
ng-container directive
In most cases when using structural directives or components you are dealing with an extra DOM
element that serves as a root one. There are scenarios, however, when you will want to have raw
content emitted at run-time, with no wrapping elements.
Components 165
The “ng-container” directive allows you to group single or multiple elements and components
producing no HTML output.
1 <ng-container>
2 <!-- Your content comes here -->
3 </ng-container>
Let’s take a simple repeater example and render a sequence of strings wrapped with a div element
and compare the same solution with ng-container use case.
First, take any existing, or generate a new Angular application using Angular CLI tools. Create an
“items” property with a set of strings values so you can use that property with the “*ngFor” directive.
src/app/app.component.ts
Next, update the component template and append the following code:
src/app/app.component.html
Once you run the application, the main page should contain a header element and a list of strings
like following:
If you now look at the generated HTML (right-clicking on the list and using “Inspect Element” menu
item), you will see something like in the following example:
Components 166
Note that every string is wrapped with the “div” element. That is what we will now try to avoid by
using ng-container directive.
For comparison, let’s leave the exiting layout as it is now, and append a new block at the bottom
with the following code.
src/app/app.component.html
1 <h2>Using ng-container</h2>
2 <ng-container *ngFor="let item of items">
3 {{ item }}
4 </ng-container>
That is the same code, but we replaced “div” element with the “ng-container” one. At run-time,
however, the result should look different.
1 Using ng-container
2
3 One Two Three Four
Note that repeated values are now forming a string rather than a vertical list. If you now check the
source code, you will see that there are no additional elements on the page. Angular replaces them
with the HTML comments instead.
Components 167
Angular does not restrict you to the “ngFor” directive only. You can use the “ng-container” in any
parts of the template where you need to have a “virtual” container element, or there’s a need to
group multiple elements without introducing an extra content in the DOM.
Most times using “ng-container” should also simplify layout and CSS maintenance.
ng g component my-panel
Next, you should edit the main application template and replace its contents with the following
HTML block:
1 <app-my-panel></app-my-panel>
For demonstration and testing purposes, let’s update the generated panel component. First, replace
the “p” element with the “div”.
32
https://stackblitz.com/edit/dwa-ng-container?file=app%2Fapp.component.html
Components 168
src/app/my-panel/my-panel.component.html
1 <div>
2 my-panel works!
3 </div>
Then you can update its stylesheet, add the border to all the “div” children of the root component
layout.
src/app/my-panel/my-panel.component.css
Run the application and should see the main page with the panel component looking like on the
picture below.
Default Panel
To provide a basic support for external content projection, add the “ng-content” element somewhere
in the panel template layout.
src/app/my-panel/my-panel.component.html
1 <div>
2 my-panel works!
3 <ng-content></ng-content>
4 </div>
Now your panel component can receive custom HTML elements and Angular components inside
its selector tags. Update the main application template with a header element inside the panel to see
that in practice.
Components 169
src/app/app.component.html
1 <app-my-panel>
2 <h2>My custom external content</h2>
3 </app-my-panel>
Once your application restarts, the main page should now look similar to the next picture:
Note you can see both the native panel content presented by the “my-panel works!” and the external
“h2” element from the application level.
For your custom Angular components, treat the “ng-content” element as a placeholder for something
that comes from the outside.
1 <ng-content select="<selector>"></ng-content>
For the first step, provide support for injecting Header, Content and Footer content for our panel
component, and see CSS class name selectors in action.
You should start by adding three new content placeholders that point to unique class names.
Components 170
src/app/my-panel/my-panel.component.html
1 <div>
2 <ng-content select=".my-panel-header"></ng-content>
3 </div>
4 <div>
5 my-panel works!
6 <ng-content select=".my-panel-content"></ng-content>
7 </div>
8 <div>
9 <ng-content select=".my-panel-footer"></ng-content>
10 </div>
To populate those placeholders, you components or DOM elements now need to declare correspond-
ing CSS class names.
src/app/app.component.html
1 <app-my-panel>
2 <div class="my-panel-header">
3 Header
4 </div>
5 <h2 class="my-panel-content">
6 My custom external content
7 </h2>
8 <div class="my-panel-footer">
9 Footer
10 </div>
11 </app-my-panel>
At runtime, the main application page should now look like the following:
Components 171
You have projected custom content for panel’s Header, Content and Footer areas.
When dealing with CSS class name-based selectors, things may get complicated once your layout
grows. Header and Footer content might need own complex layout, styling, effects or conditional
rendering. The best approach for handling application growth is to introduce separate components
that encapsulate the logic and presentation for panel primitives.
You can practice better separation of concerns by introducing three granular components to compose
the panel.
ng g component my-panel/my-panel-header
ng g component my-panel/my-panel-content
ng g component my-panel/my-panel-footer
Note that by default, Angular CLI generates “app-“ prefixes for the component selectors. So “My-
PanelHeaderComponent” will have “app-my-panel-header” selector. Same for other components we
create.
Now update the panel template to reference the DOM element selectors instead of the CSS class
names like in the example below:
Components 172
src/app/my-panel/my-panel.component.html
1 <div>
2 <ng-content select="app-my-panel-header"></ng-content>
3 </div>
4 <div>
5 my-panel works!
6 <ng-content select="app-my-panel-content"></ng-content>
7 </div>
8 <div>
9 <ng-content select="app-my-panel-footer"></ng-content>
10 </div>
That allows us to refactor the main page and use Angular components instead of the DOM elements
with class names:
src/app/app.component.html
1 <app-my-panel>
2 <app-my-panel-header></app-my-panel-header>
3 <app-my-panel-content></app-my-panel-content>
4 <app-my-panel-footer></app-my-panel-footer>
5 </app-my-panel>
Run the web application again and ensure that all content is on the expected places.
The main benefit for using Angular components over the DOM elements is that your components
can have own content projection support. Besides that components can hide the complexity of the
layout and expose different input properties and output events.
Fallback content
You can further improve coding experience for the developers that reuse your panel component.
It is possible to provide a default fallback content to support the cases when custom projection is
missing. Also, the panel may provide shortcuts for common features, like providing a simple string
value for the Header via the input property, in case developer does not need complex layout.
Importing the references to content children of the component into the code can further help reuse
them in the template and building conditional visibility expressions for other elements.
Let’s import the “header” component and also introduce the optional “title” shortcut property.
src/app/my-panel/my-panel.component.ts
Now update the panel template, if there is no “header” element provided and there is a “title” input
property defined, we display the value of the “title”.
Components 174
src/app/my-panel/my-panel.component.html
1 <div>
2 <ng-container *ngIf="!header && title">{{ title }}</ng-container>
3 <ng-content select="app-my-panel-header"></ng-content>
4 </div>
5 <div>
6 my-panel works!
7 <ng-content select="app-my-panel-content"></ng-content>
8 </div>
9 <div>
10 <ng-content select="app-my-panel-footer"></ng-content>
11 </div>
If you want to test new functionality, comment out the header tags in the main page template and
reload the application.
1 <app-my-panel>
2 <!-- <app-my-panel-header></app-my-panel-header> -->
3 <app-my-panel-content></app-my-panel-content>
4 <app-my-panel-footer></app-my-panel-footer>
5 </app-my-panel>
We have set the default title value to “Default title”, that is the text you should now see rendered.
Now set custom value for the “title” attribute to test that fallback content works as we expect:
Components 175
The panel should reflect your changes and web application should now render the “Custom title”
value in the Header area.
The dual rendering approach we have just tried helps a lot when you need to provide default layout
portions, but still want to allow developers replacing them on demand. Imagine a dialog with the
toolbar, for instance, where developers can project a new toolbar implementation, but can also have
a predefined set of buttons.
33
https://stackblitz.com/github/DenisVuyka/dwa-content-projection?file=src%2Fapp%2Fapp.component.html
Dependency Injection
Dependency Injection, also known as DI, is one of the major features of the Angular framework.
With dependency injection, Angular greatly improves development and testing process by providing
an infrastructure that helps to move share coded into the separate application services and blocks
that can be centrally maintained, reused or replaced at run time.
In this chapter, we are going to try key features of dependency injection in practice.
Source code
You can find the source code for this chapter in the “angular/dependency-injection34 ”
folder.
Preparing a project
First, let’s use Angular CLI tool and generate a new project called “dependency-injection”.
ng new dependency-injection
cd dependency-injection
Next, generate two components “component1” and “component2”, as in the example below:
ng g component component1
ng g component component2
Finally, update the main application component template to use both components we have just
created:
src/app/app.component.html
1 <app-component1></app-component1>
2 <app-component2></app-component2>
If you now run the application with the “ng serve –open” command you should see two default
component templates that get generated automatically by Angular CLI:
34
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/dependency-injection
Dependency Injection 177
component1 works!
component2 works!
Services
Creating LogService
As an example, let’s build a shared Logger service now. Logging is an excellent example of how to
turn a frequently used functionality into an injectable service. That is something you are not going
to reimplement in each component.
You can save your time by using Angular CLI to generate a new “log” service utilising the following
command:
ng g service services/log
That should give you a scaffold for a new service called “LogService”:
src/app/services/log.service.ts
We use a special “@Injectable” decorator here to mark the class and instruct Angular that the given
class should participate in the dependency injection layer.
All classes marked with “@Injectable” can get imported into other entities like services, components,
directives or pipes. The Angular framework creates instances of those classes, usually in the form
of “singletons”, and injects into other primitives on demand.
Note the warning message that Angular CLI generates for every new service scaffold:
Dependency Injection 178
installing service
create src/app/services/log.service.spec.ts
create src/app/services/log.service.ts
WARNING Service is generated but not provided, it must be provided to be used
We are going to walk through Modules feature later in this chapter. For now, just edit the
“app.module.ts” file and add the “LogService” file to the “providers” section as in the following
example:
src/app/app.module.ts
1 ...
2 import { LogService } from './services/log.service';
3
4 @NgModule({
5 declarations: [
6 ...
7 ],
8 imports: [
9 ...
10 ],
11 providers: [
12 LogService
13 ],
14 bootstrap: [AppComponent]
15 })
16 export class AppModule { }
That allows injecting “LogService” into all application components, including the “component1” and
“component2” we created earlier.
Next, let’s extend the service implementation with an “info” method we can reuse across the
components:
src/app/services/log.service.ts
1 @Injectable()
2 export class LogService {
3
4 constructor() { }
5
6 info(message: string) {
7 console.log(`[info] ${message}`);
8 }
Dependency Injection 179
9
10 }
At this point, we got a minimal logging service implementation that we can use in our components.
src/app/component1/component1.component.ts
1 import { Component } from '@angular/core';
2 import { LogService } from './../services/log.service';
3
4 @Component({...})
5 export class Component1Component {
6
7 constructor(logService: LogService) {
8 logService.info('Component 1 created');
9 }
10
11 }
You can now try to update the second component implementation yourself and add the same
LogService integration as in the example above.
Once you are finished updating the code, run the application, and you should see the following
output in the browser console:
Providers
In the previous examples, you should have noticed that to import and use a class decorated with
“@Injectable” one needs to declare its Type in the “providers” array of the main application Module.
That makes Angular “know” about the services when instantiating components for your web
application.
You can have numerous services that perform various sets of functionality, all registered within the
root module:
Dependency Injection 180
1 @NgModule({
2 ...,
3 providers: [
4 ...
5 LogService,
6 AuthenticationService,
7 AvatarService,
8 UserService,
9 ChatService
10 ],
11 ...
12 })
13 export class AppModule { }
The concept of “providers” in Angular goes beyond the collection of classes, in fact, it supports
several powerful ways to control how dependency injection behaves at runtime.
Besides strings, the framework supports an object-based notation for defining providers.
Using a class
Earlier in this chapter, we have been using a string value to define a LogService dependency in the
“providers” section. We can express the same value with the help of the following notation:
1 @NgModule({
2 ...
3 providers: [
4 { provide: LogService, useClass: LogService }
5 ],
6 ...
7 })
8 export class AppModule { }
We are using “LogService” both as a “key” for injection and as a “value” to build a new instance for
injection. The main feature of this approach is that “key” and “value” can be different classes. That
allows swapping the value of the service with a custom implementation if needed, or with a Mock
object for an improved unit testing experience.
Now, let’s create a custom logger implementation “CustomLogService”:
Dependency Injection 181
ng g service services/custom-log
Next, implement the “info” method, but this time it should contain some different output for us to
distinguish both implementations:
Finally, you need to import this class into the main application module and declare as a new provider
with the “LogService” key. Don’t forget to comment out or remove the former logger declaration as
in the example below:
The code above means that all the components that inject the “LogService” as part of the constructor
parameters are going to receive the “CustomLogService” implementation at runtime. Essentially we
are swapping the value of the logger, and no component is going to notice that. That is the behaviour
developers often use for unit testing purposes.
If you now run the web application and navigate to browser console, you should see the following
output:
Dependency Injection 182
Strings now contain “[custom]: “ as a prefix, which proves the “Component1” and “Component2”
are now dealing with the “CustomLogService” code that has been successfully injected using the
“LogService” key.
Before we jump into configuration details, let’s extend our newly introduced “CustomLogService”
service with the custom “prefix” support. We are going to implement a special “setPrefix” method:
As you can see from the code above the “info” method is going to use a custom prefix for all the
messages.
Next, create an exported function “customLogServiceFactory” that is going to control how the
CustomLogService instance gets created. In our case we are going to provide a custom prefix like in
the example below:
Dependency Injection 183
As you can imagine, there could be more sophisticated configuration scenarios for all application
building blocks, including services, components, directives and pipes.
Finally, you can use the factory function for the “LogService”. In this case, we both replace the
real instance with the CustomLogService, and pre-configure the latter with a custom prefix for info
messages:
1 @NgModule({
2 ...
3 providers: [
4 { provide: LogService, useFactory: customLogServiceFactory }
5 ],
6 ...
7 })
8 export class AppModule { }
This time, when the application runs, you should see the following output:
When you use the default provider registration for a service, directive or component, the Angular
framework automatically manages and injects dependencies if needed. In the case of factories, you
can use additional “deps” property to define dependencies and allow your factory function to access
corresponding instances during execution.
Let’s imagine we need to manually bootstrap the “AuthenticationService” that depends on the
“RoleService” and “LogService” instances.
Dependency Injection 184
1 @Injectable()
2 export class AuthenticationService {
3 constructor(private roles: RoleService,
4 private log: LogService) {
5 }
6 ...
7 }
1 @NgModule({
2 ...,
3 providers: [
4 {
5 provide: AuthenticationService,
6 useFactory: authServiceFactory,
7 deps: [ RoleService, LogService ]
8 }
9 ],
10 ...
11 })
12 export class AppModule { }
With the code above we instruct Angular to resolve “RoleService” and “LogService” and use with
our custom factory function when the “AuthenticationService” singleton instance gets created for
the first time.
Finally, your factory implementation should look similar to the following one:
src/app/app.module.ts
Now we define a custom provider with the key “DATE_NOW” that is going to use our new factory.
1 @NgModule({
2 ...,
3 providers: [
4 { provide: 'DATE_NOW', useFactory: dateFactory }
5 ],
6 ...
7 })
8 export class AppModule { }
For the next step, you can import the ‘@Inject’ decorator from the “angular/core” namespace and
use it with the “Component1” we created earlier in this chapter:
There are two points of interest in the code above. First, we inject “LogService” instance as a
“logService” parameter using its type definition: “logService: LogService”. Angular is smart enough
to resolve the expected value based on the “providers” section in the Module, using “LogService” as
the key.
Second, we inject a date value into the “now” parameter. This time Angular may experience
difficulties resolving the value based on the “Date” type, so we have to use the “@Inject” decorator
to explicitly bind “now” parameter to the “DATE_NOW” value.
The browser console output, in this case, should be as the following one:
Dependency Injection 186
Another important use case for the @Inject decorator is using custom types in TypeScript when
the service has different implementation class associated with the provider key, like in our early
examples with LogService and CustomLogService.
Below is an alternative way you can use to import CustomLogService into the component and use
all the API exposed:
1 @Component({...})
2 export class Component1Component {
3
4 constructor(@Inject(LogService) logService: CustomLogService) {
5 logService.info('Component 1 created');
6 }
7
8 }
In this case, you are getting access to real CustomLogService class that is injected by Angular for
all the “LogService” keys. If your custom implementation has extra methods and properties, not
provided by the LogService type, you can use them from within the component now.
This mechanism is often used in unit testing when the Mock classes expose additional features to
control the execution and behaviour flow.
Using a value
Another scenario for registering providers in the Angular framework is providing instances directly,
without custom or default factories.
The format, in this case, should be as following:
The second scenario is related to configuration objects you can pass to initialise or setup other
components and services.
Let’s now register a “logger.config” provider with a JSON object value:
1 @NgModule({
2 ...,
3 providers: [
4 {
5 LogService,
6 {
7 provide: 'logger.config',
8 useValue: {
9 logLevel: 'info',
10 prefix: 'my-logger'
11 }
12 }
13 }
14 ],
15 ...
16 })
17 export class AppModule { }
Now any component, service or directive can receive the configuration values by injecting it as
“logger.config”. To enable static type checking you can create a TypeScript interface describing the
settings object:
Dependency Injection 188
Finally, proceed to the “LogService” code and inject the JSON object using the “logger.config” token
and “LoggerConfig” interface like in the following example:
1 @Injectable()
2 export class LogService {
3
4 constructor(@Inject('logger.config') config: LoggerConfig) {
5 console.log(config);
6 }
7
8 info(message: string) {
9 console.log(`[info] ${message}`);
10 }
11
12 }
For the sake of simplicity we just log the settings content to the browser console. Feel free to extend
the code with configuring the log service behaviour based on the incoming setting values.
If you run the web application right now and open the browser console you should see the next
output:
{
logLevel: 'info',
prefix: 'my-logger'
}
Registering providers with exact values is a compelling feature when it comes to global configuration
and setup. Especially if you are building redistributable components, directives or services that
developers can configure from the application level.
Using an alias
The next feature we are going to see in action is “provider alias”. You are probably not going to use
this feature frequently in applications, but it is worth taking a look at what it does if you plan to
create and maintain redistributable component libraries.
Dependency Injection 189
Let’s imagine a scenario when you have created a shared component library with an “Authentica-
tionService” service that performs various login and logout operations that you and other developers
can reuse across multiple applications and other component libraries.
After some time you may find another service implementation with the same APIs, or let’s assume
you want to replace the service with a newer “SafeAuthenticationService” implementation.
The main issue you are going to come across when replacing Types is related to breaking changes.
The old service might be in use in a variety of modules and applications, many files import the Type,
use in constructor parameters to inject it, and so on.
For the scenario above is where “alias” support comes to the rescue. It helps you to smooth the
transition period for old content and provide backwards compatibility with existing integrations.
Let’s now take a look at the next example:
1 @NgModule({
2 ...
3 providers: [
4 SafeAuthenticationService,
5 { provide: AuthenticationService, useExisting: SafeAuthenticationService }
6 ],
7 ...
8 })
9 export class AppModule { }
As you can see from the example above, we register a new “SafeAuthenticationService” service and
then declare an “AuthenticationService” that points to the same “SafeAuthenticationService”.
Now all the components that use “AuthenticationService” are going to receive the instance of the
“SafeAuthenticationService” service automatically. All the newly introduced components can now
reference new service without aliases.
Injection Tokens
As you might have already understood the Angular dependency injection layer keeps a map of
providers that are being identified by “keys”, also known as “injection tokens”, and uses this map to
resolve, create and inject instances at runtime.
The injection tokens can be of different types. We have already tried Types and Strings in action in
previous sections.
Type tokens
Type-based injection tokens are the most commonly used way to register providers. Typically you
import the service type from the corresponding file and put it into the “providers” section of the
module.
1 providers: [
2 { provide: LogService, useClass: LogService },
3 { provide: LogService, useFactory: customLogServiceFactory },
4 { provide: LogService, useValue: logService },
5 { provide: AuthenticationService, useExisting: SafeAuthenticationService }
6 ]
In all the cases above we use a real Type reference to register a new provider.
String tokens
Another way to register a provider involves the string-based injection tokens.
Another way to register a provider involves the string-based injection tokens. Typically you are
going to use strings when there is no Type reference available, for example when registering plain
values or objects:
Dependency Injection 191
1 providers: [
2 { provide: 'DATE_NOW', useFactory: dateFactory },
3 { provide: 'APP_VERSION', useValue: '1.1.0' },
4 {
5 provide: 'logger.config',
6 useValue: {
7 logLevel: 'info',
8 prefix: 'my-logger'
9 }
10 }
11 ]
Generic InjectionToken
Also, Angular provides a special generic class InjectionToken<T> to help you create custom
injection tokens backed by specific types: primitives, classes or interfaces. That enables static type
checks and prevents many type-related errors at early stages.
Let’s create separate file “tokens.ts” to hold our custom injection tokens, and create a simple string-
based one:
Now we can use this token within the main application module to register a URL value that all
components and services can use when needed:
From this moment we can use the same token to import registered value in the service or a
component like in the example below:
Dependency Injection 192
1 ...
2 import { REST_API_URL } from './../tokens';
3
4 @Injectable()
5 export class LogService {
6
7 constructor(@Inject(REST_API_URL) restApiUrl: string) {
8 console.log(restApiUrl);
9 }
10
11 ...
12 }
At runtime, you should see the actual value of the REST_API_URL provider in the browser console:
http://localhost:4200/api.
As mentioned earlier, you can also use interfaces or classes with the InjectionToken<T>. That does
not affect the process of dependency injection but gives you an opportunity for static compile-time
checks and auto completion if your code editor supports TypeScript.
Let’s create a token for the “LoggerConfig” interface we set up in this chapter earlier:
You can now define a type-safe configuration object and register it with the dependency injection
system using main application module:
Finally, you can use that token to inject configuration into the LogService and use it to setup the
service accordingly:
1 @Injectable()
2 export class LogService {
3
4 constructor(@Inject(LOGGER_CONFIG) config: LoggerConfig) {
5 console.log(config);
6 }
7
8 ...
9 }
src/app/services/composite-log.service.ts
11
12 }
The “LogPlugin” interface contains a bare minimum of APIs, at this point we need a “name” for demo
and debugging purposes, alongside the level of the messages our plugin supports and the method to
write a log message.
Next, create an injection token “LOGGER_PLUGIN” backed by the interface we have just created
above.
src/app/tokens.ts
1 import { LogPlugin } from './services/composite-log.service';
2
3 export const LOGGER_PLUGIN = new InjectionToken<LogPlugin>('logger.plugin');
We are going to use that token to register various logger plugins, and also inject existing plugin
instances for the “CompositeLogService”.
After that let’s create a couple of Loggers that implement the LogPlugin interface. There is going to
be one class for error messages and one for warnings.
src/app/services/loggers.ts
1 import { LogPlugin } from './composite-log.service';
2
3 export class ErrorLogPlugin implements LogPlugin {
4
5 name = 'Error Log Plugin';
6 level = 'error';
7
8 log(message: string) {
9 console.error(message);
10 }
11 }
12
13 export class WarningLogPlugin implements LogPlugin {
14
15 name = 'Warning Log Plugin';
16 level = 'warn';
17
18 log(message: string) {
19 console.warn(message);
20 }
21 }
Dependency Injection 195
Now you are ready to register the service and its plugins with the main application module like in
the following example:
src/app/app.module.ts
Please note that the most important part that enables multiple injections is the “multi” attribute we
set to “true” when registering a provider.
Now let’s get back to our “CompositeLogService” and inject instances of all previously registered
plugins using the following format:
To demonstrate the instances, we are going to enumerate the injected collection and log all plugin
names to the browser console:
src/app/services/composite-log.service.ts
The service is ready for testing. The only thing we have left is to inject it somewhere. The main
application component is the best place to test all the newly introduced code quickly.
src/app/app.component.ts
1 import { CompositeLogService } from './services/composite-log.service';
2
3 @Component({...})
4 export class AppComponent {
5 constructor(private logService: CompositeLogService) {
6 }
7 }
Now if you run the application the console log is going to contain the following output:
In the real-life scenario, you would most probably want the log service to use different types of
plugins for certain purposes. Let’s now extend our service and introduce a “log” method redirects
logging calls to the plugins that support the corresponding levels.
src/app/services/composite-log.service.ts
1 @Injectable()
2 export class CompositeLogService {
3 ...
4
5 log(level: string, message: string) {
6 const logger = this.plugins.find(p => p.level === level);
7 if (logger) {
8 logger.log(message);
9 }
10 }
11 }
Dependency Injection 197
To test how it works you can even use the “log” method within the service itself. Update the
constructor to send a message once all the external plugins are enumerated:
src/app/services/composite-log.service.ts
1 @Injectable()
2 export class CompositeLogService {
3 constructor(@Inject(LOGGER_PLUGIN) private plugins: LogPlugin[]) {
4 if (plugins && plugins.length > 0) {
5 for (const plugin of plugins) {
6 console.log(`Loading plugin: ${plugin.name} (level: ${plugin.level})`);
7 }
8 this.log('warn', 'All plugins loaded');
9 }
10 }
11 }
For the sake of simplicity, we are going to use the “warn” level because we got only “warn” and
“error” loggers registered. Feel free to extend the collection of the loggers with the “info” or “debug”
one for example.
Once you run the web application, the main component should provide the following output to the
browser console:
Now you are ready to deal with multiple instances injected as collections and got a basic scenario
working in practice.
Optional dependencies
Previously in this chapter, we have successfully created a “CompositeLogService” service based on
multiple plugins injected with the same custom token. However, what happens when there are no
logger plugins registered within the application? Let’s comment out the plugin registration section
in the app module providers to see what happens at runtime.
Dependency Injection 198
@NgModule({
providers: [
CompositeLogService // ,
// { provide: LOGGER_PLUGIN, useClass: ErrorLogPlugin, multi: true },
// { provide: LOGGER_PLUGIN, useClass: WarningLogPlugin, multi: true }
]
})
Now if you rebuild and run your application it is going to crash with the following error in the
browser console:
The error above is an expected behaviour. Essentially, when you declare a dependency within
the constructor parameters, you instruct the Angular framework to ensure the corresponding
dependency indeed exists, and injection can happen.
There are scenarios when having an instance is not mandatory, and application can function without
it, like in our case with logger plugins - the log service can have default fallback behaviour in case
no plugins are present.
For those kinds of scenarios, the Angular framework provides us with the “@Optional” decorator
that allows making particular injections “optional” rather than “mandatory” during dependency
injection phase.
As you can see from the code above, we now have a possibility to check whether any plugins are
available, and perform the fallback behaviour if needed. Just for the demo purposes, we log the
information message to the console:
Dependency Injection 199
The “@Optional” decorator is a handy mechanism that helps you to prevent the runtime errors
when some content is missing or is not registered correctly.
Typically you are going to use this API only for concrete scenarios like unit testing or dynamic
content creation. You can get more detailed information including code examples in the following
article: Reflective Injector35 .
Summary
We have covered the main scenarios for service and provider registration and tested them in action.
You can also refer to the Dependency Injection36 article for even more detailed information on how
dependency injection works in Angular.
35
https://angular.io/api/core/ReflectiveInjector
36
https://angular.io/guide/dependency-injection#dependency-injection
Events
There are three main event cases we are going to review in this chapter. With Angular, you can raise
Component events, DOM events and Service events.
Source code
You can find the source code in the “angular/events37 ” folder.
To address all three scenarios let’s create a simple Panel component that consists of the Body, Header
and Footer. The Header and Footer are going to be separate components.
First, generate a new Angular application using the Angular CLI tool. Then execute the following
commands to generate the prerequisites:
ng g component panel
ng g component panel-header
ng g component panel-footer
Next, update the Panel component template like in the example below:
src/app/panel/panel.component.html
1 <app-panel-header></app-panel-header>
2
3 <p>
4 panel works!
5 </p>
6
7 <app-panel-footer></app-panel-footer>
Finally, we can replace the default auto-generated content of the main application template with the
Panel we have just created above:
37
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/events
Events 201
src/app/app.component.html
1 <app-panel>
2 </app-panel>
If you make a pause at this point and run the “ng serve –open” command you should see the following
output on the main page:
panel-header works!
panel works!
panel-footer works!
At this point, we got all the basic prerequisites for testing the events.
Component events
It is now time to improve our Header component. Let’s provide a way to set the content text for
the component to display when rendered, and a simple click event “contentClick” that gets emitted
every time user clicks the content.
src/app/panel-header/panel-header.component.ts
From the earlier chapters, you already know that we use “@Input” decorator for the class properties
we want to set or bind from the outside. We also need to use the “@Output” decorator to mark our
events.
Output Events
You can get more details on the component events in the Output Events section of the
Components chapter.
Also, note that in the example above we also add the “onContentClicked” method that is going to
raise our “contentClick” event.
Below is an example of a minimalistic Header component template we can use to display a “content”
value:
src/app/panel-header/panel-header.component.html
1 <p (click)="onContentClicked()">
2 {{ content }}
3 </p>
As you can see, we link the “click” event of the enclosed “p” element with the “onContentClicked”
handler that temporarily sends a message to the console log for testing/debugging purposes and also
invokes the “contentClicked” event that other components can use.
Also, we set a default value for the “content” property to the “Panel header” string. So at the runtime,
the content or application main page is going to look similar to the following:
Panel header
panel works!
panel-footer works!
However, we are not going to use the Header component directly. It is the Panel component that
needs it. So we should allow our Panel component to control the header content, and also react on
header click events. Let’s toggle the panel content as an example.
Edit the panel component class and add the “header” input property to hold the text for the Header,
and “displayBody” property to serve as a flag for showing and hiding the main panel content, like
in the example below:
Events 203
src/app/panel/panel.component.ts
1 import { ..., Input } from '@angular/core';
2
3 @Component({...})
4 export class PanelComponent {
5
6 @Input()
7 header = 'My panel header';
8
9 displayBody = true;
10 }
For the next step, let’s update the panel component template to link the Header properties with the
newly introduced class members:
src/app/panel/panel.component.html
1 <app-panel-header
2 [content]="header"
3 (contentClick)="displayBody = !displayBody">
4 </app-panel-header>
5
6 <ng-container *ngIf="displayBody">
7 <p>
8 panel works!
9 </p>
10 </ng-container>
11
12 <app-panel-footer></app-panel-footer>
You can now run the application and test your components by clicking the panel header text multiple
times. The panel should toggle its body content every time a header gets clicked.
Below is how the panel should look like by default, in the expanded state:
My panel header
panel works!
panel-footer works!
Also, the next example shows how the panel looks like in the “collapsed” state:
Events 204
My panel header
panel-footer works!
Congratulations, you just got the component events working, and tested them in practice. Now
feel free to extend the PanelFooterComponent and add similar “content” and “contentClick”
implementations.
src/app/panel/panel.component.ts
18 this.displayBody = !this.displayBody;
19 this.headerClick.next();
20 }
21
22 onFooterClicked() {
23 this.footerClick.next();
24 }
25
26 }
As you can see from the code above, we also get two methods to raise our events. The “onHead-
erClicked” method is still toggling the panel body before raising the “headerClick” event.
Next, our “<app-panel> component is going to watch the “contentClick” events of the child elements,
and emit events for developers. Update the HTML template and subscribe to the header and footer
events like in the example below:
src/app/panel/panel.component.html
1 <app-panel-header
2 [content]="header"
3 (contentClick)="onHeaderClicked()">
4 </app-panel-header>
5
6 <ng-container *ngIf="displayBody">
7 <p>
8 panel works!
9 </p>
10 </ng-container>
11
12 <app-panel-footer
13 [content]="footer"
14 (contentClick)="onFooterClicked()">
15 </app-panel-footer>
Finally, let’s test our panel events in action. Update your main application template and subscribe
to our newly introduced events for header and footer clicks:
Events 206
src/app/app.component.html
1 <app-panel
2 (headerClick)="onHeaderClicked()"
3 (footerClick)="onFooterClicked()">
4 </app-panel>
For the sake of simplicity we are going to log messages to browser console similar to the following:
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3
4 onHeaderClicked() {
5 console.log('App component: Panel header clicked');
6 }
7
8 onFooterClicked() {
9 console.log('App component: Panel footer clicked');
10 }
11
12 }
If you compile and run your web application with the “ng serve –open” command, you should be
able to see messages in the console every time a header or footer elements of the panel get clicked.
DOM events
With multiple levels of nesting, the re-raising of child events quickly turns into a challenging
and time-consuming process. Like with our previous Panel example, to wrap it with another
Angular component developers may have to watch for panel events and raise them for the own
implementation. That is where native DOM events with bubbling support come to the rescue.
If set to bubble, a DOM event gets raised for every element up against the parent hierarchy.
Developers get an opportunity reacting on events that occur in deeply nested components.
First, to raise a custom DOM event we need to get access to a native element of the component. You
can achieve that by injecting an “ElementRef” instance in the component constructor, and saving as
a private property called “elementRef”.
src/app/panel-header/panel-header.component.ts
Next, we need updating the “onContentClicked” handler to raise an additional event. You can leave
the code to raise the “contentClick” event, so that component supports raising two types of the
events at the same time. Let’s introduce a new “header-click” custom event that is set to bubble and
contains clicked content in the “details” section.
src/app/panel-header/panel-header.component.ts
1 @Component({...})
2 export class PanelHeaderComponent {
3
4 ...
5
6 onContentClicked() {
7 this.contentClick.next();
8
9 this.elementRef.nativeElement.dispatchEvent(
10 new CustomEvent('header-click', {
11 detail: {
12 content: this.content
13 },
14 bubbles: true
15 })
16 );
17 }
18 }
Events 208
We are now ready to see the newly introduced event in practice. Let’s update the main application
component template with a top-most “div” element that is going to listen to the “header-click” event.
That is a good case to test event handling and see how event bubbles up to non-Angular elements.
src/app/app.component.html
1 <div (header-click)="onDomHeaderClick($event)">
2 <app-panel
3 (headerClick)="onHeaderClicked()"
4 (footerClick)="onFooterClicked()">
5 </app-panel>
6 </div>
For testing purposes, we are going just to log the event details to the browser console.
1 @Component({...})
2 export class AppComponent {
3
4 ...
5
6 onDomHeaderClick(event) {
7 console.log(event);
8 }
9
10 }
Finally, if you run your web application at this point, and click the panel header content, the
following content should appear in the browser console output:
As you can see from the picture above you can access all data in the custom event including the
“detail” object we have created earlier, and that contains our component-specific information.
Dispatching the custom DOM events is an excellent option if you want to allow developers to wrap
your components, or you are using composite components and trying to avoid re-throwing multiple
events.
Events 209
Service events
When working with events in Angular, you can achieve a significant level of flexibility by utilising
the application services and service events.
Service-based events allow multiple components to communicate with each other regardless of the
component and DOM structure using the publish/subscribe approach.
Before we dive into details let’s use Angular CLI and generate a new service using the following
command:
ng g service panel
You need to manually register the newly generated service within one of your modules. For now,
let’s add the “PanelService” to the main application module in the “app.module.ts” file:
src/app/app.module.ts
Next, extend the service with a couple of events for header and footer clicks:
src/app/panel.service.ts
In the example above, we are using generic “Subject<T>” to allow both emitting and subscribing to
the same event. We are going to pass either “PanelHeaderComponent” or “PanelFooterComponent”
instance as the event argument.
Let’s update the “PanelHeaderComponent” class and emit the “headerClicked” event like in the
following example:
src/app/panel-header/panel-header.component.ts
As you can see, the component now injects the “PanelService” instance and saves a reference to the
private “panelService” property so that click handler can use to emit the corresponding event.
Subscribing to the event is also simple. The component, in our case main application one, injects the
“PanelService” and uses “headerClicked.subscribe” to wire the event handler code:
src/app/app.component.ts
12 );
13 }
14 }
Now if you run your web application and click the header you should see the following output in
the browser console:
Congratulations, you have just established a basic communication channel between header and
footer components with the rest of the application content. Many other components and services
now can subscribe and react to click events. Of course, we got over-simplified examples; you can
imagine more complex scenarios involving different events in your application.
Source code
You can find the source code in the “angular/events39 ” folder.
39
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/events
Modules
Directives
A directive is one of the core building blocks in Angular. It allows easy manipulation of the DOM
structure of HTML elements, change their appearance and layout, or even extend the behaviour if
needed.
Introduction
There are three types of directives in Angular you are going to work with:
• Components - also known as Directives with Templates, see Components chapter for a
detailed overview;
• Attribute Directives - extend elements, change the look and feel of the host elements;
• Structural Directives - change elements, manipulate DOM layout, add or remove elements
or completely change underlying view;
Let’s start with a new project to host various kinds of directives we are going to experiment with:
ng new directive-example
cd directive-example
ng serve --open
Attribute Directives
Attribute directives change appearance or behaviour of a host DOM element. Usually, they look like
regular HTML element attributes.
Let’s create a simple clickable directive and call it ‘Clickme’.
ng g directive directives/clickme
Angular CLI generates directive with unit test and updates main application module:
Directives 214
installing directive
create src/app/directives/clickme.directive.spec.ts
create src/app/directives/clickme.directive.ts
update src/app/app.module.ts
src/app/directives/clickme.directive.ts
Directives can interact with the host by listening to events and setting properties of the target
element.
1 interface HostListener {
2 eventName : string
3 args : string[]
4 }
src/app/directives/clickme.directive.ts
1 @HostListener('domEvent', ['$event'])
2 onEvent(event) {
3 // ...
4 }
Angular should be listening for the ‘domEvent’ event (if supported by host element) and invoke
onEvent method of the directive controller. The event handler is also going to get a reference to
the original DOM event, as we instruct Angular to map specific $event argument to the event
parameter. That means directive can inspect properties of the original event and even prevent default
behaviour or event bubbling if necessary.
The easiest way to demonstrate HostListener in action is by wiring a standard click event.
src/app/directives/clickme.directive.ts
Next put a div element to the app.component.html and decorate it with your appHighlight
directive:
src/app/app.component.html
1 <h1>
2 {{title}}
3 </h1>
4
5 <div class="click-area" appClickme>
6 Click me
7 </div>
You also need to have some space to click on; this is what we are going to use highlight-area CSS
class for:
Directives 216
src/app/app.component.css
1 .click-area {
2 width: 100px;
3 height: 100px;
4 background-color: beige;
5 }
Below is how the main page looks like after your changes:
You get the default ‘app works!’ label generated by the Angular CLI, and coloured div element. Now
if you click anywhere on the beige area the browser should display an alert:
So as you can see in the example above, with the help of HostListener, you can listen to any event
emitted by host element. We have added appClickme directive as an attribute of the div, and every
click on the div automatically triggers onClicked method in the ClickmeDirective. You can use
any target for the directive, for example:
src/app/app.component.html
1 <button appClickme>Click me</button>
2 <span appClickme>Click me</span>
3 <input type="text" appClickme>
1 interface HostBinding {
2 hostPropertyName : string
3 }
If you want your directive to change element attributes, you can use this decorator with class
methods like below:
src/app/directives/clickme.directive.ts
For example, if you apply directive to a div element, the property binding should cause the following
attributes rendering at run time:
src/app/app.component.html
Please note that if host property name parameter is not defined, then a class property name should
be taken as a fallback value.
Directives 218
1 @HostBinding()
2 title: string = 'element title';
This time, if you apply the directive to the input element, for instance, you should see the title
property name as an attribute of the host:
Keep in mind that in this case, the property should exist for a given element. Angular should throw
an error if you try to bind a missing property. Let’s try to bind a value property to demonstrate this
behaviour.
src/app/directives/clickme.directive.ts
1 ...
2 export class ClickmeDirective {
3
4 @HostBinding()
5 value: string = 'input value';
6
7 ...
8 }
You may still have a click area example on the main page, or you can do it once again:
However, if you replace div with an input element that natively supports value property, you should
get it rendered properly:
Directives 219
You can, however, fix the issue and provide compatibility with all HTML elements by utilising
attr.value instead of value for the property binding:
src/app/directives/clickme.directive.ts
1 ...
2 export class ClickmeDirective {
3
4 @HostBinding('attr.value')
5 value: string = 'input value';
6
7 ...
8 }
In this case you are going to get the following HTML when both <input> and <div> are present on
the page:
src/app/app.component.html
Your main application component page should now render without errors.
You bind single style attribute values using @HostBinding('style.<attribute>) format, where
<attribute> is a valid name of the CSS style attribute.
Directives 220
src/app/directives/clickme.directive.ts
1 ...
2 export class ClickmeDirective {
3 ...
4 @HostBinding('style.background-color')
5 background: string = 'lightblue';
6 }
Now the directive is painting its host element’s background into light-blue.
src/app/app.component.html
Instead of binding single style attributes, you may want to operate CSS class names, to be able
providing external themes, or separating presentation layer from directive implementation. It can
be achieved utilising @HostBinding('class.<class-name>') where <class-name> is the name of
the CSS class.
Note that having corresponding CSS class implementation is optional if you directive is not enforcing
styles directly. Developers can choose whether to implement or override the class, or leave defaults.
You can bind class names to the boolean values or expressions. Angular appends provided CSS class
name to the host element if the resulting value is true, and automatically removes it if value changes
back to false.
Directives 221
src/app/directives/clickme.directive.ts
1 ...
2 export class ClickmeDirective {
3 ...
4
5 @HostBinding('class.is-selected')
6 isSelected: boolean = true;
7 }
src/app/app.component.html
So this is how Angular renders component at run time. Note the class value now has both click-
area we defined manually, and is-selected class provided by the directive controller.
src/app/app.component.html
Typically you are going to apply or change CSS classes of the host element as a response to the host
events wired by HostBinding decorators. For example, the directive can listen to mouse events and
toggle hovered styles:
src/app/directives/clickme.directive.ts
1 ...
2 export class ClickmeDirective {
3 ...
4
5 @HostBinding('class.is-hovered')
6 isHovered: boolean = false;
7
8 @HostListener('mouseenter')
9 onMouseEnter() {
Directives 222
10 this.isHovered = true;
11 // other code if needed
12 }
13
14 @HostListener('mouseleave')
15 onMouseLeave() {
16 this.isHovered = false;
17 // other code if needed
18 }
19 }
Our directive toggles the isHovered property value upon mouse enter and leave, but it does not
directly change the way its host element looks. Instead, you or developers that use your directive
can optionally add a custom is-hovered CSS class to alter how the element looks and behaves on
mouse interaction.
The example below adds a thin dashed border to the element when a user hovers it:
src/app/app.component.css
1 .is-hovered {
2 border: 1px dashed darkblue;
3 }
You can now run the application and move the mouse cursor in and out of the click area.
Of course, you can control hover styles in pure CSS. The code above is more a simple demonstration
of capabilities to give you more ideas on what is possible with HostListener and HostBinding
combined.
Source code
You can find the source code in the “angular/directives/directive-example40 ” folder.
40
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/directive-example
Directives 223
NgStyle
The NgStyle directive is used to modify CSS style attributes of the host element or component.
1 <element [ngStyle]="OPTIONS">
2 ...
3 </element>
Where OPTIONS is an object literal NgStyle that binds and maps properties to style attributes. Object
keys should represent style names with an optional .<unit> suffix, for example, width.px, font-
style.em.
You should see three buttons with custom styles once you run the application:
In this case, you declare object literal within the component class implementation, for example:
Directives 224
src/app/app.component.ts
1 ...
2 export class AppComponent {
3 ...
4 inputStyle = {
5 'border': '1px green solid',
6 'background-color': 'white',
7 'color': 'blue'
8 };
9 }
That allows you to compose styles based on some other conditions dynamically.
Source code
You can find the source code in the “angular/directives/attribute-directives41 ” folder.
NgClass
The NgClass directive allows binding CSS class names on an HTML element.
1 <element [ngClass]="OPTIONS">
2 ...
3 </element>
With this format you specify a string expression that corresponds to a CSS class name:
41
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/attribute-directives
Directives 225
1 <element [ngClass]="'class1'">
2 ...
3 </element>
You can also provide multiple class names per single space delimited string expression:
This format is very similar to that of NgStyle one. All object keys are CSS class names and get added
to host element only if value evaluates to a truthy value. In a case of a non-truthy value, Angular
removes class names from the host.
1 <element [ngClass]="{
2 'class1': true,
3 'class2': false,
4 'class3': true }">
5 ...
6 </element>
Binding to Array
In all the cases described above you can also bind directive options to underlying controller
properties or methods:
Directives 226
src/app/app.component.html
1 <element [ngClass]="currentClass">...</element>
2 <element [ngClass]="getClassObj()">...</element>
3 <element [ngClass]="getClassArr()">...</element>
src/app/app.component.ts
Source code
You can find the source code in the “angular/directives/attribute-directives42 ” folder.
NgNonBindable
You use NgNonBindable directive to switch off Angular evaluating code or binding values for a
particular element and its content. For example if you want displaying source code:
42
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/attribute-directives
Directives 227
1 <h2>NgNonBindable</h2>
2 <pre>
3 You can bind <strong>title</strong> property like following:
4 <code ngNonBindable>
5 <div>{{title}}</div>
6 </code>
7 </pre>
Please note that NgNonBindable is a very specific directive, and typically you are not going to use
it often, if at all.
Source code
You can find the source code in the “angular/directives/attribute-directives43 ” folder.
Structural Directives
Structural directives allow you to control how element renders at run time.
• NgIf
• NgFor
• NgSwitch
43
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/attribute-directives
Directives 228
NgIf
You are going to use NgIf directive when you want to display or hide decorated element based on
condition.
The most common usage format for this directive is
1 <element *ngIf="<condition>">
2 ...
3 </element>
src/app/app.component.ts
Next, put a <button> element to the component template, this button should toggle the extraCon-
tent value on each click.
src/app/app.component.html
Finally, let’s add some content that should be displayed only when extraContent property value
gets set to true
src/app/app.component.html
1 <div *ngIf="extraContent">
2 <h2>Extra content comes here (ngIf)</h2>
3 </div>
Now if you run the application you should be able to toggle additional content by clicking the button
multiple times.
Directives 229
Very often you may need two different templates within the component based on the condition
evaluation. Traditionally developers are using separate NgIf directives assigned to truthy and falsy
results of the same expression:
The NgIf directive also supports else blocks for showing alternative content when the condition
expression evaluates to a falsy value. In this case, you need to provide a reference to a separate
ng-template:
To see that on practice return to the project created earlier and add the additional template:
src/app/app.component.html
1 <ng-template #emptyView>
2 <h3>No extra content available</h3>
3 </ng-template>
Template above can be referenced by emptyView id. Now update the main element to utilize the
newly created template:
Directives 230
src/app/app.component.html
1 <div *ngIf="extraContent; else emptyView">
2 <h2>Extra content comes here (ngIf)</h2>
3 </div>
If you run the application right now and click the Toggle extra content button, you should see
the content of the emptyView template.
It is possible to store both templates as external references. By default, Angular treats inline template
as a then block, but you can define it explicitly using the following syntax:
src/app/app.component.html
1 <button (click)="extraContent = !extraContent">
2 Toggle extra content
3 </button>
4 <div *ngIf="extraContent; then mainView else emptyView"></div>
5
6 <ng-template #mainView>
7 <h2>Extra content comes here (ngIf)</h2>
8 </ng-template>
9
10 <ng-template #emptyView>
11 <h3>No extra content available</h3>
12 </ng-template>
Source code
You can find the source code in the “angular/directives/structural-directives44 ” folder.
44
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/structural-directives
Directives 231
NgFor
The main purpose of NgFor directive is to display iterable collections utilising a custom HTML
template for each entry.
Binding to arrays
Let’s start with a simple example that best demonstrates NgFor directive in action.
src/app/app.component.html
1 <ul>
2 <li *ngFor="let num of [1,2,3,4,5]">
3 <span>{{num}}</span>
4 </li>
5 </ul>
In the example above we are using NgFor directive with a collection of five numbers to render an
unordered (bulleted) list. Angular treats the content of the list item as a template and repeatedly
applies to each array entry.
You should see the following HTML once application compiles and restarts:
If you inspect the source code of the page you should see the structure similar to the one below:
Directives 232
1 <ul>
2 <li>
3 <span>1</span>
4 </li>
5 <li>
6 <span>2</span>
7 </li>
8 <li>
9 <span>3</span>
10 </li>
11 <li>
12 <span>4</span>
13 </li>
14 <li>
15 <span>5</span>
16 </li>
17 </ul>
src/app/app.component.ts
We are going to have two objects in the users collection. The list entry template should now look
like the next one:
Directives 233
src/app/app.component.html
1 <ul>
2 <li *ngFor="let user of users">
3 <div>{{user.firstName + ' ' + user.lastName}}</div>
4 </li>
5 </ul>
Once your project and compiled and reloaded you should see a list of full user names:
The NgFordirectives exports several values that you can map to the the local template variables:
• index: number holds the numeric position of the current array item
• first: boolean indicates whether the current array item is the first one
• last: boolean indicates whether the current array item is the last one
• even: boolean indicates whether current array item’s position index is even
• odd: boolean indicates whether current array item’s position index is odd
You can use these additional values to improve the user interface. For example, let’s add row numbers
to the user list:
src/app/app.component.html
1 <ul>
2 <li *ngFor="let user of users; let i = index">
3 <div>
4 <span>{{i + 1}}: </span>
5 <span>{{user.firstName + ' ' + user.lastName}}</span>
6 </div>
7 </li>
8 </ul>
Directives 234
Note the use of let i = index, this is where you bind index property to a local template variable
i. That allows you displaying it via <span>{{i + 1}}: </span>
You can use all local variables for conditional styling and layout. For example, you may want
drawing a table or a list with stripes based on even and odd value, rendering some header or footer
for the first and last items.
It is possible to use all variables at the same time separating them by semicolons:
src/app/app.component.html
1 <ul>
2 <li *ngFor="let user of users; let isFirst = first; let isLast = last;">
3 <span>{{user.firstName + ' ' + user.lastName}}</span>
4 <span *ngIf="isFirst">(this is the first item)</span>
5 <span *ngIf="isLast">(this is the last item)</span>
6 </li>
7 </ul>
When running the application, you should notice that first and last items in the list get different text
appended to them based on the condition expression.
Every time a collection changes Angular drops existing DOM elements and the renders entire set of
new ones. That is fine when you are dealing with static collections. However, you may see significant
performance drops when using frequently changed arrays, for example when using dynamic editing
or populating collections from the server. The complexity of item templates can also slow down
rendering and affect overall application performance.
Angular provides a special trackBy feature that allows you to track underlying objects by unique
id values and to rebuild DOM elements only for the entries that change. For many scenarios that
often ends up with a huge performance boosts as large portions of the DOM remain unchanged.
The trackBy value should go after the main ngFor expression and must be separated by a semicolon:
Directives 235
src/app/app.component.html
1 <ul>
2 <li *ngFor="let user of users; trackBy: trackByUserId">
3 <span>{{user.firstName + ' ' + user.lastName}}</span>
4 </li>
5 </ul>
The trackBy always binds to a component method having a numeric index and a current collection
object as parameters:
src/app/app.component.ts
1 trackByUserId(index: number, user: any) {
2 return user.id;
3 }
In the example above we tell Angular to keep track of users in the list based on the id property value
so that it can better detect what item has been added or removed.
NgSwitch
The NgSwitch directive is used for conditional rendering of element templates depending on the
expression value. You can treat it as an advanced NgIf directive with multiple else clauses.
You need three separate directives to make NgSwitch work:
1 <element [ngSwitch]="expression">
2 <element *ngSwitchCase="condition1">...</element>
3 <element *ngSwitchCase="condition2">...</element>
4 <element *ngSwitchDefault>...</element>
5 </element>
To check how NgSwitch operates in practice let’s build a simple component that displays different
UI layouts based on the selected role of the user. Open the app.component.ts and add the roles
and selectedRole properties like below:
Directives 236
src/app/app.component.ts
1 roles = [
2 { id: 0, name: 'empty' },
3 { id: 1, name: 'unknown' },
4 { id: 2, name: 'user' },
5 { id: 3, name: 'guest' },
6 { id: 4, name: 'administrator' }
7 ];
8
9 selectedRole = 'empty';
Next place a <select> element to be able to select a role from the dropdown list:
src/app/app.component.html
1 <select [(ngModel)]="selectedRole">
2 <option *ngFor="let role of roles">{{role.name}}</option>
3 </select>
src/app/app.component.html
1 <div [ngSwitch]="selectedRole">
2
3 <div *ngSwitchCase="'administrator'">
4 Special layout for the <strong>administrator</strong> role
5 </div>
6
7 <div *ngSwitchCase="'guest'">
8 Special layout for the <strong>guest</strong> role
9 </div>
10
11 <div *ngSwitchDefault>
12 General UI for the roles
13 </div>
14
15 </div>
As a result, we are going to use dedicated UI templates for administrator and guest roles:
Directives 237
As you can see from the examples above, the NgSwitch directive is a poweful and flexible way for
conditional element rendering in Angular.
ng g directive repeat
The command above gives you a basic RepeatDirective implementation and a simple unit test. It
also modifies the main application module app.module.ts to register new directive within module
declarations.
installing directive
create src/app/repeat.directive.spec.ts
create src/app/repeat.directive.ts
update src/app/app.module.ts
src/app/repeat.directive.ts
Note that Angular CLI automatically prepends default app prefix to the directive selector value. This
helps us to avoid name conflicts with the existing directives from either standard Angular or third
party libraries.
You can use your new directive with any HTML element like the following:
1 <element *appRepeat>...</element>
It is also possible to use the plain format without Angular’s syntactic sugar:
1 <ng-template [appRepeat]>...</ng-template>
src/app/repeat.directive.ts
14 this.viewContainer.createEmbeddedView(this.templateRef);
15 }
16 }
17
18 }
First of all, you get access to the underlying template by injecting TemplateRef instance and using
it as a local reference. You also save a reference to the ViewContainerRef instance as you need it to
turn the template reference to a real HTML element.
As a second step, we define an appRepeat setter that is going to receive configuration settings from
the directive value. Note that to map an attribute value to a function parameter the directive setter
must have the same name as the HTML selector value. In our case, it is appRepeat one. That gives
you the possibility using the *directive="expression" format similar to the one below:
1 <element *appRepeat="5">...</element>
The child content of the decorated element gets treated as a template. With the help of ViewCon-
tainer and Template references we turn it into the HTML DOM multiple times:
1 <div *appRepeat="5">
2 <div>Hello</div>
3 </div>
1 Hello
2 Hello
3 Hello
4 Hello
5 Hello
The Hello template is rendered 5 times as we expect it. However, rendering HTML elements without
data context is not very useful. Let’s extend our directive with a custom context set to the position
index of the element:
Directives 240
Now you can define a local variable for your template bound to the data context:
You can also change example able to be like the next one:
This time the application output when using any of these formats is going to be as following:
1 Hello 0
2 Hello 1
3 Hello 2
4 Hello 3
5 Hello 4
Let’s get back to the data context again. Any local variable that has no value defined is going to point
to the default $implicit value. You can define as many variables as needed within your directive
implementation. For example, try to expose first and last variables similar to those of the NgFor
directive:
Next you can wire newly created local variables and reuse them within the template:
Directives 241
src/app/app.component.html
1 <div *appRepeat="5; let idx; let isFirst = first; let isLast = last;">
2 <div>
3 Hello {{idx}}
4 <span *ngIf="isFirst">(first)</span>
5 <span *ngIf="isLast">(last)</span>
6 </div>
7 </div>
Once the application compiles and runs you should see the following result:
1 Hello 0 (first)
2 Hello 1
3 Hello 2
4 Hello 3
5 Hello 4 (last)
You can keep enhancing your directive with more complex layout and behaviour.
ng new app-attach-directive
cd app-attach-directive/
ng g directive attach
src/app/attach.directive.ts
Angular CLI automatically prepended the name with the default app prefix to avoid naming
conflicts, so you are going to use appAttach in HTML. Before implementing the directive let’s
prepare the testing environment. We are going to use main application component to see all the
changes in action.
src/app/app.component.ts
src/app/app.component.html
For demonstration and testing purposes we also provide some basic styling via attach-area CSS
class. The light blue box on the page shows the ‘clickable’ area wired with the appAttach directive.
Directives 243
src/app/app.component.css
1 .attach-area {
2 width: 200px;
3 height: 50px;
4 background-color: lightblue;
5 cursor: pointer;
6 }
ng serve --open
You can leave the terminal running in the background. Angular CLI keeps watching for files and
automatically recompiles and reloads application upon any changes.
Now provide the initial directive implementation like the following:
src/app/attach.directive.ts
First of all, you reserve a private property upload to store reference on the original <input> element.
The directive also needs a reference to its host element and an instance of the Renderer class, to
modify HTML layout. We inject both in the constructor and store as private properties el and
renderer.
Once directive gets initialized it creates a new hidden <input type="file"> HTML element next to
the host one. You can invoke its methods from the code, for example, a click event that causes a file
dialogue to appear. Let’s listen to the host’s click event and redirect it to the hidden input element
like below:
src/app/attach.directive.ts
1 @HostListener('click', ['$event'])
2 onClick(event: Event) {
3 if (this.upload) {
4 event.preventDefault();
5 this.upload.click();
6 }
7 }
Once the user selects a file in the dialogue, our directive should know about that. We can achieve
that by listening to the change event of the input element and accessing the file list as soon as the
event occur.
Directives 245
src/app/attach.directive.ts
1 ngOnInit(): void {
2 ...
3 this.upload.addEventListener('change', e => this.onAttachFiles(e));
4 }
5
6 private onAttachFiles(e: Event): void {
7 const input = (<HTMLInputElement>e.currentTarget);
8 const files = this.getFiles(input.files);
9 this.raiseEvent(files);
10 }
Inside the handler, you extract the list of File objects from the event using the getFiles method
with the following implementation:
src/app/attach.directive.ts
Typically you may want to create an array of Model classes based on the File instances, or wrapping
files into some other components. For the sake of simplicity let’s just return File objects as they are.
There can be multiple approaches to handling file uploads. The directive might be doing all the
upload work itself, it could be triggering some application service, or it can raise DOM events for
other components react on them. We are going to take the latter approach and raise a custom attach-
files event.
The raiseEvent method receives an array of File instances and raises attach-files event like
below:
Directives 246
src/app/attach.directive.ts
You can now handle this custom event from either host element or any other HTML element up
against the parent hierarchy. Let’s create a list of files the user attaches using our directive.
src/app/app.component.html
As you can see from the layout above, we wire attach-files event with the onAttachFiles method
of main application component class and are going to store attached files in the files collection.
Directives 247
src/app/app.component.ts
Once application recompiles and reloads you can to test new behaviour by attaching a file to see its
name appear in the list:
Another feature you may want to add to the directive is the ability to toggle single and multiple
file selection support. The standard <input type="file"> element allows this via the multiple
HTML attribute. We can introduce the same property for the directive and propagate its value to
the enclosed upload element like this:
Directives 248
src/app/attach.directive.ts
1 ...
2 export class AttachDirective implements OnInit {
3 ...
4
5 @Input()
6 multiple: boolean;
7
8 ngOnInit(): void {
9 ...
10 if (this.multiple) {
11 this.upload.setAttribute('multiple', '');
12 }
13 }
14 }
That allows us binding to or setting [multiple] property value on an HTML element alongside
appAttach directive.
src/app/app.component.html
Now if you run the application you should be able to select multiple files in the file dialogue. The
list at the bottom should display names for all of them.
Directives 249
So we got a directive that attaches to an HTML element, alters its behaviour and even redirects user
events to an additional hidden content. You can keep extending the implementation if needed, for
example adding new properties, or controlling the types of files to be selected.
Source code
You can find the source code in the “angular/directives/app-attach-directive45 ” folder.
45
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/app-attach-directive
Walkthrough: Upload Directive
In this walkthrough, we are going to create a directive that turns its host element into a drop target
for files.
Our directive itself does not upload files to a server, but instead, it is going to be responsible for
detecting dropped Files and raising special DOM events. Other parts of the application (components,
directives, services) can then react on those events and perform additional actions if needed.
Let’s start by generating a new application and calling it app-upload-directive:
ng new app-upload-directive
cd app-upload-directive/
You can test application was created successfully by running it with the default browser:
ng serve --open
Now let’s create an upload directive from the Angular CLI blueprint:
ng g directive directives/upload
The ng tool should put the code and unit tests into the src/app/directives folder, and should also
update main application module.
installing directive
create src/app/directives/upload.directive.spec.ts
create src/app/directives/upload.directive.ts
update src/app/app.module.ts
Angular CLI generates the following code for the new directive:
Walkthrough: Upload Directive 251
upload.directive.ts
ng test
There should be an additional test for UploadDirective class: should create an instance. This test
is a good starting point for unit testing your future directive.
We are going to decorate a simple <div> element and turn it into a file drop area.
Walkthrough: Upload Directive 252
app.component.html
1 <h1>
2 {{title}}
3 </h1>
4
5 <div class="my-drop-area">
6 <span>Drop your files here...</span>
7 </div>
As it is a <div> element let’s add some style to be able distinguishing it. Setting fixed size and
background colour should be more than enough for now.
app.component.css
1 .my-drop-area {
2 width: 150px;
3 height: 50px;
4 background-color: lightgray;
5 }
The main application page now should look like the following:
upload.directive.ts
1 import { ..., HostBinding, HostListener } from '@angular/core';
2
3 export class UploadDirective {
4
5 @HostBinding('class.app-upload__dragover')
6 isDragOver: boolean;
7
8 }
Every time isDragOver becomes true the host element gets a CSS class app-upload__dragover
applied to it. Once it is set back to false the CSS class is automatically removed.
Now add the following code to the directive implementation:
Walkthrough: Upload Directive 253
upload.directive.ts
1 @HostListener('dragenter')
2 onDragEnter() {
3 this.isDragOver = true;
4 }
5
6 @HostListener('dragover', ['$event'])
7 onDragOver(event: Event) {
8 if (event) {
9 event.preventDefault();
10 }
11 this.isDragOver = true;
12 }
13
14 @HostListener('dragleave')
15 onDragLeave() {
16 this.isDragOver = false;
17 }
The code above performs a simple drag management to update isDragOver state and so host element
style. For dragenter and dragover events we are going to enable additional styles, and disable on
dragleave.
In order to turn host element into a drop zone you also need handling drop event:
upload.directive.ts
1 @HostListener('drop', ['$event'])
2 onDrop(event: DragEvent) {
3 event.preventDefault();
4 event.stopPropagation();
5 this.isDragOver = false;
6 }
Keep in mind that directive only adds app-upload__dragover class to the host element, it does not
modify element’s style attributes directly. Developers that are using your directive should be able
to define look and feel of the decorated element at the application level.
Traditionally file drop areas use dashed borders when being dragged over, let’s make our element
draw a thin blue border and also change background colour:
Walkthrough: Upload Directive 254
app.component.css
1 .app-upload__dragover {
2 border: 1px dashed blue;
3 background-color: white;
4 }
And you need decorating div element with the directive to see it in action:
app.component.html
The element should be changing the style when you drag a file over its area:
Now that we have drag events wired with element styles, it is time to implement drop handling.
Find the onDrop method we have introduced earlier and replace with the following code:
upload.directive.ts
1 @HostListener('drop', ['$event'])
2 onDrop(event: DragEvent) {
3 event.preventDefault();
4 event.stopPropagation();
5 this.isDragOver = false;
6
7 const files = this.collectFiles(event.dataTransfer);
8 console.log(files);
9 this.onFilesDropped(files);
10 }
Walkthrough: Upload Directive 255
Once user drops files on the host element, the directive is going extract information on files from
the DataTrasfer instance (collectFiles), and pass discovered files to the onFilesDropped method
to raise corresponding DOM events.
DataTransfer
The DataTransfer object is used to hold the data that is being dragged during a drag and
drop operation. It may hold one or more data items, each of one or more data types. For
more information see DataTranfer46 article.
Essentially we need to extract File objects into a separate collection in a safe manner:
upload.directive.ts
Finally, we need to raise an upload-files event to allow other components handling it. We are
going to create an instance of the CustomEvent for that purpose.
CustomEvent
The CustomEvent interface represents events initialized by an application for any purpose.
For more information see CustomEvent47 article.
The directive also needs access to the native DOM element of the host to raise a custom event,
so importing ElementRef instance for the constructor is required. Reference to the native HTML
element should be injected as a constructor parameter and used as private property el.
46
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
47
https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
Walkthrough: Upload Directive 256
upload.directive.ts
You need using Element.dispatchEvent() to raise the upload-files event. As part of the Cus-
tomEvent interface, we can define custom detail value, so that external event handlers can get
additional information.
We are going to provide an object containing a list of files (files) the user has dropped on the host
element, and reference to the directive (sender) instance that raised the event.
upload.directive.ts
Note the bubbles property being set to true to enable event bubbling. It indicates whether the given
event bubbles up through the DOM or not. In our case we allow any HTML element up the visual
tree handle this event or stop its propagation.
You can get more details on custom events in the article Creating and triggering events48 .
Now it is time to handle upload-files event at the application level. Open the app.component.html
file and add onUploadFiles event handler like shown below:
48
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
Walkthrough: Upload Directive 257
app.component.html
1 <div class="my-drop-area" appUpload (upload-files)="onUploadFiles($event)">
2 <span>Drop your files here...</span>
3 </div>
Given that our event supports bubbling any element in the parent hierarchy can handle it. You could
also define the handler as following:
app.component.html
1 <div (upload-files)="onUploadFiles($event)">
2 <div class="my-drop-area" appUpload>
3 <span>Drop your files here...</span>
4 </div>
5 </div>
To see the content of the event, we are going to put the console.log for now:
app.component.ts
1 onUploadFiles(event: CustomEvent) {
2 console.log(event);
3 }
Now if you compile and run the web application, and drop one or multiple files on the drop are, the
console output should be similar to the following one:
As you may see from the picture above, the handler is getting CustomEvent that holds details value
with a File collection, reference to the directive instance, and several standard properties.
Let’s try adding some visualisation and display a list of previously uploaded files on the main page.
Append the following HTML to the app.component.html file content:
Walkthrough: Upload Directive 258
app.component.html
1 ...
2 <div>
3 <h3>Uploaded files:</h3>
4 <ul>
5 <li *ngFor="let file of uploadedFiles">
6 {{file}}
7 </li>
8 </ul>
9 </div>
List element binds to the uploadedFiles collection holding uploaded file names. The upload-files
event handler just collects the file names and fills the collection.
app.component.ts
Now run your web application or switch to the browser if running the live development server, and
try dropping one or multiple files several times. You should see file names appear in the list below
the drop area like shown on the picture below:
Walkthrough: Upload Directive 259
Source code
You can find the source code in the “angular/directives/app-upload-directive49 ” folder.
When working on your Angular web application, you may want some other component or service
handle the upload-files event and perform actual uploading to a backend server, preferably
utilising the injectable service. It is always a good practice splitting functionality into small
interchangeable building blocks, each doing one thing at a time.
49
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/directives/app-upload-directive
Pipes
Pipes transform template data into display values at runtime.
Source code
You can find the source code in the “angular/pipes/standard-pipes50 ” folder.
Introduction
There are many cases when you may want to perform minor changes to the values that users see
when the application is running. Text transformation is an excellent example. Imagine that you need
to display a text label or field in the upper case, regardless of how the user originally typed it. You
can do that with the help of CSS by wrapping the text into some HTML element with style or class
applied. Alternatively, you can use the UpperCasePipe, a built-in Angular pipe that transforms text
values to upper case.
Pipes have the following usage formats:
You append the name of the pipe to the expression separating them with the pipe operator ( | ).
Let’s now build a quick example to see the UpperCasePipe in action:
1 <h2>Uppercase</h2>
2 <div>
3 {{'Hello world' | uppercase }}
4 </div>
The Hello world example above renders to the following result at runtime:
50
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/pipes/standard-pipes
Pipes 261
No matter how you define the text value, for instance, “hEllo worlD” or “hello WOLRD”, it is going
to be uppercased when the application runs.
It is also possible to provide multiple parameters given the pipe supports that. In this case you are
going to separate all values by a colon ( : ) symbol, for example:
The simplest example is date value conversion. There are many different formats you can choose to
display a date on the HTML page. Angular provides a DatePipe pipe out of the box that helps you
converting dates to various string representations.
Add the following currentDate property to the AppComponent class in the app.component.ts:
1 ...
2 export class AppComponent {
3 ...
4 currentDate: number = Date.now();
5 }
The property holds the current date value evaluated with the Date.now() function. You can take the
next HTML template to display the date:
Pipes 262
1 <h2>Date</h2>
2 <div>
3 Date: {{ currentDate | date }}
4 </div>
We transform currentDate value with a DatePipe pipe using default parameters. If you run the
application right now the output should be similar to the following one:
Note that the value of the date depends on your current date.
Typically you may want to render dates in short or long forms, or even with a custom format. For
that purpose the DatePipe in Angular supports format parameter to fine-tune the resulting output.
Let’s add two more examples to the page:
1 <div>
2 Short date: {{ currentDate | date:'shortDate' }}
3 </div>
4 <div>
5 Long date: {{ currentDate | date:'longDate' }}
6 </div>
Chaining Pipes
You can use multiple value transformations by chaining pipes together. The pipes are combined
utilising the same pipe operator ( | ), and every pipe can optionally have own properties to alter its
behaviour.
Pipes 263
The simple use case to demonstrate the chaining is using Date and Uppercase pipes at the same time.
Append the following code to the main application template:
1 <div>
2 Long date (uppercase): {{ currentDate | date:'fullDate' | uppercase }}
3 </div>
So you can have quite powerful combinations of pipes transforming your data values as you need
them to be.
Built-in Pipes
Angular provides a set of handy pipes out of the box. All of them can save your time and significantly
reduce coding efforts related to values transformation and formatting.
You can notice that many pipes have similar usage format, so it should be pretty easy to remember
most of the use cases. Let’s start with simple pipes and proceed to more complex by the end of this
chapter.
UpperCase
The UpperCasePipe pipe transforms text to upper case and has the following usage format:
<element>
{{ <string_expression> | uppercase }}
</element>
You can use it with text expressions inside HTML element body or property binding:
Pipes 264
1 <h2>Uppercase</h2>
2 <ul>
3 <li>'Hello world' => {{ 'Hellow world' | uppercase }}</li>
4 </ul>
LowerCase
The LowerCasePipe pipe transforms text to lower case and has the following usage format:
<element>
{{ <string_expression> | lowercase }}
</element>
<h2>Lowercase</h2>
<ul>
<li>'hELLO wORLD' => {{ 'hELLO wORLD' | lowercase }}</li>
</ul>
TitleCase
The TitleCasePipe converts input text to the title case. That means that first character gets
converted to upper case while the rest of the string becomes lowercased.
Pipes 265
<element>
{{ <text_expression> | titlecase }}
</element>
Converting to title case becomes handy when dealing with usernames. Your application may have
an input form, like a personal profile, where users can enter their first and last names for instance.
Regardless of the actual value, whether it is “john” or “jOHN”, you can render it like a “John” at run
time:
1 <h2>Titlecase</h2>
2 <ul>
3 <li>'jOHN' => {{ 'jOHN' | titlecase }}</li>
4 </ul>
Date
You have already seen the DatePipe usage earlier in this chapter. This pipe allows you to format a
date value using a custom or one of the predefined locale rules.
<element>
{{ <date_expression> | date[:format] }}
</element>
According to the Angular documentation you can provide a custom date format based on the
following rules:
51
https://www.w3.org/TR/NOTE-datetime
Pipes 266
As you can see from the table above, there may be plenty of different combinations depending on
how you want a date and time presented. If you want to get a year portion as a full 4-digit value you
are going to take y symbol (2017), or yy for a 2-digit only (17). Some of the date components support
multiple presentations. Assuming your browser is set to en-UK locale, for example, you can display
September month as S using its narrow form via L symbol, short form Sep using MMM symbol, long
September form with MMMM, or numeric forms 9 and doble-digit 09 by means of M and MM symbols
respectively.
Note that you can use any other symbol as a date component separator, for instance:
• dd/MM/yy ⇒ 13/04/79
• dd-MM-yy ⇒ 13-04-79
• dd=MM=yy ⇒ 13=04=79
Here’s a basic example you can try with the main application component:
Pipes 267
The Date pipe is going to use the end user’s locale when formatting time zones. Note that when
you use an ISO string52 with no time portion, for example, 1879-03-14, the pipe does not apply time
zone offset.
<element{{'1879-03-14' | date:'yMMMdjms'}}</element>
<!-- Mar 14, 1879, 12:00:00 AM -->
Internationalisation API
This pipe is using Internationalisation API that may not be fully supported by all modern
browsers. You can use Polyfill.io53 service to get the Intl polyfill only if your user’s current
browser needs it.
Also, you can see details and additional options at the project side: Intl.js54
The DatePipe pipe comes with the following predefined rules to help you formatting the dates:
52
https://www.w3.org/TR/NOTE-datetime
53
https://cdn.polyfill.io/v2/docs/
54
https://github.com/andyearnshaw/Intl.js/
Pipes 268
You can use the following HTML template and corresponding component property birthday to test
all the rules:
Decimal
The DecimalPipe pipe formats a number as text, taking into account user’s locale and optionally
custom display format.
The usage format of the pipe is as follows:
<element>
{{ <number_expression> | number[:digitFormat] }}
</element>
minIntegerDigits.minFractionDigits-maxFractionDigits
Minimum numbers
By setting the minimum numbers like minIntegerDigits and minFractionDigits, you
enforce resulting value to have a certain size. The pipe automatically adds leading or
trailing zeros if the actual value is less than expected.
For example applying a minimum number of 3 to the value 1 results in 003 text rendered
by the pipe.
Let’s now see how this pipe works in practice. Open existing or create a new Angular application
and put the following block to the main application component template:
1 <h2>Decimal (12.123456)</h2>
2 <ul>
3 <li>number => {{ 12.123456 | number }}</li>
4 <li>number:'2.1-2' => {{ 12.123456 | number:'2.1-2' }}</li>
5 <li>number:'3.0-0' => {{ 12.123456 | number:'3.0-0' }}</li>
6 <li>number:'3.10-15' => {{ 12.123456 | number:'3.10-15' }}</li>
7 </ul>
Please note the last case in the example above. It demonstrates how leading, and trailing zeros get
added to the output based on conditions.
Internationalisation API
This pipe is using Internationalisation API that may not be fully supported by all modern
browsers. You can use Polyfill.io55 service to get the Intl polyfill only if your user’s current
browser needs it.
Also, you can see details and additional options at the project side: Intl.js56
Currency
The CurrencyPipe pipe formats a number as a currency text, taking into account user’s locale and
optionally custom display format.
You can use this pipe with three optional parameters utilising the following format:
<element>
{{ <number_expression> | currency[:code[:symbol[:digitFormat]]] }}
</element>
The code is a parameter of string type that refers to ISO 421757 currency code. For example, you
can use GBP for the Great Britain Pound Sterling, or USD for United States Dollar.
The symbol parameter holds boolean value to indicate whether pipe needs to render currency symbol
like £ or just use the currency code value, in this case, GBP. This parameter defaults to false and
CurrencyPipe is going to use currency codes if symbol parameter is not defined explicitly.
Finally, the digitFormat value is the same as of the DecimalPipe, and is represented by the following
formatting rule:
55
https://cdn.polyfill.io/v2/docs/
56
https://github.com/andyearnshaw/Intl.js/
57
https://en.wikipedia.org/wiki/ISO_4217
Pipes 271
minIntegerDigits.minFractionDigits-maxFractionDigits
Minimum numbers
By setting the minimum numbers like minIntegerDigits and minFractionDigits, you
enforce resulting value to have a certain size. The pipe automatically adds leading or
trailing zeros if the actual value is less than expected.
For example applying a minimum number of 3 to the value 1 results in 003 text rendered
by the pipe.
1 <h2>Currency</h2>
2 <ul>
3 <li>GBP (code): {{ 150 | currency:'GBP' }}</li>
4 <li>GBP (symbol): {{ 150 | currency:'GBP':true }}</li>
5 <li>USD: {{ 0.9876 | currency:'USD':true:'2.2-2' }}</li>
6 </ul>
Once your application starts you should see the following output:
Internationalisation API
This pipe is using Internationalisation API that may not be fully supported by all modern
browsers. You can use Polyfill.io58 service to get the Intl polyfill only if your user’s current
browser needs it.
Also, you can see details and additional options at the project side: Intl.js59
58
https://cdn.polyfill.io/v2/docs/
59
https://github.com/andyearnshaw/Intl.js/
Pipes 272
Percent
The PercentPipe formats a number input as a percentage, where “1” corresponds to “100%”, and
“0.5” for instance corresponds to “50%”. This pipe also takes into account user’s locale and allows
you to customise resulting numeric format if needed.
The usage format is similar to that of the DecimalPipe, CurrencyPipe and many other number-based
pipes Angular provides out of the box:
<element>
{{ <number_expression> | percent[:digitFormat] }}
</element>
minIntegerDigits.minFractionDigits-maxFractionDigits
Minimum numbers
By setting the minimum numbers like minIntegerDigits and minFractionDigits, you
enforce resulting value to have a certain size. The pipe automatically adds leading or
trailing zeros if the actual value is less than expected.
For example applying a minimum number of 3 to the value 1 results in 003 text rendered
by the pipe.
Now create a new Angular project or open an existing one and append the following block to the
main application component template:
Pipes 273
1 <h2>Percent</h2>
2 <ul>
3 <li>1.0 equals to {{ 1.0 | percent }}</li>
4 <li>0.5 equals to {{ 0.5 | percent }}</li>
5 <li>0.123456 equals to {{ 0.123456 | percent }}</li>
6 </ul>
If you run the application with the code above you should see the following output:
Notice that by default PercentPipe displays three digits after the fraction, so 0.123456 gets rounded
and shown as 12.346.
As with other number conversion pipes, you are also able to control how PercentPipe displays
digits after fraction. For example, let’s make the transformed output more precise and render five
numbers instead of the default three:
1 <h2>Percent</h2>
2 <ul>
3 <li>0.123456 corresponds to {{ 0.123456 | percent:'1.1-5' }}</li>
4 </ul>
This time we should see the full number as we define it initially, and without rounding:
It is also possible to add leading zero to the final output by putting a minimum threshold for the
number of digits:
1 <h2>Percent</h2>
2 <ul>
3 <li>0.025 corresponds to {{ 0.025 | percent:'2.1' }}</li>
4 </ul>
The example above shows how to force PercentPipe always to use two numbers during conversion,
so you are going to get a leading zero for small values.
Pipes 274
Internationalisation API
This pipe is using Internationalisation API that may not be fully supported by all modern
browsers. You can use Polyfill.io60 service to get the Intl polyfill only if your user’s current
browser needs it.
Also, you can see details and additional options at the project side: Intl.js61
Json
The JsonPipe pipe often helps with debugging your Angular code. Its main purpose is converting
JavaScript objects into JSON strings.
<element>
{{ <object_expression> | json }}
</element>
You are going to use this pipe if you want to peek inside a payload or response object coming
from HTTP service call for instance. Let’s start by adding a complexObject property to the main
application component:
As you see the property holds a User object with some nested properties. You can now render it
using the JsonPipe pipe as below:
60
https://cdn.polyfill.io/v2/docs/
61
https://github.com/andyearnshaw/Intl.js/
Pipes 275
1 <h2>Json</h2>
2 <pre>{{ complexObject | json }}</pre>
Slice
The SlicePipe pipe extracts a section of a string or array into a new object. It performs a selection
from a “begin” to an “end” index where the “end” does not get included in the resulting set. Note
that the original string or array is not modified.
Slice behaviour
The implementation of this pipe is based on the standard Array.prototype.slice()62 method
for JavaScript Arrays, and String.prototype.slice()63 method for JavaScript Strings.
If needed, please refer to the corresponding API documentation for more details on how
slice gets performed.
The format when using the pipe with arrays and collections is the following:
<element>
{{ array_expression | slice:begin[:end] }}
</element>
First, add an initial collection of numbers to the main application component class, to be able testing
the pipe:
62
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
63
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/slice
Pipes 276
1 ...
2 export class AppComponent {
3 ...
4 numberCollection = [0, 1, 2, 3, 4, 5, 6];
5 }
As the next, try displaying first three items from the array by setting begin index to 0 to point to
the start of the collection, and the end index to 3.
1 <h2>Slice (arrays)</h2>
2 <ul>
3 <li>
4 First 3 items:
5 <ul>
6 <li *ngFor="let num of numberCollection | slice:0:3">{{num}}</li>
7 </ul>
8 </li>
9 </ul>
Given the ending index is not included, the resulting subset should have the following items
rendered: 0, 1, 2
You can also count items from the end of the array. For this purpose, you are going to use negative
values for the start option. For example value 3 signals the pipe to start taking items from the third
position from the beginning. Using value -3 makes the pipe count items from the end of the array.
Let’s now try negative values in action and display last 3 items from the numbers collection:
Pipes 277
1 <h2>Slice (arrays)</h2>
2 <ul>
3 <li>
4 Last 3 items:
5 <ul>
6 <li *ngFor="let num of numberCollection | slice:-3">{{num}}</li>
7 </ul>
8 </li>
9 </ul>
This time we do not need to specify the end index as we are going to take all array values starting
with the third one from the end.
Feel free to experiment with start and end index values more to see how SlicePipe works with
arrays depending on your conditions.
The format when using the pipe with strings is identical to that of the arrays. The SlicePipe pipe
treats strings as arrays of characters, so you can apply the same begin and end parameters as you
did earlier.
<element>
{{ string_expression | slice:begin[:end] }}
</element>
Let’s test this pipe with the “Hello world!” string and extract the first word:
Pipes 278
1 <h2>Slice (strings)</h2>
2 <ul>
3 <li>
4 First word of 'Hello world!':
5 {{ 'Hello world!' | slice:0:5 }}
6 </li>
7 </ul>
Upon application start, you should see that pipe successfully transformed the string value and
rendered only the word “Hello”.
We can also take the last word by using negative number for the begin parameter value:
1 <h2>Slice (strings)</h2>
2 <ul>
3 <li>
4 Last word of 'Hello world!':
5 {{ 'Hello world!' | slice:-6:-1 }}
6 </li>
7 </ul>
You should see the word “world” rendered by the pipe as soon as your application reloads.
Note that we use -1 for the end value because sentence ends with an exclamation mark that we want
to exclude from the final output. Otherwise, you can omit the end setting entirely and leave just -6
for the start value.
I18nSelect
For the time being the I18nSelectPipe is an experimental pipe that allows you to render different
strings from the object depending on the input value.
Pipes 279
Experimental
Please note that this pipe is an experimental one and its functionality may change. We are
going to update this section with new features for the I18nSelectPipe if they appear, or
remove this warning if the pipe gets updated to the stable state.
<element>
{{ <expression> | i18nSelect:mapping }}
</element>
Now we can wire both properties together in the component HTML template as below:
1 <h2>I18nSelect</h2>
2 <ul>
3 <li>{{ titleMode | i18nSelect:titleValues }}</li>
4 </ul>
Based on settings above there are four different values you can see on the screen at runtime:
Given that by default titleMode property value is set to primary, you should see the following
content once the application starts:
Now try changing the value of the titleMode property to secondary like in the example below:
For the next experiment, let’s try setting titleMode to something titleValues does not contain a
property for. For example, change the value to a “something else” string.
As soon as your page reloads you can see that I18nSelectPipe this time takes the titleMode.other
content as a fallback value.
Another good scenario for I18nSelectPipe is rendering text that gets adopted to some input criteria,
for instance, gender value of the current user can be a good example:
Pipes 281
Your component may display different user interface and content based on the gender value at run
time.
Finally, you an use the I18nSelectPipe pipe for localising text, the i18n prefix in its name stands
for internationalisation. Imagine you are implementing a colour picker button component, and
you need to support multiple languages. You can easily split the translation support into two
pieces: currentLanguage property holding currently used language code and a buttonLabels object
containing all supported translations.
Let’s see that in practice by providing tree “Pick colour” labels translated into British English (en-
GB), American English (en-US) and Ukrainian (uk). For all other languages that our component does
not support out of the box, we are going to use en-GB locale via other property as a fallback option.
Now switch to the HTML template of your application component or any other component you are
using for testing purposes, and add the following content:
1 <h2>I18nSelect</h2>
2 <ul>
3 <li>
4 <button>{{ currentLanguage | i18nSelect:buttonLabels }}</button>
5 </li>
6 </ul>
Pipes 282
You should see the en-GB translation by default as per currentLanguage property value we set earlier:
Now if you change the currentLanguage to “uk” code the button should have the following content
rendered by I18nSelectPipe:
Finally, if you change your currentLanguage property value to something that is not supported by
default, like “fr” or “it” for instance, the pipe is going to switch to the “other” value at run time.
I18nPlural
This pipe helps you with pluralizing your string values based on a number input value and optionally
taking into account the current user’s locale.
Experimental
Please note that this pipe is an experimental one and its functionality may change. We are
going to update this section with new features for the I18nPluralPipe if they appear, or
remove this warning if the pipe gets updated to the stable state.
<element>
{{ <number_expression> | i18nPlural:mapping }}
</element>
The mapping is an object literal with properties, represented in TypeScript by a dictionary containing
key/value pairs of a string type.
Let’s imagine we have a list of posts to render on the page. Each post may also contain comments,
so we are also going to show a list of comment instances in a user-friendly manner.
Create a new application with Angular CLI or open an existing one, and append the following
content to the main application controller:
Pipes 283
We have a collection of three simple posts with comment counters as commentsCount properties
holding different number values.
There are at least three different cases for display value formatting in our case:
Now let’s create a mapping dictionary to hold string values for all the use cases above:
Finally we are ready to see I18nPluralPipe in action. Append the following HTML to the component
template:
Pipes 284
1 <h2>I18nPluralPipe</h2>
2 <ul>
3 <li *ngFor="let post of posts">
4 {{ post.content }}
5 <ul>
6 <li>{{ post.commentsCount | i18nPlural: commentLabels }}</li>
7 </ul>
8 </li>
9 </ul>
So this is how the output result should look like when the application starts:
Async
The AsyncPipe pipe subscribes to an instance of a Promise or Observable and transforms the
underlying value upon every change.
<element>
{{ <expression> | async }}
</element>
Unsubscribing
When the component gets destroyed, the async pipe unsubscribes automatically to avoid
potential memory leaks.
Let’s try to display a list of comments based on an Observable. First, create a comments property in
your application component class:
Pipes 285
We are going to render an unordered list of comments; we also need a button to fetch comments
from the server and update comments observable with new values.
Open the HTML template of the main application component and append the following:
1 <h2>Async</h2>
2 <ul>
3 <li *ngFor="let comment of comments | async">
4 {{ comment }}
5 </li>
6 </ul>
7 <button (click)="checkComments()">Check comments</button>
For the sake of simplicity, we are not going to make any HTTP calls on the checkComments call. Let’s
just return a predefined set of comments after a short delay to emulate delays with a response.
The checkComments method sets comments variable value to an observable that evaluates with 1-
second delay and returns a set of sample comments.
For testing purposes you can also add a button to reset comments:
Pipes 286
The corresponding resetComments method is going to just set the comments value to null.
If you now run the application the main page should look like the following:
Try clicking the Check comments button and the results should get displayed in 1 second:
Another good example to demonstrate the ‘AsyncPipe in action is displaying current time
on the page. Our component needs to implement standard OnInit interface to setup current-
Time‘ observable once it is ready.
Pipes 287
Upon initialization, the component assigns an Observable of Date type to the currentTime property.
The Observable instance updates the underlying value to a new Date object every second, that causes
AsyncPipe to render new date automatically.
As Angular allows chaining multiple pipes together, you can also use a DatePipe to format resulting
output to display only the time portion of the date.
You can get the basic HTML template for the application component below:
1 <h2>Async (date/time)</h2>
2 <ul>
3 <li>
4 Current time: {{ currentTime | async | date:'mediumTime' }}
5 </li>
6 </ul>
Once application compiles and starts you should get a time value updated every second:
Source code
You can find the source code in the “angular/pipes/standard-pipes64 ” folder.
64
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/pipes/standard-pipes
Pipes 288
Custom Pipes
Besides providing a set of standard out-of-box pipes the Angular framework provides support for
creating your custom ones.
To create a custom pipe you need to import the @Pipe decorator and apply it to your class.
You can give your class any name you want. The Angular is going to use the @Pipe decorator
metadata when parsing component templates, in our case the pipe gets used as customDate:
<element>
{{ <expression> | customDate }}
</element>
Also, your class should implement a PipeTransform interface with a transform method:
interface PipeTransform {
transform(value: any, ...args: any[]) : any
}
At runtime, the Angular calls your pipe’s transform method providing original input value together
with optional pipe parameters. For example, if your myPipe pipe expects to receive 3 additional
parameters you can declare your transform method like the following:
To get type checking support from TypeScript, you can also provide type definitions for your
parameters and method return type.
You can now use your pipe in HTML templates like in the example below:
Pipes 289
1 <element>
2 {{ 'hello world' | myPipe:1:2:3 }}
3 </element>
ng new custom-pipes
You can use Angular CLI to generate the file-size pipe scaffold.
ng g pipe pipes/file-size
The command above generates a pipe, basic unit test and updates main application module so that
you can use this pipe across all the application components.
installing pipe
create src/app/pipes/file-size.pipe.spec.ts
create src/app/pipes/file-size.pipe.ts
update src/app/app.module.ts
The default pipe scaffold already contains a @Pipe decorator applied and inherits the PipeTransform
interface:
src/app/pipes/file-size.pipe.ts
First, let’s change the signature of the transform method to accept bytes and decimals parameters
of a number type. We also make both of them optional by providing default values and change the
return type of the method to a string.
src/app/pipes/file-size.pipe.ts
1 @Pipe({
2 name: 'fileSize'
3 })
4 export class FileSizePipe implements PipeTransform {
5
6 transform(bytes: number = 0, decimals: number = 2): string {
7 return null;
8 }
9
10 }
Converting Bytes
There are many different ways to convert a file size from bytes to other units of mea-
surement. For this example we are going to take the accepted answer from the following
Stackoverflow question: Correct way to convert size in bytes to KB, MB, GB in Javascript65
1 @Pipe({
2 name: 'fileSize'
3 })
4 export class FileSizePipe implements PipeTransform {
5
6 transform(bytes: number = 0, decimals: number = 2): string {
7 if (bytes === 0) {
8 return '0 Bytes';
9 }
10 const k = 1024,
11 dm = decimals || 2,
12 sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
13 i = Math.floor(Math.log(bytes) / Math.log(k));
14 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
15 }
16
17 }
65
https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
Pipes 291
If no bytes value provided the pipe returns a “0 Bytes” string, for all other cases if converts input
number to the most appropriate measuring unit. The transform method also takes into account
the number of decimals to use after the point, provided within the second decimals parameter. By
default, it is going to take two numbers.
In order to see the pipe in action open the main application component template and append the
following HTML block:
1 <h2>fileSize</h2>
2 <ul>
3 <li>520.12345 => {{ 520.12345 | fileSize }}</li>
4 <li>520.12345 => {{ 520.12345 | fileSize:4 }}</li>
5 <li>1024 => {{ 1024 | fileSize }}</li>
6 <li>5,347,737.6 => {{ 5347737.6 | fileSize }}</li>
7 <li>1,288,490,188.8 => {{ 1288490188.8 | fileSize }}</li>
8 </ul>
Now run the application with ng serve --open command and you should see the following output:
@Pipe({
name: 'myCustomPipe',
pure: false
})
Before diving into details on category differences, let’s prepare the testing project.
We are going to display a list of short blog posts filtered by a pure or impure pipe and allow creating
a new simple blog post entry with various properties like creation date, content or public state.
Preparing Project
Use Angular CLI to generate a new project called “pure-impure”.
Pipes 292
ng new pure-impure
cd pure-impure
As we are going to filter and display Posts let’s create a Post interface that defines the main attributes
of a Post instance. You can do that by utilising Angular CLI that supports generating interface classes
as well.
ng g interface post
The Angular CLI puts interfaces classes to the src/app folder by default:
installing interface
create src/app/post.ts
Open the newly generated post.ts file and update it according to the list above:
src/app/post.ts
For the next step let’s update the main application component class. We need a posts property to
hold a collection of created Post instances. Also, the newPostContent and newPostPublic properties
of string and boolean types are going to power our simple post editor.
Pipes 293
src/app/app.component.ts
The createPost method performs some basic validation for content to be defined and creates a new
post. The creation date is assigned automatically to current date and time. Upon pushing the new
post to the posts collection, our createPost method also clears the input field.
Note that posts collection is defined and initialized with an empty array by default. We add items
to it via createPost method, but the initial collection is always the same. To better demonstrate
capabilities of both pipes we also need to change the object reference for the posts collection, for
example creating a new collection of Post instances, or resetting it by assigning an empty array
value.
For the sake of simplicity let’s just be making a copy of the existing collection and reassigning posts
property, all we need so far is changing the object reference. Append the following mutateArray
method to the main application component class:
Pipes 294
1 @Component({ ... })
2 export class AppComponent {
3 ...
4
5 mutateArray() {
6 this.posts = this.posts.concat();
7 }
8 }
Finally edit the component template and add the following HTML content:
src/app/app.component.html
1 <h2>Posts</h2>
2 <div>
3 <textarea [(ngModel)]="newPostContent"></textarea>
4 </div>
5 <div>
6 <label>
7 <input type="checkbox" [(ngModel)]="newPostPublic">
8 Is Public
9 </label>
10 </div>
11 <div>
12 <button (click)="createPost()">post</button>
13 <button (click)="mutateArray()">mutate array</button>
14 </div>
15
16 <ul>
17 <li *ngFor="let post of posts">
18 {{ post.content }} ({{ post.created | date:'short' }})
19 </li>
20 </ul>
The layout above is pretty simple. You get a text area element to enter a content for a new post, a
checkbox to toggle the public state, and two buttons post and mutate array. Under the post editor
there is a list of previously created posts, by default it is empty.
Once you compile and start the application you should see the following:
ng serve --open
Pipes 295
Now you are ready testing pure and impure pipes in action.
Pure Pipes
A “pure pipe” is a pipe that gets executed by Angular only when a “pure change” happens to the
underlying input value, for example:
• a “primitive” value changes, for example of a String, Number, Boolean or other primitive
types;
• object reference changes, for instance an entire Array, Date, Object and other reference-based
types;
When you use a “pure pipe”, the Angular ignores all changes to the complex objects it gets as
input parameters for the pipe. For example, when using with arrays, the pipe renders data if your
component class initializes default collection of items. However, the pipe does not update the view
if you add new items to the collection at run time.
Let’s use Angular CLI to generate our simple pure pipe and call it “public-posts”.
ng g pipe pipes/public-posts
You should get your pipe scaffold and a basic unit test in the src/app/pipes folder:
Pipes 296
installing pipe
create src/app/pipes/public-posts.pipe.spec.ts
create src/app/pipes/public-posts.pipe.ts
update src/app/app.module.ts
src/app/pipes/public-posts.pipe.ts
Essentially the pipe takes a collection of Post instances as an input parameter posts and returns a
filtered result using isPublic property.
Pipe name
Note that your pipe’s public name is going to be publicPosts. You can give the component
class any name you want, in our case, it is PublicPostsPipe, but Angular is going to use
@Pipe metadata when parsing component templates.
Now let’s add a couple of initial posts for our pipe to filter at run time:
Pipes 297
1 @Component({ ... })
2 export class AppComponent {
3
4 posts: Post[] = [
5 <Post> {
6 content: 'default public post',
7 isPublic: true,
8 created: new Date()
9 },
10 <Post> {
11 content: 'default private post',
12 isPublic: false,
13 created: new Date()
14 }
15 ];
16
17 }
The first post is going to be public while the second one is configured to be private by default.
For the next step, please update the application component template to show the posts collection,
filtered by our publicPosts pipe, in a separate unstructured list element.
If you run your application right now, you should see one entry in the “Public posts” list and two
entries in the “All posts” one.
Pipes 298
So the pipe is working with predefined data as we expect it. Now try adding couple more posts by
entering content into the input area and clicking the “post” button. Please ensure that “Is Public”
checkbox element is set to “checked” state.
You should see the “All posts” list is getting updated, while “Public posts” one remains the same.
That is the expected behaviour as we are modifying a complex object, in this case, an array of Post
instances, without actually changing object reference for the pipe.
The “All posts” list uses the ngFor directive, so Angular detects the change and updates it accordingly.
During the preparation phase, we have created a “mutate array” button that modifies posts by
replacing the collection with its copy.
If you click this button right now the “Public posts” should instantly update the view with new
values:
Pipes 299
Impure Pipes
An “impure pipe” is a pipe that gets executed by Angular during every component change detection
cycle. All custom pipes are “pure” by default, in order to change its state to “impure” you need to
explicitly define that in the @Pipe decorator metadata:
@Pipe({
name: 'myCustomPipe',
pure: false
})
Performance
A single component may cause many change detection cycles based on various factors,
for example, user interaction, keyboard or mouse events. Keep in mind that you pipe may
affect overall application performance if it is slow, or not optimised for frequent runs (data
caching for instance).
Let’s create another pipe scaffold called “public-posts-impure” in the same project using Angular
CLI:
ng g pipe pipes/public-posts-impure
The command above should produce a new pipe next to the existing one in the src/app/pipes
folder.
Pipes 300
installing pipe
create src/app/pipes/public-posts-impure.pipe.spec.ts
create src/app/pipes/public-posts-impure.pipe.ts
update src/app/app.module.ts
src/app/pipes/public-posts-impure.pipe.ts
The implementation of the pipe is pretty much the same we used before. It takes a collection of posts
as an input and returns a filtered result based on public state via isPublic property values.
We are going to use this pipe as “publicPostsImpure” in the HTML templates, and we also explicitly
set the pure property value in the @Pipe decorator metadata to false:
@Pipe({
name: 'publicPostsImpure',
pure: false
})
Finally extend the main application component template with the list showing all public posts
filtered by the publicPostsImpure pipe:
Pipes 301
This time if you run the application and add several posts with “Is Public” element checked, you
should see the “Public posts (impure)” list gets updated at real-time. That happens because our
custom publicPostsImpure pipe gets executed by Angular during each change detection cycle.
Global Application Configuration
In this chapter, we are going to focus on application settings and configuration files.
If you are building a large application or an application that performs the server side or RESTful APIs
calls, sooner or later you will come across the need to store global application settings somewhere.
For example the APIs URL string, you may hardcode it somewhere in the application or service,
but that will require rebuilding and redeploying your application every time the URL value needs
changes. It is much easier storing and loading configuration parameters in a separate file that
developers can maintain without changing the main application code.
Let’s create an Application Configuration service that loads global settings from the external server-
side file before all other services get loaded, and provides access to configuration properties for all
other application components and services.
src/app.config.json
1 {
2 "title": "My application"
3 }
Configuration content
Theoretically, you could even dynamically generate the file if necessary, for example
building the output based on some database values.
In our case, we just store the custom application title in the config file to see the basic flow in action.
You can extend the file content later on with more properties and values.
We also need configuring Angular CLI to copy the newly created file to the application output
directory upon every build. Edit the “apps” section of the “.angular-cli.json” file and append the
name of the settings file to the “assets” collection like in the example below:
Global Application Configuration 303
.angular-cli.json
1 {
2 "apps": {
3 "assets": [
4 "app.config.json"
5 ]
6 }
7 }
ng g interface app-config
ng g service app-config
Don’t forget to register the newly generated service with the main application module, as
Angular CLI does not do that by default.
Now, edit the “AppConfig” interface and add the “title” property we have declared in the server-side
config earlier in this chapter:
Global Application Configuration 304
src/app/app-config.ts
Next, let’s wire our “AppConfigService” with the “Http” service to be able making HTTP calls and
fetching remote files:
src/app/app-config.service.ts
src/app/app-config.service.ts
As you can see from the code above, we also add a basic debugging output to the browser console
log. It should help us to see whether the application is using an external configuration file or fallback
values. Logging to console is a fully optional step, and you may want to remove that code at the
later stages.
ensure the application configuration service gets the “app.config.json” file loaded and applied before
others.
The Angular framework provides a special injection token called “APP_INITIALIZER” that you
should use to execute your code during the application startup. That also helps to register providers
and to use provider factories.
For the sake of simplicity the example below shows only the newly added content:
src/app/app.module.ts
So how the code above works and how do we load an external configuration at the application
startup?
First of all, we declare the “AppConfigService” in the “providers” section of the module. Then
we use the “APP_INITIALIZER” injection token to declare a custom provider based on the
“setupAppConfigServiceFactory” factory; the provider also references the “AppConfigService” as
a dependency to inject when the factory gets invoked.
Global Application Configuration 307
At the runtime, the Angular resolves and creates an instance of the “AppConfigService”, and then
uses it when calling the “setupAppConfigServiceFactory”. The factory itself calls the “load” method
to fetch the “app.config.json” file.
If you run the web application right now the browser console output should be as following:
The settings service is up and running correctly, and we are now able to use configuration settings
in the components.
src/app/app.component.ts
All we need at this point is to inject the “AppConfigService” as an “appConfig” parameter and assign
the title property to the “appConfig.data.title” value. Don’t forget that at this point the JSON file is
already loaded and configuration properties are ready for use.
Global Application Configuration 308
If you run the web application now, the main page should contain the title value fetched from the
external file. Try editing the “title” value in the “app.config.json” and you should see the Angular
CLI automatically reload the page. The page automatically reflects new values.
We just got a working example of the configuration service. We have also checked the external
settings being successfully fetched and applied to component properties. The examples in the chapter
should help you building configuration support for your applications.
Internationalisation (i18n)
In this chapter, we are going to build a simple multi-language support for an application. We are
about to create services to translate application strings, switch languages based on user interactions,
and also using third-party libraries for more sophisticated translation scenarios.
For a basic multi-language support we need at least three blocks:
ng new app-i18n
cd app-i18n
Let’s also use Angular CLI to generate our translation layer blocks, the TranslateService and the
TranslatePipe:
ng g service translate
ng g pipe translate
We also need at least a single language file to demonstrate the translation process in action, and
optionally use as a default or fallback locale. Create a new “i18n” folder in the “src/assets” one, and
put an “en.json” file there with the following content:
src/assets/i18n/en.json
1 {
2 "TITLE": "My i18n Application (en)"
3 }
src/app/translate.service.ts
Next, let’s register our service provider and a custom factory that triggers the TranslateService to
load default or predefined locale file before all other services and components get created. That helps
us ensure that all other elements of the application get access to translation feature at startup.
Internationalisation (i18n) 311
src/app/app.module.ts
Now we can have a quick test to ensure the file got loaded and translation service has the data
preserved. Inject the service into the main application component and dump the entire data set to
the browser console output.
src/app/app.component.ts
Once you run the web application and head to the browser console, you should see the following
output:
Internationalisation (i18n) 312
We got the translation service working as expected; now we can proceed to create the corresponding
pipe.
src/app/translate.pipe.ts
Now the pipe needs to map the key to the underlying resource string. Let’s get also provide a fallback
behaviour and return the resource key if the corresponding translation is missing. That should help
you to identify the problems with translation earlier.
Internationalisation (i18n) 313
src/app/translate.pipe.ts
1 @Pipe({...})
2 export class TranslatePipe implements PipeTransform {
3 ...
4
5 transform(key: any): any {
6 return this.translate.data[key] || key;
7 }
8
9 }
Don’t forget that your newly created pipe needs to be present in the main application module.
The Angular CLI tool automatically registers it, if you have added the pipe manually then see the
following code for example:
src/app/app.module.ts
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3
4 title = 'TITLE';
5
6 ...
7 }
If you open the “app.component.html” template file, you should see the “title” property referenced
in the welcome message:
src/app/app.component.html
1 <h1>
2 Welcome to {{ title }}!
3 </h1>
We can just append the pipe to the “title” value to enable automatic translation of the value based
on the resource strings in the translation file.
src/app/app.component.html
1 <h1>
2 Welcome to {{ title | translate }}!
3 </h1>
Once you compile and run the web application, you should see the welcome message getting the
text directly from the language file.
Internationalisation (i18n) 315
Switching languages
We have the default locale working so far, but switching languages at runtime require some extra
configuration steps.
First, let’s create an additional file “ua.json” in the “assets/i18n” folder. For testing purposes, you
can just copy the contents of the “en.json” file and slightly modify the strings to be able to see the
difference at runtime.
src/assets/i18n/ua.json
1 {
2 "TITLE": "My i18n Application (ua)"
3 }
The main application controller class needs a new “setLang” method so that we can switch
to a different language with, for instance, a button click. With our current TranslateService
implementation we need just the name of the locale:
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3 title = 'TITLE';
4
5 constructor(private translateService: TranslateService) {
6 console.log(translateService.data);
7 }
8
9 setLang(lang: string) {
10 this.translateService.use(lang);
11 }
12 }
Get back to the application component template and add a couple of buttons to switch between the
languages like in the example below:
Internationalisation (i18n) 316
src/app/app.component.html
1 <div>
2 <button (click)="setLang('ua')">Language: UA</button>
3 <button (click)="setLang('en')">Language: EN</button>
4 </div>
If you try running the application right now, you should notice that buttons do not work. If you run
the Dev Tools and inspect the Network tab, you are going to see that a corresponding language file
is, in fact, fetched every time you click the buttons. You do not see the changes on the page because
of how the pipes in Angular work by default. For the performance reasons, they cache the result
once the value is transformed and always use evaluated values in subsequent calls.
In our particular case, we need the pipe to refresh, so the caching needs to be switched off. You can
achieve that by setting the “pure” attribute of the @Pipe decorator metadata to “false”.
src/app/translate.pipe.ts
1 @Pipe({
2 name: 'translate',
3 pure: false
4 })
5 export class TranslatePipe implements PipeTransform {
6
7 ...
8
9 }
Try rerunning your web application, and clicking the buttons to switch between languages. You
should see the label text getting updated each time the button gets clicked.
Internationalisation (i18n) 317
Summary
You just have got a basic lightweight multi-language functionality that you can use in your web
application. Note, however, that translation layer may require many code enhancements and
performance optimisations, for you to be ready to use it for production purposes.
You can also check out one of my Angular projects “@ngstack/translate66 ” that provides i18n-
features for Angular applications and components. That library can save a lot of your time and
effort required to build a multi-lingual web application.
66
https://www.npmjs.com/package/@ngstack/translate
Advanced Angular
This chapter assumes you are already familiar with the Angular development and want to advance
your experience further to specific areas. We are going to review various practical scenarios and
challenges that you can come across, and provide example solutions.
List component
First, let’s create a simple component to render an unordered list of bound items, and call it
“TemplatedListComponent”.
With the Angular CLI the command should look like the following:
ng g component templated-list
The Angular CLI generates all required scaffold for your component and updates main application
module, so you are strongly recommended using it to save your time.
As you can remember from the Angular CLI chapter, all generated artefacts should have prefixes to
avoid conflicts with standard HTML elements or third party components. In our case, the Angular
CLI tool automatically prepends the default “app-“ prefix to the component selector value:
Advanced Angular 319
src/app/templated-list/templated-list.component.ts
1 @Component({
2 selector: 'app-templated-list',
3 templateUrl: './templated-list.component.html',
4 styleUrls: ['./templated-list.component.css']
5 })
6 export class TemplatedListComponent { ... }
For now, the only property that our newly created list needs is an array of items to display. Every
item must have at least a “title” property, so we use a generic Array type with the corresponding
constraints.
src/app/templated-list/templated-list.component.ts
1 @Component({...})
2 export class TemplatedListComponent {
3
4 @Input()
5 items: Array<{title: string}> = [];
6
7 }
Note that array is empty by default. By our design, the items are not hard coded into the component
and should be coming from the application instead.
For the template part, a simple unordered list with “ngFor” directive should be more than enough
to demonstrate the content:
src/app/templated-list/templated-list.component.html
1 <ul>
2 <li *ngFor="let item of items">
3 {{ item.title }}
4 </li>
5 </ul>
Next, let’s provide our List with some data and see it working. Edit your main application component
class and add the “listItems” property with some basic entries:
Advanced Angular 320
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent {
3 ...
4
5 listItems: Array<{title: string}> = [
6 { title: 'item 1' },
7 { title: 'item 2' },
8 { title: 'item 3' }
9 ]
10 }
Don’t forget to modify the HTML template of the component and declare the <app-templated-list>
component with the corresponding bindings:
src/app/app.component.html
1 <h2>Templated List</h2>
2 <app-templated-list [items]="listItems">
3 </app-templated-list>
If you run your web application right now you should see the HTML unordered list element with
three entries like on the picture below:
Row templates
So we got a simple list component that binds to an array of objects and renders standard unordered
HTML element where every entry binds its content to the “title” property of the underlying object.
Now let’s update the code to provide support for external templates.
First, you need a property to hold custom template reference. That requires importing a @Con-
tentChild decorator and TemplateRef type.
Advanced Angular 321
src/app/templated-list/templated-list.component.ts
The @ContentChild decorator helps you to bind the property value to the first child element of the
specific type in out List component at run time. In our particular case, we are instructing decorator
to look for the first <ng-template> instance exposed via TemplateRef reference.
For example:
<app-templated-list>
<ng-template>
...
</ng-template>
</app-templated-list>
For the next step, let’s update our List component template to use provided template property value
for each entry.
src/app/templated-list/templated-list.component.html
1 <ul>
2 <ng-template ngFor [ngForOf]="items" [ngForTemplate]="template">
3 </ng-template>
4 </ul>
Now the “TemplatedListComponent” expects a “template” instance as its child element in the form
of <ng-template>. It then takes the content of the template and applies to each “*ngFor” entry. So
application developers can define entire row template like the following:
Advanced Angular 322
src/app/app.component.html
1 <h2>Templated List</h2>
2 <app-templated-list [items]="listItems">
3 <ng-template>
4 <li>
5 List item template content
6 </li>
7 </ng-template>
8 </app-templated-list>
If you now run your web app you should see the following output:
As you can see on the picture above the template works as expected. We define a static content and
see it rendered three times. Now to see the data change across the list entries update component
template like in the next example:
1 <h2>Templated List</h2>
2 <app-templated-list [items]="listItems">
3 <ng-template let-item="$implicit" let-i="index">
4 <li>[{{i}}] Hello: {{item.title}}</li>
5 </ng-template>
6 </app-templated-list>
Upon building the elements, the “ngFor” directive takes each object in the collection and uses it as
a data context for every corresponding list item and its custom template. To get direct access to
this object we are using the let-item="$implicit" attribute. That reads as “bind the object to the
template variable called ‘item’”.
You can omit the “$implicit” value and use just the “let-item” attribute if you like. You can also give
the template variable any name; it can be “let-data”, “let-context” or any name of your choice.
The “ngFor” directive provides us with the numeric index for each item in the collection. We bind
it inside the template to the “i” variable using the let-i="index" syntax.
So to demonstrate both data context bindings, we used the following list item template:
Advanced Angular 323
Once your web application runs you should see the following result:
We are now able to see index value, static “Hello” text and “title” property of the underlying object.
You are going to use the above approach when building list-like or grid-like components that hide
all complexity from the developers but at the same time allow customising entries or rows using
templates.
Source code
You can find the source code in the “angular/components/dynamic-content67 ” folder.
Dynamic Components
Another interesting scenario involves changing the content of the component based on some
condition evaluation result. For example, rendering different child component depending on the
value of the “type” property:
<component type="my-type-1"></component>
<component type="my-type-2"></component>
Let’s start with a component scaffold generated by the Angular CLI using the following command:
ng g component dynamic-content
For the first step, update the HTML template of the newly generated component with the following
one:
67
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/dynamic-content
Advanced Angular 324
src/app/dynamic-content/dynamic-content.component.html
1 <div>
2 <div #container></div>
3 </div>
Note how we declare a separate <div> element with the “#container” id. We are going to use it as
the injection point. All dynamic content should get inserted in the DOM below this element. The
component maps it to the “container” property of the “ViewContainerRef” type to allow you access
container element from the code.
Later on, you should be able to use this component similar to the following example:
<dynamic-content type="some-value"></dynamic-type>
Now let’s introduce two more components to display depending on the “type” property value. We
also are going to need an additional “fallback” component to use for all the “unknown” types.
For the sake of simplicity, you can append new components to the same “dynamic-content”
component file.
Advanced Angular 325
src/app/dynamic-content/dynamic-content.component.ts
1 @Component({
2 selector: 'app-dynamic-sample-1',
3 template: `<div>Dynamic sample 1</div>`
4 })
5 export class DynamicSample1Component {}
6
7 @Component({
8 selector: 'app-dynamic-sample-2',
9 template: `<div>Dynamic sample 2</div>`
10 })
11 export class DynamicSample2Component {}
12
13 @Component({
14 selector: 'app-unknown-component',
15 template: `<div>Unknown component</div>`
16 })
17 export class UnknownDynamicComponent {}
Developers are going to use the “type” property that has the type of “string” and gets used as an
alias for real component types. You are going to need some mapping of string values to types, either
in the form of separate injectable service (recommended approach) or just a part of the component
implementation like in the example below:
1 @Component({...})
2 export class DynamicContentComponent {
3 ...
4
5 private mappings = {
6 'sample1': DynamicSample1Component,
7 'sample2': DynamicSample2Component
8 };
9
10 getComponentType(typeName: string) {
11 const type = this.mappings[typeName];
12 return type || UnknownDynamicComponent;
13 }
14
15 }
Advanced Angular 326
As you can see from the code above, for “sample1” and “sample2” values the newly introduced
components “DynamicSample1Component” and “DynamicSample2Component” are returned. For
all other cases, we are going to take the “UnknownDynamicComponent” component.
Finally, we are ready to create components dynamically. Below is the simplified version of
component class implementation that shows the main blocks of interest:
1 import {
2 ...,
3 OnInit, OnDestroy,
4 ComponentRef, ComponentFactoryResolver
5 } from '@angular/core';
6
7 @Component({...})
8 export class DynamicContentComponent implements OnInit, OnDestroy {
9 ...
10
11 private componentRef: ComponentRef<{}>;
12
13 constructor(private resolver: ComponentFactoryResolver) { }
14
15 ngOnInit() {
16 if (this.type) {
17 const componentType = this.getComponentType(this.type);
18 const factory = this.resolver.resolveComponentFactory(componentType);
19 this.componentRef = this.container.createComponent(factory);
20 }
21 }
22
23 ngOnDestroy() {
24 if (this.componentRef) {
25 this.componentRef.destroy();
26 this.componentRef = null;
27 }
28 }
29
30 }
We inject the “ComponentFactoryResolver” into the component at runtime and use it to build the
dynamic content upon initialization, and tear down when our parent component gets destroyed.
The process of content creation is quite straightforward. First, we resolve the component type by
the string value. Second, we resolve a component factory for the given component type. Finally, we
Advanced Angular 327
use that factory to build a component. Newly created content gets automatically appended to the
DOM after the “#container” element.
Please note that you must register every component you create dynamically within the “en-
tryComponents” section of your module. That instructs the Angular framework to maintain
corresponding factories at runtime.
src/app.module.ts
1 @NgModule({
2 imports: [ ... ],
3 declarations: [ ... ],
4 entryComponents: [
5 DynamicSample1Component,
6 DynamicSample2Component,
7 UnknownDynamicComponent
8 ],
9 bootstrap: [AppComponent]
10 })
11 export class AppModule { }
You can now test all three cases with the following layout:
src/app/app.component.html
1 <h2>Dynamic Content</h2>
2 <app-dynamic-content type="sample1"></app-dynamic-content>
3 <app-dynamic-content type="sample2"></app-dynamic-content>
4 <app-dynamic-content type="some-other-type"></app-dynamic-content>
Runtime context
The easiest way to maintain different types of dynamic components in the code is to build a common
interface or an abstract class with a shared API. For example:
Advanced Angular 328
Note that for the sake of simplicity we are declaring “context” property of “any” type. In real-life
scenarios, however, you may want to use some particular type to benefit from the static checks the
Typescript compiler provides.
Now you can update all previously created components to expose the “DynamicComponent” class
and take “context” into account.
1 @Component({
2 selector: 'app-dynamic-sample-1',
3 template: `<div>Dynamic sample 1 ({{context?.text}})</div>`
4 })
5 export class DynamicSample1Component extends DynamicComponent {}
6
7 @Component({
8 selector: 'app-dynamic-sample-2',
9 template: `<div>Dynamic sample 2 ({{context?.text}})</div>`
10 })
11 export class DynamicSample2Component extends DynamicComponent {}
12
13 @Component({
14 selector: 'app-unknown-component',
15 template: `<div>Unknown component ({{context?.text}})</div>`
16 })
17 export class UnknownDynamicComponent extends DynamicComponent {}
Next, the “DynamicContentComponent” needs updates to receive the value of the “context” from
the outside using bindings, and pass it to the underlying child component.
11 instance.context = this.context;
12 }
13 }
14 }
That is it, and we are ready to test the whole flow. Add a simple object to the main application
component class to use as a “context” value for the dynamic elements:
src/app.component.ts
Now bind this context to all three components we declared earlier in the template:
src/app.component.html
To demonstrate “live updates” feature, we add an input element to the page. The element is using
“two-way” binding to the “context.text” property. According to our setup, all dynamically created
components should automatically reflect the new value as we type.
Note that to use “ngModel” directive you need to import and reference the “FormModule” within
your main application module like in the example below:
src/app/app.module.ts
1 ...
2 import { FormsModule } from '@angular/forms';
3
4 @NgModule({
5 ...
6 imports: [
7 BrowserModule,
8 FormsModule
Advanced Angular 330
9 ],
10 ...
11 })
12 export class AppModule { }
The setup is ready, and you can run your web application to see it in action. By default, it should
be displaying the “test” value within each dynamically created component. Try typing some text in
the “context” input to see all of those components’ titles change on the fly.
Dynamic forms and form persistence is the best example of where you might need the features we
tried above. If you need displaying a form or a complex component based on some definition file
(JSON, XML, or other), you may end up creating a dynamic component that builds final content
based on the schema and/or persisted state, and a form component built from multiple dynamic
content containers.
Source code
You can find the source code in the “angular/components/dynamic-content68 ” folder.
Runtime Compilation
For some advanced scenarios, you might want to take full control over Angular component and
template compilation.
In this part, we are going to implement the following features:
Let’s start by generating a separate scaffold for the component utilising the Angular CLI tools:
68
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/dynamic-content
Advanced Angular 331
ng g component runtime-content
You can take initial implementation from the “DynamicContentComponent” we created in previous
chapters.
The component template needs a predefined injection point:
src/app/runtime-content/runtime-content.component.html
1 <div>
2 <div #container></div>
3 </div>
Also, we map “#container” element to the underlying “container” property of the component class.
src/app/runtime-content/runtime-content.component.ts
The main idea behind the new component we introduce is to allow a user to define HTML template
on the fly. So let’s add a basic UI for editing:
src/app/runtime-content/runtime-content.component.html
1 <div>
2 <h3>Template</h3>
3 <div>
4 <textarea rows="5" [(ngModel)]="template"></textarea>
5 </div>
6 <button (click)="compileTemplate()">Compile</button>
7
8 <h3>Output</h3>
9 <div #container></div>
10 </div>
Advanced Angular 332
Note that to use “ngModel” directive you must import a “FormsModule” into your main application
module. You should have it already configured from the previous part of the chapter where we
created the DynamicContent component.
Set the default template value to something the user can compile without typing beforehand. That
should help us testing the flow as well:
1 @Component({...})
2 export class RuntimeContentComponent {
3 ...
4
5 template = '<div>\nHello, {{name}}\n</div>';
6
7 }
When the main page gets rendered it should look similar to the following:
Now the most important part of the component implementation, the runtime compilation:
src/app/runtime-content/runtime-content.component.ts
1 import {
2 ...,
3 Compiler, ComponentFactory,
4 NgModule, ModuleWithComponentFactories
5 } from '@angular/core';
6 import { CommonModule } from '@angular/common';
7
8 @Component({...})
9 export class RuntimeContentComponent {
10 ...
11
Advanced Angular 333
12 private createComponentFactorySync(
13 compiler: Compiler,
14 metadata: Component,
15 componentClass: any): ComponentFactory<any> {
16
17 const cmpClass = componentClass || class RuntimeComponent {
18 name = 'Denys'
19 };
20 const decoratedCmp = Component(metadata)(cmpClass);
21
22 @NgModule({
23 imports: [CommonModule],
24 declarations: [decoratedCmp] }
25 )
26 class RuntimeComponentModule { }
27
28 const module: ModuleWithComponentFactories<any> =
29 compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
30
31 return module.componentFactories.find(
32 f => f.componentType === decoratedCmp
33 );
34 }
35
36 }
The code above takes custom metadata and optionally a component class reference via “compo-
nentClass” parameter. If you do not provide the class as part of the “createComponentFactorySync”
call, the fallback “RuntimeComponent” one gets used instead. The fallback class also has a “name”
property predefined. That is what we are going to use for testing.
The resulting component also gets decorated with the metadata we provide:
Next, a component gets created with the predefined “CommonModule” import. You may extend the
list if you want to support more functionality. We also put our component as part of the module’s
“declarations” section as per Angular requirements.
Advanced Angular 334
Finally, the function uses Angular “Compiler” service to compile the module and all included
components. Compiled module provides access to the underlying component factories, and this is
exactly the feature we need.
For the last step, we need to wire the “Compile” button with the following method:
1 @Component({...})
2 export class RuntimeContentComponent {
3 ...
4
5 compileTemplate() {
6
7 let metadata = {
8 selector: `runtime-component-sample`,
9 template: this.template
10 };
11
12 let factory = this.createComponentFactorySync(
13 this.compiler,
14 metadata,
15 null
16 );
17
18 if (this.componentRef) {
19 this.componentRef.destroy();
20 this.componentRef = null;
21 }
22 this.componentRef = this.container.createComponent(factory);
23 }
24 }
Every time the user clicks the “Compile” button our “RuntimeContentComponent” component takes
the template value, compiles it to a new component backed by the RuntimeComponent class (with
the predefined “name” property), and renders:
Advanced Angular 335
To fully test the flow in action now change the default HTML template value and provide some
custom content, for example changing the colour style of the root “div” element to “blue”. Modify
the text content as well.
Click “Compile” button once again, and you should see the following result now:
Binding events
Your component users are not limited to user property bindings. The template supports event
binding as well. All event handlers have to be defined in the underlying class or be present in the
“RuntimeComponent” class we use as a fallback value.
To see events in action let’s modify the fallback class and add the “onClick” method like in the
following example:
Advanced Angular 336
1 @Component({...})
2 export class RuntimeContentComponent {
3 ...
4
5 private createComponentFactorySync(...): ComponentFactory<any> {
6
7 const cmpClass = componentClass || class RuntimeComponent {
8 name = 'Denys';
9
10 onClick() {
11 alert('Clicked');
12 }
13 };
14
15 ...
16 }
17 }
Now you can wire the click events in the template with the “onClick” method like in the next picture:
If you press the “Compile” button once again and then click the “div” element you should see the
browser alert dialogue:
Advanced Angular 337
The best scenario for this feature is when you want to store component templates somewhere in the
external storage, and then build components on the fly, similar to how it gets performed in various
RAD environments, online layout builders, and other design tools.
Source code
You can find the source code in the “angular/components/dynamic-content69 ” folder.
Plugins
Plugins are used to allow third-party developers extend your application by utilising the redis-
tributable extensions that provide new features for the application without recompiling its code.
In this section, we are going to use multiple ways to extend your application, starting from the
string-based configurations to build and compose components at runtime, and finishing with the
complete drop-in plugins you can put into the folder to add new features to the running application.
69
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/components/dynamic-content
Advanced Angular 338
It is essential to have Routing enabled as later in this section we are also going to see how to create
new routes dynamically at runtime, and how a plugin can contribute new application routes with
external content presented to a user.
Edit the main application component template and replace the HTML markup that Angular CLI
generates by default with the following one:
src/app/app.component.html
1 <h1>Plugins</h1>
2
3 <router-outlet></router-outlet>
That is pretty much all to get started with extensibility. You can test the project by running “ng start
–open” command, to see that everything compiles and runs fine. After that, proceed to the next
section below.
Source code
You can find the source code in the “angular/plugins70 ” folder.
I strongly recommend creating a separate module that contains all the configurable components that
you plan to create at runtime. That should significantly simplify future maintenance and discovery
of such components, and you are going to see that shortly in practice.
Let’s use Angular CLI once again to generate a new flat module and call it “plugins”:
Note the “–module=app” switch we are using in this case. It instructs Angular CLI to also include
our newly generated module into the main application module. The “–flat” switch forces the CLI
to generate module file in the application source folder, without creating a separate subfolder as it
does for components, directives and other Angular entities.
The console output in your case should look similar to the next example:
While we are at the command line, let’s also create two components “page1” and “page2”. These are
the components we would like to create dynamically. Moreover, you can save some time by using
the “–module” switch to include both components into the “plugins” module like in the code below:
Now, feel free to inspect what you have as a result in the “plugins” module that should reside in
the “src/app” folder and contain references to both “Page1Component” and “Page2Component”
components. The Angular CLI also imports the most frequently used “CommonModule” module
for you:
src/app/plugins.module.ts
1 import { NgModule } from '@angular/core';
2 import { CommonModule } from '@angular/common';
3 import { Page1Component } from './page1/page1.component';
4 import { Page2Component } from './page2/page2.component';
5
6 @NgModule({
7 imports: [
8 CommonModule
9 ],
10 declarations: [Page1Component, Page2Component]
11 })
12 export class PluginsModule { }
Both “page” component classes should be almost identical and look similar to the following:
Advanced Angular 340
src/app/page1/page1.component.ts
1 import { Component, OnInit } from '@angular/core';
2
3 @Component({
4 selector: 'app-page1',
5 templateUrl: './page1.component.html',
6 styleUrls: ['./page1.component.css']
7 })
8 export class Page1Component implements OnInit {
9
10 constructor() { }
11
12 ngOnInit() {
13 }
14
15 }
The Angular CLI provides dummy HTML templates in the form of “<component-name> works!” for
every component it generates. That saves much time when you are preparing project structure or
have a quick prototyping phase. As we focus on the architecture and dynamic compilation, it is not
very important what templates component have. Let’s leave the default values for now, and you can
revisit that later.
Next, we need to create a couple of buttons to trigger component creation, and a placeholder to insert
the newly compiled content. Update the main application component template like in the example
below.
src/app/app.component.html
1 <h1>Plugins</h1>
2
3 <ul>
4 <li>
5 <button (click)="createView('app-page1')">page 1</button>
6 </li>
7 <li>
8 <button (click)="createView('app-page2')">page 2</button>
9 </li>
10 </ul>
11
12 <div #content></div>
13
14 <router-outlet></router-outlet>
Advanced Angular 341
Pay attention to the empty “div” element: “<div #content></div>”. That is the place we are going
put our dynamic components. You can put this element to any other places, as long as it has the
“content” Angular reference name, the component controller is going to find it.
Now, if you run the application, the main page should look like in the next picture:
List of plugins
We also need to finish the component controller preparation. The class should get the reference to
the “content” placeholder and have the “createView” handler for the buttons:
src/app/app.component.ts
Note that we also import the “PluginsModule”, as it contains all the components we would like to
find and create according to our initial scenario.
Advanced Angular 342
We inject the instance of the “Compiler” class into the component constructor and use it to compile
the “PluginsModule” on the fly. The private “module” variable is going to hold the resulting instance
so that we do not compile the module more times than it is needed.
src/app/app.component.ts
As you can see above, as long as we have the compiled module instance, we can perform searches
to find a component factory based on specific metadata. In the particular case, we are interested in
the selector names.
As soon as factory got found, we can clear the “content” placeholder, and create a new instance of
the component in that place.
Run the application and click the first button “page 1” on the main page. You should see the “page1
works!” label immediately appear at the bottom of the page.
Advanced Angular 343
Plugin 1
Now click the second button, and this time the placeholder content is replaced with the “page2
works!” text that comes with the “Page2Component” instance our application component creates on
the fly.
Plugin 2
Congratulations on having the first step complete. You are now able to take a string, map it to the
component selector, and build the corresponding component at runtime.
The whole idea of string to component type mapping usually arises when the exact values are coming
from the external sources, like configuration files. Otherwise, you could just declare the needed
component tags in the HTML template without any compilation.
As we have started this chapter with the example of the configurable Sidebar component, let’s go
ahead and introduce a barebone implementation that takes external “plugins.config.json” file as a
configuration, and builds a set of components.
Below is our configuration file that is going to reside in the “src/assets” folder. Upon startup, the
Angular server automatically exposes this folder to the application access and also copies its content
to the output directory during production builds.
Advanced Angular 344
src/assets/plugins.config.json
1 {
2 "sidebar": {
3 "components": [
4 "app-page1",
5 "app-page2"
6 ]
7 }
8 }
So we got the “sidebar” with two components “app-page1” and “app-page2” defined by default. Let’s
now generate the corresponding component by utilising the following command:
As we now have more than one module, we need to tell Angular CLI where to put the resulting
component. In our case, it is “app” that corresponds to the “app.module.ts” file.
In the main application template, append the “Configurable Sidebar” header element together with
the newly introduced “app-sidebar” tag:
src/app/app.component.html
1 ...
2
3 <h1>Configurable Sidebar</h1>
4 <app-sidebar></app-sidebar>
You also need to import the “HttpClientModule” to allow your application components and services
make Http calls with the help of the HttpClient instance.
src/app/app.module.ts
1 ...
2 import { HttpClientModule } from '@angular/common/http';
3
4 @NgModule({
5 declarations: [
6 ...,
7 SidebarComponent
8 ],
9 imports: [
Advanced Angular 345
10 ...,
11 HttpClientModule
12 ],
13 entryComponents: [
14 ...
15 ],
16 providers: [],
17 bootstrap: [AppComponent]
18 })
19 export class AppModule { }
Next, edit the SidebarComponent template, and update the default auto-generated content with the
following snippet:
src/app/sidebar/sidebar.component.html
1 <p>
2 Sidebar
3 </p>
4
5 <div #content></div>
Similar to previous examples, we have the placeholder “div” element that we reference as “content”,
and we compile the “PluginsModule” module to get access to all its factories at the runtime:
src/app/sidebar/sidebar.component.ts
1 import {
2 ...,
3 ViewChild, ViewContainerRef, AfterViewInit, Compiler
4 } from '@angular/core';
5 import { HttpClient } from '@angular/common/http';
6 import { PluginsModule } from '../plugins.module';
7
8 @Component({...})
9 export class SidebarComponent implements AfterViewInit {
10
11 @ViewChild('content', { read: ViewContainerRef })
12 content: ViewContainerRef;
13
14 private module;
15
16 constructor(private http: HttpClient,
Advanced Angular 346
We use the “ngAfterViewInit” lifecycle hook as we need access to the “content” placeholder. You can
also mark the handler as “async” to get the benefits of the “async / await” support that Typescript
provides:
src/app/sidebar/sidebar.component.ts
Optimisation
Typically you should be aiming to have a separate application service to deal with
HTTP and configuration loading. In that case, you do not repeat the same calls in every
component, get a better separation of concerns and improved unit testing support.
If you now switch to the browser tab with your application and run the developer tools, the console
output should be similar to the next one:
Advanced Angular 347
Configuration file
I strongly recommend introducing a separate interface that describes the content of the configuration
file. In the long run, it is going to help you with type checks, addresses typos and errors at early stages.
For the sake of simplicity, let’s create an interface called “AppConfig” in the same “sidebar.component.ts”
file. You can later extract it to a separate file if there’s more than one place to use it.
1 interface AppConfig {
2 sidebar: {
3 components: string[]
4 };
5 }
Now you can use this interface with HttpClient like this:
As a bonus, you are going to get automatic completion for your configuration files when working
with typed variables:
We have already covered how to search for the component factories using selector values; this can
be easily used now with the external configuration to build multiple components and inject them
into the placeholder in the specific order:
Let’s try to test the configuration is indeed dynamic. Edit the JSON file and change the order of the
components like in the following example:
Advanced Angular 349
1 {
2 "sidebar": {
3 "components": [
4 "app-page2",
5 "app-page1"
6 ]
7 }
8 }
Next, reload the application page or wait till the Angular CLI web server automatically reloads. You
should now see components created in the order we configured:
That means you are now ready to build composite components driven by the external configuration.
With the approach above, you can quickly create applications that change the layout without
rebuilding and re-deploying new version to the server.
Source code
You can find the source code in the “angular/plugins71 ” folder.
Let’s generate a couple of components to use for navigation. We are going to call them “Home” and
“About”:
Now edit the “app-routing.module.ts” file and introduce new routes backed by the components
we have just generated. The ‘about’ route corresponds to the “/about” URL and displays the
“AboutComponent” component. By default, when the application starts, we are going to render
the “HomeComponent”.
src/app/app-routing.module.ts
Next, we create the links on the home page to allow the users navigate to the corresponding pages.
Find the “router-outlet” tag on the main application template, and replace it with the following
snippet:
Advanced Angular 351
src/app/app.component.html
1 ...
2
3 <h1>Routes</h1>
4
5 <ul>
6 <li>
7 <a routerLink="/">Home</a>
8 </li>
9 <li>
10 <a routerLink="/about">About</a>
11 </li>
12 </ul>
13
14 <router-outlet></router-outlet>
Start the application, and you should see two links together with the label “home works!” underneath.
That is our default “Home” component already loaded in the router outlet area.
Basic routes
Click the “About” link to see the “AboutComponent”, that proves the router is up and running, and
ready for our further customisations.
For the next step, we need one more component that we are going to create dynamically at runtime.
Let’s imagine that we have a configuration switch or settings that allows us to enable the “Settings”
feature for our application.
Use Angular CLI to create a new “SettingsComponent” and automatically declare it within the “App”
module:
Advanced Angular 352
Note that “Settings” component is part of the application, but it gets created dynamically. According
to Angular rules we need to register in the module’s “entryComponents” section all the components
that we are going to create dynamically by using their factories.
Update the main application module according to the example of the code below:
src/app/app.module.ts
1 ...
2 import { SettingsComponent } from './settings/settings.component';
3
4 @NgModule({
5 declarations: [
6 ...,
7 SettingsComponent
8 ],
9 imports: [
10 ...
11 ],
12 entryComponents: [
13 SettingsComponent
14 ],
15 providers: [],
16 bootstrap: [AppComponent]
17 })
18 export class AppModule { }
We also need a way to render and test all dynamic routes. Let’s create the collection variable to hold
the information we need to create the list of links on the main page, similar to “Home” and “About”
we have created earlier. Our main component class also needs importing and injecting the Router
instance.
src/app/app.component.ts
1 ...
2 import { Router } from '@angular/router';
3
4 @Component({...})
5 export class AppComponent {
6
7 ...
8
Advanced Angular 353
To display a link, we need a title and a route path value. For a minimal Router entry, we also need
the type of the component to associate with the given route. That means we can create the following
method to register a new route and also fill the “links” collection:
src/app/app.component.ts
The main point of interest for us, in this case, is the “router.config” property that holds a collection
of all registered routes available to the application and Router. The “createRoute” method inserts a
new record containing a path and the component Type.
Why is it essential to use “unshift” instead of the “push” method for the routes array? Very often the
last route in the collection is a “catch-all” fallback path that handles missing pages and redirects to
some error or “page not found” component, like in the following example:
By pushing new content to the routes collection we risk adding it after the “catch-all” path, and so
the routes are not going to be available at runtime. That is why we “prepend” new routes by using
“Array.prototype.unshift” rather than append them with “Array.prototype.push” function.
Advanced Angular 354
Let’s try the route generator out with the SettingsComponent we have recently created. Update the
main application component class with the necessary imports, and call the “createRoute” method in
the class constructor to prepend the “Settings” route pointing to the “/settings” URL like in the next
snippet:
src/app/app.component.ts
1 ...
2 import { SettingsComponent } from './settings/settings.component';
3
4 @Component({...})
5 export class AppComponent {
6 ...
7
8 constructor(private compiler: Compiler, private router: Router) {
9 ...
10 this.createRoute('Settings', 'settings', SettingsComponent);
11 }
12
13 createView(name: string) {
14 ...
15 }
16
17 createRoute(text: string, path: string, componentType: any) {
18 this.router.config.unshift({
19 path: path,
20 component: componentType
21 });
22
23 this.links.push({ text, path });
24 }
25 }
Finally, we need to create a list of new routes on the main page. You should already have an
unordered list with the “Home” and “About” links, let’s update it with the dynamic portion now:
Advanced Angular 355
src/app/app.component.html
1 ...
2
3 <h1>Routes</h1>
4
5 <ul>
6 <li><a routerLink="/">Home</a></li>
7 <li><a routerLink="/about">About</a></li>
8
9 <li *ngFor="let link of links">
10 <a [routerLink]="link.path">
11 {{ link.text }}
12 </a>
13 </li>
14 </ul>
15
16 <router-outlet></router-outlet>
As soon as you reload the page, you are going to see three links in the “Routes” section: “Home”,
“About” and newly introduced “Settings”. Click on the “Settings” one, and you are going to see the
“setting works!” label in the router outlet area. That means your application successfully renders the
content you provide at runtime, congratulations!
Dynamic route
We have walked through a simplified scenario for route creation. As you can now imagine, that
approach can be used in more sophisticated cases, when you store route names, paths and component
Advanced Angular 356
aliases in the external configuration files, and load them on demand together with new route
generation, as we did earlier in this chapter.
Source code
You can find the source code in the “angular/plugins72 ” folder.
External plugins
We have finally approached the most advanced and the most valuable area - plugins that are loaded
by the application at runtime from external sources, and provide new features based on specific
extension points.
For generic plugin capabilities we need to have application support at least for the following items:
Traditionally, application or framework developers provide a set of extension points that third-party
developers can use to build plugins or extensions. Let’s take the Routing example we used across
this chapter as a perfect scenario for extensibility.
We are going to enable other developers to build extra components and register new routes in our
application. Ultimately it should be possible to drop the JavaScript file in some specialised “modules”
folder and register the module in the application configuration file. The application should not
“know” about loaded types, and the whole plugin integration should happen dynamically, without
recompiling the application or even restarting the web server.
Extension decorator
It is essential to keep the plugin feature as simple as possible so that your third-party developers do
not study a new programming language or the practices and patterns that are relevant only to your
particular implementation. The ES2016 “decorator” pattern might be the best choice, as Angular
developers are already familiar with decorators and annotations.
Let’s imagine we have a unique “@Extension” decorator to mark all the components we want
the target application to discover and use. We could also provide some extra metadata for such
a decorator, for example, public alias to use in the configuration files, maybe an icon or even a list
of dependencies on other plugins or modules.
For example, it can look like the following when applied to a class:
72
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/plugins
Advanced Angular 357
1 @Extension('my-extension')
2 class MyExtension {
3 ...
4 }
Decorators can be chained, so that means we can use them with Angular components like this as
well:
1 @Extension('my-button')
2 @Component({
3 selector: 'my-button,
4 templateUrl: 'my-button.html'
5 })
6 export class MyButtonComponent {
7 ...
8 }
As you can see, we need at least one project to hold the Extension and other APIs that we share
across all plugin implementations, let’s call it “Plugins Core”. It can also contain some management
services, like “Plugin Manager” to help the end application maintain plugins easily.
Finally, we are going to extend the main application to allow adding new routes and components
via the configuration file. For the current scenario, our simple extension point is a Router extension
and new pages for the application, but there can be many different extension points in the future.
Advanced Angular 358
Create a separate “plugins-core” folder for the shared Extension APIs to use with all new plugins. I
am going to use the Rollup.js to build and bundle the code. However, you can use any other means to
create a redistributable Angular library. You can refer to the Reusable component libraries chapter
for more details.
Rollup.js
Rollup is a module bundler for JavaScript which compiles small pieces of code into
something larger and more complex, such as a library or application.
To get more information about Rollup, please refer to the official documentation:
https://rollupjs.org73
Note, however, that “Rollup” is not the only option for building redistributable libraries. You can
also check the following alternatives:
73
https://rollupjs.org
Advanced Angular 359
• ng-packagr74
• Nx extensions for Angular75
Create a new “package.json” file with the following content to configure dependencies for the new
project:
package.json
1 {
2 "name": "plugins-core",
3 "version": "1.0.0",
4 "main": "dist/bundle.js",
5 "license": "MIT",
6 "scripts": {
7 "build": "rimraf dist/bundle.js && rollup -c"
8 },
9 "devDependencies": {
10 "babel-core": "^6.26.0",
11 "babel-plugin-external-helpers": "^6.22.0",
12 "babel-plugin-transform-decorators-legacy": "^1.3.4",
13 "babel-preset-env": "^1.6.1",
14 "rimraf": "^2.6.2",
15 "rollup-plugin-babel": "^3.0.3",
16 "rollup-plugin-json": "^2.3.0",
17 "rollup-plugin-node-resolve": "^3.0.2"
18 }
19 }
Note the “scripts” section and the “build” command. You are going to need it later to produce the
bundle to use with the main application.
Run the following command now to install all dependencies:
1 npm install
The Rollup is using Babel libraries under the hood. We need to provide the following configuration
to enable support for decorators:
74
http://spektrakel.de/ng-packagr
75
https://nrwl.io/nx
Advanced Angular 360
.babelrc
1 {
2 "presets": [
3 ["env", {
4 "modules": false
5 }]
6 ],
7 "plugins": [
8 "external-helpers",
9 "transform-decorators-legacy"
10 ]
11 }
The last project preparation step is the configuration file for the Rollup itself. Use the following
source code for the minimal working configuration that takes “src/main.js” file as an input, and
produces “dist/bundle.js” file as the bundled output.
rollup.config.js
Finally, we are ready to start implementing our “Extension” decorator. The code is pretty simple,
given that decorators are JavaScript functions:
src/extension.js
As per our design, the “Extension” decorator is going to keep a registry of all the classes it has
decorated. That enables quick access to all the “registered” extensions at runtime without extra
initialisation overhead for each decorated class or component.
Our decorator requires a public name of the decorated element, to use within application configura-
tions. We also reserve an optional array of dependencies that our plugin requires when loaded into
the application. Feel free to add more properties later on when your plugin architecture evolves.
While we are here, let’s also provide a couple of utility functions to generate a list of providers.
One is “getProviders”, to be able using it within the Angular Injectors and modules. Another one is
“getExtensionType” to allow us quickly resolving extension type (or constructor) based on the public
name. You are going to see both of them in action shortly.
src/extension.js
1 ...
2
3 Extension.prototype.getProviders = function () {
4 var registry = this.registry;
5 return Object.keys(registry).map(function (key) {
6 return {
7 provide: key,
8 useClass: registry[key].ctor,
9 deps: registry[key].deps
10 };
11 });
12 };
13
14 Extension.prototype.getExtensionType = function (name) {
Advanced Angular 362
15 return this.registry[name].ctor;
16 }
For the next step, we introduce a “PluginManager” class to provide a single place for controlling and
using our plugins at runtime. Typically it is the end application that calls these methods when setting
up the extension points or resolving and compiling components coming from the plugin library.
src/plugin-manager.js
Finally, create the “main” library entry point and export the classes and functions we have created.
Note that we export the instance of the PluginManager as a singleton. That is not mandatory but
saves time for creating new instances at the application level, especially when accessing plugins
from multiple places and files in the code.
src/main.js
That is pretty much all. You can now build the redistributable bundle with the “build” script:
For real-life scenarios, you may probably want to publish your redistributable library to NPM, so that
developers can install it as part of the application and use as a third party addon. For demonstration
and quick testing purposes let’s emulate the NPM deployment behaviour by running “npm link” in
the root project folder:
1 npm link
Now you can run “npm link plugins-core” in any of your local projects folders, and emulate the
process of installing from NPM. The main benefit is that you can keep working on the library
code, all applications and libraries that link it are going to get updates automatically. Once you are
have finished with the development, you can, of course, use the real publishing via “npm publish”
commands, but this is out of our current scope.
You shall see the linking example later in this chapter.
Source code
You can find the source code in the “angular/plugins-core76 ” folder.
We have created a shared Plugins Core library with the previous steps. It is now time to build our
first plugin library.
Create a separate “plugins-example” folder and place the following “package.json” file there:
package.json
1 {
2 "name": "plugins-example",
3 "version": "1.0.0",
4 "main": "dist/bundle.js",
5 "license": "MIT",
6 "scripts": {
7 "build": "rimraf dist && rollup -c"
8 },
9 "devDependencies": {
10 "rimraf": "^2.6.2",
76
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/plugins-core
Advanced Angular 364
11 "rollup-plugin-node-resolve": "^3.0.2",
12 "rollup-plugin-typescript": "^0.8.1",
13 "rollup-plugin-typescript2": "^0.10.0",
14 "typescript": "^2.6.2"
15 },
16 "dependencies": {
17 "@angular/core": "^5.2.2",
18 "rollup": "^0.55.1"
19 }
20 }
As with the previous project, install the dependencies running the command below:
1 npm install
Our project needs to depend on the “plugins-core” library. Typically we should be adding it to the
package file and installing with all other dependencies, however for the sake of simplicity we are
going to use “npm link” feature to provide a live link to the library. That helps to work on both
projects at the same time without publishing to NPM.
Run the following command in the project root to establish a link:
As we are going to create Angular components, we need to setup the TypeScript integration for the
Rollup. Put the following “tsconfig.json” file in the project root to enable basic support we need right
now:
tsconfig.json
1 {
2 "compilerOptions": {
3 "target": "es5",
4 "module": "system",
5 "lib": ["es2017", "dom"],
6 "declaration": true,
7 "sourceMap": true,
8 "removeComments": true,
9 "moduleResolution": "node",
10 "typeRoots": [ "node_modules/@types" ],
11 "experimentalDecorators": true,
12 "emitDecoratorMetadata": true
13 }
14 }
Advanced Angular 365
TypeScript Configuration
You can always generate a recommended configuration file by running the “tsc –init”
command in any folder. That requires, however, a TypeScript to be installed globally via
“npm install -g typescript” command.
Now finish the project scaffold setup by also adding the Rollup configuration file like below:
rollup.config.js
1 import resolve from 'rollup-plugin-node-resolve';
2 import typescript from 'rollup-plugin-typescript2';
3
4 export default {
5 input: 'src/main.ts',
6 output: {
7 file: 'dist/bundle.js',
8 format: 'system'
9 },
10 plugins: [
11 resolve({
12 // pass custom options to the resolve plugin
13 customResolveOptions: {
14 moduleDirectory: 'node_modules'
15 }
16 }),
17 typescript({
18 typescript: require('typescript')
19 })
20 ],
21 external: [
22 'plugins-core',
23 '@angular/core'
24 ]
25 }
Please pay attention to the “external” section of the Rollup configuration. It contains references to
the libraries in your “package.json” that should never get bundled into the resulting output. That
means that every plugin library is not going to contain the full copy of the Angular or another
version of the “plugins-core”. Having such dependencies marked as “external”, however, requires
the main application to import them alongside our plugins.
If your library depends on other Angular libraries, like “@angular/forms” or “@angular/http”,
include those in the “external” section too.
Advanced Angular 366
Let’s now create the first plugin component marked with our “Extension” decorator. It is going to
be a dummy button element that we reference as “my-button” extension.
src/my-button/my-button.component.ts
Create one more component with a label element and called “my-label”:
src/my-label/my-label.component.ts
Both components also produce log messages to the browser console for testing purposes. The only
thing that is now left is to export both components in the “main” class.
Advanced Angular 367
src/main.ts
Finally, you should also create a link to this project to test the library without publishing to NPM
every time you make changes to the project.
npm link
We now got the “plugins-core” and “plugins-example” libraries compiled and ready for use with an
Angular application as external plugins.
Source code
You can find the source code in the “angular/plugins-example77 ” folder.
Your component library does not restrict you to a particular set of dependencies. You can add
many additional libraries to the “rollup” configuration, and plugin components can also have own
“providers” sections to get additional services imported.
For example, you can add a FormBuilder integration using the following steps.
First, update the “rollup.config.js” file and add “@angular/forms” to the exclusion list. That prevents
entire forms library from getting bundled into your library output.
77
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/plugins-example
Advanced Angular 368
1 export default {
2 ...,
3
4 external: [
5 'plugins-core',
6 '@angular/core',
7 '@angular/forms'
8 ]
9 }
Next, import the Forms related types, and update your component decorator to include the
“FormBuilder” provider:
src/my-label/my-label.component.ts
Once you have component configuration set, you can import the FormBuilder and use its APIs from
within the plugin component.
Advanced Angular 369
src/my-label/my-label.component.ts
Finally, you need updating the “SystemJS” settings of the main application to include extra libraries
to use for plugins.
In the example below I am adding “@angular/common”, “@angular/forms” and “@angular/plat-
form-browser” to the list. Those libraries will get available for all the loaded plugins.
src/assets/plugins.config.json
1 {
2 ...,
3
4 "system": {
5 "baseURL": "/assets/modules",
6 "paths": {
7 "npm:": "https://unpkg.com/"
8 },
9 "map": {
10 "@angular/core": "npm:@angular/core/bundles/core.umd.js",
11 "@angular/common": "npm:@angular/common/bundles/common.umd.js",
Advanced Angular 370
12 "@angular/forms": "npm:@angular/forms/bundles/forms.umd.js",
13 "@angular/platform-browser": "npm:@angular/platform-browser/bundles/platfo\
14 rm-browser.umd.js",
15 "rxjs": "npm:rxjs",
16
17 "plugins-core": "/modules/plugins-core/bundle.js",
18 "plugins-example": "/modules/plugins-example/bundle.js"
19 }
20 },
21 }
You might need to import “FormsModule” and “ReactiveFormsModule” also to the root application
module.
src/app/app.component.ts
As I have mentioned earlier, you have full control over what the dynamic module contains. This
opens the door to at least two great scenarios for injecting external content into your plugins at
run-time.
We have already touched the first one. That is the “imports” section and extra libs you can provide
for every plugin you construct. That can be Forms modules, Material modules, you custom or third-
party libraries.
Every time we create an instance of the plugin component of the “componentType”, the component
templates can use form fields, and material buttons provided with the “MatButtonModule” module.
Advanced Angular 371
Another way to inject data into the plugin components is by utilising “providers” section. You can
create and pre-configure the services prior to exposing them to components and other services that
you resolve and create on the fly.
That is a powerful feature that allows you having complete control over the dependency resolution
process for external components.
Switch to the “plugins” application we earlier in this chapter. Use the following commands to link
both “plugins-core” and “plugins-example” libraries to emulate installation from NPM:
Also, you need to install a “systemjs” library. That is a module loader we are going to use to get our
plugins into the running application.
Advanced Angular 372
SystemJS
Configurable module loader enabling dynamic ES module workflows in browsers and
NodeJS. Built with the ES Module Loader project, which is based on principles and APIs
from the WhatWG Loader specification, modules in HTML and NodeJS.
For more details please refer to the official project page78 .
As we are using Angular CLI for the application, the “systemjs” library needs to be present within
the “scripts” section of the “.angular-cli.json” configuration file.
.angular-cli.json
1 {
2 "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 "project": {
4 "name": "plugins"
5 },
6 "apps": [
7 {
8 ...,
9 "scripts": [
10 "../node_modules/systemjs/dist/system.js"
11 ],
12 ...
13 }
14 ],
15 ...
16 }
It would be much easier if we also automate plugin bundle copying process. According to our design,
all external plugins need to reside in the “modules” folder of the application, either local or running
at the server side. As we continuously develop our plugin libraries, it is much easier if Angular CLI
automatically copies resulting bundles into appropriate places. We already linked the libraries via
the “npm link” command, the only thing that is left is to add extra rules for the “assets” folder:
78
https://github.com/systemjs/systemjs/blob/master/README.md
Advanced Angular 373
.angular-cli.json
1 {
2 "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 "project": {
4 "name": "plugins"
5 },
6 "apps": [
7 {
8 ...,
9 "assets": [
10 ...,
11 {
12 "glob": "bundle.js",
13 "input": "../node_modules/plugins-core/dist",
14 "output": "./modules/plugins-core"
15 },
16 {
17 "glob": "bundle.js",
18 "input": "../node_modules/plugins-example/dist",
19 "output": "./modules/plugins-example"
20 }
21 ],
22 ...,
23 "scripts": [
24 "../node_modules/systemjs/dist/system.js"
25 ],
26 ...
27 }
28 ],
29 ...
30 }
For the next step, we need to provide some basic configuration for SystemJS loader. The best way
would be to re-use the same “plugins.config.json” configuration file we have created earlier. In this
case, you can manage both the loader and the plugin configuration in the same place, and also
without rebuilding the application.
To keep the configuration short, we are going to load all missing dependencies directly from the
“UNPKG79 ”, a fast CDN for NPM libraries. The “plugins-core” and “plugins-example” point to the
local bundle files we copy with the Angular CLI.
79
https://unpkg.com/
Advanced Angular 374
src/assets/plugins.config.json
1 {
2 "system": {
3 "baseURL": "/assets/modules",
4 "paths": {
5 "npm:": "https://unpkg.com/"
6 },
7 "map": {
8 "@angular/core": "npm:@angular/core/bundles/core.umd.js",
9 "rxjs": "npm:rxjs",
10
11 "plugins-core": "/modules/plugins-core/bundle.js",
12 "plugins-example": "/modules/plugins-example/bundle.js"
13 }
14 },
15 "sidebar": {
16 "components": [
17 "app-page2",
18 "app-page1"
19 ]
20 }
21 }
Update the application component imports section with the “SystemJS” type declaration. That allows
us using auto-completion and type checking for the SystemJS APIs:
src/app/app.component.ts
1 import {..., AfterViewInit} from '@angular/core';
2 import { HttpClient } from '@angular/common/http';
3
4 import { System } from 'systemjs';
5 declare var SystemJS: System;
As you remember, we can load configuration using HttpClient, and use a TypeScript interface to
enable static type checks and IDE support. Let’s start with the basic one:
1 interface PluginsConfig {
2 system: any;
3 }
To finish the preparations, add the HttpClient and mark the “ngAfterViewInit” handler as “async”,
like in the next example:
Advanced Angular 375
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent implements AfterViewInit {
3 ...
4
5 constructor(..., private http: HttpClient) {
6 ...
7 }
8
9 async ngAfterViewInit() {
10 }
11 }
Let’s try to load the configuration and log its content to the browser console to ensure the file loads
fine:
src/app/app.component.ts
1 @Component({...})
2 export class AppComponent implements AfterViewInit {
3 ...
4
5 constructor(..., private http: HttpClient) {
6 ...
7 }
8
9 async ngAfterViewInit() {
10 const url = '/assets/plugins.config.json';
11 const config = <PluginsConfig> await this.http.get(url).toPromise();
12 console.log(config);
13 }
14 }
Run the application or reload the page. The developer tools console should look similar to the one
below:
Advanced Angular 376
Loaded configuration
Before we load a plugin, the SystemJS loader needs to get configured. We load and apply settings
using the “config” method like in the next example:
1 SystemJS.config(config.system);
src/app/app.component.ts
This time the browser console output should contain the “plugins-core” library content:
Advanced Angular 377
Try expanding the “Extension” and “prototype” sections in the console to check the “registry”
content. It should be an empty object like this:
src/app/app.component.ts
Now, the Extension decorator should contain two entries in the registry. As you can see from the
console output they are “my-button” and “my-label”:
You already know the names of the extensions, because you have defined them in the decorators for
each component. Also, you have the corresponding libraries loaded into the application at runtime.
It is now possible to use the same technique for dynamic module compilation with the component
constructors, fetched using Plugin Manager APIs.
The code below demonstrates the “my-label” plugin compiled and rendered within the “content”
element, similar to what we did earlier in this chapter.
Advanced Angular 379
src/app/app.component.ts
1 export class AppComponent implements AfterViewInit {
2
3 async ngAfterViewInit() {
4 ...
5
6 SystemJS.config(config.system);
7
8 const core = await SystemJS.import('plugins-core');
9 const pluginExample = await SystemJS.import('plugins-example');
10 console.log(core);
11
12 const componentType = core.pluginManager.getType('my-label');
13
14 const RuntimeModule = NgModule({
15 imports: [
16 // extra modules if needed by your plugins
17 // for example: FormsModule, HttpClientModule, etc
18 ],
19 declarations: [componentType]
20 })(class {});
21
22 const module = this.compiler.compileModuleAndAllComponentsSync(
23 RuntimeModule
24 );
25
26 const factory = module.componentFactories.find(
27 f => f.componentType === componentType
28 );
29
30 this.content.clear();
31 this.content.createComponent(factory, 0);
32 }
33
34 }
Switch to the running application and reload the page, if you do not have live reloading enabled.
The main page now contains the “My Label” element. That is the content of the plugin we have just
dynamically loaded.
Another challenge to address - dependencies for your plugins. In most common scenarios the plugin
branch needs to be attached to the running application tree, and have access to all the shared
infrastructure. The perfect examples are Authentication layer, Translation services, User Preferences,
Application Configuration. Every loaded plugin should not create a new copy, but reuse already
configured instances of the services.
You can achieve tight integration of the plugins by using custom Injector instances. It is possible to
create the Injector that inherits the main application tree of Injectors, and at the same time contains
custom settings that a plugin brings if you need that level of control.
src/app/app.component.ts
1 import { ..., Injector } from '@angular/core';
2
3 export class AppComponent implements AfterViewInit {
4
5 constructor(private compiler: Compiler,
6 private router: Router,
7 private http: HttpClient,
8 private injector: Injector) {
9 ...
10 }
11
12 async ngAfterViewInit() {
13 ...
14
15 const pluginInjector = Injector.create([
16 ...core.pluginManager.getProviders()
Advanced Angular 381
17 ], this.injector);
18
19 this.content.clear();
20 this.content.createComponent(factory, 0, pluginInjector);
21 }
22
23 }
First of all, we import an instance of the Injector into the component class constructor. That is
the same Injector that provides all other dependencies for the given class by the way. It is created
for every component and resolves all the required dependencies. If there are no values registered
with the component Injector, the Angular goes up the component tree to the parent component and
checks its Injector. It repeats the same procedure until it reaches the top of the application tree and
its module.
We take the component injector and create a new instance based on it. Moreover, we populate the
“providers” section of the current plugin. That allows a plugin to register new or override existing
providers if needed, and allows Angular traverse the whole injector tree.
As soon as you have your custom injector, it becomes trivial to create a new component with the
factory, corresponding element position and injector instance:
1 this.content.clear();
2 this.content.createComponent(factory, 0, pluginInjector);
Note the usage of the “Extension.prototype.getProviders” that we have created earlier. That method
allows us to collect all components marked with the “Extension” decorator, and produce a list of
“providers” and their “dependencies”, to attach to any custom injector or dynamic module:
Advanced Angular 382
plugins-core:/src/extension.js
1 Extension.prototype.getProviders = function () {
2 var registry = this.registry;
3
4 return Object.keys(registry).map(function (key) {
5 return {
6 provide: key,
7 useClass: registry[key].ctor,
8 deps: registry[key].deps
9 };
10 });
11 };
Visually the hierarchy of dependencies and injectors should look like the following:
Advanced Angular 383
Injectors
We have already used a few times the “ViewContainerRef.createContent” method to create and
inject content.
1 this.content.clear();
2 this.content.createComponent(factory, 0, pluginInjector);
The biggest feature of this method is that it returns the instance of the “ComponentRef” type.
That allows you accessing the real instance of the component you have just created, and setting
its properties, or calling methods.
Advanced Angular 384
ComponentRef Members
You can try to check how it works by logging the value of one property the plugin defines, for
example a form:
1 console.log(componentRef.instance.form);
Use this technique to set up dynamic components after they get created. You can set any properties
and call any methods to provide post-constructor setup, or prime your component with extra
infrastructure references.
Sometimes it might be easier for you to set the corresponding properties at run time than piping all
injectors and dependencies.
As plugins can be of many types unknown at compile time, you can create a set of interfaces to
provide a contract for the external developers. For example, you may want all plugin developers to
inherit a “PluginComponent” interface with a set of properties your application can rely at run-time.
We have been using manual import of the plugins by their names so far. In real life, you are most
probably going to make the application fully automatic, based on multiple extension points and
configuration settings that are coming either from the file or RESTful service call.
Let’s introduce an application Router extension point similar to the one we have been using before.
This time, however, the application is going to load routes from the configuration file. Every route
now should be powered by the content exposed by the plugin, and not known to the application at
compile time.
There may be many ways to express plugins setup in the configuration. Here’s a simple example for
you to get started:
Advanced Angular 385
1 {
2 "system": {...},
3 "sidebar": {...},
4
5 "plugins": {
6 "modules": {
7 "plugins-core": "/modules/plugins-core/bundle.js" ,
8 "plugins-example": "/modules/plugins-example/bundle.js"
9 },
10 "routes": [
11 {
12 "name": "Plugin 1 (label)",
13 "path": "plugin1",
14 "component": {
15 "module": "plugins-example",
16 "componentType": "my-label"
17 }
18 },
19 {
20 "name": "Plugin 2",
21 "path": "plugin2 (button)",
22 "component": {
23 "module": "plugins-example",
24 "componentType": "my-button"
25 }
26 }
27 ]
28 }
29 }
In our case, we introduce an extra section “plugins” to our existing “plugin.config.json” file. It
contains the list of available modules and a couple of routes that we want to create when the
application starts. Each route instance consists of the name, route path, and component information:
module and alias that we used within Extension decorator.
To enable static checks, let’s also extend the “PluginsConfig” interface:
Advanced Angular 386
1 interface PluginsConfig {
2 system: any;
3 plugins: {
4 modules: any,
5 routes: Array<{
6 name: string,
7 path: string,
8 component: {
9 module: string,
10 componentType: string
11 }
12 }>
13 };
14 }
Next, edit the “ngAfterViewInit” hook for the main application component class, and add the
“loadExternalRoutes” call right after the SystemJS configuration:
src/app/app.component.ts
At runtime, we can now quickly get a list of the plugins, their modules and custom routes. Also, you
can now get the corresponding component type using its alias and calling “pluginManager.getType”
method.
We have already implemented a way to create a dynamic route and display it on the page. The
challenging part is that Angular requires the route component to be a “known” type. Due to
some architectural reasons, it does not allow us to put an entirely dynamic component type as
“route.componentType” value.
Advanced Angular 387
You can solve the difficulty with the route components by introducing a statically known to the ap-
plication component, that has dynamic content. Let’s imagine we have a “DynamicPageComponent”
component that has an empty template and serves as a wrapper for the dynamic content exported by
the external plugin. We already know it is technically feasible, given the dynamic sidebar component
we have previously created.
The “loadExternalRoutes” implementation may look like the following one:
src/app/app.component.ts
1 ...
2 import { DynamicPageComponent } from './dynamic-page/dynamic-page.component';
3
4 export class AppComponent implements AfterViewInit {
5 ...
6
7 private async loadExternalRoutes(config: PluginsConfig) {
8 const core = await SystemJS.import('plugins-core');
9
10 for (const route of config.plugins.routes) {
11 const module = await SystemJS.import(route.component.module);
12 const componentType = core.pluginManager.getType(
13 route.component.componentType
14 );
15
16 this.createRoute(
17 route.name,
18 route.path,
19 DynamicPageComponent,
20 componentType
21 );
22 }
23 }
24
25 }
src/app/app.component.ts
As you can see from the example above, we use a known “DynamicPageComponent” for the route,
and also provide the required factory from the Extension decorator. The “data” value contains a
property bag with arbitrary data that any other component can access, a handy way passing different
optional configurations. In our case, the dynamic page component is going to build its content using
the factory provided in the property bag.
Next, you can generate the “DynamicPageComponent” by running the next Angular CLI command:
The component must also be declared as “dynamically created” by putting it to the “entryCompo-
nents” section of the top-level module:
Advanced Angular 389
src/app/app.module.ts
1 @NgModule({
2 ...,
3 entryComponents: [
4 ...,
5 DynamicPageComponent
6 ],
7 ...
8 })
9 export class AppModule { }
According to our design, the only thing that we need in the component’s template is the container
element:
src/app/dynamic-page/dynamic-page.component.html
1 <div #content></div>
Similar to the dynamic sidebar, the component class implementation needs to get a reference to the
corresponding DOM element, and import injector and compile services, alongside the current route
data:
src/app/dynamic-page/dynamic-page.component.ts
1 import {
2 Component, OnInit, OnDestroy,
3 Injector, ViewChild, ViewContainerRef,
4 Compiler, NgModule, ComponentRef
5 } from '@angular/core';
6 import { ActivatedRoute } from '@angular/router';
7
8 @Component({...})
9 export class DynamicPageComponent implements OnInit, OnDestroy {
10
11 @ViewChild('content', { read: ViewContainerRef })
12 content: ViewContainerRef;
13
14 component: ComponentRef<any>;
15
16 constructor(
17 private route: ActivatedRoute,
18 private injector: Injector,
Advanced Angular 390
The “ActivatedRoute” represents the current route. We can import it to get access to the underlying
details, including the property bag defined earlier, by using “route.snapshot.data” property value.
The code to create and render a dynamic component should already be familiar to you:
src/app/dynamic-page/dynamic-page.component.ts
1 @Component({...})
2 export class DynamicPageComponent implements OnInit, OnDestroy {
3 ...
4
5 ngOnInit() {
6 const componentType = this.route.snapshot.data['factory'];
7
8 if (componentType) {
9 this.compiler.clearCacheFor(componentType);
10
11 const RuntimeModule = NgModule({
12 imports: [/* extra libs */],
13 providers: [/* extra providers */],
14 declarations: [componentType]
15 })(class {});
16
17 const module = this.compiler.compileModuleAndAllComponentsSync(
18 RuntimeModule
19 );
20
21 const factory = module.componentFactories.find(
22 f => f.componentType === componentType
23 );
24
25 this.content.clear();
Advanced Angular 391
26 this.component = this.content.createComponent(
27 factory, 0, this.injector
28 );
29 }
30 }
31
32 ngOnDestroy() {
33 }
34
35 }
A critical thing to keep in mind - users might visit the route multiple times, so we need to manage
all dynamically created resources and clean them up as soon as possible. That is why we call the
following code every time we build a new component:
1 this.compiler.clearCacheFor(componentType);
As soon as the user leaves the page, the component needs to be released from memory as well:
src/app/dynamic-page/dynamic-page.component.ts
1 @Component({...})
2 export class DynamicPageComponent implements OnInit, OnDestroy {
3 ...
4
5 ngOnDestroy() {
6 if (this.component) {
7 this.component.destroy();
8 this.component = null;
9 }
10 }
11
12 }
Now start the application and take a look at the main page. The “Routes” section now contains 5
links - the “Home”, “About” and “Settings” we created earlier, and two more links created with the
plugins:
Advanced Angular 392
Generated routes
Now click the “Plugin 1 (label)” link, and you should see the “My Label” component automatically
created underneath. That content is coming from the plugin, and our application has just compiled
and created it on demand!
Next, click the second link to see another component provided by the plugin:
Summary
We have successfully finished the most advanced application setup with fully dynamic external
plugins in action. Feel free to enhance your applications with extension points, and plugins that
other developers can create and run with your apps.
Source code
You can find the source code in the “angular/plugins80 ” folder.
80
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/plugins
Reusable Component Libraries
We have already used the Angular CLI to generate applications. For the time being the CLI does not
support creating the reusable libraries, so we are going to use “ng-packagr81 ” tool for this purpose.
The “ng-packagr” transpiles your component libraries using the “Angular Package Format82 ”.
Resulting bundles are ready to be consumed by Angular CLI, Webpack, or SystemJS. For more details
on the features, please refer to the project page.
In this chapter, we are going to use Angular CLI to create a new Angular application and two reusable
component libraries: a header and a sidebar components. Both libraries can be built and published
to NPM separately. Also, the application is going to be linked with the component libraries locally.
That allows developing and testing components with the main application.
ng new ng-framework
For convenience purposes, you can change the “start” script to also launch the application in the
browser. To do that, open the “package.json” file and append “–open” argument to the “start”
command:
1 {
2 "scripts": {
3 "start": "ng serve --open"
4 }
5 }
81
https://github.com/dherges/ng-packagr
82
https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview
Reusable Component Libraries 395
At this point, the project setup is complete, and you are ready to start building reusable component
libraries.
mkdir -p modules/header/src
mkdir -p modules/sidebar/src
As you can see, the “modules” folder contains all the component projects as subfolders.
Let’s start with the “header” component first, and you can then perform similar steps to implement
the “sidebar”.
First, create a component class and give it a unique selector to avoid clashes at runtime. The value
can be anything of your choice, for this guide we are going to use “ngfw-header”.
modules/header/src/header.component.ts
The template is going to be very simple. An “h1” element wrapping the “ng-content” is the minimal
implementation we need right now. You can always extend it later with the more sophisticated
layout and behaviour.
Reusable Component Libraries 396
modules/header/src/header.component.html
1 <h1>
2 <ng-content></ng-content>
3 </h1>
Add some CSS to be sure the styles are also loaded from the package when we start using it with
the application. In our case, the colour of the header is going to be red.
modules/header/src/header.component.css
1 h1 {
2 color: red;
3 }
Finally, as per Angular architecture, we need to have a module that imports all necessary depen-
dencies and provides declarations and exports. The most important part of our scenario is to put the
“HeaderComponent” to the “exports” collection.
modules/header/src/header.module.ts
modules/header/package.json
1 {
2 "name": "@denysvuika/ng-framework-header",
3 "version": "1.0.0",
4 "author": "Denys Vuika <[email protected]>",
5 "license": "MIT",
6 "private": false,
7 "peerDependencies": {
8 "@angular/common": "^5.0.0",
9 "@angular/core": "^5.0.0"
10 }
11 }
modules/header/public_api.ts
Now you can repeat the steps above to implement the second “sidebar” component. The only
difference is the name of the project in the “package.json” file.
modules/sidebar/package.json
1 {
2 "name": "@denysvuika/ng-framework-sidebar",
3 ...
4 }
Also, let’s also change the colour to “green”, just to see we are dealing with two different stylesheets
at runtime.
modules/sidebar/src/sidebar.component.css
1 h1 {
2 color: green;
3 }
modules/header/ng-package.json
1 {
2 "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 "lib": {
4 "entryFile": "public_api.ts"
5 }
6 }
Note that the same file should be present in both our library root folders. Feel free to copy and paste
it, since the file does not contain any project-specific content at this point.
To build a project with “ng-packagr”, we can provide a path to the configuration file. That also allows
us to build more than one project from the NPM scripts. You can refer to the example below, with
only the change needed for the build, for the sake of simplicity.
package.json
1 {
2 "scripts": {
3 ...,
4 "build:modules": "ng-packagr -p ./modules/header/ng-package.json && ng-p\
5 ackagr -p ./modules/sidebar/ng-package.json"
6 }
7 }
Now go to the root folder and execute the following command to build our libraries:
1 $ ng-packagr -p ./modules/header/ng-package.json
2 Building Angular library
3 Generating bundle for @denysvuika/ng-framework-header
4 Cleaning bundle build directory
5 Processing assets
6 Running ngc
7 Compiling to FESM15
8 Compiling to FESM5
9 Compiling to UMD
10 Minifying UMD bundle
Reusable Component Libraries 399
The resulting bundles are in the “dist” folder for every compiled project. Try expanding the one for
the “header” component, and you should see the structure similar to that in the screenshot below:
Reusable Component Libraries 400
Reusable Component Libraries 401
Publishing to NPM
You need to have an NPM account to publish libraries. Once you get it, the process is relatively
simple. You need to run the “npm publish” command from within every “dist” folder the “ng-
packagr” creates. If you publish the very first version of the library, the “access” attribute needs
to be present as well, for example:
1 cd modules/header/dist
2 npm publish --access public
Package scope
You will need to change the scope of your package to be able to publish the project under
your account.
tsconfig.json
1 {
2 "compileOnSave": false,
3 "compilerOptions": {
4 ...,
5
6 "baseUrl": ".",
7 "paths": {
8 "@denysvuika/ng-framework-header": [ "modules/header/dist" ],
9 "@denysvuika/ng-framework-sidebar": [ "modules/sidebar/dist" ]
10 }
11 }
12 }
Reusable Component Libraries 402
So now, every time you reference the “@denysvuika/ng-framework-header” namespace, the appli-
cation is going to fetch the code from the “modules/header/dist” folder instead of the “node_mod-
ules” one. Similar behaviour is going to be for the “sidebar” component as well. You can map as
many paths as you need.
For the second step, you need to update the “tsconfig.app.json” file and map the namespaces to
the corresponding “public_api.ts” files. We define the mappings for both “compilerOptions” and
“angularCompilerOptions” as in the next example:
src/tsconfig.app.json
1 {
2 "extends": "../tsconfig.json",
3 "compilerOptions": {
4 "outDir": "../out-tsc/app",
5 "baseUrl": "./",
6 "module": "es2015",
7 "types": [],
8 "paths": {
9 "@denysvuika/ng-framework-header": [ "../modules/header/public_api.ts" ],
10 "@denysvuika/ng-framework-sidebar": [ "../modules/sidebar/public_api.ts" ]
11 }
12 },
13 "exclude": [
14 "test.ts",
15 "**/*.spec.ts"
16 ],
17 "angularCompilerOptions": {
18 "paths": {
19 "@denysvuika/ng-framework-header": [ "../modules/header/public_api.ts" ],
20 "@denysvuika/ng-framework-sidebar": [ "../modules/sidebar/public_api.ts" ]
21 }
22 }
23 }
Finally, let’s use our libraries in the application as if they got installed from the NPM. Import
the “HeaderModule” and “SidebarModule” from the corresponding namespaces into the main
application module.
Reusable Component Libraries 403
src/app/app.module.ts
1 import { BrowserModule } from '@angular/platform-browser';
2 import { NgModule } from '@angular/core';
3
4 import { HeaderModule } from '@denysvuika/ng-framework-header';
5 import { SidebarModule } from '@denysvuika/ng-framework-sidebar';
6
7 import { AppComponent } from './app.component';
8
9 @NgModule({
10 declarations: [
11 AppComponent
12 ],
13 imports: [
14 BrowserModule,
15 HeaderModule,
16 SidebarModule
17 ],
18 providers: [],
19 bootstrap: [AppComponent]
20 })
21 export class AppModule { }
Replace the content of the main application component template with the next markup:
src/app/app.component.html
1 <ngfw-header>Header</ngfw-header>
2 <ngfw-sidebar>Sidebar</ngfw-sidebar>
Serve the application with “npm start”, and you should see the following output:
The configuration is complete. You are now ready to build, test and publish component libraries.
Reusable Component Libraries 404
See also
Building an Angular 4 Component Library with the Angular CLI and ng-packagr83
Source code
You can find the source code in the “ng-framework84 ” repository. Don’t forget to change
the package names and NPM scopes if you intend publishing projects to NPM.
83
https://medium.com/@ngl817/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e
84
https://gitlab.com/DenysVuika/ng-framework
Using with Docker
In this chapter, we are going to provide Docker support for an Angular application. We are about to
create a Docker image that contains the prebuilt code, test it in the container, publish and consume
from Docker Hub, and even automate builds and publishing with Travis CI.
As a prerequisite, you need to get a community edition of the Docker for your platform:
Additional Resources
If you want to get more information on the Docker and how to use it, please refer to the following
Udemy course: Docker Mastery: The Complete Toolset From a Docker Captain87 .
If you use Visual Studio Code for development, the “Docker” extension might help you a lot: Docker
for VS Code88 . The Docker extension makes it easy to build and deploy containerized applications
from Visual Studio Code.
ng new ng-docker
cd ng-docker
Before we continue, please ensure the project builds and runs successfully on your local machine by
running the following command:
npm start
Next, visit the http://localhost:4200/ to see the default application content that Angular CLI
provides you out of the box.
85
https://store.docker.com/editions/community/docker-ce-desktop-mac
86
https://store.docker.com/editions/community/docker-ce-desktop-windows
87
https://www.udemy.com/docker-mastery/
88
https://marketplace.visualstudio.com/items?itemName=PeterJausovec.vscode-docker
Using with Docker 406
Creating Dockerfile
First of all, you need to build an application to get the “dist” folder with the content ready to
redistribute.
In the project root, create a file named “Dockerfile” with the following content:
Dockerfile
1 FROM nginx
2
3 COPY nginx.conf /etc/nginx/nginx.conf
4
5 WORKDIR /usr/share/nginx/html
6 COPY dist/ .
The image extends the public “nginx89 ” one. Besides, we provide an external configuration to serve
our application and copy the contents of the “dist” folder into the image.
The minimal “nginx” configuration can be as following:
nginx.conf
1 worker_processes 1;
2
3 events {
4 worker_connections 1024;
5 }
6
7 http {
8 server {
9 listen 80;
10 server_name localhost;
11
12 root /usr/share/nginx/html;
13 index index.html index.htm;
14 include /etc/nginx/mime.types;
15
16 gzip on;
89
https://hub.docker.com/_/nginx/
Using with Docker 407
17 gzip_min_length 1000;
18 gzip_proxied expired no-cache no-store private auth;
19 gzip_types text/plain text/css application/json application/javascript a\
20 pplication/x-javascript text/xml application/xml application/xml+rss text/javasc\
21 ript;
22
23 location / {
24 try_files $uri $uri/ /index.html;
25 }
26 }
27 }
Deployment
There are multiple deployment scenarios documented in the official documentation:
“Deployment90 ”. Please refer to that article if you want to get more information on available
options and best practices.
docker image ls
Excluding the images you may already have created or pulled, you should see the at least the
following output:
Testing in a container
It is now an excellent time to test our image in a container. Use the following command to create a
temporary container out of our image, and run the application at port 3000:
90
https://angular.io/guide/deployment
Using with Docker 408
Once you stop the process with “Ctrl+C”, the Docker is going to perform a cleanup.
Running the container should not take much time. If you now visit the http://localhost:3000/ in
your browser, you should see the Angular CLI application up and running.
Note that the log output gets redirected to your console. You should see the “nginx” output if you
switch to the console right now:
You can now stop the process and let Docker cleanup the data, or continue experimenting with the
application.
Creating docker-compose.yml
For the next step, let’s create a simple “docker-compose” file with our Angular application.
Using with Docker 409
1 version: '3.1'
2
3 services:
4 app:
5 image: 'ng-docker'
6 build: '.'
7 ports:
8 - 3000:80
Note that we have put the “build” parameter so that Docker builds our local image instead of pulling
it from the repository. The Application runs on port 3000 and maps to port 80 inside the container.
You can now run the following command to test the container and docker compose file:
docker-compose up
Once again, visit the http://localhost:3000 address and ensure the application is up and running.
As soon as you are done testing, press “Ctrl+C” to stop the process, and run the next command if
you want to perform a cleanup operation:
docker-compose down
The Docker cleans only the containers created by our docker-compose file. Add the “–rmi all”
parameter if you want to remove the images as well.
The console output, in this case, should be similar to the example below:
You now need to publish your image to the docker hub to allow other people use your docker-
compose file or build their custom containers with your Angular application image.
Using with Docker 410
npm install
npm run build
Note that typically you are going to replace the account prefix with your account name.
To publish the image run the next command with your account name instead of the “account” prefix:
In less than a minute your image should be published and available online.
Now let’s create a temporary container and pull the image from the public repository. Replace the
“account” prefix with your Docker Hub account name.
This time you should see Docker downloading and unpacking your image from the internet. Once the
setup is over, visit the http://localhost:3000 and ensure the application is available and running
fine.
91
https://hub.docker.com/
Using with Docker 411
.travis.yml
1 sudo: required
2
3 language: node_js
4 node_js:
5 - "8"
6
7 cache:
8 directories:
9 - ./node_modules
10
11 services:
12 - docker
13
14 before_install:
15 - "export DISPLAY=:99.0"
16 - "sh -e /etc/init.d/xvfb start"
17
18 script:
19 - npm install
20 - npm run build
21 - npm run test -- --single-run --no-progress
22
23 after_success:
24 - docker build -t account/ng-docker .
25 - docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"
26 - docker push account/ng-docker
The configuration file allows to build your Angular application and run unit tests.
92
https://travis-ci.org/
Using with Docker 412
script:
- npm install
- npm run build
- npm run test -- --single-run --no-progress
As soon as test run is successful, we instruct Travis to build a new Docker image, log in to Docker
Hub and push the image to your account.
after_success:
- docker build -t account/ng-docker .
- docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"
- docker push account/ng-docker
Note that we store credentials as encrypted environment variables, and refer to as $DOCKER_USERNAME
and $DOCKER_PASSWORD.
Also, you can either hardcode the correct “account” prefix or use the “$DOCKER_USERNAME”
value there as well.
Now, if you push the code and switch to the Travis output, you are going to see something like the
following:
Using with Docker 413
See also
Using Docker in Builds93
Source code
You can find the source code in the “angular/ng-docker94 ” folder.
93
https://docs.travis-ci.com/user/docker/
94
https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/ng-docker
Webpack
Webpack is the most popular module bundler for modern JavaScript applications. It takes modules
with dependencies and generates static assets representing those modules.
In this chapter, we are going to learn how to integrate Typescript with webpack.
Source code
You can find the source code in the “webpack/webpack-example95 ” folder.
mkdir webpack-example
cd webpack-example
npm init -y
Typescript configuration
Create a new tsconfig.json file with the following content:
95
https://github.com/DenisVuyka/developing-with-angular/tree/master/webpack/webpack-example
Webpack 415
1 {
2 "compilerOptions": {
3 "target": "es5",
4 "module": "commonjs",
5 "moduleResolution": "node",
6 "sourceMap": true,
7 "emitDecoratorMetadata": true,
8 "experimentalDecorators": true,
9 "lib": [ "es2015", "dom" ],
10 "noImplicitAny": false,
11 "suppressImplicitAnyIndexErrors": true,
12 "outDir": "./dist/",
13 "allowJs": true
14 },
15 "exclude": [
16 "node_modules",
17 "dist"
18 ]
19 }
It is a typical Typescript configuration with additional support for experimental decorators required
by Angular, and source map generation to allow debugging Typescript code in browser developer
tools.
1 module.exports = {
2 entry: './src/main.ts',
3 output: {
4 path: './dist',
5 filename: 'bundle.js'
6 },
7 module: {
8 rules: [
9 {
10 test: /\.ts$/,
11 loader: 'ts-loader',
12 exclude: /node_modules/,
13 }
Webpack 416
14 ]
15 },
16 resolve: {
17 extensions: [".ts", ".js"]
18 }
19 };
We are keeping all source files in the src folder. The main entry is going to be ./src/main.ts file.
Based on configuration webpack is going to create a bundle called bundle.js and store it in the
dist folder. If you delete dist folder (in the future you might be doing that before each production
build), it gets automatically recreated during the next bundle compilation.
Also, we define a ts-loader webpack loader to work with all TypeScript files in our project. Your
project may contain many 3rd party libraries stored in the node_modules folder, so we exclude it
processing.
Finally, resolve.extensions config is set to .ts and .js. That means webpack is going to resolve
class imports by appending given extensions.
For example:
Webpack should start probing the following files to get the content for import:
widgets.ts
widgets.js
We have already configured sourceMap: true property in the tsconfig.json during previous steps.
Now update the configuration file with the following rules:
Webpack 417
1 ...
2 rules: [
3 {
4 enforce: 'pre',
5 test: /\.js$/,
6 loader: "source-map-loader"
7 },
8 {
9 enforce: 'pre',
10 test: /\.ts$/,
11 use: "source-map-loader"
12 }
13 ...
14 ]
15 ...
Based on settings above webpack is instructed to run source-map-loader for .js and .ts files before
any other loaders through the enforce: 'pre'.
Finally, we enable source mapping by setting the devtool property value:
1 devtool: 'inline-source-map'
1 {
2 "dependencies": {
3 "@angular/common": "2.4.7",
4 "@angular/compiler": "2.4.7",
5 "@angular/compiler-cli": "2.4.7",
6 "@angular/core": "2.4.7",
7 "@angular/forms": "2.4.7",
8 "@angular/http": "2.4.7",
9 "@angular/material": "2.0.0-beta.1",
10 "@angular/platform-browser": "2.4.7",
11 "@angular/platform-browser-dynamic": "2.4.7",
12 "@angular/router": "3.4.7",
13 "core-js": "2.4.1",
14 "reflect-metadata": "0.1.9",
Webpack 418
15 "rxjs": "5.1.0",
16 "zone.js": "0.7.6"
17 }
18 }
Above is a common set of Angular libraries used for web applications. Save the file and run npm
install to download and install all dependencies.
src/main.ts
1 import 'core-js/es6';
2 import 'core-js/es7/reflect';
3 require('zone.js/dist/zone');
4
5 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
6 import { enableProdMode } from '@angular/core';
7 import { AppModule } from './app/app.module';
8
9 const platform = platformBrowserDynamic();
10 platform.bootstrapModule(AppModule);
src/app/app.component.ts
src/app/app.module.ts
1 import { NgModule } from '@angular/core';
2 import { BrowserModule } from '@angular/platform-browser';
3
4 import { AppComponent } from './app.component';
5
6 @NgModule({
7 imports: [ BrowserModule ],
8 declarations: [ AppComponent ],
9 bootstrap: [ AppComponent ]
10 })
11 export class AppModule { }
You can create either default HTML template or provide your custom one. In our case, we are going
to provide a custom template containing Angular application component.
src/index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Webpack 2</title>
7 <base href="/">
8 </head>
9 <body>
10 <my-app>Loading AppComponent content here ...</my-app>
11 </body>
12 </html>
webpack.config.js
Development server
Once the page is ready, you need some development server to test it. It is suggested to use webpack-
dev-server that besides serving capabilities also supports watching for file changes and livereload.
Install it with the following command:
webpack.config.js
• contentBase tells server where static content reside, in our case it is dist subdirectory;
• host and port properties are set to 0.0.0.0:3000, you can use either http://localhost:3000
or http://0.0.0.0:3000 to browse your site once the server is up and running. You can also
access the website from the local network in case you are using multiple computers or virtual
machines.
• by setting historyApiFallback to true you enable single page application mode support,
the server responds with index.html content for all 404 responses;
• compress enables gzip compression for all content served, significantly improves the load
time of your web application;
For a full list of available options, please refer to the official documentation97 .
1 {
2 "scripts": {
3 "start": "webpack-dev-server"
4 }
5 }
npm start
Webpack bundles all scripts and serves resulting application at http://localhost:3000. You should
see the following text when browsing the main page at given address:
The webpack-dev-server remains running and watching for changes. It also enables live reload
feature for browsers by default. Every time you change and save your source code, the application
is getting recompiled in the background, and the browser should reload automatically.
To test live reload open src/app/app.component.ts and edit template without stopping the server:
97
https://webpack.js.org/configuration/dev-server/
Webpack 422
src/app/app.component.ts
After several seconds your browser page should reload and render updated HTML content:
Another remarkable feature is a fast in-memory compilation. Webpack does not create files on the
disk when running in development mode.
WARNING in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js
71:15-36 Critical dependency: the request of a dependency is an expression
It is a known issue and should be addressed in the future. Meanwhile, there is a workaround for this
involving ContextReplacementPlugin plugin.
Add the following plugin to the plugins collection in your webpack.config.js file:
webpack.config.js
9 // The (\\|\/) piece accounts for path separators in *nix and Windows
10 /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
11 path.join(__dirname, 'src'), // location of your src
12 {} // a map of your routes
13 )
14
15 ]
16
17 };
app.component.ts
1 @Component(...)
2 export class AppComponent {
3
4 constructor() {
5 console.log('debug');
6 }
7
8 }
98
https://angular.io/docs/ts/latest/guide/style-guide.html#!#application-structure-and-angular-modules
Webpack 425
app.component.ts
app.component.html
app.component.css
app.component.spec.ts
While Angular provides ways referencing external content it does not do any runtime importing or
path resolution, it relies on module loaders and bundlers instead.
1 @Component({
2 selector: 'my-app',
3 template: '<h1>Simple inline template</h1>'
4 })
5 export class AppComponent {}
Most of the popular code editors already support mixed content and provide syntax highlighting
and auto-completion. However, while it might be convenient to keep small HTML template close to
the code, once the HTML grows it becomes harder to maintain it. Also, it is recommended splitting
component implementation into different files for better maintainability.
To use an external template you replace template property with the templateUrl one pointing to
the html file:
1 @Component({
2 selector: 'my-app',
3 templateUrl: './path/to/template.html'
4 })
5 export class AppComponent {}
There’s a special angular2-template-loader loader for webpack that inlines all HTML templates
and CSS styles in Angular components.
Install it with the following command:
Now it can be associated with the TypeScript files in your project. Replace previously defined .ts
rule in the webpack.config.js file with the following one:
Webpack 426
webpack.config.js
1 module.exports = {
2 module: {
3 rules: [
4 {
5 test: /\.ts$/,
6 use: [
7 'ts-loader',
8 'angular2-template-loader'
9 ],
10 exclude: /node_modules/
11 }
12 ]
13 }
14 }
Loaders are invoked from left to right, or as in our case from bottom to top. That means
TypeScript passes every TypeScript file to angular2-template-loader at first. Then ts-loader
receives processed TypeScript files with inlined HTML templates and transpile them to JavaScript.
You also need adding html-loader webpack loader that enables reading HTML files:
Update webpack.config.js configuration file associate all project .html files with the html-loader:
webpack.config.js
1 module.exports = {
2 module: {
3 rules: [
4 {
5 test: /\.html$/,
6 loader: 'html-loader'
7 }
8 ]
9 }
10 }
You can now extract and place HTML template of the component into a separate file:
Webpack 427
src/app/app.component.html
src/app/app.component.ts
As you can see besides template property Angular also provides templateUrl for the Component
decorator.
Now if you run the project with npm start script you should see the following content on the main
page:
src/app/app.component.ts
1 @Component({
2 selector: 'my-app',
3 templateUrl: './app.component.html',
4 styleUrls: ['./app.component.css']
5 })
6 export class AppComponent {}
Create a new app.component.css style next to the html and ts files with the following content:
Webpack 428
src/app/app.component.css
1 .main-title {
2 color: blueviolet;
3 }
Assign newly introduced main-title class to the first element of the app.component.html file:
src/app/app.component.html
1 <h1 class="main-title">Hello from Angular app and Webpack.</h1>
2 <small>(external template)</small>
The angular2-template-loader is capable of loading .html and .css files. It is working best with
the raw-loader that lets you importing files as strings to inline them into compiled Angular code.
For current scenario we need to use raw-loader only with our Angular components and their styles,
all other CSS files should not be affected. That is why the rule is restricting loader only to content
coming from src/app folder (and all subfolders):
Now if you rebuild and start the application with npm start command you should see the styles
applied:
Webpack 429
Loading images
The easiest way to provide support for image loading with webpack is to use file-loader loader.
Install it for your project with the following command:
Here’s the rule to be added to your webpack.config.js file. The rule is addressing the most common
set of extensions for images, icons and web fonts.
webpack.config.js
1 module.exports = {
2 module: {
3 rules: [
4 {
5 test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
6 loader: 'file-loader?name=assets/[name].[hash].[ext]'
7 }
8 ]
9 }
10 };
Let’s put an angular.svg image to src/resources folder and update app.component.html like
following:
src/app/app.component.html
Once the application is rebuilt and restarted you should see an image displayed on the page:
Webpack 430
TSLint
TSLint checks your TypeScript code for readability, maintainability, and functionality errors. You
can use it as a standalone utility or part of your webpack pipeline using special loaders and plugins.
Global installation
You may want to install TSLint globally to be able using its CLI commands from any place like for
instance generating new configuration files for a project. Also, many development environments
expect global installation to enable live checking and style error reporting for TypeScript code.
Local installation
It is recommended to install TSLint as a local project library not to depend on specific global version.
That also helps to reduce the number of prerequisites needed to setup and compile your project as
developers need to run only npm install to get all dependencies downloaded and installed.
Configuring rules
Install TSLint globally and locally like shown above and let’s start by creating a minimal configu-
ration file for the project by generating it with the CLI:
tslint --init
You should get a new file tslint.json in the current folder that contains a default set of rules. To
ensure it is working fine just run a quick code style check from the command line:
Webpack 431
The command above runs TSLint with the newly created tslint.json as a configuration file, and
instructs linter to check all .ts files starting with the src folder (including all subfolders).
You should get several errors from the linter related to quote mark usage:
That is because we have been using single quote marks across the code examples. Now when you
know that the linter works, you can decide whether to correct the code and switch to “double quotes”,
or change the rule to use single quotation marks instead.
Let’s change the rule. Open tslint.json and find the following section:
Webpack 432
tslint.json
1 {
2 "rules": {
3 "quotemark": [
4 true,
5 "double"
6 ]
7 }
8 }
Change the setting double to single, save the file and run code check again:
1 module.exports = {
2 module: {
3 rules: [
4 {
5 enforce: 'pre',
6 test: /\.ts$/,
7 loader: 'tslint-loader',
8 exclude: /node_modules/
9 }
10 ]
11 }
12 }
The rule executes before other rules (enforce: 'pre') and runs for all .ts files in your project folder
and all its subfolders excluding node_modules one. You are going to see warnings every time TSLint
finds an issue with the TypeScript code, including hot reloading when you change the code.
Unit testing
We are going to use Karma with Webpack for unit testing. The benefit of such configuration is that
Webpack transpiles TypeScript into JavaScript in memory and redirects output to Karma test runner
on the fly. No files get created on disk during the process.
Open the package.json file and append the new entry to the scripts section like shown below:
Webpack 433
package.json
1 {
2 "scripts": {
3 ...
4 "test": "karma start"
5 },
6 }
The script we added allows running unit tests with the command npm test.
You also need appending the following entries to the devDependencies section:
package.json
1 {
2 "devDependencies": {
3 ...
4 "@types/jasmine": "2.5.41",
5 "jasmine-core": "2.5.2",
6 "karma": "1.4.1",
7 "karma-chrome-launcher": "2.0.0",
8 "karma-jasmine": "1.1.0",
9 "karma-mocha-reporter": "2.2.2",
10 "karma-webpack": "2.0.2",
11 "null-loader": "0.1.1",
12 "rimraf": "2.5.4"
13 }
14 }
Save the file and run npm install to download and install new libraries.
Next, you need to create a karma-test-shim.js file in your project root folder.
karma-test-shim.js
1 Error.stackTraceLimit = Infinity;
2
3 require('core-js/es6');
4 require('core-js/es7/reflect');
5
6 require('zone.js/dist/zone');
7 require('zone.js/dist/long-stack-trace-zone');
8 require('zone.js/dist/proxy');
Webpack 434
9 require('zone.js/dist/sync-test');
10 require('zone.js/dist/jasmine-patch');
11 require('zone.js/dist/async-test');
12 require('zone.js/dist/fake-async-test');
13
14 var appContext = require.context('./src', true, /\.spec\.ts/);
15
16 appContext.keys().forEach(appContext);
17
18 var testing = require('@angular/core/testing');
19 var browser = require('@angular/platform-browser-dynamic/testing');
20
21 testing.TestBed.initTestEnvironment(
22 browser.BrowserDynamicTestingModule,
23 browser.platformBrowserDynamicTesting()
24 );
This file preloads core-js and zone.js libraries and provides basic configuration for the Angular
testing framework. Only files ending with .spec.ts load into the application context.
Finally we need a karma.conf.js file to setup Webpack with Karma:
karma.conf.js
20 stats: 'errors-only'
21 },
22
23 webpackServer: {
24 noInfo: true
25 },
26
27 reporters: ['mocha'],
28 port: 9876,
29 colors: true,
30 logLevel: config.LOG_INFO,
31 autoWatch: false,
32 browsers: ['Chrome'],
33 singleRun: true
34 };
35
36 config.set(_config);
37 };
At this point, Webpack is using PhantomJS headless browser and loads karma-test-shim.js that
references the rest of the content. Let’s create a couple of unit tests to see the entire flow in action:
src/app/app.component.spec.ts
20 fixture = TestBed.createComponent(AppComponent);
21 comp = fixture.componentInstance;
22 de = fixture.debugElement.query(By.css('h1'));
23 });
24
25 it('should create the component', () => expect(comp).toBeDefined());
26
27 it('should have expected <h1> text', () => {
28 fixture.detectChanges();
29 const h1 = de.nativeElement;
30 expect(h1.innerText).toMatch(/angular/i,
31 '<h1>Hello from Angular app and Webpack."');
32 });
33 });
npm test
AppComponent
� should create the component
� should have expected <h1> text
Code coverage
Having support for code coverage reports is often critical when running unit tests, especially when
dealing with JavaScript and TypeScript.
It is important to remember that code and unit tests get transpiled from TypeScript to JavaScript
before execution. We are going to use webpack to perform the following:
Let’s start with adding additional development dependencies to the package.json file:
Webpack 437
package.json
1 {
2 "devDependencies": {
3 ...
4 "karma-coverage": "1.1.1",
5 "karma-remap-istanbul": "0.6.0",
6 "karma-sourcemap-loader": "0.3.7",
7 "sourcemap-istanbul-instrumenter-loader": "0.2.0",
8 "rimraf": "2.5.4",
9 "wsrv": "0.1.6"
10 }
11 }
Save the file and run npm install to download and install newly added dependencies.
That adds libraries required to enable support for re-mapping of code coverage. Also, we are adding
rimraf library to be able cleaning previously generated reports when running unit tests, and a
lightweight web server wsrv to see the final report in the browser.
The webpack setup remains pretty much the same we have created in previous chapters. We only
need appending a rule to instrument JavaScript files with Istanbul for subsequent code coverage
reporting. The rule executes as the last one (via enforce: 'post' setting) after all other rules
invoked.
webpack.test.js
1 module.exports = {
2 module: {
3 ...
4 rules: [
5 ...
6 {
7 test: /src\/.+\.ts$/,
8 exclude: /(node_modules|\.spec\.ts$)/,
9 loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=\
10 true',
11 enforce: 'post'
12 }
13 ]
14 }
15 }
Next, you need extending preprocessors and reporters sections, as well as adding remapIstan-
bulReporter settings:
Webpack 438
karma.conf.js
1 {
2 ...
3
4 preprocessors: {
5 './karma-test-shim.js': [
6 'webpack',
7 'sourcemap'
8 ]
9 },
10
11 reporters: [
12 'mocha',
13 'karma-remap-istanbul'
14 ],
15
16 remapIstanbulReporter: {
17 reports: {
18 'html': './coverage',
19 'lcovonly': './coverage/lcov.info'
20 }
21 }
22
23 ...
24 }
We are going to use sourcemap preprocessor together with a webpack one for all unit tests in the
project. There going to be two test reporters: mocha and karma-remap-istanbul.
mocha: Karma reporter with mocha style logging should give you excellent human-readable console
output:
Webpack 439
package.json
1 {
2 "scripts": {
3 ...
4 "test": "rimraf coverage && karma start",
5 "coverage": "wsrv coverage -o"
6 },
7 }
Upon every test run the karma and webpack are automatically generating code coverage report, re-
mapping it to TypeScript and storing within the coverage folder. To avoid any potential problems
with the subsequent runs we are going to remove previous report folder with the rimraf utility we
have installed earlier.
You can now run tests with the following command:
npm test
Once executed this command automatically launches your default browser pointing to the coverage
report. It is possible to browse all instrumented files and see details for TypeScript content:
Webpack 441
Source code
You can find the source code in the “webpack/webpack-example99 ” folder.
Code splitting
Code splitting is one of the most important and popular features of webpack. It allows you to prepare
web applications for production and greatly optimises them by splitting into separate bundles.
Smaller bundles improve application load time as bundle files get cached by the browsers or loaded
on demand.
As a starting point, we are going to use the webpack project created in previous chapters.
Alternatively, you can take the source code from the “webpack/webpack-example100 ” folder
Multiple configurations
For real-life scenarios it is strongly recommended splitting webpack configuration into at least 3
main blocks:
All configuration files may contain same setting blocks, so you should use a shared webpack.common.js
file to avoid code duplication.
Let’s start by installing a webpack-merge library that helps to merge multiple webpack configuration
files:
Now we can create webpack.common.js file hosting shared settings. You can take most of the content
already created for the webpack.config.js file in previous chapters. The basic structure of the file
can look like the following:
webpack.common.js
Regardless of the configuration mode (development, production, custom) webpack should be taking
./src/main.ts file as the main entry point, and saving the resulting bundle as ./dist/bundle.js
file.
Webpack 443
Development mode
Note that we do not set devtool and devServer configuration in the webpack.common.js file because
these settings are mode-specific. Running development server makes sense only for development
mode, and style of source mapping to enhance the debugging process (devtool) usually differs across
configurations. Let’s create webpack.dev.js to hold these values.
webpack.dev.js
You can now replace the content of the webpack.config.js with the next code:
webpack.config.js
1 module.exports = require('./webpack.dev.js');
All the tooling that defaults to webpack.config.js is automatically going to use development config.
To test our current setup just use start command:
npm start
Production mode
Now let’s create a separate configuration file for production builds. There are many optimisations
that you can add to module settings, but we need webpack to perform at least the following
additional actions:
webpack.prod.js
Creating a bundle can take some time especially when running different optimisations. There may
be multiple loaders and plugins invoked in the bundling process, and an error raised by one usually
means broken bundle files. To save time, we are going to use NoEmitOnErrorsPlugin webpack plugin
that stops the build if there is an error.
The UglifyJsPlugin plugin is used to minify JavaScript we get by transpiling TypeScript. We also
instruct plugin to remove all comments from the code to make resulting bundle much smaller.
The DefinePlugin plugin allows using environment variables in your application code. We use it
to declare process.env object that can later be accessed from main.ts TypeScript file to determine
whether the enableProdMode function should be called.
Finally a LoaderOptionsPlugin plugin is used to set or override options of various plugins. In our
case, the minification of HTML is switched off as a workaround for Angular component templates.
Now edit package.json file to add a script for triggering production builds:
package.json
1 {
2 "scripts": {
3 ...
4 "build": "rimraf dist && webpack --config webpack.prod.js --progress --b\
5 ail"
6 }
7 }
Webpack 446
Note that rimraf utility is used to delete the dist folder before each build. In this case, we can be
sure there are no files left from previous runs.
Run the following command to create production build:
As a result, you get compiled and bundled application that can be served by any web server of your
choice. To test it locally we can use wsrv tool and the following script in package.json:
package.json
1 {
2 "scripts": {
3 ...
4 "serve:prod": "wsrv dist --spa --open"
5 }
6 }
Use the next command to serve your production build and run it with the default browser
automatically:
CSS splitting
Webpack allows you to bundle multiple CSS files into a separate bundle that can be loaded and
cached by browsers. With the help of additional plugins and loaders you can use import keyword
for .css files similar to how JavaScript or TypeScript modules are imported:
1 import './main.css';
You need to install three additional libraries to be able importing and bundling CSS with webpack:
Based on previous chapters webpack configuration for the project should already have angular2-
template-loader that inlines all html templates (templateUrl) and styles (styleUrls) for Angular
components.
We are going to use extract-text-webpack-plugin plugin to get the rest of the CSS files and put
them into a separate bundle.
Webpack 447
webpack.common.js
webpack.dev.js
For development mode the webpack-dev-server automatically reloads browser page with the latest
bundle files. That means we do not need maintaining browser cache and generating hash suffixes
for bundles.
The resulting file has a regular format, just a name with an extension, something like main.css.
Webpack 448
webpack.prod.js
For production mode, the CSS file gets a hash suffix based on its content. This allows controlling
browser cache on the client side. Browsers should cache all bundles on the first application load so
that the subsequent loads are much faster. If content of the bundle changes (one of the bundled CSS
files changes for instance), its corresponding hash code updates as well. Updated file be downloaded
and cached like a previous one.
The resulting file will be named like main.0f40125588b03ddb8b86.css.
Now create a simple main.css file to test configuration above:
src/main.css
1 /* Main theme */
2
3 .title {
4 color: black;
5 }
As it was mentioned earlier, you can import CSS files similar to how modules are imported. Open
main.ts file and put the import of the newly created main.css inside:
src/main.ts
1 ...
2 import './main.css';
3
4 const platform = platformBrowserDynamic();
5 platform.bootstrapModule(AppModule);
It is important to note that besides importing styles by relative paths you can also bundle content
from external libraries installed to node_modules. For example getting the deeppurple-amber.css
theme style from the @angular/material library looks like following:
Webpack 449
1 import '@angular/material/core/theming/prebuilt/deeppurple-amber.css';
Once the build is finished you should get two additional files in the dist folder similar to the
following:
main.0f40125588b03ddb8b86.css
main.0f40125588b03ddb8b86.css.map
Webpack not only builds all your additional CSS files into a single bundle, but it also changes the
resulting index.html page to include the bundle:
dist/index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Webpack 2</title>
7 <base href="/">
8 <link href="main.0f40125588b03ddb8b86.css" rel="stylesheet">
9 </head>
10 <body>
11 <my-app>Loading AppComponent content here ...</my-app>
12 <script type="text/javascript" src="bundle.js"></script>
13 </body>
14 </html>
• polyfills bundle
• vendors bundle
• application bundle
src/polyfills.ts
1 import 'core-js/es6';
2 import 'core-js/es7/reflect';
3 require('zone.js/dist/zone');
4
5 if (process.env.ENV === 'production') {
6 // Production
7 } else {
8 // Development and test
9 Error['stackTraceLimit'] = Infinity;
10 require('zone.js/dist/long-stack-trace-zone');
11 }
All libraries for the polyfills are resolved via node_modules folder.
For the polyfills bundle, we set core-js and zone.js. If the application is running in development
mode, we also enable long stack traces for zone.js library. That provides more details on exceptions
happened within Angular components.
Based on the application features and target browsers you may need adding more polyfills to this
file.
src/vendor.ts
1 // Angular
2 import '@angular/platform-browser';
3 import '@angular/platform-browser-dynamic';
4 import '@angular/core';
5 import '@angular/common';
6 import '@angular/http';
7 import '@angular/router';
8
9 // RxJS
10 import 'rxjs';
11
12 // Other vendors for example jQuery, Lodash or Bootstrap
13 // You can import js, ts, css, sass, ...
Vendors file contains a bare minimum for a typical Angular-based application. You are going to put
here all additional third party libraries being used. All libraries for the vendors.ts file are resolved
via node_modules folder.
Finally the main.ts file should look like following:
Webpack 451
src/main.ts
Now let’s tell webpack we have polyfills.ts, vendors.ts and main.ts bundles. You need to change
webpack.common.js like shown below:
webpack.common.js
Before:
1 module.exports = {
2 entry: './src/main.ts',
3 ...
4 }
After:
1 module.exports = {
2 entry: {
3 'polyfills': './src/polyfills.ts',
4 'vendor': './src/vendor.ts',
5 'app': './src/main.ts'
6 },
7 plugins: [
8 ...
9 new webpack.optimize.CommonsChunkPlugin({
10 name: ['app', 'vendor', 'polyfills']
11 })
12 ]
13 ...
14 }
Webpack 452
We instruct webpack to use three entries instead of a single one and use CommonsChunkPlugin to
improve result greatly.
1 module.exports = {
2 ...
3 output: {
4 path: path.join(__dirname, 'dist'),
5 filename: '[name].js',
6 chunkFilename: '[id].chunk.js'
7 }
8 }
webpack.prod.js
1 module.exports = webpackMerge(commonConfig, {
2
3 output: {
4 path: path.join(__dirname, 'dist'),
5 publicPath: '/',
6 filename: '[name].[hash].js',
7 chunkFilename: '[id].[hash].chunk.js'
8 },
9 ...
10
11 });
At this point, the minimal configuration should be complete. To see code splitting in action just run
the production build with the build npm command
101
https://webpack.js.org/plugins/commons-chunk-plugin/
Webpack 453
Now if you inspect the dist folder you should see the structure similar to the following one:
Size Name
102 app.e9ba0634398c31b37d5a.css
295 app.e9ba0634398c31b37d5a.css.map
1923 app.e9ba0634398c31b37d5a.js
8716 app.e9ba0634398c31b37d5a.js.map
572 index.html
104047 polyfills.e9ba0634398c31b37d5a.js
792348 polyfills.e9ba0634398c31b37d5a.js.map
1048488 vendor.e9ba0634398c31b37d5a.js
7962491 vendor.e9ba0634398c31b37d5a.js.map
As you can see the app bundle is now very small (± 2kB) and containing only your application-
specific code. Next comes the polyfills bundle (± 104kB) that contains cross-browser support.
Finally the vendor bundle (± 1MB) having Angular framework and all third party libraries used by
your web application combined together.
All files have hash codes in the names, so browsers are able caching them and downloading new
versions only when the content of the files (and so the hash code) changes.
Building a Mobile App with Cordova
and Angular
Apache Cordova is an open-source mobile development framework. It allows you to use standard
web technologies - HTML5, CSS3, and JavaScript for cross-platform development.
You can get an excellent overview and technical details on the official “Overview102 ” page.
The Apache Cordova framework allows you to write the same code to address at least the following
platforms:
102
https://cordova.apache.org/docs/en/latest/guide/overview/index.html
Building a Mobile App with Cordova and Angular 455
• Android
• Blackberry 10
• iOS
• macOS (OS X)
• Ubuntu
• Windows
• WP8
In this chapter, we are going to setup Apache Cordova with the Angular framework and see a simple
iOS application powered by the Angular in action.
We are going to need the Angular CLI tools, so ensure you also have it installed globally:
Angular CLI
You can get more information in the corresponding Angular CLI chapter.
With the commands above we get a new Cordova application called “myCordovaApp” in the current
directory and switch to the application directory after that.
By default, Cordova creates just an application scaffold to use with different platforms of your
choice. You need to add each platform manually depending on the requirements.
In this chapter, we are going to focus on two platforms: Browser and iOS.
Building a Mobile App with Cordova and Angular 456
You can add the support for the Browser platform by executing the following Cordova CLI command:
To run the newly created application with the given platform, we are going to use the following
command format:
As with adding a new one, you need to specify the platform name and optionally some parameters
if particular platform plugin supports that.
Let’s see that in action by running the Browser platform we have added earlier.
With the command above, the Cordova CLI builds the project and launches it within a separate
instance of the browser upon completion:
Building a Mobile App with Cordova and Angular 457
The Cordova CLI can now build your app for iOS platform alongside the Browser one. We can utilise
the same “cordova run” command to launch the app in the iOS simulator. We just need to provide
the target, for example, let’s try the “iPhone 6s” emulator with our newly generated app:
It may take some time to compile the application for iOS for the first time. Once the compilation
completes, you should get the iPhone emulator up and running with your app.
Building a Mobile App with Cordova and Angular 458
Next, edit the “index.html” file created in the “webapp/src” folder, and replace with the following
content:
webapp/src/index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Security-Policy" content="default-src 'self' d\
5 ata: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline\
6 '; media-src *; img-src 'self' data: content:;">
7 <meta name="format-detection" content="telephone=no">
8 <meta name="msapplication-tap-highlight" content="no">
9 <meta name="viewport" content="user-scalable=no, initial-scale=1, maximu\
10 m-scale=1, minimum-scale=1, width=device-width">
11 <link rel="icon" type="image/x-icon" href="favicon.ico">
12 <meta charset="utf-8">
13 <title>Hello World</title>
14 <base href=".">
15 </head>
16 <body>
17 <script type="text/javascript" src="cordova.js"></script>
18 <app-root></app-root>
19 </body>
20 </html>
The file above is a hybrid made of the default Cordova “index.html” combined with the default
Angular CLI one.
We also need to update the “.angular-cli.json” file and set the default output directory to point to
the “www” folder used by Cordova when preparing the code for various platforms.
webapp/.angular-cli.json
1 {
2 "apps": {
3 "outDir": "../www"
4 }
5 }
Building a Mobile App with Cordova and Angular 460
Build scripts
At this point, our project consists of the code produced and maintained by two different command-
line tools. We are going to use Angular CLI utilities when working with the Angular part of the
project, and Cordova CLI when preparing the code for devices or emulators.
The easiest way to improve the development experience is to create a set of NPM commands
addressing all the common scenarios. Edit the root “package.json” file and put the following content
in the “scripts” section:
1 {
2 "scripts": {
3 "build": "(cd webapp && ng build --prod) && cordova build",
4 "start": "(cd webapp && ng build --prod) && cordova run browser",
5 "ios": "cordova run ios --target='iPhone-6s'",
6 "serve": "cordova serve 3000"
7 }
8 }
The “build” script, executed with the “npm run build” command, is going to build our Angular
application in production mode, and then use Cordova tools to compile the entire project for all the
configured platforms.
You are going to use the “start” script to build your Angular application and run it with the Browser
platform. You can use “npm run start” or shorthand version “npm start” for that.
The third common script we have in the “package.json” is the “ios” one, called with the “npm run
ios” command and is used to invoke iOS emulator targeting the iPhone 6s. Please note that this script
by default does not rebuild everything, and gets typically invoked after “npm run build” command.
Finally, the “serve” command may help you quickly serving the web part in the browser to see the
latest state of your code when your focus is the functionality rather than the platform.
npm start
Once Angular application compiles and Cordova runs it in the separate browser instance, you should
see the default Angular CLI project template on your screen.
Building a Mobile App with Cordova and Angular 461
You should see the emulator window that contains a content similar to that on the picture below:
Building a Mobile App with Cordova and Angular 462
Routing support
Routing is one of the major features when it comes to Mobile apps created with HTML5, so to finalise
our initial project setup we should also ensure, for instance, that the Angular Router works inside
the iOS simulator as expected.
Let’s use Angular CLI to generate a couple of extra components to use with the router outlet:
1 cd webapp
2 ng g component page1
3 ng g component page2
Now update the main application module file “app.module.ts” with the routing settings:
Building a Mobile App with Cordova and Angular 463
Finally, we need to update the main application component template “app.component.html” with
the following simple Router setup appended to the end of the file:
1 <ul>
2 <li>
3 <a routerLink="/page1">Page 1</a>
4 </li>
5 <li>
6 <a routerLink="/page2">Page 2</a>
7 </li>
8 </ul>
9
10 <router-outlet></router-outlet>
First of all, we can test the code with the Browser platform to see if everything works as expected, and
fix the code errors if any. As you can imagine debugging and fixing bugs in the browser developer
tools is much easier than using an emulator and remote debugging.
Building a Mobile App with Cordova and Angular 464
npm start
Once application runs in the browser, scroll to the bottom of the page where “Page 1” and “Page
2” links are rendered, and try clicking them. The content under the links section should change to
reflect current page content. By default, for “Page 2” it is going to be something like “page2 works!”.
Scroll to the bottom of the page and test both routes as we did previously with the Browser platform
plugin. You should see the same “page2 works!” message when tapping the “Page 2” link, and “page1
works!” for the first link.
Building a Mobile App with Cordova and Angular 465
Summary
Congratulations, you now have a working configuration that allows you to compile and run Angular
applications with Apache Cordova, and address various platforms and devices at the same time.
Don’t forget to check out the latest Apache Cordova documentation103 to get more details on the
framework and scenarios related to cross-platform application development with HTML5.
103
https://cordova.apache.org/docs/en/latest/
Changelog
This chapter contains a list of notable changes introduced to the book with each version.
Revision 15 (2018-06-03)
• Updated: ngOnDestroy
• New Listening for View and Content changes
Revision 14 (2018-04-08)
• Updated Plugins chapter with more details
• Updated Angular CLI chapter with more details on Modules
• Updated Directives chapter with ng-container directive
• Updated Internationalisation (i18n) chapter with a reference to “@ngstack/translate104 ”
Revision 13 (2018-02-12)
• Plugins
Revision 12 (2017-12-17)
• Using with Docker
Revision 11 (2017-12-03)
• Reusable component libraries
Revision 10 (2017-11-12)
• Components
– Component Lifecycle
104
https://www.npmjs.com/package/@ngstack/translate
Changelog 467
Revision 9 (2017-10-15)
• Components
– Providers
– Host
– Queries
Revision 8 (2017-09-17)
• Global Application Configuration
• Internationalisation (i18n)
Revision 7 (2017-09-14)
• Events
Revision 6 (2017-08-24)
• Building a Mobile Application with Cordova and Angular
Revision 5 (2017-08-13)
• Dependency Injection
Revision 4 (2017-07-22)
• ES6: Destructuring assignment
Revision 3 (2017-07-09)
• NG: Advanced Angular, Dynamic Content
Revision 2 (2017-07-02)
• NG: Components Chapter (part 1, basic components)
• Updated Introduction with a link to the project board
Changelog 468
Revision 1 (2017-06-09)
Initial version of the book published to public.