Introduction to Angular 2

Introduction to Angular 2

Brief introduction to ECMAScript 2015, ECMAScript 2016, TypeScript and Angular 2.
If you want to practice your skills after completing the presentation, take a look at this exercise: https://github.com/mgechev/angular2-github-app-bootstrap

The slides and the exercise are based on Angular 2 alpha28.

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

June 29, 2015
Tweet

Transcript

  1. Introduction to Angular 2 Minko Gechev github.com/mgechev twitter.com/mgechev blog.mgechev.com

  2. Minko Gechev github.com/mgechev twitter.com/mgechev { "job": "Freelancer", "hobbies": [ "open

    source", "blogging", "teaching", "sports" ], "communityEvents": [ "SofiaJS", "BeerJS Sofia" ] }
  3. Angular 2 (quick recap)

  4. – angular.io “Angular is a development platform for building mobile

    and desktop web applications”
  5. Client Server

  6. Client Server GET

  7. Client Server GET

  8. Client Server GET POST

  9. Client Server GET POST

  10. Client Server GET POST Angular 2

  11. Written in AtSc…TypeScript?

  12. None
  13. TypeScript Superset of JavaScript • Strongly & statically typed •

    By Microsoft
  14. Component based

  15. None
  16. None
  17. List Post Vote Label Button Label

  18. List Post Vote Label Button Label

  19. One-way Data Flow

  20. None
  21. None
  22. A B C D E F

  23. Based on the Web Standards

  24. Web Components

  25. New Router

  26. Angular 1 Router

  27. None
  28. Real Modules

  29. Angular 1 Modules

  30. None
  31. No More $scope.$apply

  32. None
  33. Just to make sure we’re on the same page…

  34. ECMAScript 5 Property Descriptors (quick recap)

  35. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  36. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  37. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  38. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  39. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  40. ECMAScript 5 Property Descriptor Object.defineProperty(obj, 'name', { set: function (val)

    { console.log('Setting the value'); this._name = val; }, get: function (val) { console.log('Getting the value'); return this._name; } }); obj.name = 'foo'; console.log(obj.name); // Setting the value // Getting the value // foo
  41. ECMAScript 2015 (quick recap)

  42. “ES2015 classes are a simple sugar over the prototype- based

    OO pattern…” Classes
  43. ECMAScript 6 Classes class Developer extends Person { constructor(name, language)

    { super(name); this._language = language; } get language() { return this._language; } set language(language) { this.doSomethingElse(); this._language = language; } talk() { super.talk(); console.log(`I'm a professional ${this.language} developer`); } }
  44. ECMAScript 6 Classes class Developer extends Person { constructor(name, language)

    { super(name); this._language = language; } get language() { return this._language; } set language(language) { this.doSomethingElse(); this._language = language; } talk() { super.talk(); console.log(`I'm a professional ${this.language} developer`); } }
  45. ECMAScript 6 Classes class Developer extends Person { constructor(name, language)

    { super(name); this._language = language; } get language() { return this._language; } set language(language) { this.doSomethingElse(); this._language = language; } talk() { super.talk(); console.log(`I'm a professional ${this.language} developer`); } }
  46. ECMAScript 6 Classes class Developer extends Person { constructor(name, language)

    { super(name); this._language = language; } get language() { return this._language; } set language(language) { this.doSomethingElse(); this._language = language; } talk() { super.talk(); console.log(`I'm a professional ${this.language} developer`); } }
  47. ECMAScript 6 Classes class Developer extends Person { constructor(name, language)

    { super(name); this._language = language; } get language() { return this._language; } set language(language) { this.doSomethingElse(); this._language = language; } talk() { super.talk(); console.log(`I'm a professional ${this.language} developer`); } }
  48. “Arrows are a function shorthand using the => syntax…Unlike functions,

    arrows share the same lexical this as their surrounding code.” Arrow Functions
  49. “Arrows are a function shorthand using the => syntax…Unlike functions,

    arrows share the same lexical this as their surrounding code.” Arrow Functions
  50. Arrow Functions [1, 2, 3, 4].reduce((a, c) => a +

    c, 0); [1, 2, 3].map(c => c + 1); function Developer() { this.talk = () => { console.log(`I speak ${this._language} fluently`); }; }
  51. Arrow Functions [1, 2, 3, 4].reduce((a, c) => a +

    c, 0); [1, 2, 3].map(c => c + 1); function Developer() { this.talk = () => { console.log(`I speak ${this._language} fluently`); }; }
  52. Arrow Functions [1, 2, 3, 4].reduce((a, c) => a +

    c, 0); [1, 2, 3].map(c => c + 1); function Developer() { this.talk = () => { console.log(`I speak ${this._language} fluently`); }; }
  53. Arrow Functions [1, 2, 3, 4].reduce((a, c) => a +

    c, 0); [1, 2, 3].map(c => c + 1); function Developer() { this.talk = () => { console.log(`I speak ${this._language} fluently`); }; }
  54. Let “Let allows block scope definition of variables.”

  55. Functional lexical scope for (var i = 0; i <

    10; i += 1) { var foo = i + 1; } console.log(typeof i); // number console.log(typeof foo); // number
  56. Local lexical scope for (let i = 0; i <

    10; i += 1) { let foo = i + 1; } console.log(typeof i); // undefined console.log(typeof foo); // undefined
  57. Promises “Promises could be used instead of callbacks for handling

    asynchronous actions. They are used as containers for resolving “future” values.”
  58. Promises fetch('/data.json') .then(data => { return data.json(); }, error =>

    { console.error(error); }) .then(json => doStuff(json));
  59. Promises Promise.all([ fetch('/a.json'), fetch('/b.json'), fetch('/c.json') ]).then(res => { return Promise.all([

    res[0].text(), res[1].text(), res[2].text() ]); }) .then(data => { console.log(data.join('')); });
  60. Modules “Language-level support for modules for component definition.”

  61. Modules “Language-level support for modules for component definition.”

  62. Modules // Developer.js export default class Developer { // ...

    } // app.js import Developer from './Developer'; // Math.js let log2 = Math.log2.bind(Math); let sin = Math.sin.bind(Math); export { log2, sin }; // app.js import * as math from './Math'; math.log2(10);
  63. Modules // Developer.js export default class Developer { // ...

    } // app.js import Developer from './Developer'; // Math.js let log2 = Math.log2.bind(Math); let sin = Math.sin.bind(Math); export { log2, sin }; // app.js import * as math from './Math'; math.log2(10);
  64. Modules // Developer.js export default class Developer { // ...

    } // app.js import Developer from './Developer'; // Math.js let log2 = Math.log2.bind(Math); let sin = Math.sin.bind(Math); export { log2, // same as log2: log2 sin }; // app.js import * as math from './Math'; math.log2(10);
  65. Module Loaders “Additional imperative API to the declarative module syntax,

    which allows you to programmatically work with modules and configure the module loading.”
  66. Module Loaders System.import('/app') .then(m => m.init(), e => console.error(e));

  67. ECMAScript 2016

  68. – github.com/wycats/javascript-decorators “Decorators make it possible to annotate and modify

    classes and properties at design time.” ES2016 Decorators
  69. Decorators @Aspect class LoggingAspect { @Before(/.*/, /.*/) before(args) { console.log(`${args.name}

    called with ${args.args}`); } @After(/.*/, /.*/) after(args) { console.log(`${args.name} execution completed`); } @OnThow(/.*/, /.*/) errorHandler(args) { console.error(`${args.name} threw an error`, args.error); } }
  70. Decorators @Aspect class LoggingAspect { @Before(/.*/, /.*/) before(args) { console.log(`${args.name}

    called with ${args.args}`); } @After(/.*/, /.*/) after(args) { console.log(`${args.name} execution completed`); } @OnThow(/.*/, /.*/) errorHandler(args) { console.error(`${args.name} threw an error`, args.error); } }
  71. TypeScript (quick recap)

  72. None
  73. None
  74. TypeScript • Superset of ECMAScript • Allows optional static typing

    • Better support by text editors/IDEs • Reduce bugs • Faster code
  75. None
  76. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  77. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  78. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  79. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  80. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  81. TypeScript Class class Developer { age:string; constructor(private languages:string[], private name:string,

    age) { this.age = (age).toString(2); } private codeInPHP() { // body } public writeSoftware() { // body } public coffeeBreak(nextTask:{(name:string):void}) { setTimeout(nextTask, 1000); } }
  82. Interfaces

  83. Interfaces interface A { foo():void; bar(a:number):string; } interface B {

    baz(a:string):string } interface C extends A, B { } class D implements C { // ... }
  84. Interfaces interface A { foo():void; bar(a:number):string; } interface B {

    baz(a:string):string } interface C extends A, B { } class D implements C { // ... }
  85. Interfaces interface A { foo():void; bar(a:number):string; } interface B {

    baz(a:string):string } interface C extends A, B { } class D implements C { // ... }
  86. Interfaces interface A { foo():void; bar(a:number):string; } interface B {

    baz(a:string):string } interface C extends A, B { } class D implements C { // ... }
  87. Type Inference

  88. Type Inference let answer = 42; let callback:{(a:number, ...nums:any[]):number}; callback

    = (a, nums) => nums.reduce((a, c) => a + c, a); let foo; foo = 42;
  89. Type Inference let answer = 42; let callback:{(a:number, ...nums:any[]):number}; callback

    = (a, nums) => nums.reduce((a, c) => a + c, a); let foo; foo = 42;
  90. Type Inference let answer = 42; let callback:{(a:number, ...nums:any[]):number}; callback

    = (a, nums) => nums.reduce((a, c) => a + c, a); let foo; foo = 42;
  91. Type Inference let answer = 42; let callback:{(a:number, ...nums:any[]):number}; callback

    = (a, nums) => nums.reduce((a, c) => a + c, a); let foo; foo = 42;
  92. Adopting Existing Libraries

  93. Ambient Declarations interface JQueryStatic { /** * Perform an asynchronous

    HTTP (Ajax) request. * * @param settings A set of key/value pairs that … */ ajax(settings: JQueryAjaxSettings): JQueryXHR; /** * Perform an asynchronous HTTP (Ajax) request. * * @param url A string containing the URL to which the request is sent. * @param settings A set of key/value pairs that… */ ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR; // ... } declare var jQuery: JQueryStatic; declare var $: JQueryStatic;
  94. Add Type Definition Reference /// <reference path="jQuery.d.ts" /> let elems

    = $('.elems'); //...
  95. None
  96. Main Concepts

  97. Annotations

  98. Annotations import {Component, View, NgFor} from 'angular2/angular2'; import {Home} from

    './components/home/home'; import {About} from './components/about/about'; @Component({ selector: 'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html?v=<%= VERSION %>', directives: [RouterOutlet, RouterLink] }) class App {}
  99. Annotations import {Component, View, NgFor} from 'angular2/angular2'; import {Home} from

    './components/home/home'; import {About} from './components/about/about'; @Component({ selector: 'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html?v=<%= VERSION %>', directives: [RouterOutlet, RouterLink] }) class App {}
  100. None
  101. ES7 Decorators != Angular Annotations

  102. ES7 Decorators != Angular Annotations Annotations are implemented with decorators

  103. Directives

  104. Directives A few directives… • ng-for • ng-if • ng-switch

    • ng-style
  105. ng-if <user-details *ng-if="selectedUser !== null"> </user-details>

  106. ng-for <tr *ng-for="#user of users"> <td> {{user}} </td> </tr>

  107. Directives Components

  108. UserDetails.ts import {Component, View} from ‘angular2/angular2'; import {NgIf} from 'angular2/angular2';

    @Component({ selector: 'user-details', properties: ['user'] }) @View({ templateUrl: ‘./user-details.html’, directives: [NgIf] }) export class UserDetails { user:any; constructor() {} }
  109. UserDetails.ts import {Component, View} from ‘angular2/angular2'; import {NgIf} from 'angular2/angular2';

    @Component({ selector: 'user-details', properties: ['user'] }) @View({ templateUrl: ‘./user-details.html’, directives: [NgIf] }) export class UserDetails { user:any; constructor() {} }
  110. UserDetails.ts import {Component, View} from ‘angular2/angular2'; import {NgIf} from 'angular2/angular2';

    @Component({ selector: 'user-details', properties: ['user'] }) @View({ templateUrl: ‘./user-details.html’, directives: [NgIf] }) export class UserDetails { user:any; constructor() {} }
  111. Services

  112. None
  113. Service.ts @Injectable() export class Http { constructor(private _backend: XHRBackend, private

    _defaultOptions: BaseRequestOptions) {} get(url: string, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({method: RequestMethods.GET}))); } post(url: string, body: any, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({body: body, method: RequestMethods.POST}))); } }
  114. Service.ts @Injectable() export class Http { constructor(private _backend: XHRBackend, private

    _defaultOptions: BaseRequestOptions) {} get(url: string, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({method: RequestMethods.GET}))); } post(url: string, body: any, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({body: body, method: RequestMethods.POST}))); } }
  115. Service.ts @Injectable() export class Http { constructor(private _backend: XHRBackend, private

    _defaultOptions: BaseRequestOptions) {} get(url: string, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({method: RequestMethods.GET}))); } post(url: string, body: any, options?: IRequestOptions) { return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options) .merge({body: body, method: RequestMethods.POST}))); } }
  116. Change Detection

  117. A B E D C

  118. A B E D C

  119. A B E D C

  120. A B E D C

  121. Detection mechanisms • No front-end state • Event based •

    Difference based • Dirty checking
  122. No front-end state

  123. Server

  124. Server

  125. Server

  126. Event Based

  127. A B E D C

  128. A B E D C Change Event

  129. A B E D C

  130. Difference Based

  131. A B E D C Page Body Header Content Sidebar

  132. A B E D C Page Body Header Content Sidebar

  133. A B E D C Page Body Header Content Sidebar

    Page Body Header Content Sidebar
  134. A B E D C

  135. A B E D C Page Body Header Content Sidebar

  136. Dirty Checking

  137. <section> <span>{{user.name}}</span> </section>

  138. addWatcher({ last: eval('user.name'); exp: 'user.name', fn: result => { element.text(result);

    } }); <section> <span>{{user.name}}</span> </section>
  139. Dirty Checking let digest = (component) => { let dirty,

    watcher, current, i; let { watchers, children } = component; do { dirty = false; for (i = 0; i < watchers.length; i++) { watcher = watchers[i]; current = eval(watcher.exp); if (!equals(watcher.last, current)) { watcher.last = clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < children.length; i++) digest(children[i]); };
  140. Dirty Checking let digest = (component) => { let dirty,

    watcher, current, i; let { watchers, children } = component; do { dirty = false; for (i = 0; i < watchers.length; i++) { watcher = watchers[i]; current = eval(watcher.exp); if (!equals(watcher.last, current)) { watcher.last = clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < children.length; i++) digest(children[i]); };
  141. Dirty Checking let digest = (component) => { let dirty,

    watcher, current, i; let { watchers, children } = component; do { dirty = false; for (i = 0; i < watchers.length; i++) { watcher = watchers[i]; current = eval(watcher.exp); if (!equals(watcher.last, current)) { watcher.last = clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < children.length; i++) digest(children[i]); };
  142. Dirty Checking let digest = (component) => { let dirty,

    watcher, current, i; let { watchers, children } = component; do { dirty = false; for (i = 0; i < watchers.length; i++) { watcher = watchers[i]; current = eval(watcher.exp); if (!equals(watcher.last, current)) { watcher.last = clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < children.length; i++) digest(children[i]); };
  143. Dirty Checking let digest = (component) => { let dirty,

    watcher, current, i; let { watchers, children } = component; do { dirty = false; for (i = 0; i < watchers.length; i++) { watcher = watchers[i]; current = eval(watcher.exp); if (!equals(watcher.last, current)) { watcher.last = clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < children.length; i++) digest(children[i]); };
  144. List Post Vote Label Button Label

  145. List Post Vote Label Button Label Click!

  146. List Post Vote Label Button Label

  147. None
  148. Not exactly…

  149. List Post Vote Label Button Label Click!

  150. List Post Vote Label Button Label

  151. What about two-way data binding?

  152. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val; });
  153. What if…

  154. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val; });
  155. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val + 1; });
  156. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val + 1; });
  157. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val + 1; });
  158. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val + 1; });
  159. Two-way binding input.onchange = () => { component[property] = input.value;

    digest(); }; addWatcher(property, (val) => { input.value = val + 1; });
  160. No two-way data binding

  161. Zone

  162. The dirty checking should be run on any change

  163. Zone monkey patches standard async APIs

  164. Zone helps us keep track of the changes

  165. unbuffer x | unbuffer command done find echo echo Pipes

    find ls ls command # process each line, using variables as parsed into $var1, $var2, etc # (note that this may be a subshell: var1, var2 etc will not be available # after the while loop terminates; some shells, such as zsh and newer # versions of Korn shell, process the commands to the left of the pipe # operator in a subshell) done
  166. Pipe Interface export interface Pipe { supports(obj): boolean; onDestroy(): void;

    transform(value: any): any; }
  167. Pipes A way of extending the change detection • Format

    data • Perform smarter change detection
  168. Dynamic Change Detector _pipeCheck(proto: ProtoRecord, throwOnChange: boolean) { // ...

    var currValue = pipe.transform(context); if (!isSame(prevValue, currValue)) { // ... this._setChanged(proto, true); return change; } else { // ... this._setChanged(proto, true); return null; } } else { // ... this._setChanged(proto, false); return null; } }
  169. Pipes A few examples: • IterableChanges • PromisePipe • UpperCasePipe

    • ObservablePipe • JsonPipe
  170. PromisePipe <div> {{ description | async }} </div>

  171. UpperCasePipe <div> {{ "foo" | UpperCase }} </div>

  172. IterableDiff (foreach.js) set ngForOf(value: any) { this._ngForOf = value; this._pipe

    = this.pipes .get("iterableDiff",…); }
  173. Dependency Injection

  174. Router

  175. Router import {RouteConfig, RouterOutlet, RouterLink, routerInjectables} from 'angular2/router'; @Component({ selector:

    'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html', directives: [RouterOutlet, RouterLink] }) class App {} bootstrap(App, [routerInjectables]);
  176. Router import {RouteConfig, RouterOutlet, RouterLink, routerInjectables} from 'angular2/router'; @Component({ selector:

    'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html', directives: [RouterOutlet, RouterLink] }) class App {} bootstrap(App, [routerInjectables]);
  177. Router import {RouteConfig, RouterOutlet, RouterLink, routerInjectables} from 'angular2/router'; @Component({ selector:

    'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html', directives: [RouterOutlet, RouterLink] }) class App {} bootstrap(App, [routerInjectables]);
  178. Router import {RouteConfig, RouterOutlet, RouterLink, routerInjectables} from 'angular2/router'; @Component({ selector:

    'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html', directives: [RouterOutlet, RouterLink] }) class App {} bootstrap(App, [routerInjectables]);
  179. Router import {RouteConfig, RouterOutlet, RouterLink, routerInjectables} from 'angular2/router'; @Component({ selector:

    'app' }) @RouteConfig([ { path: '/', component: Home, as: 'home' }, { path: '/about', component: About, as: 'about' } ]) @View({ templateUrl: './app.html', directives: [RouterOutlet, RouterLink] }) class App {} bootstrap(App, [routerInjectables]);
  180. Router Directives <section class="sample-app-content"> <nav> <a router-link="home">Home</a> <a router-link="about">About</a> </nav>

    <router-outlet></router-outlet> </section>
  181. Router Directives <section class="sample-app-content"> <nav> <a router-link="home">Home</a> <a router-link="about">About</a> </nav>

    <router-outlet></router-outlet> </section>
  182. Router Directives <section class="sample-app-content"> <nav> <a router-link="home">Home</a> <a router-link="about">About</a> </nav>

    <router-outlet></router-outlet> </section>
  183. References • Angular 2 Official Website • Change And Its

    Detection In JavaScript Frameworks • Angular2 - First Impressions • ng-conf 2015 Keynote 2 - Misko Hevery and Rado Kirov • angular2-seed • angular2-education
  184. Thank you! github.com/mgechev twitter.com/mgechev blog.mgechev.com