Ng Book 2 The Guide To Angular 4
User Manual:
Open the PDF directly: View PDF .
Page Count: 681
Download | |
Open PDF In Browser | View PDF |
ng-book The Complete Guide to Angular Written by Nate Murray, Felipe Coury, Ari Lerner, and Carlos Taborda © 2017 Fullstack.io All rights reserved. No portion of the book manuscript may be reproduced, stored in a retrieval system, or transmitted in any form or by any means beyond the number of purchased copies, except for a single backup or archival copy. The code may be used freely in your projects, commercial or otherwise. The authors and publisher have taken care in preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damagers in connection with or arising out of the use of the information or programs container herein. Published in San Francisco, California by Fullstack.io. FULLSTACK .io We’d like to thank: • Our technical editors: Frode Fikke, Travas Nolte, Daniel Rauf • Nic Raboy, and Burke Holland for contributing the NativeScript chapter Contents Book Revision . . . . . . . . . . Bug Reports . . . . . . . . . . . Chat With The Community! . . Vote for New Content (new!) . . Be notified of updates via Twitter We’d love to hear from you! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 1 1 1 How to Read This Book . . . . . . . . . Running Code Examples . . . . . . Angular CLI . . . . . . . . . . . . Code Blocks and Context . . . . . . Code Block Numbering . . . . . A Word on Versioning . . . . . . . . Getting Help . . . . . . . . . . . . . Emailing Us . . . . . . . . . . . . . Technical Support Response Time Chapter Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 3 3 3 4 4 5 5 6 Writing Your First Angular Web Application Simple Reddit Clone . . . . . . . . . . . . . Getting started . . . . . . . . . . . . . . . . Node.js and npm . . . . . . . . . . . . . . TypeScript . . . . . . . . . . . . . . . . . Browser . . . . . . . . . . . . . . . . . . Special instruction for Windows users . . . Angular CLI . . . . . . . . . . . . . . . . Example Project . . . . . . . . . . . . . Writing Application Code . . . . . . . . Running the application . . . . . . . . . . . Making a Component . . . . . . . . . . . Importing Dependencies . . . . . . . . . Component Decorators . . . . . . . . . . Adding a template with templateUrl . . Adding a template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 4 4 4 4 5 5 6 10 10 12 13 14 14 15 CONTENTS Adding CSS Styles with styleUrls . . . . . . . Loading Our Component . . . . . . . . . . . . . Adding Data to the Component . . . . . . . . . . . Working With Arrays . . . . . . . . . . . . . . . . Using the User Item Component . . . . . . . . . . Rendering the UserItemComponent . . . . . . . Accepting Inputs . . . . . . . . . . . . . . . . . Passing an Input value . . . . . . . . . . . . . . Bootstrapping Crash Course . . . . . . . . . . . . declarations . . . . . . . . . . . . . . . . . . . imports . . . . . . . . . . . . . . . . . . . . . . providers . . . . . . . . . . . . . . . . . . . . . bootstrap . . . . . . . . . . . . . . . . . . . . . Expanding our Application . . . . . . . . . . . . . Adding CSS . . . . . . . . . . . . . . . . . . . . The Application Component . . . . . . . . . . . Adding Interaction . . . . . . . . . . . . . . . . Adding the Article Component . . . . . . . . . Rendering Multiple Rows . . . . . . . . . . . . . . Creating an Article class . . . . . . . . . . . . . Storing Multiple Articles . . . . . . . . . . . . Configuring the ArticleComponent with inputs Rendering a List of Articles . . . . . . . . . . . Adding New Articles . . . . . . . . . . . . . . . . Finishing Touches . . . . . . . . . . . . . . . . . . Displaying the Article Domain . . . . . . . . . Re-sorting Based on Score . . . . . . . . . . . . Deployment . . . . . . . . . . . . . . . . . . . . . Building Our App for Production . . . . . . . . Uploading to a Server . . . . . . . . . . . . . . Installing now . . . . . . . . . . . . . . . . . . . Full Code Listing . . . . . . . . . . . . . . . . . . . Wrapping Up . . . . . . . . . . . . . . . . . . . . . Getting Help . . . . . . . . . . . . . . . . . . . . . TypeScript . . . . . . . . . . . . . . Angular 4 is built in TypeScript . What do we get with TypeScript? Types . . . . . . . . . . . . . . . Trying it out with a REPL . . Built-in types . . . . . . . . . . . Classes . . . . . . . . . . . . . . Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 16 17 20 23 24 25 25 27 29 29 29 29 30 31 32 34 38 47 47 52 53 55 57 58 58 59 60 61 62 62 62 63 63 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 64 65 66 67 68 70 70 CONTENTS Methods . . . . . . . Constructors . . . . Inheritance . . . . . Utilities . . . . . . . . . Fat Arrow Functions Template Strings . . Wrapping up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 73 74 76 76 78 79 How Angular Works . . . . . . . . . . . . . . . . . . . . . . . . . . Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Navigation Component . . . . . . . . . . . . . . . . . . . The Breadcrumbs Component . . . . . . . . . . . . . . . . . . The Product List Component . . . . . . . . . . . . . . . . . . How to Use This Chapter . . . . . . . . . . . . . . . . . . . . . . Product Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Component Decorator . . . . . . . . . . . . . . . . . . . . . . . . Component selector . . . . . . . . . . . . . . . . . . . . . . Component template . . . . . . . . . . . . . . . . . . . . . . Adding A Product . . . . . . . . . . . . . . . . . . . . . . . . Viewing the Product with Template Binding . . . . . . . . . . Adding More Products . . . . . . . . . . . . . . . . . . . . . . Selecting a Product . . . . . . . . . . . . . . . . . . . . . . . . Listing products using. . . . . . . . . . . . The ProductsListComponent . . . . . . . . . . . . . . . . . . . . Configuring the ProductsListComponent @Component Options Component inputs . . . . . . . . . . . . . . . . . . . . . . . . Component outputs . . . . . . . . . . . . . . . . . . . . . . . Emitting Custom Events . . . . . . . . . . . . . . . . . . . . . Writing the ProductsListComponent Controller Class . . . . . Writing the ProductsListComponent View Template . . . . . . The Full ProductsListComponent Component . . . . . . . . . The ProductRowComponent Component . . . . . . . . . . . . . . . ProductRowComponent Configuration . . . . . . . . . . . . . . ProductRowComponent template . . . . . . . . . . . . . . . . . The ProductImageComponent Component . . . . . . . . . . . . . The PriceDisplayComponent Component . . . . . . . . . . . . . The ProductDepartmentComponent . . . . . . . . . . . . . . . . . NgModule and Booting the App . . . . . . . . . . . . . . . . . . . Booting the app . . . . . . . . . . . . . . . . . . . . . . . . . . The Completed Project . . . . . . . . . . . . . . . . . . . . . . . Deploying the App . . . . . . . . . . . . . . . . . . . . . . . . . . A Word on Data Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 80 81 81 82 84 85 86 88 88 88 89 91 92 93 94 97 98 98 100 101 103 104 106 108 109 110 111 111 112 113 115 116 116 117 CONTENTS Built-in Directives . . . Introduction . . . . NgIf . . . . . . . . NgSwitch . . . . . . NgStyle . . . . . . . NgClass . . . . . . . NgFor . . . . . . . . Getting an index NgNonBindable . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 119 119 120 122 125 128 133 134 135 Forms in Angular . . . . . . . . . . . . . . . . . . . . . . . . . . . . Forms are Crucial, Forms are Complex . . . . . . . . . . . . . . . FormControls and FormGroups . . . . . . . . . . . . . . . . . . . FormControl . . . . . . . . . . . . . . . . . . . . . . . . . . . FormGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Our First Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loading the FormsModule . . . . . . . . . . . . . . . . . . . . Simple SKU Form: @Component Decorator . . . . . . . . . . Simple SKU Form: template . . . . . . . . . . . . . . . . . . . Simple SKU Form: Component Definition Class . . . . . . . . Try it out! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using FormBuilder . . . . . . . . . . . . . . . . . . . . . . . . . Reactive Forms with FormBuilder . . . . . . . . . . . . . . . . . Using FormBuilder . . . . . . . . . . . . . . . . . . . . . . . . Using myForm in the view . . . . . . . . . . . . . . . . . . . . . Try it out! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding Validations . . . . . . . . . . . . . . . . . . . . . . . . . Explicitly setting the sku FormControl as an instance variable Custom Validations . . . . . . . . . . . . . . . . . . . . . . . . Watching For Changes . . . . . . . . . . . . . . . . . . . . . . . . ngModel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 136 136 136 137 138 139 140 140 144 144 146 147 147 148 149 151 152 158 160 161 163 Dependency Injection . . . . . . . . . . . . Injections Example: PriceService . . . Dependency Injection Parts . . . . . . . Playing with an Injector . . . . . . . . . Providing Dependencies with NgModule Providers are the Key . . . . . . . . . Providers . . . . . . . . . . . . . . . . . Using a Class . . . . . . . . . . . . . Using a Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 165 169 170 174 176 176 176 181 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONTENTS Dependency Injection in Apps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 More Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . Using @angular/http . . . . . . . . . . . . . . . . . . . . . . import from @angular/http . . . . . . . . . . . . . . . . . A Basic Request . . . . . . . . . . . . . . . . . . . . . . . . . Building the SimpleHttpComponent Component Definition Building the SimpleHttpComponent template . . . . . . . . Building the SimpleHttpComponent Controller . . . . . . . Full SimpleHttpComponent . . . . . . . . . . . . . . . . . . Writing a YouTubeSearchComponent . . . . . . . . . . . . . . Writing a SearchResult . . . . . . . . . . . . . . . . . . . Writing the YouTubeSearchService . . . . . . . . . . . . . Writing the SearchBoxComponent . . . . . . . . . . . . . . Writing SearchResultComponent . . . . . . . . . . . . . . Writing YouTubeSearchComponent . . . . . . . . . . . . . . @angular/http API . . . . . . . . . . . . . . . . . . . . . . . Making a POST request . . . . . . . . . . . . . . . . . . . . PUT / PATCH / DELETE / HEAD . . . . . . . . . . . . . . . . . RequestOptions . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 186 187 187 188 189 189 190 192 192 194 195 200 207 208 212 212 213 214 215 Routing . . . . . . . . . . . . . . . . . . . . . . . Why Do We Need Routing? . . . . . . . . . . How client-side routing works . . . . . . . . The beginning: using anchor tags . . . . . The evolution: HTML5 client-side routing Writing our first routes . . . . . . . . . . . . Components of Angular routing . . . . . . . Imports . . . . . . . . . . . . . . . . . . . Routes . . . . . . . . . . . . . . . . . . . . Installing our Routes . . . . . . . . . . . . RouterOutlet using . . RouterLink using [routerLink] . . . . . . Putting it all together . . . . . . . . . . . . . Creating the Components . . . . . . . . . HomeComponent . . . . . . . . . . . . . . . AboutComponent . . . . . . . . . . . . . . ContactComponent . . . . . . . . . . . . . Application Component . . . . . . . . . . Configuring the Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 216 217 218 218 219 219 219 220 221 222 224 224 226 226 227 227 228 229 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONTENTS Routing Strategies . . . . . . . . . . . . . . Running the application . . . . . . . . . . . Route Parameters . . . . . . . . . . . . . . ActivatedRoute . . . . . . . . . . . . . Music Search App . . . . . . . . . . . . . . First Steps . . . . . . . . . . . . . . . . . The SpotifyService . . . . . . . . . . . The SearchComponent . . . . . . . . . . Trying the search . . . . . . . . . . . . . TrackComponent . . . . . . . . . . . . . Wrapping up music search . . . . . . . . Router Hooks . . . . . . . . . . . . . . . . . AuthService . . . . . . . . . . . . . . . LoginComponent . . . . . . . . . . . . . ProtectedComponent and Route Guards . Nested Routes . . . . . . . . . . . . . . . . Configuring Routes . . . . . . . . . . . . ProductsModule . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 232 234 235 236 238 239 240 249 251 253 253 254 255 257 264 265 266 270 Data Architecture in Angular 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 An Overview of Data Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Data Architecture in Angular 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 Data Architecture with Observables - Part 1: Services Observables and RxJS . . . . . . . . . . . . . . . . . Note: Some RxJS Knowledge Required . . . . . . Learning Reactive Programming and RxJS . . . . Chat App Overview . . . . . . . . . . . . . . . . . . Components . . . . . . . . . . . . . . . . . . . . Models . . . . . . . . . . . . . . . . . . . . . . . Services . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . Implementing the Models . . . . . . . . . . . . . . . User . . . . . . . . . . . . . . . . . . . . . . . . . Thread . . . . . . . . . . . . . . . . . . . . . . . . Message . . . . . . . . . . . . . . . . . . . . . . . Implementing UsersService . . . . . . . . . . . . . currentUser stream . . . . . . . . . . . . . . . . Setting a new user . . . . . . . . . . . . . . . . . UsersService.ts . . . . . . . . . . . . . . . . . . The MessagesService . . . . . . . . . . . . . . . . . the newMessages stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 273 273 273 275 276 277 278 278 279 279 279 280 281 282 283 284 285 285 CONTENTS the messages stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Operation Stream Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sharing the Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding Messages to the messages Stream . . . . . . . . . . . . . . . . . . . . . . . Our completed MessagesService . . . . . . . . . . . . . . . . . . . . . . . . . . . Trying out MessagesService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The ThreadsService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A map of the current set of Threads (in threads) . . . . . . . . . . . . . . . . . . . A chronological list of Threads, newest-first (in orderedthreads) . . . . . . . . . . The currently selected Thread (in currentThread) . . . . . . . . . . . . . . . . . . The list of Messages for the currently selected Thread (in currentThreadMessages) Our Completed ThreadsService . . . . . . . . . . . . . . . . . . . . . . . . . . . . Data Model Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 287 289 290 293 296 298 298 303 303 305 308 310 Data Architecture with Observables - Part 2: View Components Building Our Views: The AppComponent Top-Level Component . The ChatThreadsComponent . . . . . . . . . . . . . . . . . . . . ChatThreadsComponent template . . . . . . . . . . . . . . . The Single ChatThreadComponent . . . . . . . . . . . . . . . . . ChatThreadComponent Controller and ngOnInit . . . . . . . ChatThreadComponent template . . . . . . . . . . . . . . . . The ChatWindowComponent . . . . . . . . . . . . . . . . . . . . The ChatMessageComponent . . . . . . . . . . . . . . . . . . . . The ChatMessageComponent template . . . . . . . . . . . . . The ChatNavBarComponent . . . . . . . . . . . . . . . . . . . . The ChatNavBarComponent @Component . . . . . . . . . . . . The ChatNavBarComponent template . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 311 314 315 315 317 317 318 328 330 331 331 333 334 Introduction to Redux with TypeScript . . . . Redux . . . . . . . . . . . . . . . . . . . . . Redux: Key Ideas . . . . . . . . . . . . . Core Redux Ideas . . . . . . . . . . . . . . What’s a reducer? . . . . . . . . . . . . Defining Action and Reducer Interfaces Creating Our First Reducer . . . . . . . Running Our First Reducer . . . . . . . . Adjusting the Counter With actions . . . Reducer switch . . . . . . . . . . . . . . Action “Arguments” . . . . . . . . . . . Storing Our State . . . . . . . . . . . . . . Using the Store . . . . . . . . . . . . . . Being Notified with subscribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 337 337 338 338 339 340 340 341 342 344 345 346 346 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONTENTS The Core of Redux . . . . . . A Messaging App . . . . . . . . Messaging App state . . . . Messaging App actions . . . Messaging App reducer . . . Trying Out Our Actions . . . Action Creators . . . . . . . . Using Real Redux . . . . . . . Using Redux in Angular . . . . . Planning Our App . . . . . . . . Setting Up Redux . . . . . . . . Defining the Application State Defining the Reducers . . . . Defining Action Creators . . Creating the Store . . . . . . Providing the Store . . . . . . . Bootstrapping the App . . . . . . The AppComponent . . . . . . . . imports . . . . . . . . . . . . The template . . . . . . . . . The constructor . . . . . . . Putting It All Together . . . . What’s Next . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 351 351 352 353 356 357 359 361 362 362 362 362 363 364 366 367 368 368 369 370 372 373 373 Intermediate Redux in Angular . . . . . . . . Context For This Chapter . . . . . . . . . . Chat App Overview . . . . . . . . . . . . . Components . . . . . . . . . . . . . . . Models . . . . . . . . . . . . . . . . . . Reducers . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . Implementing the Models . . . . . . . . . . User . . . . . . . . . . . . . . . . . . . . Thread . . . . . . . . . . . . . . . . . . . Message . . . . . . . . . . . . . . . . . . App State . . . . . . . . . . . . . . . . . . . A Word on Code Layout . . . . . . . . . The Root Reducer . . . . . . . . . . . . . The UsersState . . . . . . . . . . . . . . The ThreadsState . . . . . . . . . . . . Visualizing Our AppState . . . . . . . . Building the Reducers (and Action Creators) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374 374 375 375 376 377 377 378 378 378 379 380 380 381 381 382 383 384 CONTENTS Set Current User Action Creators . . . . . UsersReducer - Set Current User . . . . . Thread and Messages Overview . . . . . . Adding a New Thread Action Creators . . Adding a New Thread Reducer . . . . . . Adding New Messages Action Creators . . Adding A New Message Reducer . . . . . Selecting A Thread Action Creators . . . . Selecting A Thread Reducer . . . . . . . . Reducers Summary . . . . . . . . . . . . . Building the Angular Chat App . . . . . . . . The top-level AppComponent . . . . . . . . The ChatPage . . . . . . . . . . . . . . . . Container vs. Presentational Components . Building the ChatNavBarComponent . . . . . . Redux Selectors . . . . . . . . . . . . . . . Threads Selectors . . . . . . . . . . . . . . Unread Messages Count Selector . . . . . Building the ChatThreadsComponent . . . . . ChatThreadsComponent Controller . . . . . ChatThreadsComponent template . . . . . The Single ChatThreadComponent . . . . . . . ChatThreadComponent template . . . . . . Building the ChatWindowComponent . . . . . . The ChatMessageComponent . . . . . . . . . . Setting incoming . . . . . . . . . . . . . . The ChatMessageComponent template . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384 385 386 386 387 388 389 391 392 393 393 395 397 398 399 401 402 403 404 405 407 408 409 410 417 418 419 420 Advanced Components . . . . . . . . . . . . . . . . . . . . . . . Styling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . View (Style) Encapsulation . . . . . . . . . . . . . . . . . . Shadow DOM Encapsulation . . . . . . . . . . . . . . . . No Encapsulation . . . . . . . . . . . . . . . . . . . . . . . Creating a Popup - Referencing and Modifying Host Elements Popup Structure . . . . . . . . . . . . . . . . . . . . . . . . Using ElementRef . . . . . . . . . . . . . . . . . . . . . . Binding to the host . . . . . . . . . . . . . . . . . . . . . . Adding a Button using exportAs . . . . . . . . . . . . . . Creating a Message Pane with Content Projection . . . . . . . Changing the Host’s CSS . . . . . . . . . . . . . . . . . . . Using ng-content . . . . . . . . . . . . . . . . . . . . . . Querying Neighbor Directives - Writing Tabs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 422 425 429 431 434 434 436 438 441 443 444 444 446 CONTENTS ContentTabComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 ContentTabsetComponent Component . . . . . . . . . . . . . . . . . . . . . . . . . . 448 Using the ContentTabsetComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450 Lifecycle Hooks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OnInit and OnDestroy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OnChanges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . DoCheck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . AfterContentInit, AfterViewInit, AfterContentChecked and AfterViewChecked Advanced Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rewriting ngIf - ngBookIf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rewriting ngFor - NgBookFor . . . . . . . . . . . . . . . . . . . . . . . . . . . Change Detection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Customizing Change Detection . . . . . . . . . . . . . . . . . . . . . . . . . . Zones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Observables and OnPush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing . . . . . . . . . . . . . . . . Test driven? . . . . . . . . . . . End-to-end vs. Unit Testing . . . Testing Tools . . . . . . . . . . . Jasmine . . . . . . . . . . . . Karma . . . . . . . . . . . . . Writing Unit Tests . . . . . . . . Angular Unit testing framework Setting Up Testing . . . . . . . . Testing Services and HTTP . . . HTTP Considerations . . . . Stubs . . . . . . . . . . . . . Mocks . . . . . . . . . . . . . Http MockBackend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TestBed.configureTestingModule and Providers Testing getTrack . . . . . . . . . . . . . . . . . . Testing Routing to Components . . . . . . . . . . . . Creating a Router for Testing . . . . . . . . . . . Mocking dependencies . . . . . . . . . . . . . . . Spies . . . . . . . . . . . . . . . . . . . . . . . . . Back to Testing Code . . . . . . . . . . . . . . . . . fakeAsync and advance . . . . . . . . . . . . . . inject . . . . . . . . . . . . . . . . . . . . . . . . Testing ArtistComponent’s Initialization . . . . . Testing ArtistComponent Methods . . . . . . . . Testing ArtistComponent DOM Template Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 453 457 463 476 483 484 486 492 496 503 504 508 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509 509 509 510 510 511 511 511 512 514 515 515 516 517 517 518 525 525 528 529 531 534 535 535 536 538 CONTENTS Testing Forms . . . . . . . . . . . Creating a ConsoleSpy . . . . . Installing the ConsoleSpy . . . Configuring the Testing Module Testing The Form . . . . . . . . Refactoring Our Form Test . . . Testing HTTP requests . . . . . . Testing a POST . . . . . . . . . . Testing DELETE . . . . . . . . . Testing HTTP Headers . . . . . Testing YouTubeSearchService Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540 543 544 545 545 547 551 551 553 554 555 562 Converting an AngularJS 1.x App to Angular 4 . . . . Peripheral Concepts . . . . . . . . . . . . . . . . . . What We’re Building . . . . . . . . . . . . . . . . . Mapping AngularJS 1 to Angular 4 . . . . . . . . . . Requirements for Interoperability . . . . . . . . . . . The AngularJS 1 App . . . . . . . . . . . . . . . . . The ng1-app HTML . . . . . . . . . . . . . . . . Code Overview . . . . . . . . . . . . . . . . . . . ng1: PinsService . . . . . . . . . . . . . . . . . . ng1: Configuring Routes . . . . . . . . . . . . . . ng1: HomeController . . . . . . . . . . . . . . . . ng1: / HomeController template . . . . . . . . . . ng1: pin Directive . . . . . . . . . . . . . . . . . ng1: pin Directive template . . . . . . . . . . . . ng1: AddController . . . . . . . . . . . . . . . . ng1: AddController template . . . . . . . . . . . ng1: Summary . . . . . . . . . . . . . . . . . . . Building A Hybrid . . . . . . . . . . . . . . . . . . . Hybrid Project Structure . . . . . . . . . . . . . . Bootstrapping our Hybrid App . . . . . . . . . . . What We’ll Upgrade . . . . . . . . . . . . . . . . A Minor Detour: Typing Files . . . . . . . . . . . Writing ng2 PinControlsComponent . . . . . . . . Using ng2 PinControlsComponent . . . . . . . . . Downgrading ng2 PinControlsComponent to ng1 Adding Pins with ng2 . . . . . . . . . . . . . . . Upgrading ng1 PinsService and $state to ng2 . Writing ng2 AddPinComponent . . . . . . . . . . . Using AddPinComponent . . . . . . . . . . . . . . Exposing an ng2 service to ng1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563 563 564 565 567 567 569 570 570 572 573 573 574 574 576 578 581 581 582 584 586 589 592 594 595 597 598 599 605 605 CONTENTS Writing the AnalyticsService . . . . . . Downgrade ng2 AnalyticsService to ng1 Using AnalyticsService in ng1 . . . . . . Summary . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606 606 607 608 609 NativeScript: Mobile Applications for the Angular Developer . . . . . . . . What is NativeScript? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Where NativeScript Differs from Other Popular Frameworks . . . . . . What are the System and Development Requirements for NativeScript? Creating your First Mobile Application with NativeScript and Angular . . Adding Build Platforms for Cross Platform Deployment . . . . . . . . . Building and Testing for Android and iOS . . . . . . . . . . . . . . . . Installing JavaScript, Android, and iOS Plugins and Packages . . . . . . Understanding the Web to NativeScript UI and UX Differences . . . . . . Planning the NativeScript Page Layout . . . . . . . . . . . . . . . . . . Adding UI Components to the Page . . . . . . . . . . . . . . . . . . . . Styling Components with CSS . . . . . . . . . . . . . . . . . . . . . . . Developing a Geolocation Based Photo Application . . . . . . . . . . . . . Creating a Fresh NativeScript Project . . . . . . . . . . . . . . . . . . . Creating a Multiple Page Master-Detail Interface . . . . . . . . . . . . . Creating a Flickr Service for Obtaining Photos and Data . . . . . . . . . Creating a Service for Calculating Device Location and Distance . . . . Including Mapbox Functionality in the NativeScript Application . . . . Implementing the First Page of the Geolocation Application . . . . . . . Implementing the Second Page of the Geolocation Application . . . . . Try it out! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NativeScript for Angular Developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610 610 611 612 614 614 614 615 616 616 618 619 620 621 622 626 631 635 636 642 643 644 Changelog . . . . . . . . . . . . Revision 62 - 2017-06-23 . Revision 61 - 2017-05-24 . Revision 60 - 2017-04-27 . Revision 59 - 2017-04-07 . Revision 58 - 2017-03-24 . Revision 57 - 2017-03-23 . Revision 56 - 2017-03-22 . Revision 55 - 2017-03-17 . Revision 54 - 2017-03-10 . Revision 53 - 2017-03-01 . Revision 52 - 2017-02-22 . Revision 51 - 2017-02-14 . Revision 50 - 2017-02-10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645 645 645 645 646 646 646 646 647 647 647 647 648 648 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONTENTS Revision 49 - 2017-01-18 . Revision 48 - 2017-01-13 . Revision 47 - 2017-01-06 . Revision 46 - 2017-01-03 . Revision 45 - 2016-12-05 . Revision 44 - 2016-11-17 . Revision 43 - 2016-11-08 . Revision 42 - 2016-10-14 . Revision 41 - 2016-09-28 . Revision 40 - 2016-09-20 . Revision 39 - 2016-09-03 . Revision 38 - 2016-08-29 . Revision 37 - 2016-08-02 . Revision 36 - 2016-07-20 . Revision 35 - 2016-06-30 . Revision 34 - 2016-06-15 . Revision 33 - 2016-05-11 . Revision 32 - 2016-05-06 . Revision 31 - 2016-04-28 . Revision 30 - 2016-04-20 . Revision 29 - 2016-04-08 . Revision 28 - 2016-04-01 . Revision 27 - 2016-03-25 . Revision 26 - 2016-03-24 . Revision 25 - 2016-03-21 . Revision 24 - 2016-03-10 . Revision 23 - 2016-03-04 . Revision 22 - 2016-02-24 . Revision 21 - 2016-02-20 . Revision 20 - 2016-02-11 . Revision 19 - 2016-02-04 . Revision 18 - 2016-01-29 . Revision 17 - 2016-01-28 . Revision 16 - 2016-01-14 . Revision 15 - 2016-01-07 . Revision 14 - 2015-12-23 . Revision 13 - 2015-12-17 . Revision 12 - 2015-11-16 . Revision 11 - 2015-11-09 . Revision 10 - 2015-10-30 . Revision 9 - 2015-10-15 . Revision 8 - 2015-10-08 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648 648 648 648 648 648 649 649 649 650 650 650 650 650 650 650 651 651 651 652 652 652 652 652 652 652 652 653 653 653 654 654 654 654 654 654 655 655 656 656 656 657 CONTENTS Revision 7 - 2015-09-23 Revision 6 - 2015-08-28 Revision 5 - 2015-08-01 Revision 4 - 2015-07-30 Revision 3 - 2015-07-21 Revision 2 - 2015-07-15 Revision 1 - 2015-07-01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 657 657 657 657 658 658 658 CONTENTS 1 Book Revision Revision 62 - Covers up to Angular 4 (4.2.0, 2017-06-23) Bug Reports If you’d like to report any bugs, typos, or suggestions just email us at: us@fullstack.io¹. Chat With The Community! We’re experimenting with a community chat room for this book using Gitter. If you’d like to hang out with other people learning Angular, come join us on Gitter²! Vote for New Content (new!) We’re constantly updating the book, writing new blog posts, and producing new material. You can now cast your vote for new content here³. Be notified of updates via Twitter If you’d like to be notified of updates to the book on Twitter, follow @fullstackio⁴ We’d love to hear from you! Did you like the book? Did you find it helpful? We’d love to add your face to our list of testimonials on the website! Email us at: us@fullstack.io⁵. ¹mailto:us@fullstack.io?Subject=ng-book%202%20feedback ²https://gitter.im/ng-book/ng-book ³https://fullstackio.canny.io/ng-book ⁴https://twitter.com/fullstackio ⁵mailto:us@fullstack.io?Subject=ng-book%202%20testimonial How to Read This Book This book aims to be the single most useful resource on learning Angular. By the time you’re done reading this book, you (and your team) will have everything you need to build reliable, powerful Angular apps. Angular is a rich and feature-filled framework, but that also means it can be tricky to understand all of its parts. In this book, we’ll walk through everything from installing the tools, writing components, using forms, routing between pages, and calling APIs. But before we dig in, there are a few guidelines I want to give you in order to get the most out of this book. Briefly, I want to tell you: • how to approach the code examples and • how to get help if something goes wrong Running Code Examples This book comes with a library of runnable code examples. The code is available to download from the same place where you downloaded this book. We use the program npm⁶ to run every example in this book. This means you can type the following commands to run any example: 1 2 npm install npm start If you’re unfamiliar with npm, we cover how to get it installed in the Getting Started section in the first chapter. After running npm start, you will see some output on your screen that will tell you what URL to open to view your app. If you’re ever unclear on how to run a particular sample app, check out the README.md in that project’s directory. Every sample project contains a README.md that will give you the instructions you need to run each app. ⁶https://www.npmjs.com/ How to Read This Book 3 Angular CLI With a couple of minor exceptions, every project in this book was built on Angular CLI⁷. Unless specified otherwise, you can use the ng commands in each project. For instance, to run an example you can run ng serve (this is, generally, what is run when you type npm start). For most projects you can compile them to JavaScript with ng build (we’ll talk about this more in the first chapter). And you can run end-to-end tests with ng e2e, etc. Without getting too far into the details, Angular CLI is based on Webpack, a tool which helps process and bundle our various TypeScript, JavaScript, CSS, HTML, and image files. Angular CLI is not a requirement for using Angular. It’s simply a wrapper around Webpack (and some other tooling) that makes it easy to get started. Code Blocks and Context Nearly every code block in this book is pulled from a runnable code example, which you can find in the sample code. For example, here is a code block pulled from the first chapter: code/first-app/angular-hello-world/src/app/app.component.ts 8 9 10 export class AppComponent { title = 'app works!'; } Notice that the header of this code block states the path to the file which contains this code: code/first-app/angular-hello-world/src/app/app.component.ts. If you ever feel like you’re missing the context for a code example, open up the full code file using your favorite text editor. This book is written with the expectation that you’ll also be looking at the example code alongside the manuscript. For example, we often need to import libraries to get our code to run. In the early chapters of the book we show these import statements, because it’s not clear where the libraries are coming from otherwise. However, the later chapters of the book are more advanced and they focus on key concepts instead of repeating boilerplate code that was covered earlier in the book. If at any point you’re not clear on the context, open up the code example on disk. Code Block Numbering In this book, we sometimes build up a larger example in steps. If you see a file being loaded that has a numeric suffix, that generally means we’re building up to something bigger. ⁷https://github.com/angular/angular-cli How to Read This Book 4 For instance, in the Dependency Injection chapter you may see a code block with the filename: price.service.1.ts. When you see the .N.ts syntax that means we’re building up to the ultimate file, which will not have a number. So, in this case, the final version would be: price.service.ts. We do it this way so that a) we can unit test the intermediate code and b) you can see the whole file in context at a particular stage. A Word on Versioning As you may know, the Angular covered in this book is a descendant of an earlier framework called “AngularJS”. This can sometimes be confusing, particularly when reading supplementary blogs or documentation. The official branding guidelines state that “AngularJS” is a term reserved for AngularJS 1.x, that is, the early versions of “Angular”. Because the new version of Angular used TypeScript (instead of JavaScript) as the primary language, the ‘JS’ was dropped, leaving us with just Angular. For a long time the only consistent way to distinguish the two was folks referred to the new Angular as Angular 2. However, the Angular team in 2017 switched to semantic versioning with a new major-release upgrade slated for every 6 months. Instead of calling the next versions Angular 4, Angular 5, and so on, the number is also dropped and it’s just Angular. In this book, when we’re referring to Angular we’ll just say Angular or sometimes Angular 4, just to avoid confusion. When we’re talking about “the old-style JavaScript Angular” we’ll use the term AngularJS or AngularJS 1.x. Getting Help While we’ve made every effort to be clear, precise, and accurate you may find that when you’re writing your code you run into a problem. Generally, there are three types of problems: • A “bug” in the book (e.g. how we describe something is wrong) • A “bug” in our code • A “bug” in your code If you find an inaccuracy in how we describe something, or you feel a concept isn’t clear, email us! We want to make sure that the book is both accurate and clear. Similarly, if you’ve found a bug in our code we definitely want to hear about it. If you’re having trouble getting your own app working (and it isn’t our example code), this case is a bit harder for us to handle. How to Read This Book 5 Your first line of defense, when getting help with your custom app, should be our unofficial community chat room⁸. We (the authors) are there from time-to-time, but there are hundreds of other readers there who may be able to help you faster than we can. If you’re still stuck, we’d still love to hear from you, and here are some tips for getting a clear, timely response. Emailing Us If you’re emailing us asking for technical help, here’s what we’d like to know: • • • • • • • What revision of the book are you referring to? What operating system are you on? (e.g. Mac OS X 10.8, Windows 95) Which chapter and which example project are you on? What were you trying to accomplish? What have you tried⁹ already? What output did you expect? What actually happened? (Including relevant log output.) The absolute best way to get technical support is to send us a short, self-contained example of the problem. Our preferred way to receive this would be for you to send us a Plunkr link by using this URL¹⁰. That URL contains a runnable, boilerplate Angular app. If you can copy and paste your code into that project, reproduce your error, and send it to us you’ll greatly increase the likelihood of a prompt, helpful response. When you’ve written down these things, email us at us@fullstack.io¹¹. We look forward to hearing from you. Technical Support Response Time We perform our free, technical support once per week. If you need a faster response time, and help getting any of your team’s questions answered, then you may consider our premium support option¹². ⁸https://gitter.im/ng-book/ng-book ⁹http://mattgemmell.com/what-have-you-tried/ ¹⁰https://angular.io/resources/live-examples/quickstart/ts/eplnkr.html ¹¹mailto:us@fullstack.io ¹²mailto:us@fullstack.io?Subject=Angular%20Premium%20Support&Body=Hello%21%20I%27m%20interested%20in%20premium%20Angular% 20support%20for%20our%20team How to Read This Book 6 Chapter Overview Before we dive in, I want to give you a feel for the rest of the book and what you can expect inside. The first few chapters provide the foundation you need to get up and running with Angular. You’ll create your first apps, use the built-in components, and start creating your components. Next we’ll move into intermediate concepts such as using forms, using APIs, routing to different pages, and using Dependency Injection to organize our code. After that, we’ll move into more advanced concepts. We spend a good part of the book talking about data architectures. Managing state in client/server applications is hard and we dive deep into two popular approaches: using RxJS Observables and using Redux. In these chapters, we’ll show how to build the same app, two different ways, so you can compare and contrast and evaluate what’s best for you and your team. After that, we’ll discuss how to write complex, advanced components using Angular’s most powerful features. Then we talk about how to write tests for our app and how we can upgrade our Angular 1 apps to Angular 4+. Finally, we close with a chapter on writing native mobile apps with Angular using NativeScript. By using this book, you’re going to learn how to build real Angular apps faster than spending hours parsing out-dated blog posts. So hold on tight - you’re about to become an Angular expert, and have a lot of fun along the way. Let’s dig in! • Nate (@eigenjoy¹³) ¹³https://twitter.com/eigenjoy Writing Your First Angular Web Application Simple Reddit Clone In this chapter we’re going to build an application that allows the user to post an article (with a title and a URL) and then vote on the posts. You can think of this app as the beginnings of a site like Reddit¹⁴ or Product Hunt¹⁵. In this simple app we’re going to cover most of the essentials of Angular including: • • • • • Building custom components Accepting user input from forms Rendering lists of objects into views Intercepting user clicks and acting on them Deploying our app to a server By the time you’re finished with this chapter you’ll know how to take an empty folder, build a basic Angular application, and deploy it to production. After working through this chapter you’ll have a good grasp on how Angular applications are built and a solid foundation to build your own Angular app. Here’s a screenshot of what our app will look like when it’s done: ¹⁴http://reddit.com ¹⁵http://producthunt.com 2 Writing Your First Angular Web Application Completed application First, a user will submit a new link and after submitting the users will be able to upvote or downvote each article. Each link will have a score and we can vote on which links we find useful. 3 Writing Your First Angular Web Application App with new article In this project, and throughout the book, we’re going to use TypeScript. TypeScript is a superset of JavaScript ES6 that adds types. We’re not going to talk about TypeScript in depth in this chapter, but we’ll go over TypeScript more in depth in the next chapter. Don’t worry if you’re having trouble with some of the new syntax. If you’re familiar with ES5 (“normal” JavaScript) / ES6 (ES2015) you should be able to follow along and we’ll talk more about TypeScript in a bit. Writing Your First Angular Web Application 4 Getting started Node.js and npm To get started with Angular, you’ll need to have Node.js installed. There are a couple of different ways you can install Node.js, so please refer to the Node.js website¹⁶ for detailed information. Make sure you install Node 6.9.0 or higher. If you’re on a Mac, your best bet is to install Node.js directly from the Node.js website instead of through another package manager (like Homebrew). Installing Node.js via Homebrew is known to cause some issues. The Node Package Manager (npm for short) is installed as a part of Node.js. To check if npm is available as a part of our development environment, we can open a terminal window and type: $ npm -v If a version number is not printed out and you receive an error, make sure to download a Node.js installer that includes npm. Your npm version should be 3.0.0 or higher. TypeScript Once you have Node.js setup, the next step is to install TypeScript. Make sure you install at least version 2.1 or greater. To install it, run the following npm command: 1 $ npm install -g typescript Do I have to use TypeScript? No, you don’t have to use TypeScript to use Angular, but you probably should. Angular does have an ES5 API, but Angular is written in TypeScript and generally that’s what everyone is using. We’re going to use TypeScript in this book because it’s great and it makes working with Angular easier. That said, it isn’t strictly required. Browser We highly recommend using the Google Chrome Web Browser¹⁷ to develop Angular apps. We’ll use the Chrome developer toolkit throughout this book. To follow along with our development and debugging we recommend downloading it now. ¹⁶https://nodejs.org/download/ ¹⁷https://www.google.com/chrome/ Writing Your First Angular Web Application 5 Special instruction for Windows users Throughout this book, we will be using Unix/Mac commands in the terminal. Most of these commands, like ls and cd, are cross-platform. However, sometimes these commands are Unix/Macspecific or contain Unix/Mac-specific flags (like ls -1p). As a result, be alert that you may have to occasionally determine the equivalent of a Unix/Mac command for your shell. Fortunately, the amount of work we do in the terminal is minimal and you will not encounter this issue often. Windows users should be aware that our terminal examples use Unix/Mac commands. Angular CLI Angular provides a utility to allow users to create and manage projects from the command line. It automates tasks like creating projects, adding new controllers, etc. It’s generally a good idea to use Angular CLI as it will help create and maintain common patterns across our application. To install Angular CLI, just run the following command: 1 $ npm install -g @angular/cli@1.0.0 Once it’s installed you’ll be able to run it from the command line using the ng command. When you do, you’ll see a lot of output, but if you scroll back, you should be able to see the following: 1 $ ng --version If everything installed correctly, you should see the current version output to your terminal. Congratulations! If you’re running OSX or Linux, you might receive this line in the output: 1 Could not start watchman; falling back to NodeWatcher for file system events. This means that we don’t have a tool called watchman installed. This tool helps Angular CLI when it needs to monitor files in your filesystem for changes. If you’re running OSX, it’s recommended to install it using Homebrew with the following command: 1 $ brew install watchman Writing Your First Angular Web Application 6 If you’re on OSX and got an error when running brew, it means that you probably don’t have Homebrew installed. Please refer to the page http://brew.sh/ to learn how to install it and try again. If you’re on Linux, you may refer to the page https://ember-cli.com/user-guide/#watchman for more information about how to install watchman. If you’re on Windows instead, you don’t need to install anything and Angular CLI will use the native Node.js watcher. If you’re curious about all of the things that Angular CLI can do, try out this command: 1 $ ng --help Don’t worry about understanding all of the options - we’ll be covering the important ones in this chapter. Now that we have Angular CLI and its dependencies installed, let’s use this tool to create our first application. Example Project Open up the terminal and run the ng new command to create a new project from scratch: 1 $ ng new --ng4 angular-hello-world If you’re using a version of Angular CLI that is newer than @angular/cli@1.0.0-rc.4, you may omit the --ng4 option as Angular 4 will be the default. Once you run it, you’ll see the following output: 1 2 3 4 5 6 7 8 9 10 11 installing ng2 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 Writing Your First Angular Web Application 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 7 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.json create .angular-cli.json create e2e/app.e2e-spec.ts create e2e/app.po.ts create e2e/tsconfig.json create .gitignore create karma.conf.js create package.json create protractor.conf.js create tslint.json Successfully initialized git. Installing packages for tooling via npm. Installed packages for tooling via npm. Note: the exact files that your project generates may vary slightly depending on the version of @angular/cli that was installed. This will run for a while while it’s installing npm dependencies. Once it finishes we’ll see a success message: 1 Project 'angular-hello-world' successfully created. There are a lot of files generated! Don’t worry about understanding all of them yet. Throughout the book we’ll walk through what each one means and what it’s used for. Let’s go inside the angular-hello-world directory, which the ng command created for us and see what has been created: Writing Your First Angular Web Application 1 2 3 4 5 6 7 8 9 10 11 12 $ cd angular-hello-world $ tree -F -L 1 . ├── README.md ├── .angular-cli.json ├── e2e/ ├── karma.conf.js ├── node_modules/ ├── package.json ├── protractor.conf.js ├── src/ └── tslint.json // // // // // // // // // 8 an useful README angular-cli configuration file end to end tests unit test configuration installed dependencies npm configuration e2e test configuration application source linter config file The tree command is completely optional. But if you’re on OSX it can be installed via brew install tree For now, the folder we’re interested in is src, where we’ll put our custom application code. Let’s take a look at what was created there: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ cd src $ tree -F . |-- app/ | |-- app.component.css | |-- app.component.html | |-- app.component.spec.ts | |-- app.component.ts | `-- app.module.ts |-- assets/ |-- environments/ | |-- environment.prod.ts | `-- environment.ts |-- favicon.ico |-- index.html |-- main.ts |-- polyfills.ts |-- styles.css |-- test.ts `-- tsconfig.json Using your favorite text editor, let’s open index.html. You should see this code: Writing Your First Angular Web Application 9 code/first-app/angular-hello-world/src/index.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 AngularHelloWorld Loading... Let’s break it down a bit: code/first-app/angular-hello-world/src/index.html 1 2 3 4 5 6 7 8 9 10AngularHelloWorld If you’re familiar with writing HTML files, this first part is straightforward, we’re declaring the core structure of the HTML document and a few bits of metadata such as page charset, title and base href. If we continue to the template body, we see the following: Writing Your First Angular Web Application 10 code/first-app/angular-hello-world/src/index.html 11 12 13 14 Loading... The app-root tag is where our application will be rendered. The text Loading... is a placeholder that will be displayed before our app code loads. For instance, we could put a loading “spinner” img tag here and the user would see this as our JavaScript and Angular app is loading. But what is the app-root tag and where does it come from? app-root is a component that is defined by our Angular application. In Angular we can define our own HTML tags and give them custom functionality. The app-root tag will be the “entry point” for our application on the page. Let’s try running this app as-is and then we’ll dig in to see how this component is defined. Writing Application Code Running the application Before making any changes, let’s load our app from the generated application into the browser. Angular CLI has a built in HTTP server that we can use to run our app. To use it, head back to the terminal, and change directories into the root of our application. 1 2 3 4 5 6 7 $ cd angular-hello-world $ ng serve ** NG Live Development Server is running on http://localhost:4200. ** // ... // a bunch of other messages // ... Compiled successfully. Our application is now running on localhost port 4200. Let’s open the browser and visit: http://localhost:4200¹⁸ ¹⁸http://localhost:4200 11 Writing Your First Angular Web Application Note that if you get the message: 1 Port 4200 is already in use. Use '--port' to specify a different port This means that you already have another service running on port 4200. If this is the case you can either 1. shut down the other service or 2. use the --port flag when running ng serve like this: 1 ng serve --port 9001 The above command would change the URL you open in your browser to something like: http://localhost:9001 Another thing to notice is that, on some machines, the domain localhost may not work. You may see a set of numbers such as 127.0.0.1. When you run ng serve it should show you what URL the server is running on, so be sure to read the messages on your machine to find your exact development URL. Running application Now that we have the application setup, and we know how to run it, it’s time to start writing some code. Writing Your First Angular Web Application 12 Making a Component One of the big ideas behind Angular is the idea of components. In our Angular apps, we write HTML markup that becomes our interactive application, but the browser only understands a limited set of markup tags; Built-ins like
Var is A
Var is B
Var is something else
Var is A
Var is B
Var is C
Var is something els\
e
Var is A
Var is B
Var is something else
Var is A
Var is B
Var is C
Var is something else
Current choice is {{ choice }}
- First choice
- Second choice
- Third choice
- Fourth choice
- Second choice, again
- Default choice
Uses fixed yellow background
This snippet is using the NgStyle directive to set the background-color CSS property to the literal
string 'yellow'.
Another way to set fixed values is by using the NgStyle attribute and using key value pairs for each
property you want to set, like this:
Built-in Directives
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
13
14
15
Uses fixed white text on blue background
Notice that in the ng-style specification we have single quotes around background-color
but not around color. Why is that? Well, the argument to ng-style is a JavaScript object and
color is a valid key, without quotes. With background-color, however, the dash character
isn’t allowed in an object key, unless it’s a string so we have to quote it.
Generally I’d leave out quoting as much as possible in object keys and only quote keys
when we have to.
Here we are setting both the color and the background-color properties.
But the real power of the NgStyle directive comes with using dynamic values.
In our example, we are defining two input boxes with an apply settings button:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
56
57
58
59
60
61
62
63
64
65
66
67
red text
It’s important to note that we have to specify units where appropriate. For instance, it isn’t valid
CSS to set a font-size of 12 - we have to specify a unit such as 12px or 1.2em. Angular provides a
handy syntax for specifying units: here we used the notation [style.font-size.px].
The .px suffix indicates that we’re setting the font-size property value in pixels. You could easily
replace that by [style.font-size.em] to express the font size in ems or even in percentage using
[style.font-size.%].
The other two elements use the #colorinput to set the text and background colors:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ngStyle with object property from variable
{{ color }} text
style from variable
{{ color }} background
This way, when we click the Apply settings button, we call a method that sets the new values:
125
Built-in Directives
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.ts
32
33
34
35
apply(color: string, fontSize: number): void {
this.color = color;
this.fontSize = fontSize;
}
And with that, both the color and the font size will be applied to the elements using the NgStyle
directive.
NgClass
The NgClass directive, represented by a ngClass attribute in your HTML template, allows you to
dynamically set and change the CSS classes for a given DOM element.
The first way to use this directive is by passing in an object literal. The object is expected to have
the keys as the class names and the values should be a truthy/falsy value to indicate whether the
class should be applied or not.
Let’s assume we have a CSS class called bordered that adds a dashed black border to an element:
code/built-in-directives/src/styles.css
8
9
10
.bordered {
border: 1px dashed black;
background-color: #eee; }
Let’s add two div elements: one always having the bordered class (and therefore always having the
border) and another one never having it:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
2
3
This is never bordered
This is always bordered
As expected, this is how those two divs would be rendered:
Simple class directive usage
Of course, it’s a lot more useful to use the NgClass directive to make class assignments dynamic.
To make it dynamic we add a variable as the value for the object value, like this:
Built-in Directives
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
5
6
7
Using object literal. Border {{ isBordered ? "ON" : "OFF" }}
Alternatively, we can define a classesObj object in our component:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.ts
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component({
selector: 'app-ng-class-example',
templateUrl: './ng-class-example.component.html'
})
export class NgClassExampleComponent implements OnInit {
isBordered: boolean;
classesObj: Object;
classList: string[];
constructor() {
}
ngOnInit() {
this.isBordered = true;
this.classList = ['blue', 'round'];
this.toggleBorder();
}
toggleBorder(): void {
this.isBordered = !this.isBordered;
this.classesObj = {
bordered: this.isBordered
};
}
And use the object directly:
126
Built-in Directives
127
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
9
10
11
Using object var. Border {{ classesObj.bordered ? "ON" : "OFF" }}
Again, be careful when you have class names that contains dashes, like bordered-box.
JavaScript requires that object-literal keys with dashes be quoted like a string, as in:
1
...
We can also use a list of class names to specify which class names should be added to the element.
For that, we can either pass in an array literal:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
31
32
33
34
This will always have a blue background and
round corners
Or assign an array of values to a property in our component:
1
this.classList = ['blue', 'round'];
And pass it in:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
36
37
38
39
This is {{ classList.indexOf('blue') > -1 ? "" : "NOT" }} blue
and {{ classList.indexOf('round') > -1 ? "" : "NOT" }} round
In this last example, the [ngClass] assignment works alongside existing values assigned by the
HTML class attribute.
The resulting classes added to the element will always be the set of the classes provided by usual
class HTML attribute and the result of the evaluation of the [class] directive.
In this example:
128
Built-in Directives
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
31
32
33
34
This will always have a blue background and
round corners
The element will have all three classes: base from the class HTML attribute and also blue and
round from the [class] assignment:
Classes from both the attribute and directive
NgFor
The role of this directive is to repeat a given DOM element (or a collection of DOM elements) and
pass an element of the array on each iteration.
The syntax is *ngFor="let item of items".
• The let item syntax specifies a (template) variable that’s receiving each element of the items
array;
• The items is the collection of items from your controller.
To illustrate, we can take a look at the code example. We declare an array of cities on our component
controller:
1
this.cities = ['Miami', 'Sao Paulo', 'New York'];
And then, in our template we can have the following HTML snippet:
129
Built-in Directives
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
1
2
3
4
5
6
7
Simple list of strings
{{ c }}
List of objects
Name | Age | 130 Built-in Directives 18 19 20 21 22 23 24 25 26City |
---|---|---|
{{ p.name }} | {{ p.age }} | {{ p.city }} |
{{ item.city }}
And use a nested directive to iterate through the people for a given city: code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html 13 14 15 16 17 18 19 20 21 22 23 24 25 26Name | Age | City |
---|---|---|
{{ p.name }} | {{ p.age }} | {{ p.city }} |
Nested data
{{ item.city }}
Name | Age |
---|---|
{{ p.name }} | {{ p.age }} |
{{ num+1 }} - {{ c }}
{{ content }}
← This is what {{ content }} rendered
And with that ngNonBindable attribute, ng2 will not compile within that second span’s context,
leaving it intact:
Result of using ngNonBindable
Built-in Directives
135
Conclusion
Angular has only a few core directives, but we can combine these simple pieces to create dynamic,
powerful apps. However, all of these directives help us output dynamic data, they don’t let us accept
user interaction.
In the next chapter we’ll learn how to let our user input data using forms.
Forms in Angular
Forms are Crucial, Forms are Complex
Forms are probably the most crucial aspect of your web application. While we often get events from
clicking on links or moving the mouse, it’s through forms where we get the majority of our rich data
input from users.
On the surface, forms seem straightforward: you make an input tag, the user fills it out, and hits
submit. How hard could it be?
It turns out, forms can be very complex. Here’s a few reasons why:
•
•
•
•
•
•
Form inputs are meant to modify data, both on the page and the server
Changes often need to be reflected elsewhere on the page
Users have a lot of leeway in what they enter, so you need to validate values
The UI needs to clearly state expectations and errors, if any
Dependent fields can have complex logic
We want to be able to test our forms, without relying on DOM selectors
Thankfully, Angular has tools to help with all of these things.
• FormControls encapsulate the inputs in our forms and give us objects to work with them
• Validators give us the ability to validate inputs, any way we’d like
• Observers let us watch our form for changes and respond accordingly
In this chapter we’re going to walk through building forms, step by step. We’ll start with some simple
forms and build up to more complicated logic.
FormControls and FormGroups
The two fundamental objects in Angular forms are FormControl and FormGroup.
FormControl
A FormControl represents a single input field - it is the smallest unit of an Angular form.
FormControls encapsulate the field’s value, and states such as being valid, dirty (changed), or has
errors.
For instance, here’s how we might use a FormControl in TypeScript:
137
Forms in Angular
1
2
3
4
5
6
7
8
9
10
// create a new FormControl with the value "Nate"
let nameControl = new FormControl("Nate");
let name = nameControl.value; // -> Nate
// now we can query this
nameControl.errors // ->
nameControl.dirty // ->
nameControl.valid // ->
// etc.
control for certain values:
StringMapDemo Form: Sku
Demo Form: Sku
Demo Form: Sku with Builder
Demo Form: Sku with Builder
150
Forms in Angular
9
10
11
12
13
14
15
16
17
151
it will show the input tag with a red border.
To do this, we can use the property syntax to set conditional classes:
code/forms/src/app/demo-form-with-validations-explicit/demo-form-with-validations-explicit.component.html
7
8
Notice here that we have two conditions for setting the .error class: We’re checking for !sku.valid
and sku.touched. The idea here is that we only want to show the error state if the user has tried
editing the form (“touched” it) and it’s now invalid.
To try this out, enter some data into the input tag and then delete the contents of the field.
Specific validation
A form field can be invalid for many reasons. We often want to show a different message depending
on the reason for a failed validation.
To look up a specific validation failure we use the hasError method:
code/forms/src/app/demo-form-with-validations-explicit/demo-form-with-validations-explicit.component.html
17
18
Removing the sku instance variable
In the example above we set sku: AbstractControl as an instance variable. We often won’t want to
create an instance variable for each AbstractControl, so how would we reference this FormControl
in our view without an instance variable?
Instead we can use the myForm.controls property as in:
Forms in Angular
158
code/forms/src/app/demo-form-with-validations-shorthand/demo-form-with-validations-shorthand.component.html
10
11
12
13
14
15
16
17
Note that hasError is defined on both FormControl and FormGroup. This means you can pass a
second argument of path to lookup a specific field from FormGroup. For example, we could have
written the previous example as:
Forms in Angular
1
2
156
SKU is required
Putting it together
Here’s the full code listing of our form with validations with the FormControl set as an instance
variable:
code/forms/src/app/demo-form-with-validations-explicit/demo-form-with-validations-explicit.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Component } from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators,
AbstractControl
} from '@angular/forms';
@Component({
selector: 'app-demo-form-with-validations-explicit',
templateUrl: './demo-form-with-validations-explicit.component.html',
styles: []
})
export class DemoFormWithValidationsExplicitComponent {
myForm: FormGroup;
sku: AbstractControl;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
And the template:
Forms in Angular
157
code/forms/src/app/demo-form-with-validations-explicit/demo-form-with-validations-explicit.component.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Demo Form: with validations (explicit)
{
return isBlank(c.value) || c.value == "" ? {"required": true} : null;
}
A validator: - Takes a FormControl as its input and - Returns a StringMap where
the key is “error code” and the value is true if it fails
Writing the Validator
Let’s say we have specific requirements for our sku. For example, say our sku needs to begin with
123. We could write a validator like so:
Forms in Angular
159
code/forms/src/app/demo-form-with-custom-validation/demo-form-with-custom-validation.component.ts
18
19
20
21
22
function skuValidator(control: FormControl): { [s: string]: boolean } {
if (!control.value.match(/^123/)) {
return {invalidSku: true};
}
}
This validator will return an error code invalidSku if the input (the control.value) does not begin
with 123.
Assigning the Validator to the FormControl
Now we need to add the validator to our FormControl. However, there’s one small problem: we
already have a validator on sku. How can we add multiple validators to a single field?
For that, we use Validators.compose:
code/forms/src/app/demo-form-with-custom-validation/demo-form-with-custom-validation.component.ts
33
34
35
36
37
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.compose([
Validators.required, skuValidator])]
});
Validators.compose wraps our two validators and lets us assign them both to the FormControl.
The FormControl is not valid unless both validations are valid.
Now we can use our new validator in the view:
code/forms/src/app/demo-form-with-custom-validation/demo-form-with-custom-validation.component.html
19
20
Above, we click the “Sign In” button to call the signIn() function (which we’ll define in a moment).
If we have a userName, we’ll display a greeting.
172
Dependency Injection
Simple Sign In Button
Now let’s implement this functionality in our component by using the injector directly.
code/dependency-injection/src/app/user-demo/user-demo.injector.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {
Component,
ReflectiveInjector
} from '@angular/core';
import { UserService } from '../services/user.service';
@Component({
selector: 'app-injector-demo',
templateUrl: './user-demo.component.html',
styleUrls: ['./user-demo.component.css']
})
export class UserDemoInjectorComponent {
userName: string;
userService: UserService;
constructor() {
// Create an _injector_ and ask for it to resolve and create a UserService
const injector: any = ReflectiveInjector.resolveAndCreate([UserService]);
// use the injector to **get the instance** of the UserService
this.userService = injector.get(UserService);
}
173
Dependency Injection
25
26
27
28
29
30
31
32
33
34
35
36
signIn(): void {
// when we sign in, set the user
// this mimics filling out a login form
this.userService.setUser({
name: 'Nate Murray'
});
// now **read** the user name from the service
this.userName = this.userService.getUser().name;
console.log('User name is: ', this.userName);
}
}
This starts as a basic component: we have a selector, template, and CSS. Note that we have two
properties: userName, which holds the currently logged-in user’s name and userService, which
holds a reference to the UserService.
In our component’s constructor we are using a static method from ReflectiveInjector called
resolveAndCreate. That method is responsible for creating a new injector. The parameter we pass
in is an array with all the injectable things we want this new injector to know. In our case, we just
wanted it to know about the UserService injectable.
The ReflectiveInjector is a concrete implementation of Injector that uses reflection
to look up the proper parameter types. While there are other injectors that are possible
ReflectiveInjector is the “normal” injector we’ll be using in most apps.
Signed In
Dependency Injection
174
Providing Dependencies with NgModule
While it’s interesting to see how an injector is created directly, that isn’t the typical way we’d use
injections. Instead, what we’d normally do is
• use NgModule to register what we’ll inject – these are called providers and
• use decorators (generally on a constructor) to specify what we’re injecting
By doing these two steps Angular will manage creating the injector and resolving the dependencies.
Let’s convert our UserService to be injectable as a singleton across our app. First, we’re going to
add it to the providers key of our NgModule:
code/dependency-injection/src/app/user-demo/user-demo.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// imported here
import { UserService } from '../services/user.service';
@NgModule({
imports: [
CommonModule
],
providers: [
UserService // <-- added right here
],
declarations: []
})
export class UserDemoModule { }
Now we can inject UserService into our component like this:
Dependency Injection
175
code/dependency-injection/src/app/user-demo/user-demo.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { Component, OnInit } from '@angular/core';
import { UserService } from '../services/user.service';
@Component({
selector: 'app-user-demo',
templateUrl: './user-demo.component.html',
styleUrls: ['./user-demo.component.css']
})
export class UserDemoComponent {
userName: string;
// removed `userService` because of constructor shorthand below
// Angular will inject the singleton instance of `UserService` here.
// We set it as a property with `private`.
constructor(private userService: UserService) {
// empty because we don't have to do anything else!
}
// below is the same...
signIn(): void {
// when we sign in, set the user
// this mimics filling out a login form
this.userService.setUser({
name: 'Nate Murray'
});
// now **read** the user name from the service
this.userName = this.userService.getUser().name;
console.log('User name is: ', this.userName);
}
}
Notice in the constructor above that we have made userService: UserService an argument to
the UserDemoComponent. When this component is created on our page Angular will resolve and
inject the UserService singleton. What’s great about this is that because Angular is managing the
instance, we don’t have to worry about doing it ourselves. Every class that injects the UserService
will receive the same singleton.
Dependency Injection
176
Providers are the Key
It’s important to know that when we put the UserService on the constructor of the UserDemoComponent, Angular knows what to inject (and how) **because we listed UserService in the providers
key of our NgModule.
It does not inject arbitrary classes. You must configure an NgModule for DI to work.
We’ve been talking a lot about Singleton services, but we can inject things in lots of other ways.
Let’s take a look.
Providers
There are several ways we can configure resolving injected dependencies in Angular. For instance
we can:
• Inject a (singleton) instance of a class (as we’ve seen)
• Inject a value
• Call any function and inject the return value of that function
Let’s look into detail at how we create each one:
Using a Class
As we’ve discussed, injecting a singleton instance of a class is probably the most common type of
injection.
When we put the class itself into the list of providers like this:
1
providers: [ UserService ]
This tells Angular that we want to provide a singleton instance of UserService whenever UserService is injected. Because this pattern is so common, the class by itself is actually shorthand notation
for the following, equivalent configuration:
1
2
3
providers: [
{ provide: UserService, useClass: UserService }
]
Dependency Injection
177
What’s interesting to note is that the object configuration with provide takes two keys. provide is
the token that we use to identify the injection and the second useClass is how and what to inject.
Here we’re mapping the UserService class to the UserService token. In this case, the name of the
class and the token match. This is the common case, but know that the token and the injected thing
aren’t required to have the same name.
As we’ve seen above, in this case the injector will create a singleton behind the scenes and return
the same instance every time we inject it . Of course, the first time it is injected, the singleton hasn’t
been instantiated yet, so when creating the UserService instance for the first time, the DI system
will trigger the class constructor method.
Using a Value
Another way we can use DI is to provide a value, much like we might use a global constant. For
instance, we might configure an API Endpoint URL depending on the environment.
To do this, in our NgModule providers, we use the key useValue:
1
2
3
providers: [
{ provide: 'API_URL', useValue: 'http://my.api.com/v1' }
]
Above, for the provide token we’re using a string of API_URL. If we use a string for the provide
value, Angular can’t infer which dependency we’re resolving by the type. For instance we can’t
write:
1
2
3
4
5
6
// doesn't work - anti-example
export class AnalyticsDemoComponent {
constructor(apiUrl: 'API_URL') { // <--- this isn't a type, just a string
// if we put `string` that is ambiguous
}
}
So what can we do? In this case, we’ll use the @Inject() decorator like this:
Dependency Injection
1
2
3
4
5
6
7
178
import { Inject } from '@angular/core';
export class AnalyticsDemoComponent {
constructor(@Inject('API_URL') apiUrl: string) {
// works! do something w/ apiUrl
}
}
Now that we know how to do simple values with useValue and Singleton classes with useClass,
we’re ready to talk about the more advanced case: writing configurable services using factories.
Configurable Services
In the case of the UserService, no arguments are required for the constructor. But what happens
if a service’s constructor requires arguments? We can implement this by using a factory which is
a function that can return any object when injected.
For instance, let’s say we’re writing a library for recording user analytics (that is, keeping a record
of events of actions a user took on the page). In this scenario, we want to have an AnalyticsService
with a catch: the AnalyticsService should define the interface for recording events, but not the
implementation for handling the event.
179
Dependency Injection
Tracking Analytics on the events
Our user may, for instance, want to record these metrics with Google Analytics or they may want to
use Optimizely, or some other in-house solution. Let’s write an injectable AnalyticsService which
can take an implementation configuration.
First, a couple of definitions. Let’s define a Metric:
code/dependency-injection/src/app/analytics-demo/analytics-demo.interface.ts
4
5
6
7
export interface Metric {
eventName: string;
scope: string;
}
A Metric will store an eventName and a scope. We could use this for say, when a the user nate
logs-in the eventName could be loggedIn and the scope would be nate.
Dependency Injection
1
2
3
4
5
180
// just an example
let metric: Metric = {
eventName: 'loggedIn',
scope: 'nate'
}
This way we could, in theory, count the number of user logins by counting the events with eventName
loggedIn and count the number of times the specific user nate logged in by counting the loggedIn
events with user nate.
We also need to define what an analytics implementation would look like:
code/dependency-injection/src/app/analytics-demo/analytics-demo.interface.ts
12
13
14
export interface AnalyticsImplementation {
recordEvent(metric: Metric): void;
}
Here we define an AnalyticsImplementation interface to have one function: recordEvent which
takes a Metric as an argument.
Now let’s define the AnalyticsService:
code/dependency-injection/src/app/services/analytics.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Injectable } from '@angular/core';
import {
Metric,
AnalyticsImplementation
} from '../analytics-demo/analytics-demo.interface';
@Injectable()
export class AnalyticsService {
constructor(private implementation: AnalyticsImplementation) {
}
record(metric: Metric): void {
this.implementation.recordEvent(metric);
}
}
Above our AnalyticsService defines one method: record which accepts a Metric and then passes
it on to the implementation.
Dependency Injection
181
Of course, this AnalyticsService is a bit trivial and in this case, we probably wouldn’t
need the indirection. But this same pattern could be used in the case where you had a
more advanced AnalyticsService. For instance, we could add middleware or broadcast to
several implementations.
Notice how its constructor method takes a phrase as a parameter? If we try to use the “regular”
useClass injection mechanism we would see an error on the browser like:
1
Cannot resolve all parameters for AnalyticsService.
This happens because we didn’t provide the injector with the implementation necessary for the
constructor. In order to resolve this problem, we need to configure the provider to use a factory.
Using a Factory
So to use our AnalyticsService, we need to:
• create an implementation that conforms to AnalyticsImplementation and
• add it to providers with useFactory
Here’s how:
code/dependency-injection/src/app/analytics-demo/analytics-demo.module.1.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
Metric,
AnalyticsImplementation
} from './analytics-demo.interface';
import { AnalyticsService } from '../services/analytics.service';
@NgModule({
imports: [
CommonModule
],
providers: [
{
// `AnalyticsService` is the _token_ we use to inject
// note, the token is the class, but it's just used as an identifier!
provide: AnalyticsService,
Dependency Injection
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
182
// useFactory is a function - whatever is returned from this function
// will be injected
useFactory() {
// create an implementation that will log the event
const loggingImplementation: AnalyticsImplementation = {
recordEvent: (metric: Metric): void => {
console.log('The metric is:', metric);
}
};
// create our new `AnalyticsService` with the implementation
return new AnalyticsService(loggingImplementation);
}
}
],
declarations: [ ]
})
export class AnalyticsDemoModule { }
Here in providers we’re using the syntax:
1
2
3
providers: [
{ provide: AnalyticsService, useFactory: () => ... }
]
useFactory takes a function and whatever this function returns will be injected.
Also note that we provide AnalyticsService. Again, when we use provide this way, we’re using
the class AnalyticsService as the identifying token of what we’re going to inject. (If you wanted
to be confusing, you could use a completely separate class, or less-confusingly a string.)
In useFactory we’re creating an AnalyticsImplementation object that has one function: recordEvent. recordEvent is where we could, in theory, configure what happens when an event is recorded.
Again, in a real app this would probably send an event to Google Analytics or a custom event logging
software.
Lastly, we instantiate our AnalyticsService and return it.
Factory Dependencies
Using a factory is the most powerful way to create injectables, because we can do whatever we want
within the factory function. Sometimes our factory function will have dependencies of it’s own.
Say that we wanted to configure our AnalyticsImplementation to make an HTTP request to a
particular URL. In order to do this we’d need:
Dependency Injection
• The Angular Http client and
• Our API_URL value
Here’s how we could set that up:
code/dependency-injection/src/app/analytics-demo/analytics-demo.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
Metric,
AnalyticsImplementation
} from './analytics-demo.interface';
import { AnalyticsService } from '../services/analytics.service';
// added this ->
import {
HttpModule,
Http
} from '@angular/http';
@NgModule({
imports: [
CommonModule,
HttpModule, // <-- added
],
providers: [
// add our API_URL provider
{ provide: 'API_URL', useValue: 'http://devserver.com' },
{
provide: AnalyticsService,
// add our `deps` to specify the factory depencies
deps: [ Http, 'API_URL' ],
// notice we've added arguments here
// the order matches the deps order
useFactory(http: Http, apiUrl: string) {
// create an implementation that will log the event
const loggingImplementation: AnalyticsImplementation = {
recordEvent: (metric: Metric): void => {
console.log('The metric is:', metric);
183
Dependency Injection
37
38
39
40
41
42
43
44
45
46
47
48
49
184
console.log('Sending to: ', apiUrl);
// ... You'd send the metric using http here ...
}
};
// create our new `AnalyticsService` with the implementation
return new AnalyticsService(loggingImplementation);
}
},
],
declarations: [ ]
})
export class AnalyticsDemoModule { }
Here we’re importing the HttpModule, both in the ES6 import (which makes the class constants
available) and in our NgModule imports (which makes it available for dependency injection).
We’ve added an API_URL provider, as we did above. And then in our AnalyticsService provider,
we’ve added a new key: deps. deps is an array of injection tokens and these tokens will be resolved
and passed as arguments to the factory function.
Dependency Injection in Apps
To review, when writing our apps there are three steps we need to take in order to perform an
injection:
1. Create the dependency (e.g. the service class)
2. Configure the injection (i.e. register the injection with Angular in our NgModule)
3. Declare the dependencies on the receiving component
The first thing we do is create the service class, that is, the class that exposes some behavior we want
to use. This will be called the injectable because it is the thing that our components will receive via
the injection.
Reminder on terminology: a provider provides (creates, instantiates, etc.) the injectable (the thing
you want). In Angular when you want to access an injectable you inject a dependency into a function
(often a constructor) and Angular’s dependency injection framework will locate it and provide it
to you.
As we can see, Dependency Injection provides a powerful way to manage dependencies within our
app.
Dependency Injection
More Resources
• Official Angular DI Docs⁴⁸
• Victor Savkin Compare DI in Angular 1 vs. Angular 2⁴⁹
⁴⁸https://angular.io/docs/ts/latest/guide/dependency-injection.html
⁴⁹http://victorsavkin.com/post/126514197956/dependency-injection-in-angular-1-and-angular-2
185
HTTP
Introduction
Angular comes with its own HTTP library which we can use to call out to external APIs.
When we make calls to an external server, we want our user to continue to be able to interact with
the page. That is, we don’t want our page to freeze until the HTTP request returns from the external
server. To achieve this effect, our HTTP requests are asynchronous.
Dealing with asynchronous code is, historically, more tricky than dealing with synchronous code.
In JavaScript, there are generally three approaches to dealing with async code:
1. Callbacks
2. Promises
3. Observables
In Angular, the preferred method of dealing with async code is using Observables, and so that’s
what we’ll cover in this chapter.
There’s a whole chapter on RxJS and Observables: In this chapter we’re going to be
using Observables and not explaining them much. If you’re just starting to read this book
at this chapter, you should know that there’s a whole chapter on Observables that goes into
RxJS in more detail.
In this chapter we’re going to:
1. show a basic example of Http
2. create a YouTube search-as-you-type component
3. discuss API details about the Http library
Sample Code The complete code for the examples in this chapter can be found in the http
folder of the sample code. That folder contains a README.md which gives instructions for
building and running the project.
Try running the code while reading the chapter and feel free play around to get a deeper
insight about how it all works.
187
HTTP
Using @angular/http
HTTP has been split into a separate module in Angular. This means that to use it you need to import
constants from @angular/http. For instance, we might import constants from @angular/http like
this:
1
2
3
4
5
6
7
8
9
10
import {
// The NgModule for using @angular/http
HttpModule,
// the class constants
Http,
Response,
RequestOptions,
Headers
} from '@angular/http';
import from @angular/http
In our app.module.ts we’re going to import HttpModule which is a convenience collection of
modules.
code/http/src/app/app.module.ts
1
2
3
4
import
import
import
import
{
{
{
{
BrowserModule } from '@angular/platform-browser';
NgModule } from '@angular/core';
FormsModule } from '@angular/forms';
HttpModule } from '@angular/http';
In our NgModule we will add HttpModule to the list of imports. The effect is that we will be able to
inject Http (and a few other modules) into our components.
code/http/src/app/app.module.ts
14
15
16
17
18
19
20
21
@NgModule({
declarations: [
AppComponent,
SimpleHttpComponent,
MoreHttpRequestsComponent,
YouTubeSearchComponent,
SearchResultComponent,
SearchBoxComponent
188
HTTP
22
23
24
25
26
27
28
29
30
31
],
imports: [
BrowserModule,
FormsModule,
HttpModule // <-- right here
],
providers: [youTubeSearchInjectables],
bootstrap: [AppComponent]
})
export class AppModule { }
Notice that we have custom components in declarations as well as a custom provider.
We’ll talk about these later in the chapter!
Now we can inject the Http service into our components (or anywhere we use DI).
1
2
3
4
5
6
7
8
class MyFooComponent {
constructor(public http: Http) {
}
makeRequest(): void {
// do something with this.http ...
}
}
A Basic Request
The first thing we’re going to do is make a simple GET request to the jsonplaceholder API⁵⁰.
What we’re going to do is:
1. Have a button that calls makeRequest
2. makeRequest will call the http library to perform a GET request on our API
3. When the request returns, we’ll update this.data with the results of the data, which will be
rendered in the view.
Here’s a screenshot of our example:
⁵⁰http://jsonplaceholder.typicode.com
189
HTTP
Basic Request
Building the SimpleHttpComponent Component Definition
The first thing we’re going to do is import a few modules and then specify a selector for our
@Component:
code/http/src/app/simple-http/simple-http.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component, OnInit } from '@angular/core';
import {Http, Response} from '@angular/http';
@Component({
selector: 'app-simple-http',
templateUrl: './simple-http.component.html'
})
export class SimpleHttpComponent implements OnInit {
data: Object;
loading: boolean;
constructor(private http: Http) {
}
Building the SimpleHttpComponent template
Next we build our view:
HTTP
190
code/http/src/app/simple-http/simple-http.component.html
1
2
3
4
= [
{provide: YouTubeSearchService, useClass: YouTubeSearchService},
{provide: YOUTUBE_API_KEY, useValue: YOUTUBE_API_KEY},
{provide: YOUTUBE_API_URL, useValue: YOUTUBE_API_URL}
];
Here we’re specifying that we want to bind YOUTUBE_API_KEY “injectably” to the value of YOUTUBE_API_KEY. (Same for YOUTUBE_API_URL, and we’ll define YouTubeSearchService in a minute.)
To get a refresher on the different ways to create ‘injectables’, checkout the chapter on
dependency injection
If you recall, to make something available to be injected throughout our application, we need to put
it in providers for our NgModule. Since we’re exporting youTubeServiceInjectables here we can
use it in our app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// http/app.ts
import { HttpModule } from '@angular/http';
import { youTubeServiceInjectables } from "components/YouTubeSearchComponent";
// ...
// further down
// ...
@NgModule({
declarations: [
HttpApp,
// others ....
],
imports: [ BrowserModule, HttpModule ],
bootstrap: [ HttpApp ],
providers: [
youTubeServiceInjectables // <--- right here
197
HTTP
18
19
20
]
})
class HttpAppModule {}
Now we can inject YOUTUBE_API_KEY (from the youTubeServiceInjectables) instead of using the
variable directly.
YouTubeSearchService constructor
We create our YouTubeSearchService by making a service class:
code/http/src/app/you-tube-search/you-tube-search.service.ts
22
23
24
25
26
27
28
29
30
31
/**
* YouTubeService connects to the YouTube API
* See: * https://developers.google.com/youtube/v3/docs/search/list
*/
@Injectable()
export class YouTubeSearchService {
constructor(private http: Http,
@Inject(YOUTUBE_API_KEY) private apiKey: string,
@Inject(YOUTUBE_API_URL) private apiUrl: string) {
}
The @Injectable annotation allows us to inject things into this classes constructor.
In the constructor we inject three things:
1. Http
2. YOUTUBE_API_KEY
3. YOUTUBE_API_URL
Notice that we make instance variables from all three arguments, meaning we can access them as
this.http, this.apiKey, and this.apiUrl respectively.
Notice that we explicitly inject using the @Inject(YOUTUBE_API_KEY) notation.
YouTubeSearchService search
Next let’s implement the search function. search takes a query string and returns an Observable
which will emit a stream of SearchResult[]. That is, each item emitted is an array of SearchResults.
HTTP
198
code/http/src/app/you-tube-search/you-tube-search.service.ts
33
34
35
36
37
38
39
40
41
search(query: string): Observable {
const params: string = [
`q=${query}`,
`key=${this.apiKey}`,
`part=snippet`,
`type=video`,
`maxResults=10`
].join('&');
const queryUrl = `${this.apiUrl}?${params}`;
We’re building the queryUrl in a manual way here. We start by simply putting the query params in
the params variable. (You can find the meaning of each of those values by reading the search API
docs⁵⁴.)
Then we build the queryUrl by concatenating the apiUrl and the params.
Now that we have a queryUrl we can make our request. In this case we are going to use http.get
instead of http.request. While http.request can make any kind of request (POST, DELETE< GET,
etc.), http.get is a shorthand for GET requests:
code/http/src/app/you-tube-search/you-tube-search.service.ts
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
search(query: string): Observable {
const params: string = [
`q=${query}`,
`key=${this.apiKey}`,
`part=snippet`,
`type=video`,
`maxResults=10`
].join('&');
const queryUrl = `${this.apiUrl}?${params}`;
return this.http.get(queryUrl)
.map((response: Response) => {
return (response.json()).items.map(item => {
// console.log("raw item", item); // uncomment if you want to debug
return new SearchResult({
id: item.id.videoId,
title: item.snippet.title,
description: item.snippet.description,
thumbnailUrl: item.snippet.thumbnails.high.url
});
⁵⁴https://developers.google.com/youtube/v3/docs/search/list
199
HTTP
52
53
54
});
});
}
Here we take the return value of http.get and use map to get the Response from the request. From
that response we extract the body as an object using .json() and then we iterate over each item
and convert it to a SearchResult.
If you’d like to see what the raw item looks like, just uncomment the console.log and
inspect it in your browsers developer console.
Notice that we’re calling (response.json()).items. What’s going on here? We’re
telling TypeScript that we’re not interested in doing strict type checking.
When working with a JSON API, we don’t generally have typing definitions for the API
responses, and so TypeScript won’t know that the Object returned even has an items key,
so the compiler will complain.
We could call response.json()["items"] and then cast that to an Array etc., but in this
case (and in creating the SearchResult, it’s just cleaner to use an any type, at the expense
of strict type checking
YouTubeSearchService Full Listing
Here’s the full listing of our YouTubeSearchService.
In this chapter we are adding some style using the CSS framework Bootstrap⁵⁵
⁵⁵http://getbootstrap.com
HTTP
200
code/http/src/app/you-tube-search/you-tube-search.service.ts
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* YouTubeService connects to the YouTube API
* See: * https://developers.google.com/youtube/v3/docs/search/list
*/
@Injectable()
export class YouTubeSearchService {
constructor(private http: Http,
@Inject(YOUTUBE_API_KEY) private apiKey: string,
@Inject(YOUTUBE_API_URL) private apiUrl: string) {
}
search(query: string): Observable {
const params: string = [
`q=${query}`,
`key=${this.apiKey}`,
`part=snippet`,
`type=video`,
`maxResults=10`
].join('&');
const queryUrl = `${this.apiUrl}?${params}`;
return this.http.get(queryUrl)
.map((response: Response) => {
return (response.json()).items.map(item => {
// console.log("raw item", item); // uncomment if you want to debug
return new SearchResult({
id: item.id.videoId,
title: item.snippet.title,
description: item.snippet.description,
thumbnailUrl: item.snippet.thumbnails.high.url
});
});
});
}
}
Writing the SearchBoxComponent
The SearchBoxComponent plays a key role in our app: it is the mediator between our UI and the
YouTubeSearchService.
The SearchBoxComponent will :
HTTP
201
1. Watch for keyup on an input and submit a search to the YouTubeSearchService
2. Emit a loading event when we’re loading (or not)
3. Emit a results event when we have new results
SearchBoxComponent @Component Definition
Let’s define our SearchBoxComponent @Component:
code/http/src/app/you-tube-search/search-box.component.ts
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Component({
selector: 'app-search-box',
template: `
`
})
export class SearchBoxComponent implements OnInit {
@Output() loading: EventEmitter = new EventEmitter();
@Output() results: EventEmitter = new EventEmitter();
constructor(private youtube: YouTubeSearchService,
private el: ElementRef) {
}
The selector we’ve seen many times before: this allows us to create a tag.
The two @Outputs specify that events will be emitted from this component. That is, we can use the
(output)="callback()" syntax in our view to listen to events on this component. For example,
here’s how we will use the app-search-box tag in our view later on:
1
2
3
4
In this example, when the SearchBoxComponent emits a loading event, we will set the variable
loading in the parent context. Likewise, when the SearchBoxComponent emits a results event, we
will call the updateResults() function, with the value, in the parent’s context.
In the @Component class we’re specifying the properties of the events with the names loading and
results. In this example, each event will have a corresponding EventEmitter as an instance variable
of the controller class. We’ll implement that in a few minutes.
For now, remember that @Component is like the public API for our component, so here we’re just
specifying the name of the events, and we’ll worry about implementing the EventEmitters later.
HTTP
202
SearchBoxComponent template Definition
Our template is straightforward. We have one input tag:
code/http/src/app/you-tube-search/search-box.component.ts
24
25
26
template: `
`
SearchBoxComponent Controller Definition
Our SearchBoxComponent controller is a new class:
code/http/src/app/you-tube-search/search-box.component.ts
28
29
30
31
export class SearchBoxComponent implements OnInit {
@Output() loading: EventEmitter = new EventEmitter();
@Output() results: EventEmitter = new EventEmitter();
We say that this class implements OnInit because we want to use the ngOnInit lifecycle callback. If
a class implements OnInit then the ngOnInit function will be called after the first change detection
check.
ngOnInit is a good place to do initialization (vs. the constructor) because inputs set on a component
are not available in the constructor.
Here we create the EventEmitters for both loading and the results. loading will emit a boolean
when this search is loading and results will emit an array of SearchResults when the search is
finished.
SearchBoxComponent Controller Definition constructor
Let’s talk about the SearchBoxComponent constructor:
code/http/src/app/you-tube-search/search-box.component.ts
32
33
34
constructor(private youtube: YouTubeSearchService,
private el: ElementRef) {
}
In our constructor we inject :
1. Our YouTubeSearchService and
2. The element el that this component is attached to. el is an object of type ElementRef, which
is an Angular wrapper around a native element.
We set both injections as instance variables.
HTTP
203
SearchBoxComponent Controller Definition ngOnInit
On this input box we want to watch for keyup events. The thing is, if we simply did a search after
every keyup that wouldn’t work very well. There are three things we can do to improve the user
experience:
1. Filter out any empty or short queries
2. “debounce” the input, that is, don’t search on every character but only after the user has
stopped typing after a short amount of time
3. discard any old searches, if the user has made a new search
We could manually bind to keyup and call a function on each keyup event and then implement
filtering and debouncing from there. However, there is a better way: turn the keyup events into an
observable stream.
RxJS provides a way to listen to events on an element using Rx.Observable.fromEvent. We can use
it like so:
code/http/src/app/you-tube-search/search-box.component.ts
36
37
38
ngOnInit(): void {
// convert the `keyup` event into an observable stream
Observable.fromEvent(this.el.nativeElement, 'keyup')
Notice that in fromEvent:
• the first argument is this.el.nativeElement (the native DOM element this component is
attached to)
• the second argument is the string 'keyup', which is the name of the event we want to turn
into a stream
We can now perform some RxJS magic over this stream to turn it into SearchResults. Let’s walk
through step by step.
Given the stream of keyup events we can chain on more methods. In the next few paragraphs we’re
going to chain several functions on to our stream which will transform the stream. Then at the end
we’ll show the whole example together.
First, let’s extract the value of the input tag:
1
.map((e: any) => e.target.value) // extract the value of the input
Above says, map over each keyup event, then find the event target (e.target, that is, our input
element) and extract the value of that element. This means our stream is now a stream of strings.
Next:
204
HTTP
1
.filter((text: string) => text.length > 1)
This filter means the stream will not emit any search strings for which the length is less than one.
You could set this to a higher number if you want to ignore short searches.
1
.debounceTime(250)
debounceTime means we will throttle requests that come in faster than 250ms. That is, we won’t
search on every keystroke, but rather after the user has paused a small amount.
1
.do(() => this.loading.emit(true))
// enable loading
Using do on a stream is a way to perform a function mid-stream for each event, but it does not
change anything in the stream. The idea here is that we’ve got our search, it has enough characters,
and we’ve debounced, so now we’re about to search, so we turn on loading.
this.loading is an EventEmitter. We “turn on” loading by emitting true as the next event. We emit
something on an EventEmitter by calling next. Writing this.loading.emit(true) means, emit a
true event on the loading EventEmitter. When we listen to the loading event on this component,
the $event value will now be true (we’ll look more closely at using $event below).
1
2
.map((query: string) => this.youtube.search(query))
.switch()
We use .map to call perform a search for each query that is emitted. By using switch we’re,
essentially, saying “ignore all search events but the most recent”. That is, if a new search comes
in, we want to use the most recent and discard the rest.
Reactive experts will note that I’m handwaving here. switch has a more specific
technical definition which you can read about in the RxJS docs here⁵⁶.
For each query that comes in, we’re going to perform a search on our YouTubeSearchService.
Putting the chain together we have this:
⁵⁶https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/switch.md
205
HTTP
code/http/src/app/you-tube-search/search-box.component.ts
36
37
38
39
40
41
42
43
44
45
46
47
ngOnInit(): void {
// convert the `keyup` event into an observable stream
Observable.fromEvent(this.el.nativeElement, 'keyup')
.map((e: any) => e.target.value) // extract the value of the input
.filter((text: string) => text.length > 1) // filter out if empty
.debounceTime(250)
// only once every 250ms
.do(() => this.loading.emit(true))
// enable loading
// search, discarding old events if new input comes in
.map((query: string) => this.youtube.search(query))
.switch()
// act on the return of the search
.subscribe(
The API of RxJS can be a little intimidating because the API surface area is large. That said, we’ve
implemented a sophisticated event-handling stream in very few lines of code!
Because we are calling out to our YouTubeSearchService our stream is now a stream of SearchResult[]. We can subscribe to this stream and perform actions accordingly.
subscribe takes three arguments: onSuccess, onError, onCompletion.
code/http/src/app/you-tube-search/search-box.component.ts
47
48
49
50
51
52
53
54
55
56
57
58
59
60
.subscribe(
(results: SearchResult[]) => { // on sucesss
this.loading.emit(false);
this.results.emit(results);
},
(err: any) => { // on error
console.log(err);
this.loading.emit(false);
},
() => { // on completion
this.loading.emit(false);
}
);
}
The first argument specifies what we want to do when the stream emits a regular event. Here we
emit an event on both of our EventEmitters:
1. We call this.loading.emit(false), indicating we’ve stopped loading
HTTP
206
2. We call this.results.emit(results), which will emit an event containing the list of results
The second argument specifies what should happen when the stream has an error event. Here we
set this.loading.emit(false) and log out the error.
The third argument specifies what should happen when the stream completes. Here we also emit
that we’re done loading.
SearchBoxComponent: Full Listing
All together, here’s the full listing of our SearchBoxComponent Component:
code/http/src/app/you-tube-search/search-box.component.ts
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Component({
selector: 'app-search-box',
template: `
`
})
export class SearchBoxComponent implements OnInit {
@Output() loading: EventEmitter = new EventEmitter();
@Output() results: EventEmitter = new EventEmitter();
constructor(private youtube: YouTubeSearchService,
private el: ElementRef) {
}
ngOnInit(): void {
// convert the `keyup` event into an observable stream
Observable.fromEvent(this.el.nativeElement, 'keyup')
.map((e: any) => e.target.value) // extract the value of the input
.filter((text: string) => text.length > 1) // filter out if empty
.debounceTime(250)
// only once every 250ms
.do(() => this.loading.emit(true))
// enable loading
// search, discarding old events if new input comes in
.map((query: string) => this.youtube.search(query))
.switch()
// act on the return of the search
.subscribe(
(results: SearchResult[]) => { // on sucesss
this.loading.emit(false);
this.results.emit(results);
207
HTTP
52
53
54
55
56
57
58
59
60
61
62
},
(err: any) => { // on error
console.log(err);
this.loading.emit(false);
},
() => { // on completion
this.loading.emit(false);
}
);
}
}
Writing SearchResultComponent
The SearchBoxComponent was fairly complicated . Let’s handle a
much easier component now: the SearchResultComponent. The
SearchResultComponent’s job is to render a single SearchResult.
Given what we’ve already covered there aren’t any new ideas
here, so let’s take it all at once:
code/http/src/app/you-tube-search/search-result.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
Component,
OnInit,
Input
} from '@angular/core';
import { SearchResult } from './search-result.model';
@Component({
selector: 'app-search-result',
templateUrl: './search-result.component.html'
})
export class SearchResultComponent implements OnInit {
@Input() result: SearchResult;
constructor() { }
ngOnInit() {
}
}
Single Search Result Component
HTTP
A few things:
The @Component takes a single input result, on which we will put
the SearchResult assigned to this component.
The template shows the title, description, and thumbnail of the
video and then links to the video via a button.
code/http/src/app/you-tube-search/search-result.component.html
1
2
3
4
5
6
7
8
9
10
11
12
The SearchResultComponent simply stores the SearchResult in
the instance variable result.
Writing YouTubeSearchComponent
The last component we have to implement is the YouTubeSearchComponent. This is the component that ties everything together.
YouTubeSearchComponent @Component
code/http/src/app/you-tube-search/you-tube-search.component.ts
4
5
6
7
8
9
10
@Component({
selector: 'app-you-tube-search',
templateUrl: './you-tube-search.component.html'
})
export class YouTubeSearchComponent implements OnInit {
results: SearchResult[];
loading: boolean;
Our @Component decorator is straightforward: use the selector
app-you-tube-search.
208
HTTP
209
YouTubeSearchComponent Controller
Before we look at the template, let’s take a look at the YouTubeSearchComponent controller:
code/http/src/app/you-tube-search/you-tube-search.component.ts
8
9
10
11
12
13
14
15
16
17
18
19
export class YouTubeSearchComponent implements OnInit {
results: SearchResult[];
loading: boolean;
constructor() { }
ngOnInit() { }
updateResults(results: SearchResult[]): void {
this.results = results;
// console.log("results:", this.results); // uncomment to take a look
}
}
This component holds one instance variable: results which is an array of SearchResults.
We also define one function: updateResults. updateResults simply takes whatever new SearchResult[] it’s given and sets this.results to the new value.
We’ll use both results and updateResults in our template.
YouTubeSearchComponent template
Our view needs to do three things:
1. Show the loading indicator, if we’re loading
2. Listen to events on the search-box
3. Show the search results
Next lets look at our template. Let’s build some basic structure and show the loading gif next to the
header:
HTTP
210
code/http/src/app/you-tube-search/you-tube-search.component.html
1
2
3
4
5
6
7
8
9
We only want to show this loading image if loading is true, so we use ngIf to implement that
functionality.
Next, let’s look at the markup where we use our search-box:
code/http/src/app/you-tube-search/you-tube-search.component.html
10
11
12
13
14
15
16
The interesting part here is how we bind to the loading and results outputs. Notice, that we use
the (output)="action()" syntax here.
For the loading output, we run the expression loading = $event. $event will be substituted with
the value of the event that is emitted from the EventEmitter. That is, in our SearchBoxComponent,
when we call this.loading.emit(true) then $event will be true.
Similarly, for the results output, we call the updateResults() function whenever a new set of
results are emitted. This has the effect of updating our components results instance variable.
Lastly, we want to take the list of results in this component and render a search-result for each
one:
HTTP
code/http/src/app/you-tube-search/you-tube-search.component.html
19
20
21
22
23
24
25
YouTubeSearchComponent Full Listing
Here’s the full listing for the YouTubeSearchComponent:
code/http/src/app/you-tube-search/you-tube-search.component.ts
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component({
selector: 'app-you-tube-search',
templateUrl: './you-tube-search.component.html'
})
export class YouTubeSearchComponent implements OnInit {
results: SearchResult[];
loading: boolean;
constructor() { }
ngOnInit() { }
updateResults(results: SearchResult[]): void {
this.results = results;
// console.log("results:", this.results); // uncomment to take a look
}
}
and the template:
211
HTTP
212
code/http/src/app/you-tube-search/you-tube-search.component.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
There we have it! A functional search-as-you-type implemented for YouTube video search! Try
running it from the code examples if you haven’t already.
@angular/http API
Of course, all of the HTTP requests we’ve made so far have simply been GET requests. It’s important
that we know how we can make other requests too.
Making a POST request
Making POST request with @angular/http is very much like making a GET request except that we
have one additional parameter: a body.
HTTP
213
jsonplaceholder API⁵⁷ also provides a convent URL for testing our POST requests, so let’s use it for
a POST:
code/http/src/app/more-http-requests/more-http-requests.component.ts
23
24
25
26
27
28
29
30
31
32
33
34
35
36
makePost(): void {
this.loading = true;
this.http.post(
'http://jsonplaceholder.typicode.com/posts',
JSON.stringify({
body: 'bar',
title: 'foo',
userId: 1
}))
.subscribe((res: Response) => {
this.data = res.json();
this.loading = false;
});
}
Notice in the second argument we’re taking an Object and converting it to a JSON string using
JSON.stringify.
PUT / PATCH / DELETE / HEAD
There are a few other fairly common HTTP requests and we call them in much the same way.
• http.put and http.patch map to PUT and PATCH respectively and both take a URL and a body
• http.delete and http.head map to DELETE and HEAD respectively and both take a URL (no
body)
Here’s how we might make a DELETE request:
⁵⁷http://jsonplaceholder.typicode.com
214
HTTP
code/http/src/app/more-http-requests/more-http-requests.component.ts
38
39
40
41
42
43
44
45
makeDelete(): void {
this.loading = true;
this.http.delete('http://jsonplaceholder.typicode.com/posts/1')
.subscribe((res: Response) => {
this.data = res.json();
this.loading = false;
});
}
RequestOptions
All of the http methods we’ve covered so far also take an optional last argument: RequestOptions.
The RequestOptions object encapsulates:
•
•
•
•
•
•
•
•
method
headers
body
mode
credentials
cache
url
search
Let’s say we want to craft a GET request that uses a special X-API-TOKEN header. We can create a
request with this header like so:
code/http/src/app/more-http-requests/more-http-requests.component.ts
47
48
49
50
51
52
53
54
55
56
57
58
makeHeaders(): void {
const headers: Headers = new Headers();
headers.append('X-API-TOKEN', 'ng-book');
const opts: RequestOptions = new RequestOptions();
opts.headers = headers;
this.http.get('http://jsonplaceholder.typicode.com/posts/1', opts)
.subscribe((res: Response) => {
this.data = res.json();
});
}
HTTP
215
Summary
@angular/http is flexible and suitable for a wide variety of APIs.
One of the great things about @angular/http is that it has support for mocking the backend which
is very useful in testing. To learn about testing HTTP, flip on over to the testing chapter.
Routing
In web development, routing means splitting the application into different areas usually based on
rules that are derived from the current URL in the browser.
For instance, if we visit the / path of a website, we may be visiting the home route of that website.
Or if we visit /about we want to render the “about page”, and so on.
Why Do We Need Routing?
Defining routes in our application is useful because we can:
• separate different areas of the app;
• maintain the state in the app;
• protect areas of the app based on certain rules;
For example, imagine we are writing an inventory application similar to the one we described in
previous chapters.
When we first visit the application, we might see a search form where we can enter a search term
and get a list of products that match that term.
After that, we might click a given product to visit that product’s details page.
Because our app is client-side, it’s not technically required that we change the URL when we change
“pages”. But it’s worth thinking about for a minute: what would be the consequences of using the
same URL for all pages?
• You wouldn’t be able to refresh the page and keep your location within the app
• You wouldn’t be able to bookmark a page and come back to it later
• You wouldn’t be able to share the URL of that page with others
Or put in a positive light, routing lets us define a URL string that specifies where within our app a
user should be.
In our inventory example we could determine a series of different routes for each activity, for
instance:
The initial root URL could be represented by http://our-app/. When we visit this page, we could
be redirected to our “home” route at http://our-app/home.
When accessing the ‘About Us’ area, the URL could become http://our-app/about. This way if we
sent the URL http://our-app/about to another user they would see same page.
Routing
217
How client-side routing works
Perhaps you’ve written server-side routing code before (though, it isn’t necessary to complete this
chapter). Generally with server-side routing, the HTTP request comes in and the server will render
a different controller depending on the incoming URL.
For instance, with Express.js⁵⁸ you might write something like this:
1
2
3
4
5
6
7
var express = require('express');
var router = express.Router();
// define the about route
router.get('/about', function(req, res) {
res.send('About us');
});
Or with Ruby on Rails⁵⁹ you might have:
1
2
3
4
5
6
7
8
9
# routes.rb
get '/about', to: 'pages#about'
# PagesController.rb
class PagesController < ActionController::Base
def about
render
end
end
The pattern varies per framework, but in both of these cases you have a server that accepts a request
and routes to a controller and the controller runs a specific action, depending on the path and
parameters.
Client-side routing is very similar in concept but different in implementation. With client-side
routing we’re not necessarily making a request to the server on every URL change. With our
Angular apps, we refer to them as “Single Page Apps” (SPA) because our server only gives us a
single page and it’s our JavaScript that renders the different pages.
So how can we have different routes in our JavaScript code?
⁵⁸http://expressjs.com/guide/routing.html
⁵⁹http://rubyonrails.org/
218
Routing
The beginning: using anchor tags
Client-side routing started out with a clever hack: Instead of using a normal server-side URL for a
page in our SPA, we use the anchor tag as the client-side URL.
As you may already know, anchor tags were traditionally used to link directly to a place within the
webpage and make the browser scroll all the way to where that anchor was defined. For instance, if
we define an anchor tag in an HTML page:
1
2
When we change routes, we want to keep our outer “layout” template and only substitute the “inner
section” of the page with the route’s component.
In order to describe to Angular where in our page we want to render the contents for each route,
we use the RouterOutlet directive.
Our component @Component has a template which specifies some div structure, a section for
Navigation, and a directive called router-outlet.
The router-outlet element indicates where the contents of each route component will be
rendered.
We are are able to use the router-outlet directive in our template because we imported
the RouterModule in our NgModule.
Here’s the component and template for the navigation wrapper of our app:
Routing
223
code/routes/routing/src/app/app.component.ts
6
7
8
9
10
11
12
13
14
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private router: Router) {
};
}
and the template:
code/routes/routing/src/app/app.component.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
If we look at the template above, you will note the router-outlet element right below the navigation
menu. When we visit /home, that’s where HomeComponent template will be rendered. The same
happens for the other components.
Routing
224
RouterLink using [routerLink]
Now that we know where route templates will be rendered, how do we tell Angular to navigate to
a given route?
We might try linking to the routes directly using pure HTML:
1
Home
But if we do this, we’ll notice that clicking the link triggers a page reload and that’s definitely not
what we want when programming single page apps.
To solve this problem, Angular provides a solution that can be used to link to routes with no page
reload: the RouterLink directive.
This directive allows you to write links using a special syntax:
code/routes/routing/src/app/app.component.html
3
4
5
6
7
8
Note that in this section, I’m using “explicit” notation of adding an instance variable for
each FormControl. That means that in the view in this section, sku refers to a FormControl.
If you run the sample code, one neat thing you’ll notice is that if you type something in to the field,
the required validation will be fulfilled, but the invalidSku validation may not. This is great - it
means we can partially-validate our fields and show the appropriate messages.
Forms in Angular
160
Watching For Changes
So far we’ve only extracted the value from our form by calling onSubmit when the form is submitted.
But often we want to watch for any value changes on a control.
Both FormGroup and FormControl have an EventEmitter that we can use to observe changes.
EventEmitter is an Observable, which means it conforms to a defined specification for
watching for changes. If you’re interested in the Observable spec, you can find it here⁴⁶
To watch for changes on a control we:
1. get access to the EventEmitter by calling control.valueChanges. Then we
2. add an observer using the .subscribe method
Here’s an example:
code/forms/src/app/demo-form-with-events/demo-form-with-events.component.ts
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
this.sku.valueChanges.subscribe(
(value: string) => {
console.log('sku changed to:', value);
}
);
this.myForm.valueChanges.subscribe(
(form: any) => {
console.log('form changed to:', form);
}
);
}
⁴⁶https://github.com/jhusain/observable-spec
Forms in Angular
161
Here we’re observing two separate events: changes on the sku field and changes on the form as a
whole.
The observable that we pass in is an object with a single key: next (there are other keys you can
pass in, but we’re not going to worry about those now). next is the function we want to call with
the new value whenever the value changes.
If we type ‘kj’ into the text box we will see in our console:
1
2
3
4
sku changed to: k
form changed to: Object {sku: "k"}
sku changed to: kj
form changed to: Object {sku: "kj"}
As you can see each keystroke causes the control to change, so our observable is triggered. When
we observe the individual FormControl we receive a value (e.g. kj), but when we observe the whole
form, we get an object of key-value pairs (e.g. {sku: "kj"}).
ngModel
NgModel is a special directive: it binds a model to a form. ngModel is special in that it implements
two-way data binding. Two-way data binding is almost always more complicated and difficult to
reason about vs. one-way data binding. Angular is built to generally have data flow one-way: topdown. However, when it comes to forms, there are times where it is easier to opt-in to a two-way
bind.
Just because you’ve used ng-model in Angular 1 in the past, don’t rush to use ngModel right
away. There are good reasons to avoid two-way data binding⁴⁷. Of course, ngModel can be
really handy, but know that we don’t necessarily rely on two-way data binding as much
as we did in Angular 1.
Let’s change our form a little bit and say we want to input productName. We’re going to use ngModel
to keep the component instance variable in sync with the view.
First, here’s our component definition class:
⁴⁷https://www.quora.com/Why-is-the-two-way-data-binding-being-dropped-in-Angular-2
Forms in Angular
162
code/forms/src/app/demo-form-ng-model/demo-form-ng-model.component.ts
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export class DemoFormNgModelComponent {
myForm: FormGroup;
productName: string;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'productName': ['', Validators.required]
});
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
Notice that we’re simply storing productName: string as an instance variable.
Next, let’s use ngModel on our input tag:
code/forms/src/app/demo-form-ng-model/demo-form-ng-model.component.html
13
14
15
16
17
18
Now notice something - the syntax for ngModel is funny: we are using both brackets and parentheses
around the ngModel attribute! The idea this is intended to invoke is that we’re using both the input
[] brackets and the output () parentheses. It’s an indication of the two-way bind.
Notice something else here: we’re still using formControl to specify that this input should be bound
to the FormControl on our form. We do this because ngModel is only binding the input to the instance
variable - the FormControl is completely separate. But because we still want to validate this value
and submit it as part of the form, we keep the formControl directive.
Last, let’s display our productName value in the view:
163
Forms in Angular
code/forms/src/app/demo-form-ng-model/demo-form-ng-model.component.html
4
5
6
Here’s what it looks like:
Demo Form with ngModel
Easy!
Wrapping Up
Forms have a lot of moving pieces, but Angular makes it fairly straightforward. Once you get a
handle on how to use FormGroups, FormControls, and Validations, it’s pretty easy going from there!
Dependency Injection
As our programs grow in size, parts of the app need to communicate with other modules. When
module A requires module B to run, we say that B is a dependency of A.
One of the most common ways to get access to dependencies is to simply import a file. For instance,
in this hypothetical module we might do the following:
1
2
3
4
// in A.ts
import {B} from 'B'; // a dependency!
B.foo(); // using B
In many cases, simply importing code is sufficient, but other times we need to provide dependencies
in a more sophisticated way. For instance, we may want to:
• substitute out the implementation of B for MockB during testing
• share a single instance of the B class across our whole app (e.g. the Singleton pattern)
• create a new instance of the B class every time it is used (e.g. the Factory pattern)
Dependency Injection can solve these problems.
Dependency Injection (DI) is a system to make parts of our program accessible to other parts of the
program - and we can configure how that happens.
One way to think about “the injector” is as a replacement for the new operator. That
is, instead of using the language-provided new operator, Dependency Injection let’s us
configure how objects are created.
The term Dependency Injection is used to describe both a design pattern (used in many different
frameworks) and also the specific implementation of DI that is built-in to Angular.
The major benefit of using Dependency Injection is that the client component needn’t be aware of
how to create the dependencies. All the client component needs to know is how to interact with
those dependencies. This is all very abstract, so let’s dive in to some code.
Dependency Injection
165
How to use this chapter
This chapter is a tour of Angular DI system and concepts. You can find the code for this
chapter in code/dependency-injection.
While reading this chapter, run the demo project by changing into the project directory
and running:
1
2
npm install
npm start
As a preview, to get Dependency Injection to work involves configuration in your
NgModules. It can feel a bit confusing at first to figure out “where” things are coming from.
The example code has full, runnable examples with all of the context. So if you feel lost,
we’d encourage you to checkout the sample code alongside reading this chapter.
Injections Example: PriceService
Let’s imagine we’re building a store that has Products and we need to calculate the final price of that
product after sales tax. In order to calculate the full price for this product, we use a PriceService
that takes as input:
• the base price of the Product and
• the state we’re selling it to.
and then returns the final price of the Product, plus tax:
code/dependency-injection/src/app/price-service-demo/price.service.1.ts
1
2
3
4
5
6
7
8
9
10
11
12
export class PriceService {
constructor() { }
calculateTotalPrice(basePrice: number, state: string) {
// e.g. Imgine that in our "real" application we're
// accessing a real database of state sales tax amounts
const tax = Math.random();
return basePrice + tax;
}
}
Dependency Injection
166
In this service, the calculateTotalPrice function will take the basePrice of a product and the
state and return the total price of product.
Say we want to use this service on our Product model. Here’s how it could look without dependency
injection:
code/dependency-injection/src/app/price-service-demo/product.model.1.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { PriceService } from './price.service';
export class Product {
service: PriceService;
basePrice: number;
constructor(basePrice: number) {
this.service = new PriceService(); // <-- create directly ("hardcoded")
this.basePrice = basePrice;
}
totalPrice(state: string) {
return this.service.calculateTotalPrice(this.basePrice, state);
}
}
Now imagine we need to write a test for this Product class. We could write a test like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Product } from './product';
describe('Product', () => {
let product;
beforeEach(() => {
product = new Product(11);
});
describe('price', () => {
it('is calculated based on the basePrice and the state', () => {
expect(product.totalPrice('FL')).toBe(11.66); // <-- hmmm
});
})
});
Dependency Injection
167
The problem with this test is that we don’t actually know what the exact value for tax in Florida
('FL') is going to be. Even if we implemented the PriceService the ‘real’ way by calling an API or
calling a database, we have the problem that:
• The API needs to be available (or the database needs to be running) and
• We need to know the exact Florida tax at the time we write the test.
What should we do if we want to test the price method of the Product without relying on this
external resource? In this case we often mock the PriceService. For example, if we know the
interface of a PriceService, we could write a MockPriceService which will always give us a
predictable calculation (and not be reliant on a database or API).
Here’s the interface for IPriceService:
code/dependency-injection/src/app/price-service-demo/price-service.interface.ts
1
2
3
export interface IPriceService {
calculateTotalPrice(basePrice: number, state: string): number;
}
This interface defines just one function: calculateTotalPrice. Now we can write a MockPriceService that conforms to this interface, which we will use only for our tests:
code/dependency-injection/src/app/price-service-demo/price.service.mock.ts
1
2
3
4
5
6
7
8
9
10
11
import { IPriceService } from './price-service.interface';
export class MockPriceService implements IPriceService {
calculateTotalPrice(basePrice: number, state: string) {
if (state === 'FL') {
return basePrice + 0.66; // it's always 66 cents!
}
return basePrice;
}
}
Now, just because we’ve written a MockPriceService doesn’t mean our Product will use it. In order
to use this service, we need to modify our Product class:
Dependency Injection
168
code/dependency-injection/src/app/price-service-demo/product.model.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { IPriceService } from './price-service.interface';
export class Product {
service: IPriceService;
basePrice: number;
constructor(service: IPriceService, basePrice: number) {
this.service = service; // <-- passed in as an argument!
this.basePrice = basePrice;
}
totalPrice(state: string) {
return this.service.calculateTotalPrice(this.basePrice, state);
}
}
Now, when creating a Product the client using the Product class becomes responsible for
deciding which concrete implementation of the PriceService is going to be given to the new
instance.
And with this change, we can tweak our test slightly and get rid of the dependency on the
unpredictable PriceService:
code/dependency-injection/src/app/price-service-demo/product.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Product } from './product.model';
import { MockPriceService } from './price.service.mock';
describe('Product', () => {
let product;
beforeEach(() => {
const service = new MockPriceService();
product = new Product(service, 11.00);
});
describe('price', () => {
it('is calculated based on the basePrice and the state', () => {
expect(product.totalPrice('FL')).toBe(11.66);
});
});
});
Dependency Injection
169
We also get the bonus of having confidence that we’re testing the Product class in isolation. That
is, we’re making sure that our class works with a predictable dependency.
While the predictability is nice, it’s a bit laborious to pass a concrete implementation of a service
every time we want a new Product. Thankfully, Angular’s DI library helps us deal with that problem,
too. More on that below.
Within Angular’s DI system, instead of directly importing and creating a new instance of a class,
instead we will:
• Register the “dependency” with Angular
• Describe how the dependency will be injected
• Inject the dependency
One benefit of this model is that the dependency implementation can be swapped at run-time (as
in our mocking example above). But another significant benefit is that we can configure how the
dependency is created.
That is, often in the case of program-wide services, we may want to have only one instance - that
is, a Singleton. With DI we’re able to configure Singletons easily.
A third use-case for DI is for configuration or environment-specific variables. For instance, we might
define a “constant” API_URL, but then inject a different value in production vs. development.
Let’s learn how to create our own services and the different ways of injecting them.
Dependency Injection Parts
To register a dependency we have to bind it to something that will identify that dependency. This
identification is called the dependency token. For instance, if we want to register the URL of an
API, we can use the string API_URL as the token. Similarly, if we’re registering a class, we can use
the class itself as its token as we’ll see below.
Dependency injection in Angular has three pieces:
• the Provider (also often referred to as a binding) maps a token (that can be a string or a class)
to a list of dependencies. It tells Angular how to create an object, given a token.
• the Injector that holds a set of bindings and is responsible for resolving dependencies and
injecting them when creating objects
• the Dependency that is what’s being injected
We can think of the role of each piece as illustrated below:
170
Dependency Injection
Dependency Injection
A way of thinking about this is that when we configure DI we specify what is being injected and
how it will be resolved.
Playing with an Injector
Above with our Product and PriceService we manually created the PriceService using the new
operator. This mimics what Angular itself does.
Angular uses an injector to resolve a dependency and create the instance. This is done for us
behind the scenes, but as an exercise, it’s useful to explore what’s happening. It can be enlightening
to use the injector manually, because we can see what Angular does for us behind the scenes.
Let’s manually use the injector in our component to resolve and create a service. (After we’ve
resolved a dependency manually, we’ll show the typical, easy way of injecting dependencies. )
One of the common use-cases for services is to have a ‘global’ Singleton object. For instance, we
might have a UserService which contains the information for the currently logged in user. Many
different components will want to have logic based on the current user, so this is a good case for a
service.
Here’s a basic UserService that stores the user object as a property:
Dependency Injection
171
code/dependency-injection/src/app/services/user.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Injectable } from '@angular/core';
@Injectable()
export class UserService {
user: any;
setUser(newUser) {
this.user = newUser;
}
getUser(): any {
return this.user;
}
}
Say we want to create a toy sign-in form:
code/dependency-injection/src/app/user-demo/user-demo.component.html
1
2
3
4
5
6
7
8
9
10
11
12
Welcome: {{ userName }}!
Basic Request
loading...
{{data | json}}Our template has three interesting parts: 1. The button 2. The loading indicator 3. The data On the button we bind to (click) to call the makeRequest function in our controller, which we’ll define in a minute. We want to indicate to the user that our request is loading, so to do that we will show loading... if the instance variable loading is true, using ngIf. The data is an Object. A great way to debug objects is to use the json pipe as we do here. We’ve put this in a pre tag to give us nice, easy to read formatting. Building the SimpleHttpComponent Controller We start by defining a new class for our SimpleHttpComponent: code/http/src/app/simple-http/simple-http.component.ts 8 9 10 export class SimpleHttpComponent implements OnInit { data: Object; loading: boolean; We have two instance variables: data and loading. This will be used for our API return value and loading indicator respectively. Next we define our constructor: code/http/src/app/simple-http/simple-http.component.ts 12 13 constructor(private http: Http) { } 191 HTTP The constructor body is empty, but we inject one key module: Http. Remember that when we use the public keyword in public http: Http TypeScript will assign http to this.http. It’s a shorthand for: 1 2 3 4 5 6 // other instance variables here http: Http; constructor(http: Http) { this.http = http; } Now let’s make our first HTTP request by implementing the makeRequest function: code/http/src/app/simple-http/simple-http.component.ts 18 19 20 21 22 23 24 25 makeRequest(): void { this.loading = true; this.http.request('http://jsonplaceholder.typicode.com/posts/1') .subscribe((res: Response) => { this.data = res.json(); this.loading = false; }); } When we call makeRequest, the first thing we do is set this.loading = true. This will turn on the loading indicator in our view. To make an HTTP request is straightforward: we call this.http.request and pass the URL to which we want to make a GET request. http.request returns an Observable. We can subscribe to changes (akin to using then from a Promise) using subscribe. code/http/src/app/simple-http/simple-http.component.ts 20 21 this.http.request('http://jsonplaceholder.typicode.com/posts/1') .subscribe((res: Response) => { When our http.request returns (from the server) the stream will emit a Response object. We extract the body of the response as an Object by using json and then we set this.data to that Object. Since we have a response, we’re not loading anymore so we set this.loading = false 192 HTTP .subscribe can also handle failures and stream completion by passing a function to the second and third arguments respectively. In a production app it would be a good idea to handle those cases, too. That is, this.loading should also be set to false if the request fails (i.e. the stream emits an error). Full SimpleHttpComponent Here’s what our SimpleHttpComponent looks like altogether: code/http/src/app/simple-http/simple-http.component.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { Component, OnInit } from '@angular/core'; import {Http, Response} from '@angular/http'; @Component({ selector: 'app-simple-http', templateUrl: './simple-http.component.html' }) export class SimpleHttpComponent implements OnInit { data: Object; loading: boolean; constructor(private http: Http) { } ngOnInit() { } makeRequest(): void { this.loading = true; this.http.request('http://jsonplaceholder.typicode.com/posts/1') .subscribe((res: Response) => { this.data = res.json(); this.loading = false; }); } } Writing a YouTubeSearchComponent The last example was a minimal way to get the data from an API server into your code. Now let’s try to build a more involved example. 193 HTTP In this section, we’re going to build a way to search YouTube as you type. When the search returns we’ll show a list of video thumbnail results, along with a description and link to each video. Here’s a screenshot of what happens when I search for “cats playing ipads”: Can I get my cat to write Angular? For this example we’re going to write several things: 1. A SearchResult object that will hold the data we want from each result 2. A YouTubeSearchService which will manage the API request to YouTube and convert the results to a stream of SearchResult[] 3. A SearchBoxComponent which will call out to the YouTube service as the user types 4. A SearchResultComponent which will render a specific SearchResult 194 HTTP 5. A YouTubeSearchComponent which will encapsulate our whole YouTube searching app and render the list of results Let’s handle each part one at a time. Patrick Stapleton has an excellent repository named angular2-webpack-starter⁵¹. This repo has an RxJS example which autocompletes Github repositories. Some of the ideas in this section are inspired from that example. It’s a fantastic project with lots of examples and you should check it out. Writing a SearchResult First let’s start with writing a basic SearchResult class. This class is just a convenient way to store the specific fields we’re interested in from our search results. code/http/src/app/you-tube-search/search-result.model.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * SearchResult is a data-structure that holds an individual * record from a YouTube video search */ export class SearchResult { id: string; title: string; description: string; thumbnailUrl: string; videoUrl: string; constructor(obj?: any) this.id this.title this.description this.thumbnailUrl this.videoUrl { = = = = = obj && obj.id || null; obj && obj.title || null; obj && obj.description || null; obj && obj.thumbnailUrl || null; obj && obj.videoUrl || `https://www.youtube.com/watch?v=${this.id}`; } } This pattern of taking an obj?: any lets us simulate keyword arguments. The idea is that we can create a new SearchResult and just pass in an object containing the keys we want to specify. ⁵¹https://github.com/angular-class/angular2-webpack-starter 195 HTTP The only thing to point out here is that we’re constructing the videoUrl using a hard-coded URL format. You’re welcome to change this to a function which takes more arguments, or use the video id directly in your view to build this URL if you need to. Writing the YouTubeSearchService The API For this example we’re going to be using the YouTube v3 search API⁵². In order to use this API you need to have an API key. I’ve included an API key in the sample code which you can use. However, by the time you read this, you may find it’s over the rate limits. If that happens, you’ll need to issue your own key. To issue your own key see this documentation⁵³. For the sake of simplicity, I’ve registered a server key, but you should probably use a browser key if you’re going to put your javascript code online. We’re going to setup two constants for our YouTubeSearchService mapping to our API key and the API URL: 1 2 let YOUTUBE_API_KEY: string = "XXX_YOUR_KEY_HERE_XXX"; let YOUTUBE_API_URL: string = "https://www.googleapis.com/youtube/v3/search"; Eventually we’re going to want to test our app. One of the things we find when testing is that we don’t always want to test against production - we often want to test against staging or a development API. To help with this environment configuration, one of the things we can do is make these constants injectable. Why should we inject these constants instead of just using them in the normal way? Because if we make them injectable we can 1. have code that injects the right constants for a given environment at deploy time and 2. replace the injected value easily at test-time By injecting these values, we have a lot more flexibility about their values down the line. In order to make these values injectable, we use the { provide: ... , useValue: ... } syntax like this: ⁵²https://developers.google.com/youtube/v3/docs/search/list ⁵³https://developers.google.com/youtube/registering_an_application#Create_API_Keys 196 HTTP code/http/src/app/you-tube-search/you-tube-search.injectables.ts 1 2 3 4 5 6 7 8 9 10 11 import { YouTubeSearchService, YOUTUBE_API_KEY, YOUTUBE_API_URL } from './you-tube-search.service'; export const youTubeSearchInjectables: Array