index.html and app.ts, both at the root of the project:
-
- pre.prettyprint.lang-bash
- code.
- touch index.html app.ts
-
- p Your app directory should look something like:
- pre.prettyprint.lang-bash
- code.
- app.ts
- index.html
- package.json
- node_modules/
- └── ...
-
- p.
- Because the component is an addition to the core, you must install Angular's Component Router into your app.
- When using the angular2.dev.js bundle you have include the additional router.dev.js bundle.
-
- p.
- Add Angular and Component Router into your app by adding the relevant <script> tags into your
- index.html:
-
- //ANGULAR 1
- pre.prettyprint.lang-html.is-angular1.is-hidden
- code.
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <base href="/">
- <title>My app</title>
- </head>
- <body ng-app="myApp" ng-controller="AppController as app">
- <div ng-outlet></div>
- <script src="/node_modules/angular/angular.js"></script>
- <script src="/dist/router.es5.js"></script>
- <script src="/app/app.js"></script>
- </body>
- </html>
-
- pre.prettyprint.lang-html.is-angular2
- code.
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <base href="/">
- <title>My app</title>
- </head>
- <body ng-app="myApp" ng-controller="AppController as app">
- <script src="https://jspm.io/system@0.16.js"></script>
- <script src="https://code.angularjs.org/2.0.0-alpha.21/angular2.dev.js"></script>
- <script src="https://code.angularjs.org/2.0.0-alpha.21/router.dev.js"></script>
- <script>
- System.import('main');
- </script>
- </body>
- </html>
-
-
- p.is-angular2.
- Then you can add the router into your app by importing the Router module in your app.ts file:
-
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- import {Component, View, bootstrap} from 'angular2/angular2';
- import {Router, RouterOutlet, RouterLink} from 'angular2/router';
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- // self-executing bundle deploys angular APIs on the window object.
- window.angular;
- // the router APIs are part of the router sub-object.
- window.angular.router;
-
- p.is-angular1.is-hidden.
- This is a pretty typical angular app, except the ng-outlet directive. ng-outlet is like
- ng-view; it's a placeholder for part of your app loaded dynamically based on the route configuration.
-
- p.is-angular2.
- This is the same as you've seen in the rest of Angular 2, except the router-outlet directive.
- router-outlet is a placeholder for part of your app loaded dynamically based on the route configuration.
-
- p So how do we configure the app? Let's open app.ts and find out. Add this to the file:
-
- //ANGULAR 1
- pre.prettyprint.lang-javascript.is-angular1.is-hidden
- code.
- angular.module('app', ['ngNewRouter'])
- .controller('AppController', ['$router', AppController]);
-
- AppController.$routeConfig = [
- {path: '/', component: 'home' }
- ];
- function AppController ($router) {}
-
- // ANGULAR 2
- .code-box
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- import {Component, View, bootstrap} from 'angular2/angular2';
- import {routerInjectables, RouterOutlet} from 'angular2/router';
-
- import {HomeComp} from './components/home';
-
- @Component({
- selector: 'my-app'
- })
- @View({
- template: '<router-outlet></router-outlet>',
- directives: [RouterOutlet]
- })
- @RouteConfig([
- {path: '/', component: HomeComp }
- ])
- class AppComp {}
-
- bootstrap(AppComp, routerInjectables);
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- var HomeComp = function() {};
- ...
-
- var AppComp = function() {};
- AppComp.annotations = [
- new angular.ComponentAnnotation({
- selector: 'my-app'
- }),
- new angular.ViewAnnotation({
- template: '<router-outlet></router-outlet>',
- directives: [angular.router.RouterOutlet]
- }),
- new angular.router.RouteConfigAnnotation([
- {path: '/', component: HomeComp}
- ])
- ];
-
- angular.bootstrap(AppComp, routerInjectables);
-
-
- p.is-angular1.is-hidden.
- The ngComponentRouter module provides a new service, $router. In the configuration, we map paths
- to components. What's a component? Let's talk about that for a bit.
-
- p.is-angular2.
- The angular2/router module provides routerInjectables, which is an array of all of the services
- you'll need to use the component router in your app.
-
-.l-main-section
- h2#section-map-paths-to-components Map paths to components
-
- //- TODO - Alex - would it make more sense to have some paragraph styles conditionalized like this??
- p.angular1.is-hidden.
- In Angular 1, a "routable component" is a template, plus a controller, plus a router. You can configure how to map
- component names to controllers and templates in the $componentLoader service.
-
- p.
- A component's template can have "outlets," which are holes in the DOM for loading parts of your app based on the
- route configuration and it can ask the DI system for an instance of Router. A component's router tells the component what to put
- inside the outlets based on URL. The configuration maps routes to components for each outlet.
-
- p Let's make a home component that our app can route to:
-
- pre.prettyprint.lang-bash
- code.
- mkdir -p components/home
- touch components/home/home.html components/home/home.js
-
- p This creates our component directory and its corresponding files: a template and a JavaScript component.
-
- p Let's open home.html and add some content:
-
- pre.prettyprint.lang-html
- code.
- <h1>Hello {{home.name}}!</h1>
-
- p.is-angular1.is-hidden.
- Components use the "controller as" syntax, so if we want to access property name of the controller, we
- write the binding as home.name.
-
- p Let's make a controller:
- //ANGULAR 1
- pre.prettyprint.lang-javascript.is-angular1.is-hidden
- code.
- angular.module('app.home', [])
- .controller('HomeController', [function () {
- this.name = 'Friend';
- }]);
-
- // ANGULAR 2
- .code-box
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- @Component({
- selector: 'home-cmp'
- })
- @View({
- template: 'Hello {{name}}'
- })
- export class HomeComponent {
- name:string;
- constructor() {
- this.name = 'Friend';
- }
- }
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function HomeComponent() {
- this.name = 'Friend';
- }
-
- AppComponent.annotations = [
- new angular.ComponentAnnotation({
- selector: 'home-cmp'
- }),
- new angular.ViewAnnotation({
- template: 'Hello {{name}}'
- })
- ];
-
-
- p.is-angular1.is-hidden.
- To wire this up, we need to add a <script> tag to our index.html:
- pre.prettyprint.lang-html
- code.
- ...
- <script src="./components/home/home.js"></script>
-
- //ANGULAR 1
- p.is-angular1.is-hidden.
- And add the controller's module as a dependency to our main module in app.js:
- pre.prettyprint.lang-javascript.is-angular1.is-hidden
- code.
- angular.module('app', ['ngNewRouter', 'app.home'])
- .controller('AppController', ['$router', AppController]);
- // ...
-
- p.
- To wire this up, we need to import the component into the rest of our app.
- // ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- import {HomeComp} from './components/home';
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- // Use your favorite module system / bundler for ES5.
-
-
- p If you load up the app, you should see Hello Friend!
-
-.l-main-section
- h2#section-link-to-routes Link to routes
-
- p Let's add another route and then link to it. This route will have a route parameter, id.
-
- p In app.js:
- //ANGULAR 1
- pre.prettyprint.lang-javascript.is-hidden
- code.
- angular.module('app', ['ngNewRouter'])
- .controller('AppController', ['$router', AppController]);
- AppController.$routeConfig = [
- { path: '/', component: 'home' },
- { path: '/detail/:id', component: 'detail' }
- ];
- function AppController ($router) {}
-
- // ANGULAR 2
- .code-box
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- ...
- @RouteConfig([
- { path: '/', component: HomeComp },
- { path: '/detail/:id', component: DetailComp }
- ])
- class AppComp {}
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- var AppComp = function() {};
- AppComp.annotations = [
- ...
- new angular.router.RouteConfigAnnotation([
- { path: '/', component: HomeComp}
- { path: '/detail/:id', component: DetailComp }
- ])
- ];
-
- angular.bootstrap(AppComp, routerInjectables);
-
-
- p.
- We can link to our detail component using the
- router-linkng-link directive.
- Add this to templateindex.html:
-
- pre.prettyprint.lang-html.is-angular1.is-hidden.
- code.
- <body ng-app="myApp" ng-controller="AppController as app">
- <a ng-link="detail({id: 5})">link to detail</a>
- ...
-
- pre.prettyprint.lang-html.is-angular2
- code.
- <a ng-link="detail({id: 5})">link to detail</a>
-
- p This directive will generate an href and update the browser URL.
-
- p We should also implement our detail component. Let's make these new files:
-
- pre.prettyprint.lang-bash
- code.
- mkdir components/detail
- touch components/detail/detail.html components/detail/detail.ts
-
- p In detail.ts, we implement a controller that uses the id route parameter:
-
- //ANGULAR 1
- pre.prettyprint.lang-javascript.is-hidden
- code.
- angular.module('app.detail', ['ngNewRouter'])
- .controller('DetailController', ['$routeParams', DetailController]);
-
- function DetailController ($routeParams) {
- this.id = $routeParams.id;
- }
-
- // ANGULAR 2
- .code-box
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- @Component({
- selector: 'detail-cmp'
- })
- @View({
- template: 'User ID: {{id}}'
- })
- export class DetailComp {
- id: string;
- constructor(routeParams:RouteParams) {
- this.id = routeParams.get('id');
- }
- }
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function DetailComp(routeParams) {
- this.id = routeParams.get('id');
- }
-
- DetailComp.annotations = [
- new angular.ComponentAnnotation({
- selector: 'detail-cmp'
- }),
- new angular.ViewAnnotation({
- template: 'User ID: {{id}}'
- })
- ];
-
- DetailComp.parameters = [[RouteParams]];
-
-
- p.is-angular1.is-hidden.
- And then we can display the id in our template by adding this to detail.html:
-
- pre.prettyprint.lang-html.is-angular1.is-hidden
- code.
- <p>detail {{detail.id}}</p>
-
- p.is-angular1.is-hidden.
- Finally, we'd wire up the controller by adding a script tag and making our app module depend on
- app.detail.
-
-
-.l-main-section
-
- h2#section-configuring-the-router Configuring the Router
-
- p.
- Unlike other routing systems, Component Router maps URLs to components. A router takes an array of pairings like
- this:
-
- //ANGULAR 1
- pre.prettyprint.lang-javascript.is-angular1.is-hidden
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/user', component: 'user' }
- ];
-
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-javascript(data-name="typescript")
- code.
- @Component()
- @View()
- @RouteConfig([
- { path: '/user', component: UserComponent }
- ])
- class MyComp {}
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function MyComp() {};
- MyComp.annotations = [
- new angular.ComponentAnnotation({ ... }),
- new angular.ViewAnnotation({ ... }),
- new angular.router.RouteConfigAnnotation([
- {path: '/', component: UserComponent}
- ])
-
-
- .l-sub-section
- h3#section-sibling-outlets Sibling Outlets
-
-
- p You can configure multiple outlets on the same path like this:
-
- //ANGULAR 1
- .codebox.is-angular1.is-hidden
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/user',
- components: {
- master: 'userList',
- detail: 'user'
- } }
- ];
-
- pre.prettyprint.linenums.lang-html(data-name="html")
- code.
- //HTML
- <div ng-outlet="master"></div>
- <div ng-outlet="detail"></div>
-
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- //TypeScript
- @Component({})
- @View({
- template:
- `<div router-outlet="master"></div>
- <div router-outlet="detail"></div>`,
- directives: [RouterOutlet, RouterLink]
- })
- @RouteConfig({
- path: '/user', components: {
- master: UserListComp,
- detail: UserComp
- }
- })
- class MyComponent {}
-
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function MyComponent() {};
- MyComponent.annotations = [
- new angular.ComponentAnnotation({ ... }),
- new angular.ViewAnnotation({
- template:
- '<div router-outlet="master"></div>' +
- '<div router-outlet="detail"></div>',
- directives: [RouterOutlet]
- }),
- new angular.router.RouteConfigAnnotation([{
- path: '/user', components: {
- master: UserComponent,
- detail: UserComp
- }
- }])
- ];
-
- p You can link to any sibling just as you normally would:
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-html.is-angular1.is-hidden
- code.
- //HTML
- <p>These both link to the same view:</p>
- <a ng-link="userList">link to userList</a>
- <a ng-link="user">link to user component</a>
-
- //ANGULAR 2
- pre.prettyprint.linenums.lang-html.is-angular2
- code.
- //HTML
- <p>These both link to the same view:</p>
- <a router-link="userList">link to userList</a>
- <a router-link="user">link to user component</a>
-
-
- p Or, you can explicitly link to a outlet-component pair like this:
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-html.is-angular1.is-hidden
- code.
- //HTML
- <p>These both link to the same view:</p>
- <a ng-link="master:userList">link to userList</a>
- <a ng-link="detail:user">link to user component</a>
-
- //ANGULAR 2
- pre.prettyprint.linenums.lang-html.is-angular2
- code.
- //HTML
- <p>These both link to the same view:</p>
- <a router-link="master:userList">link to userList</a>
- <a router-link="detail:user">link to user component</a>
-
- .l-sub-section
- h3#section-redirecting-routes Redirecting routes
-
- p You can use `redirectTo` for migrating to a new URL scheme and setting up default routes.
-
- p.
- For example, as specified below, when a user navigates to `/`, the URL changes to `/user` and the outlet
- at that level loads the `user` component.
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-javascript.is-angular1.is-hidden(data-name="es5")
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/', redirectTo: '/user' },
- { path: '/user', component: 'user' }
- ];
- function MyController() {}
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- //TypeScript
- @Component({})
- @View({
- directives: [RouterOutlet]
- })
- @RouteConfig([
- { path: '/', redirectTo: '/user' },
- { path: '/user', component: UserComp }
- ])
- class MyComp {}
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function MyComponent() {};
- MyComponent.annotations = [
- new angular.ComponentAnnotation({ ... }),
- new angular.ViewAnnotation({
- directives: [RouterOutlet]
- }),
- new angular.router.RouteConfigAnnotation([
- { path: '/user', component: UserComp }
- { path: '/', redirectTo: '/user' },
- ])
- ];
-
-
- .l-sub-section
- h3#section-aliases Aliases
-
- p.
- When linking to a route, you normally use the name of the component. You can also specify an alias to use
- instead.
-
- p Consider the following route configuration:
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-javascript.is-angular1.is-hidden(data-name="es5")
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/', component: 'user' }
- ];
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- //TypeScript
- @Component({
- selector: 'my-comp'
- })
- @View({
- directives: [RouterOutlet]
- })
- @RouteConfig([
- { path: '/', component: UserComp }
- ])
- class MyComp {}
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- function MyComp() {};
- MyComp.annotations = [
- new angular.ComponentAnnotation({ ... }),
- new angular.ViewAnnotation({
- directives: [RouterOutlet]
- }),
- new angular.router.RouteConfigAnnotation([
- { path: '/', component: UserComp }
- ])
- ];
-
-
- p We can link to the route in our template with the name of the component:
- //ANGULAR 1
- pre.prettyprint.linenums.lang-html.is-angular1.is-hidden
- code.
- //HTML
- <a ng-link="user">link to user component</a>
-
- //ANGULAR 2
- pre.prettyprint.linenums.lang-html
- code.
- //HTML
- <a router-link="user">link to user component</a>
-
- p Or, we can define an alias myUser like this:
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-javascript.is-angular1.is-hidden(data-name="es5")
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/', component: 'user', as: 'myUser' }
- ];
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- //TypeScript
- @Component()
- @View()
- @RouteConfig([
- { path: '/', component: UserComp, as: 'myUser' }
- ])
- class MyComp {}
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- //ES5
- //TODO: Need Angular 2 ES5 Example here
-
-
- p And refer instead to the alias for the component in our template, with the same end-result:
- //ANGULAR 1
- pre.prettyprint.linenums.lang-html.is-angular1.is-hidden
- code.
- //HTML
- <a ng-link="myUser">link to user component</a>
-
- //ANGULAR 2
- pre.prettyprint.linenums.lang-html
- code.
- //HTML
- <a router-link="myUser">link to user component</a>
-
-
- p.
- This is especially useful when you have sibling components, but want to refer to an entire level of routing in
- your controller. For example:
-
- //ANGULAR 1
- pre.prettyprint.linenums.lang-javascript.is-angular1.is-hidden(data-name="es5")
- code.
- //ES5
- MyController.$routeConfig = [
- { path: '/',
- components: {
- master: 'userList',
- detail: 'user'
- },
- as: 'myUser'
- }
- ];
-
- //ANGULAR 2
- .code-box.is-angular2
- pre.prettyprint.linenums.lang-typescript(data-name="typescript")
- code.
- //TypeScript
- @RouteConfig([
- { path: '/', components:
- { master: UserListComp, detail: UserComp },
- as: 'myUser' }
- ])
- pre.prettyprint.linenums.lang-javascript(data-name="es5")
- code.
- new angular.router.RouteConfigAnnotation([
- { path: '/', components:
- { master: UserListComp, detail: UserComp },
- as: 'myUser' }
- ])
-
-
- //- TODO(btford): expand on this.
- .l-sub-section
- h3#dynamic-configuration Dynamic Configuration
-
- p.is-angular2.
- You can configure dynamic routing by asking the DI system for a Router.
-
- p.is-angular1.is-hidden
- You can configure dynamic routing by making a request for $router.
diff --git a/public/docs/ts/latest/guide/server-communication.jade b/public/docs/ts/latest/guide/server-communication.jade
index f7ef4bcac2..22eaa34fae 100644
--- a/public/docs/ts/latest/guide/server-communication.jade
+++ b/public/docs/ts/latest/guide/server-communication.jade
@@ -22,7 +22,7 @@ include ../../../../_includes/_util-fns
We use the Angular `Http` client to communicate via `XMLHttpRequest (XHR)`.
- We'll illustrate with a mini-version of the [tutorial](tutorial.html)'s "Tour of Heroes" (ToH) application.
+ We'll illustrate with a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
This one gets some heroes from the server, displays them in a list, lets us add new heroes, and save them to the server.
It works like this.
diff --git a/public/docs/ts/latest/testing/index.jade b/public/docs/ts/latest/testing/index.jade
index 0c975db214..293e311ab2 100644
--- a/public/docs/ts/latest/testing/index.jade
+++ b/public/docs/ts/latest/testing/index.jade
@@ -13,6 +13,17 @@ include ../../../../_includes/_util-fns
Our lofty goal in this chapter is make it easy for you to write Angular application tests. If that goal exceeds our reach, we can at least make testing *easier*… and easy enough that you’ll want to write tests for your application.
+.alert.is-important
+ :marked
+ ## Content out-of-date
+
+ These testing chapters were written before the Angular 2 Beta release
+ and are scheduled for significant updates.
+
+ Much of the material remains accurate and relevant but references to
+ specific features of Angular 2 and the Angular 2 testing library
+ may not be correct. Please bear with us.
+:marked
## The Testing Spectrum
Exploring behavior with tests is called “Functional Testing”. There are other important forms of testing too such as acceptance, security, performance, and deployment testing. We concentrate on functional testing in this section on unit testing.
diff --git a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade
index ae563c2de9..418142ec5e 100644
--- a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade
+++ b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade
@@ -16,7 +16,7 @@ code-example(format="linenums" language="html" escape="html").
The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief:
```
- import {Pipe} from 'angular2/angular2';
+ import {Pipe} from 'angular2/core';
@Pipe({ name: 'initCaps' })
export class InitCapsPipe {
@@ -60,14 +60,14 @@ figure.image-display
tried to load Angular and couldn't find it.
code-example(format="" language="html" escape="html").
- GET http://127.0.0.1:8080/src/angular2/angular2 404 (Not Found)
+ GET http://127.0.0.1:8080/src/angular2/core 404 (Not Found)
:marked
We are writing an Angular application afterall and
we were going to need Angular sooner or later. That time has come.
The `InitCapsPiep` clearly depends on Angular as is clear in the first few lines:
```
- import {Pipe} from 'angular2/angular2';
+ import {Pipe} from 'angular2/core';
@Pipe({ name: 'initCaps' })
export class InitCapsPipe {
diff --git a/public/docs/ts/latest/tutorial/dependency-injection.1.jade b/public/docs/ts/latest/tutorial/dependency-injection.1.jade
deleted file mode 100644
index c8fab3f81f..0000000000
--- a/public/docs/ts/latest/tutorial/dependency-injection.1.jade
+++ /dev/null
@@ -1,623 +0,0 @@
-include ../../../../_includes/_util-fns
-:markdown
- Dependency Injection is an important application design pattern.
- Angular has its own Dependency Injection framework and
- we really can't build an Angular application without it.
-
- In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it.
-
-.l-main-section
-:markdown
- ## Why Dependency Injection?
-
- Let's start with the following code.
-
- ```
- class Engine {}
-
- class Tires {}
-
- class Car {
- private engine: Engine;
- private tires: Tires;
-
- constructor() {
- this.engine = new Engine();
- this.tires = new Tires();
- }
- // Method using the engine and tires
- drive() {}
- }
- ```
-
- Our `Car` creates everything it needs inside its constructor.
- What's the problem?
-
- The problem is that our `Car` class is brittle, inflexible, and hard to test.
-
- Our `Car` needs an engine and tires. Instead of asking for them,
- the `Car` constructor creates its own copies by "new-ing" them from
- the very specific classes, `Engine` and `Tires`.
-
- What if the `Engine` class evolves and its constructor requires a parameter?
- Our `Car` is broken and stays broken until we rewrite it along the lines of
- `this.engine = new Engine(theNewParameter)`.
- We didn't care about `Engine` constructor parameters when we first wrote `Car`.
- We don't really care about them now.
- But we'll *have* to start caring because
- when the definion of `Engine` changes, our `Car` class must change.
- That makes `Car` brittle.
-
- What if we want to put a different brand of tires on our `Car`. Too bad.
- We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
-
- Right now each new car gets its own engine. It can't share an engine with other cars.
- While that makes sense for an automobile engine,
- we can think of other dependencies that should be shared ... like the onboard
- wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
- to share services that have been created previously for other consumers.
-
- When we write tests for our `Car` we're at the mercy of its hidden dependencies.
- Is it even possible to create a new `Engine` in a test environment?
- What does `Engine`itself depend upon? What does that dependency depend on?
- Will a new instance of `Engine` make an asynchronous call to the server?
- We certainly don't want that going on during our tests.
-
- What if our `Car` should flash a warning signal when tire pressure is low.
- How do we confirm that if actually does flash a warning
- if we can't swap in low-pressure tires during the test?
-
- We have no control over the car's hidden dependencies.
- When we can't control the dependencies, a class become difficult to test.
-
- How can we make `Car` more robust, more flexible, and more testable?
-
- That's super easy. We probably already know what to do. We change our `Car` constructor to this:
-
- ```
- constructor(engine: Engine, tires: Tires) {
- this.engine = engine;
- this.tires = tires;
- }
- ```
- See what happened? We moved the definition of the dependencies to the constructor.
- Our `Car` class no longer creates an engine or tires.
- It just consumes them.
-
- Now we create a car by passing the engine and tires to the constructor.
- ```
- var car = new Car(new Engine(), new Tires());
- ```
- How cool is that?
- The definition of the engine and tire dependencies are decoupled from the `Car` class itself.
- We can pass in any kind of engine or tires we like, as long as they
- conform to the general API requirements of an engine or tires.
-
- If someone extends the `Engine` class, that is not `Car`'s problem.
-.l-sub-section
- :markdown
- The consumer of `Car` has the problem. The consumer must update the car creation code to
- something like:
- ```
- var car = new Car(new Engine(theNewParameter), new Tires());
- ```
- The critical point is this: `Car` itself did not have to change.
- We'll take care of the consumer's problem soon enough.
-
-:markdown
- The `Car` class is much easier to test because we are in complete control
- of its dependencies.
- We can pass mocks to the constructor that do exactly what we want them to do
- during each test:
- ```
- var car = new Car(new MockEngine(), new MockLowPressureTires());
- ```
-
- **We just learned what Dependency Injection is**.
-
- It's a coding pattern in which a class receives its dependencies from external
- sources rather than creating them itself.
-
- Cool! But what about that poor consumer?
- Anyone who wants a `Car` must now
- create all three parts: the `Car`, `Engine`, and `Tires`.
- The `Car` class shed its problems at the consumer's expense.
- We need something that takes care of assembling these parts for us.
-
- We could write a giant class to do that:
- ```
- class SuperFactory {
- createEngine = () => new Engine();
- createTires = () => new Tires();
- createCar = () => new Car(this.createEngine(), this.createTires());
- }
- ```
- It's not so bad now with only three creation methods.
- But maintaining it will be hairy as the application grows.
- This `SuperFactory` is going to become a huge spider web of
- interdependent factory methods!
-
- Wouldn't it be nice if we could simply list the things we want to build without
- having to define which dependency gets injected into what?
-
- This is where the Dependency Injection Framework comes into play.
- Imagine the framework had something called an `Injector`.
- We register some classes with this `Injector` and it figures out how to create them.
-
- When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go.
- ```
- function main() {
- var injector = new Injector([Car, Engine, Tires, Logger]);
- var car = injector.get(Car);
- car.drive();
- }
- ```
- Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
- The consumer knows nothing about creating a `Car`.
- We don't have a gigantic factory class to maintain.
- Both `Car` and consumer simply ask for what they need and the `Injector` delivers.
-
- This is what a **Dependency InjectionFramework** is all about.
-
- Now that we know what Dependency Injection is and appreciate its benefits,
- let's see how it is implemented in Angular.
-
-.l-main-section
-:markdown
- ## Angular Dependency Injection
-
- Angular ships with its own Dependency Injection framework. This framework can also be used
- as a standalone module by other applications and frameworks.
-
- That sounds nice. What does it do for us when building components in Angular?
- Let's see, one step at a time.
-
- We'll begin with a simplified version of the `HeroesComponent`
- that we built in the [The Tour of Heroes](../tutorial/).
- ```
- import {Component} from 'angular2/angular2';
- import {Hero} from './hero';
- import {HEROES} from './mock-heroes';
-
- @Component({
- selector: 'my-heroes'
- templateUrl: 'app/heroes.component.html'
- })
- export class HeroesComponent {
-
- heroes: Hero[] = HEROES;
-
- }
- ```
- It assigns a list of mocked heroes to its `heroes` property for binding within the template.
- Pretty straight forward.
-
- Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component.
- That works in the early stages of development but it's far from ideal.
- As soon as we try to test this component or want to get our heroes data from a remote server,
- we'll have to change this component's implementation of `heroes` and
- fix every other use of the `HEROES` mock data.
-
- Let's make a service that hides how we get Hero data.
-.l-sub-section
- :markdown
- Write this service in its own file. See [this note](#forward-ref) to understand why.
-:markdown
- ```
- import {Hero} from './hero';
- import {HEROES} from './mock-heroes';
-
- class HeroService {
-
- heroes: Hero[];
-
- constructor() {
- this.heroes = HEROES;
- }
-
- getHeroes() {
- return this.heroes;
- }
- }
- ```
- Our `HeroService` exposes a `getHeroes()` method that returns
- the same mock data as before but none of its consumers need to know that.
-
- A service is nothing more than a class in Angular 2.
- It remains nothing more than a class until we register it with
- the Angular injector.
-
- ### Configuring the Injector
-
- We don't have to create the injector.
-
- Angular creates an application-wide injector for us during the bootstrap process.
- ```
- bootstrap(HeroesComponent);
- ```
-
- Let’s configure the injector at the same time that we bootstrap by adding
- our `HeroService` to an array in the second argument.
- We'll explain that array when we talk about [providers](#providers) later in this chapter.
- ```
- bootstrap(AppComponent, [HeroService]);
- ```
- That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application.
-
- ### Preparing the `HeroesComponent` for injection
-
- The `HeroesComponent` should get its heroes from this service.
- Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained
- earlier](#ctor-injection)".
-
- ```
- constructor(heroService: HeroService) {
- this.heroes = heroService.getHeroes();
- }
- ```
-
-.l-sub-section
- :markdown
- Adding a parameter to the constructor isn't all that's happening here.
-
- We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`.
- The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
-
- When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata
- into the generated JavaScript code. Within that metadata lurks the information that
- associates the `heroService` parameter with the `HeroService` class.
-
- That's how the Angular injector will know to inject an instance of the `HeroService` when it
- creates a new `HeroesComponent`.
-:markdown
- ### Creating the `HeroesComponent` with the injector (implicitly)
- When we introduced the idea of an injector above, we showed how to create
- a new `Car` with that injector.
- ```
- var car = injector.get(Car);
- ```
- Search the entire Tour of Heroes source. We won't find a single line like
- ```
- var hc = injector.get(HeroesComponent);
- ```
- We *could* write code like that if we wanted to. We just don't have to.
- Angular does that for us when it renders a `HeroesComponent`
- whether we ask for it in an HTML template ...
- ```
-