chore: remove dart remains (#3468)

This commit is contained in:
Jesús Rodríguez 2017-03-31 01:13:42 +02:00 committed by Ward Bell
parent 93d3db1fd4
commit f7c89d07b1
29 changed files with 1378 additions and 1589 deletions

View File

@ -36,5 +36,4 @@ script(src="/resources/js/directives/code-pane.js")
script(src="/resources/js/directives/code-example.js") script(src="/resources/js/directives/code-example.js")
script(src="/resources/js/directives/if-docs.js") script(src="/resources/js/directives/if-docs.js")
script(src="/resources/js/directives/live-example.js") script(src="/resources/js/directives/live-example.js")
script(src="/resources/js/directives/ngio-ex-path.js") script(src="/resources/js/directives/scroll-y-offset-element.js")
script(src="/resources/js/directives/scroll-y-offset-element.js")

View File

@ -283,9 +283,9 @@ a#rollup-plugins
Luckily, there is a Rollup plugin that modifies _RxJs_ Luckily, there is a Rollup plugin that modifies _RxJs_
to use the ES `import` and `export` statements that Rollup requires. to use the ES `import` and `export` statements that Rollup requires.
Rollup then preserves the parts of `RxJS` referenced by the application Rollup then preserves the parts of `RxJS` referenced by the application
in the final bundle. Using it is straigthforward. Add the following to in the final bundle. Using it is straigthforward. Add the following to
the `plugins` !{_array} in `rollup-config.js`: the `plugins` array in `rollup-config.js`:
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.') +makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
@ -293,8 +293,8 @@ a#rollup-plugins
*Minification* *Minification*
Rollup tree shaking reduces code size considerably. Minification makes it smaller still. Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code. This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
Add the following to the `plugins` !{_array}: Add the following to the `plugins` array:
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.') +makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')

View File

@ -1,13 +1,6 @@
block includes block includes
include _util-fns include _util-fns
//- current.path = ['docs', lang, 'latest', ...]
- var lang = current.path[1]
- var docsPath='/' + current.path[0]
- var docsLatest='/' + current.path.slice(0,3).join('/');
- var _at = lang === 'js' ? '' : '@'
- var _decoratorLink = '<a href="#' + _decorator + '">' + _decorator + '</a>'
:marked :marked
Angular has its own vocabulary. Angular has its own vocabulary.
Most Angular terms are common English words Most Angular terms are common English words
@ -29,22 +22,21 @@ a#aot
.l-sub-section .l-sub-section
:marked :marked
You can compile Angular applications at build time. You can compile Angular applications at build time.
By compiling your application<span if-docs="ts"> using the compiler-cli, `ngc`</span>, you can bootstrap directly By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
to a<span if-docs="ts"> module</span> factory, meaning you don't need to include the Angular compiler in your JavaScript bundle. to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
Ahead-of-time compiled applications also benefit from decreased load time and increased performance. Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
+ifDocsFor('ts') :marked
## Angular module
.l-sub-section
:marked :marked
## Angular module Helps you organize an application into cohesive blocks of functionality.
.l-sub-section An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
:marked
Helps you organize an application into cohesive blocks of functionality.
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
Every Angular application has an application root-module class. By convention, the class is Every Angular application has an application root-module class. By convention, the class is
called `AppModule` and resides in a file named `app.module.ts`. called `AppModule` and resides in a file named `app.module.ts`.
For details and examples, see the [Angular Modules (NgModule)](!{docsLatest}/guide/ngmodule.html) page. For details and examples, see the [Angular Modules (NgModule)](./ngmodule.html) page.
:marked :marked
## Annotation ## Annotation
@ -64,50 +56,49 @@ a#attribute-directives
For example, you can use the `ngClass` directive to add and remove CSS class names. For example, you can use the `ngClass` directive to add and remove CSS class names.
Learn about them in the [_Attribute Directives_](!{docsLatest}/guide/attribute-directives.html) guide. Learn about them in the [_Attribute Directives_](./attribute-directives.html) guide.
.l-main-section#B .l-main-section#B
+ifDocsFor('ts|js') :marked
## Barrel
.l-sub-section
:marked :marked
## Barrel A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
.l-sub-section The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
:marked
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
For example, imagine three ES2015 modules in a `heroes` folder: For example, imagine three ES2015 modules in a `heroes` folder:
code-example. code-example.
// heroes/hero.component.ts // heroes/hero.component.ts
export class HeroComponent {} export class HeroComponent {}
// heroes/hero.model.ts // heroes/hero.model.ts
export class Hero {} export class Hero {}
// heroes/hero.service.ts // heroes/hero.service.ts
export class HeroService {} export class HeroService {}
:marked :marked
Without a barrel, a consumer needs three import statements: Without a barrel, a consumer needs three import statements:
code-example. code-example.
import { HeroComponent } from '../heroes/hero.component.ts'; import { HeroComponent } from '../heroes/hero.component.ts';
import { Hero } from '../heroes/hero.model.ts'; import { Hero } from '../heroes/hero.model.ts';
import { HeroService } from '../heroes/hero.service.ts'; import { HeroService } from '../heroes/hero.service.ts';
:marked :marked
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items: You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
code-example. code-example.
export * from './hero.model.ts'; // re-export all of its exports export * from './hero.model.ts'; // re-export all of its exports
export * from './hero.service.ts'; // re-export all of its exports export * from './hero.service.ts'; // re-export all of its exports
export { HeroComponent } from './hero.component.ts'; // re-export the named thing export { HeroComponent } from './hero.component.ts'; // re-export the named thing
:marked :marked
Now a consumer can import what it needs from the barrel. Now a consumer can import what it needs from the barrel.
code-example. code-example.
import { Hero, HeroService } from '../heroes'; // index is implied import { Hero, HeroService } from '../heroes'; // index is implied
:marked :marked
The Angular [scoped packages](#scoped-package) each have a barrel named `index`. The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
.alert.is-important .alert.is-important
:marked :marked
You can often achieve the same result using [Angular modules](#angular-module) instead. You can often achieve the same result using [Angular modules](#angular-module) instead.
:marked :marked
## Binding ## Binding
@ -126,7 +117,7 @@ a#attribute-directives
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`). You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
Bootstrapping identifies an application's top level "root" [component](#component), Bootstrapping identifies an application's top level "root" [component](#component),
which is the first component that is loaded for the application. which is the first component that is loaded for the application.
For more information, see the [Setup](!{docsLatest}/guide/setup.html) page. For more information, see the [Setup](./setup.html) page.
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root. You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
@ -153,7 +144,7 @@ a#component
The *component* is one of the most important building blocks in the Angular system. The *component* is one of the most important building blocks in the Angular system.
It is, in fact, an Angular [directive](#directive) with a companion [template](#template). It is, in fact, an Angular [directive](#directive) with a companion [template](#template).
Apply the `!{_at}Component` !{_decoratorLink} to Apply the `@Component` [decorator](#decorator) to
the component class, thereby attaching to the class the essential component metadata the component class, thereby attaching to the class the essential component metadata
that Angular needs to create a component instance and render the component with its template that Angular needs to create a component instance and render the component with its template
as a view. as a view.
@ -169,8 +160,8 @@ a#component
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`). The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
This form is also known as kebab-case. This form is also known as kebab-case.
[Directive](#directive) selectors (like `my-app`) <span if-docs="ts">and [Directive](#directive) selectors (like `my-app`) and
the root of filenames (such as `hero-list.component.ts`)</span> are often the root of filenames (such as `hero-list.component.ts`) are often
spelled in dash-case. spelled in dash-case.
:marked :marked
@ -189,14 +180,14 @@ a#component
Angular has a rich data-binding framework with a variety of data-binding Angular has a rich data-binding framework with a variety of data-binding
operations and supporting declaration syntax. operations and supporting declaration syntax.
Read about the following forms of binding in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page: Read about the following forms of binding in the [Template Syntax](./template-syntax.html) page:
* [Interpolation](!{docsLatest}/guide/template-syntax.html#interpolation). * [Interpolation](./template-syntax.html#interpolation).
* [Property binding](!{docsLatest}/guide/template-syntax.html#property-binding). * [Property binding](./template-syntax.html#property-binding).
* [Event binding](!{docsLatest}/guide/template-syntax.html#event-binding). * [Event binding](./template-syntax.html#event-binding).
* [Attribute binding](!{docsLatest}/guide/template-syntax.html#attribute-binding). * [Attribute binding](./template-syntax.html#attribute-binding).
* [Class binding](!{docsLatest}/guide/template-syntax.html#class-binding). * [Class binding](./template-syntax.html#class-binding).
* [Style binding](!{docsLatest}/guide/template-syntax.html#style-binding). * [Style binding](./template-syntax.html#style-binding).
* [Two-way data binding with ngModel](!{docsLatest}/guide/template-syntax.html#ngModel). * [Two-way data binding with ngModel](./template-syntax.html#ngModel).
a#decorator a#decorator
a#decoration a#decoration
@ -280,7 +271,7 @@ a#decoration
Angular registers some of its own providers with every injector. Angular registers some of its own providers with every injector.
You can register your own providers. You can register your own providers.
Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page. Read more in the [Dependency Injection](./dependency-injection.html) page.
a#directive a#directive
a#directives a#directives
@ -370,11 +361,11 @@ a#H
.l-sub-section .l-sub-section
:marked :marked
A directive property that can be the *target* of a A directive property that can be the *target* of a
[property binding](!{docsLatest}/guide/template-syntax.html#property-binding) (explained in detail in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page). [property binding](./template-syntax.html#property-binding) (explained in detail in the [Template Syntax](./template-syntax.html) page).
Data values flow *into* this property from the data source identified Data values flow *into* this property from the data source identified
in the template expression to the right of the equal sign. in the template expression to the right of the equal sign.
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page. See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
:marked :marked
## Interpolation ## Interpolation
@ -390,8 +381,8 @@ a#H
<label>My current hero is {{hero.name}}</label> <label>My current hero is {{hero.name}}</label>
:marked :marked
Read more about [interpolation](!{docsLatest}/guide/template-syntax.html#interpolation) in the Read more about [interpolation](./template-syntax.html#interpolation) in the
[Template Syntax](!{docsLatest}/guide/template-syntax.html) page. [Template Syntax](./template-syntax.html) page.
.l-main-section#J .l-main-section#J
@ -400,7 +391,7 @@ a#jit
## Just-in-time (JIT) compilation ## Just-in-time (JIT) compilation
.l-sub-section .l-sub-section
:marked :marked
A bootstrapping method of compiling components<span if-docs="ts"> and modules</span> in the browser A bootstrapping method of compiling components and modules in the browser
and launching the application dynamically. Just-in-time mode is a good choice during development. and launching the application dynamically. Just-in-time mode is a good choice during development.
Consider using the [ahead-of-time](#aot) mode for production apps. Consider using the [ahead-of-time](#aot) mode for production apps.
@ -435,7 +426,7 @@ a#jit
* `ngAfterViewChecked`: after every check of a component's views. * `ngAfterViewChecked`: after every check of a component's views.
* `ngOnDestroy`: just before the directive is destroyed. * `ngOnDestroy`: just before the directive is destroyed.
Read more in the [Lifecycle Hooks](!{docsLatest}/guide/lifecycle-hooks.html) page. Read more in the [Lifecycle Hooks](./lifecycle-hooks.html) page.
.l-main-section#M .l-main-section#M
@ -446,7 +437,7 @@ a#jit
:marked :marked
Angular has the following types of modules: Angular has the following types of modules:
- [Angular modules](#angular-module). - [Angular modules](#angular-module).
For details and examples, see the [Angular Modules](!{docsLatest}/guide/ngmodule.html) page. For details and examples, see the [Angular Modules](./ngmodule.html) page.
- ES2015 modules, as described in this section. - ES2015 modules, as described in this section.
:marked :marked
@ -477,29 +468,28 @@ a#jit
a#N a#N
.l-main-section#O .l-main-section#O
+ifDocsFor('ts|js') :marked
## Observable
.l-sub-section
:marked :marked
## Observable An array whose items arrive asynchronously over time.
.l-sub-section Observables help you manage asynchronous data, such as data coming from a backend service.
:marked Observables are used within Angular itself, including Angular's event system and its HTTP client service.
An array whose items arrive asynchronously over time.
Observables help you manage asynchronous data, such as data coming from a backend service.
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
To use observables, Angular uses a third-party library called Reactive Extensions (RxJS). To use observables, Angular uses a third-party library called Reactive Extensions (RxJS).
Observables are a proposed feature for ES2016, the next version of JavaScript. Observables are a proposed feature for ES2016, the next version of JavaScript.
:marked :marked
## Output ## Output
.l-sub-section .l-sub-section
:marked :marked
A directive property that can be the *target* of event binding A directive property that can be the *target* of event binding
(read more in the [event binding](!{docsLatest}/guide/template-syntax.html#event-binding) (read more in the [event binding](./template-syntax.html#event-binding)
section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page). section of the [Template Syntax](./template-syntax.html) page).
Events stream *out* of this property to the receiver identified Events stream *out* of this property to the receiver identified
in the template expression to the right of the equal sign. in the template expression to the right of the equal sign.
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page. See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
.l-main-section#P .l-main-section#P
@ -527,7 +517,7 @@ a#N
:marked :marked
You can also write your own custom pipes. You can also write your own custom pipes.
Read more in the page on [pipes](!{docsLatest}/guide/pipes.html). Read more in the page on [pipes](./pipes.html).
:marked :marked
## Provider ## Provider
@ -566,18 +556,17 @@ a#Q
The Angular component router is a richly featured mechanism for configuring and managing the entire view navigation process, including the creation and destruction The Angular component router is a richly featured mechanism for configuring and managing the entire view navigation process, including the creation and destruction
of views. of views.
+ifDocsFor('ts|js')
:marked In most cases, components become attached to a router by means
In most cases, components become attached to a router by means of a `RouterConfig` that defines routes to views.
of a `RouterConfig` that defines routes to views.
A [routing component's](#routing-component) template has a `RouterOutlet` element A [routing component's](#routing-component) template has a `RouterOutlet` element
where it can display views produced by the router. where it can display views produced by the router.
Other views in the application likely have anchor tags or buttons with `RouterLink` Other views in the application likely have anchor tags or buttons with `RouterLink`
directives that users can click to navigate. directives that users can click to navigate.
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page. For more information, see the [Routing & Navigation](./router.html) page.
:marked :marked
## Router module ## Router module
@ -585,7 +574,7 @@ a#Q
:marked :marked
A separate [Angular module](#angular-module) that provides the necessary service providers and directives for navigating through application views. A separate [Angular module](#angular-module) that provides the necessary service providers and directives for navigating through application views.
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page. For more information, see the [Routing & Navigation](./router.html) page.
:marked :marked
## Routing component ## Routing component
@ -593,7 +582,7 @@ a#Q
:marked :marked
An Angular [component](#component) with a `RouterOutlet` that displays views based on router navigations. An Angular [component](#component) with a `RouterOutlet` that displays views based on router navigations.
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page. For more information, see the [Routing & Navigation](./router.html) page.
.l-main-section#S .l-main-section#S
@ -629,7 +618,7 @@ a#Q
Applications often require services such as a data service or a logging service. Applications often require services such as a data service or a logging service.
For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial. For more information, see the [Services](.ial/toh-pt4.html) page of the [Tour of Heroes](.ial/) tutorial.
a#snake-case a#snake-case
:marked :marked
@ -650,7 +639,7 @@ a#structural-directives
shape or reshape HTML layout, typically by adding and removing elements in the DOM. shape or reshape HTML layout, typically by adding and removing elements in the DOM.
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples. The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page. Read more in the [Structural Directives](./structural-directives.html) page.
.l-main-section#T .l-main-section#T
:marked :marked
@ -677,18 +666,18 @@ a#structural-directives
Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios. Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios.
Read about how to build template-driven forms Read about how to build template-driven forms
in the [Forms](!{docsLatest}/guide/forms.html) page. in the [Forms](./forms.html) page.
:marked :marked
## Template expression ## Template expression
.l-sub-section .l-sub-section
:marked :marked
A !{_Lang}-like syntax that Angular evaluates within A TypeScript-like syntax that Angular evaluates within
a [data binding](#data-binding). a [data binding](#data-binding).
Read about how to write template expressions Read about how to write template expressions
in the [Template expressions](!{docsLatest}/guide/template-syntax.html#template-expressions) section in the [Template expressions](./template-syntax.html#template-expressions) section
of the [Template Syntax](!{docsLatest}/guide/template-syntax.html#) page. of the [Template Syntax](./template-syntax.html#) page.
:marked :marked
## Transpile ## Transpile

View File

@ -172,7 +172,7 @@ figure
* Enter: `void => *` * Enter: `void => *`
* Leave: `* => void` * Leave: `* => void`
For example, in the `animations` !{_array} below there are two transitions that use For example, in the `animations` array below there are two transitions that use
the `void => *` and `* => void` syntax to animate the element in and out of the view. the `void => *` and `* => void` syntax to animate the element in and out of the view.
+makeExample('animations/ts/src/app/hero-list-enter-leave.component.ts', 'animationdef', 'hero-list-enter-leave.component.ts (excerpt)')(format=".") +makeExample('animations/ts/src/app/hero-list-enter-leave.component.ts', 'animationdef', 'hero-list-enter-leave.component.ts (excerpt)')(format=".")

View File

@ -1,14 +1,11 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _library_module = 'library module'
- var _at_angular = '@angular'
block angular-intro :marked
:marked Angular is a framework for building client applications in HTML and
Angular is a framework for building client applications in HTML and either JavaScript or a language like TypeScript that compiles to JavaScript.
either JavaScript or a language like TypeScript that compiles to JavaScript.
The framework consists of several libraries, some of them core and some optional. The framework consists of several libraries, some of them core and some optional.
:marked :marked
You write Angular applications by composing HTML *templates* with Angularized markup, You write Angular applications by composing HTML *templates* with Angularized markup,
@ -48,78 +45,77 @@ figure
figure figure
img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" ) img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
block angular-modules :marked
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
_Angular modules_ are a big deal.
This page introduces modules; the [Angular modules](ngmodule.html) page covers them in depth.
<br class="l-clear-both"><br>
:marked
Every Angular app has at least one Angular module class, [the _root module_](appmodule.html "AppModule: the root module"),
conventionally named `AppModule`.
While the _root module_ may be the only module in a small application, most apps have many more
_feature modules_, each a cohesive block of code dedicated to an application domain,
a workflow, or a closely related set of capabilities.
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
.l-sub-section
:marked :marked
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_. Decorators are functions that modify JavaScript classes.
Angular has many decorators that attach metadata to classes so that it knows
what those classes mean and how they should work.
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0" target="_blank">
Learn more</a> about decorators on the web.
:marked
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
The most important properties are:
* `declarations` - the _view classes_ that belong to this module.
Angular has three kinds of view classes: [components](#components), [directives](#directives), and [pipes](pipes.html).
_Angular modules_ are a big deal. * `exports` - the subset of declarations that should be visible and usable in the component [templates](#templates) of other modules.
This page introduces modules; the [Angular modules](ngmodule.html) page covers them in depth.
<br class="l-clear-both"><br> * `imports` - other modules whose exported classes are needed by component templates declared in _this_ module.
* `providers` - creators of [services](#services) that this module contributes to
the global collection of services; they become accessible in all parts of the app.
* `bootstrap` - the main application view, called the _root component_,
that hosts all other app views. Only the _root module_ should set this `bootstrap` property.
Here's a simple root module:
+makeExample('src/app/mini-app.ts', 'module', 'src/app/app.module.ts')(format='.')
.l-sub-section
:marked :marked
Every Angular app has at least one Angular module class, [the _root module_](appmodule.html "AppModule: the root module"), The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
conventionally named `AppModule`. :marked
Launch an application by _bootstrapping_ its root module.
While the _root module_ may be the only module in a small application, most apps have many more During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
_feature modules_, each a cohesive block of code dedicated to an application domain,
a workflow, or a closely related set of capabilities.
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
.l-sub-section
:marked
Decorators are functions that modify JavaScript classes.
Angular has many decorators that attach metadata to classes so that it knows
what those classes mean and how they should work.
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0" target="_blank">
Learn more</a> about decorators on the web.
:marked
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
The most important properties are:
* `declarations` - the _view classes_ that belong to this module.
Angular has three kinds of view classes: [components](#components), [directives](#directives), and [pipes](pipes.html).
* `exports` - the subset of declarations that should be visible and usable in the component [templates](#templates) of other modules.
* `imports` - other modules whose exported classes are needed by component templates declared in _this_ module.
* `providers` - creators of [services](#services) that this module contributes to
the global collection of services; they become accessible in all parts of the app.
* `bootstrap` - the main application view, called the _root component_,
that hosts all other app views. Only the _root module_ should set this `bootstrap` property.
Here's a simple root module:
+makeExample('src/app/mini-app.ts', 'module', 'src/app/app.module.ts')(format='.')
.l-sub-section
:marked
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
:marked
Launch an application by _bootstrapping_ its root module.
During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
+makeExample('src/main.ts', '', 'src/main.ts')(format='.')
:marked
### Angular modules vs. JavaScript modules
The Angular module &mdash; a class decorated with `@NgModule` &mdash; is a fundamental feature of Angular.
JavaScript also has its own module system for managing collections of JavaScript objects.
It's completely different and unrelated to the Angular module system.
In JavaScript each _file_ is a module and all objects defined in the file belong to that module. +makeExample('src/main.ts', '', 'src/main.ts')(format='.')
The module declares some objects to be public by marking them with the `export` key word.
Other JavaScript modules use *import statements* to access public objects from other modules.
+makeExample('src/app/app.module.ts', 'imports', '')(format='.') :marked
+makeExample('src/app/app.module.ts', 'export', '')(format='.') ### Angular modules vs. JavaScript modules
.l-sub-section The Angular module &mdash; a class decorated with `@NgModule` &mdash; is a fundamental feature of Angular.
:marked
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a> JavaScript also has its own module system for managing collections of JavaScript objects.
It's completely different and unrelated to the Angular module system.
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
The module declares some objects to be public by marking them with the `export` key word.
Other JavaScript modules use *import statements* to access public objects from other modules.
+makeExample('src/app/app.module.ts', 'imports', '')(format='.')
+makeExample('src/app/app.module.ts', 'export', '')(format='.')
.l-sub-section
:marked :marked
These are two different and _complementary_ module systems. Use them both to write your apps. <a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
:marked
These are two different and _complementary_ module systems. Use them both to write your apps.
:marked :marked
### Angular libraries ### Angular libraries
@ -127,32 +123,31 @@ block angular-modules
figure figure
img(src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" ) img(src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
block angular-libraries :marked
:marked Angular ships as a collection of JavaScript modules. You can think of them as library modules.
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
Each Angular library name begins with the `!{_at_angular}` prefix. Each Angular library name begins with the `@angular` prefix.
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements. You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
<br class="l-clear-both"><br> <br class="l-clear-both"><br>
For example, import Angular's `Component` decorator from the `@angular/core` library like this: For example, import Angular's `Component` decorator from the `@angular/core` library like this:
+makeExample('src/app/app.component.ts', 'import', '')(format='.') +makeExample('src/app/app.component.ts', 'import', '')(format='.')
:marked :marked
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements: You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.') +makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
:marked :marked
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this. In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.') +makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
:marked :marked
In this way you're using both the Angular and JavaScript module systems _together_. In this way you're using both the Angular and JavaScript module systems _together_.
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports". It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
Hang in there. The confusion yields to clarity with time and experience. Hang in there. The confusion yields to clarity with time and experience.
.l-sub-section .l-sub-section
:marked :marked
Learn more from the [Angular modules](ngmodule.html) page. Learn more from the [Angular modules](ngmodule.html) page.
.l-hr .l-hr
@ -176,7 +171,7 @@ figure
The class interacts with the view through an API of properties and methods. The class interacts with the view through an API of properties and methods.
<a id="component-code"></a> <a id="component-code"></a>
For example, this `HeroListComponent` has a `heroes` property that returns !{_an} !{_array} of heroes For example, this `HeroListComponent` has a `heroes` property that returns an array of heroes
that it acquires from a service. that it acquires from a service.
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list. `HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
@ -238,21 +233,20 @@ figure
To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class. To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class.
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**. In TypeScript, you attach metadata by using a **decorator**.
Here's some metadata for `HeroListComponent`: Here's some metadata for `HeroListComponent`:
+makeExcerpt('src/app/hero-list.component.ts', 'metadata') +makeExcerpt('src/app/hero-list.component.ts', 'metadata')
:marked :marked
Here is the `@Component` !{_decorator}, which identifies the class Here is the `@Component` decorator, which identifies the class
immediately below it as a component class. immediately below it as a component class.
block ts-decorator :marked
:marked The `@Component` decorator takes a required configuration object with the
The `@Component` decorator takes a required configuration object with the information Angular needs to create and present the component and its view.
information Angular needs to create and present the component and its view.
Here are a few of the most useful `@Component` configuration options: Here are a few of the most useful `@Component` configuration options:
:marked :marked
- `selector`: CSS selector that tells Angular to create and insert an instance of this component - `selector`: CSS selector that tells Angular to create and insert an instance of this component
@ -262,10 +256,8 @@ block ts-decorator
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates). - `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
block dart-directives
:marked :marked
- `providers`: !{_array} of **dependency injection providers** for services that the component requires. - `providers`: array of **dependency injection providers** for services that the component requires.
This is one way to tell Angular that the component's constructor requires a `HeroService` This is one way to tell Angular that the component's constructor requires a `HeroService`
so it can get the list of heroes to display. so it can get the list of heroes to display.
@ -277,8 +269,8 @@ figure
The template, metadata, and component together describe a view. The template, metadata, and component together describe a view.
Apply other metadata !{_decorator}s in a similar fashion to guide Angular behavior. Apply other metadata decorators in a similar fashion to guide Angular behavior.
`@Injectable`, `@Input`, and `@Output` are a few of the more popular !{_decorator}s. `@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.
<br class="l-clear-both"> <br class="l-clear-both">
:marked :marked
The architectural takeaway is that you must add metadata to your code The architectural takeaway is that you must add metadata to your code
@ -352,9 +344,9 @@ figure
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
according to the instructions given by **directives**. according to the instructions given by **directives**.
A directive is a class with a `@Directive` !{_decorator}. A directive is a class with a `@Directive` decorator.
A component is a *directive-with-a-template*; A component is a *directive-with-a-template*;
a `@Component` !{_decorator} is actually a `@Directive` !{_decorator} extended with template-oriented features. a `@Component` decorator is actually a `@Directive` decorator extended with template-oriented features.
<br class="l-clear-both"> <br class="l-clear-both">
.l-sub-section .l-sub-section
@ -377,8 +369,6 @@ figure
* [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list. * [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists. * [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
block dart-bool
:marked :marked
**Attribute** directives alter the appearance or behavior of an existing element. **Attribute** directives alter the appearance or behavior of an existing element.
In templates they look like regular HTML attributes, hence the name. In templates they look like regular HTML attributes, hence the name.
@ -430,7 +420,7 @@ figure
+makeExcerpt('src/app/logger.service.ts', 'class') +makeExcerpt('src/app/logger.service.ts', 'class')
:marked :marked
Here's a `HeroService` that uses a !{_PromiseLinked} to fetch heroes. Here's a `HeroService` that uses a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to fetch heroes.
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work. The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
+makeExcerpt('src/app/hero.service.ts', 'class') +makeExcerpt('src/app/hero.service.ts', 'class')
@ -491,14 +481,13 @@ figure
In brief, you must have previously registered a **provider** of the `HeroService` with the injector. In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
A provider is something that can create or return a service, typically the service class itself. A provider is something that can create or return a service, typically the service class itself.
block registering-providers :marked
:marked You can register providers in modules or in components.
You can register providers in modules or in components.
In general, add providers to the [root module](#module) so that
In general, add providers to the [root module](#module) so that the same instance of a service is available everywhere.
the same instance of a service is available everywhere.
+makeExcerpt('src/app/app.module.ts (module providers)', 'providers') +makeExcerpt('src/app/app.module.ts (module providers)', 'providers')
:marked :marked
Alternatively, register at a component level in the `providers` property of the `@Component` metadata: Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
@ -573,7 +562,6 @@ block registering-providers
> [**Router**](router.html): Navigate from page to page within the client > [**Router**](router.html): Navigate from page to page within the client
application and never leave the browser. application and never leave the browser.
block angular-feature-testing :marked
:marked > [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework using the _Angular Testing Platform_.
using the _Angular Testing Platform_.

View File

@ -56,7 +56,7 @@ block includes
### Write the directive code ### Write the directive code
Follow the [setup](setup.html) instructions for creating a new local project Follow the [setup](setup.html) instructions for creating a new local project
named <span ngio-ex>attribute-directives</span>. named <code>attribute-directives</code>.
Create the following source file in the indicated folder: Create the following source file in the indicated folder:
@ -97,7 +97,7 @@ block includes
:marked :marked
After the `@Directive` metadata comes the directive's controller class, After the `@Directive` metadata comes the directive's controller class,
called `HighlightDirective`, which contains the logic for the directive. called `HighlightDirective`, which contains the logic for the directive.
<span if-docs="ts">Exporting `HighlightDirective` makes it accessible to other components.</span> Exporting `HighlightDirective` makes it accessible to other components.
Angular creates a new instance of the directive's controller class for Angular creates a new instance of the directive's controller class for
each matching element, injecting an Angular `ElementRef` each matching element, injecting an Angular `ElementRef`
@ -113,7 +113,7 @@ block includes
applies the directive as an attribute to a paragraph (`<p>`) element. applies the directive as an attribute to a paragraph (`<p>`) element.
In Angular terms, the `<p>` element is the attribute **host**. In Angular terms, the `<p>` element is the attribute **host**.
Put the template in its own <span ngio-ex>app.component.html</span> Put the template in its own <code>app.component.html</code>
file that looks like this: file that looks like this:
+makeExample('src/app/app.component.1.html') +makeExample('src/app/app.component.1.html')
@ -176,12 +176,12 @@ figure.image-display
:marked :marked
Then add two eventhandlers that respond when the mouse enters or leaves, Then add two eventhandlers that respond when the mouse enters or leaves,
each adorned by the `HostListener` !{_decorator}. each adorned by the `HostListener` decorator.
+makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '') +makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '')
:marked :marked
The `@HostListener` !{_decorator} lets you subscribe to events of the DOM The `@HostListener` decorator lets you subscribe to events of the DOM
element that hosts an attribute directive, the `<p>` in this case. element that hosts an attribute directive, the `<p>` in this case.
.l-sub-section .l-sub-section
@ -194,7 +194,7 @@ figure.image-display
1. Talking to DOM API directly isn't a best practice. 1. Talking to DOM API directly isn't a best practice.
:marked :marked
The handlers delegate to a helper method that sets the color on the DOM element, `#{_priv}el`, The handlers delegate to a helper method that sets the color on the DOM element, `el`,
which you declare and initialize in the constructor. which you declare and initialize in the constructor.
+makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor') +makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor')
@ -226,7 +226,7 @@ a#input
:marked :marked
### Binding to an _@Input_ property ### Binding to an _@Input_ property
Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding. Notice the `@Input` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
It's called an *input* property because data flows from the binding expression _into_ the directive. It's called an *input* property because data flows from the binding expression _into_ the directive.
Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that. Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that.
@ -299,7 +299,7 @@ a#input-alias
In this section, you'll turn `AppComponent` into a harness that In this section, you'll turn `AppComponent` into a harness that
lets you pick the highlight color with a radio button and bind your color choice to the directive. lets you pick the highlight color with a radio button and bind your color choice to the directive.
Update <span ngio-ex>app.component.html</span> as follows: Update <code>app.component.html</code> as follows:
+makeExcerpt('src/app/app.component.html', 'v2', '') +makeExcerpt('src/app/app.component.html', 'v2', '')
@ -345,7 +345,7 @@ figure.image-display
:marked :marked
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective` Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
because you made it _public_ with the `@Input` !{_decorator}. because you made it _public_ with the `@Input` decorator.
Here's how the harness should work when you're done coding. Here's how the harness should work when you're done coding.
@ -400,7 +400,7 @@ figure.image-display
+makeExcerpt('src/app/highlight.directive.ts', 'color', '') +makeExcerpt('src/app/highlight.directive.ts', 'color', '')
:marked :marked
Either way, the `@Input` !{_decorator} tells Angular that this property is Either way, the `@Input` decorator tells Angular that this property is
_public_ and available for binding by a parent component. _public_ and available for binding by a parent component.
Without `@Input`, Angular refuses to bind to the property. Without `@Input`, Angular refuses to bind to the property.
@ -411,22 +411,22 @@ figure.image-display
Angular treats a component's template as _belonging_ to the component. Angular treats a component's template as _belonging_ to the component.
The component and its template trust each other implicitly. The component and its template trust each other implicitly.
Therefore, the component's own template may bind to _any_ property of that component, Therefore, the component's own template may bind to _any_ property of that component,
with or without the `@Input` !{_decorator}. with or without the `@Input` decorator.
But a component or directive shouldn't blindly trust _other_ components and directives. But a component or directive shouldn't blindly trust _other_ components and directives.
The properties of a component or directive are hidden from binding by default. The properties of a component or directive are hidden from binding by default.
They are _private_ from an Angular binding perspective. They are _private_ from an Angular binding perspective.
When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective. When adorned with the `@Input` decorator, the property becomes _public_ from an Angular binding perspective.
Only then can it be bound by some other component or directive. Only then can it be bound by some other component or directive.
You can tell if `@Input` is needed by the position of the property name in a binding. You can tell if `@Input` is needed by the position of the property name in a binding.
* When it appears in the template expression to the ***right*** of the equals (=), * When it appears in the template expression to the ***right*** of the equals (=),
it belongs to the template's component and does not require the `@Input` !{_decorator}. it belongs to the template's component and does not require the `@Input` decorator.
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=), * When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
the property belongs to some _other_ component or directive; the property belongs to some _other_ component or directive;
that property must be adorned with the `@Input` !{_decorator}. that property must be adorned with the `@Input` decorator.
Now apply that reasoning to the following example: Now apply that reasoning to the following example:
@ -435,8 +435,8 @@ figure.image-display
:marked :marked
* The `color` property in the expression on the right belongs to the template's component. * The `color` property in the expression on the right belongs to the template's component.
The template and its component trust each other. The template and its component trust each other.
The `color` property doesn't require the `@Input` !{_decorator}. The `color` property doesn't require the `@Input` decorator.
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`, * The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
not a property of the template's component. There are trust issues. not a property of the template's component. There are trust issues.
Therefore, the directive property must carry the `@Input` !{_decorator}. Therefore, the directive property must carry the `@Input` decorator.

View File

@ -1,6 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _at_angular = '@angular'
:marked :marked
Angular supports most recent browsers. This includes the following specific versions: Angular supports most recent browsers. This includes the following specific versions:

View File

@ -31,7 +31,7 @@ block includes
specifying any selectors, rules, and media queries that you need. specifying any selectors, rules, and media queries that you need.
One way to do this is to set the `styles` property in the component metadata. One way to do this is to set the `styles` property in the component metadata.
The `styles` property takes #{_an} #{_array} of strings that contain CSS code. The `styles` property takes an array of strings that contain CSS code.
Usually you give it one string, as in the following example: Usually you give it one string, as in the following example:
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.') +makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
@ -134,8 +134,8 @@ a(id='loading-styles')
### Styles in metadata ### Styles in metadata
You can add a `styles` #{_array} property to the `@Component` #{_decorator}. You can add a `styles` array property to the `@Component` decorator.
Each string in the #{_array} (usually just one string) defines the CSS. Each string in the array (usually just one string) defines the CSS.
+makeExample('component-styles/ts/src/app/hero-app.component.ts') +makeExample('component-styles/ts/src/app/hero-app.component.ts')
@ -143,32 +143,30 @@ a(id='loading-styles')
### Style URLs in metadata ### Style URLs in metadata
You can load styles from external CSS files by adding a `styleUrls` attribute You can load styles from external CSS files by adding a `styleUrls` attribute
into a component's `@Component` #{_decorator}: into a component's `@Component` decorator:
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls') +makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
block style-url .alert.is-important
.alert.is-important :marked
:marked The URL is relative to the *application root*, which is usually the
The URL is relative to the *application root*, which is usually the location of the `index.html` web page that hosts the application.
location of the `index.html` web page that hosts the application. The style file URL is *not* relative to the component file.
The style file URL is *not* relative to the component file. That's why the example URL begins `src/app/`.
That's why the example URL begins `src/app/`. To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
block module-bundlers .l-sub-section
.l-sub-section :marked
:marked If you use module bundlers like Webpack, you can also use the `styles` attribute
If you use module bundlers like Webpack, you can also use the `styles` attribute to load styles from external files at build time. You could write:
to load styles from external files at build time. You could write:
`styles: [require('my.component.css')]` `styles: [require('my.component.css')]`
Set the `styles` property, not the `styleUrls` property. The module Set the `styles` property, not the `styleUrls` property. The module
bundler loads the CSS strings, not Angular. bundler loads the CSS strings, not Angular.
Angular sees the CSS strings only after the bundler loads them. Angular sees the CSS strings only after the bundler loads them.
To Angular, it's as if you wrote the `styles` array by hand. To Angular, it's as if you wrote the `styles` array by hand.
For information on loading CSS in this manner, refer to the module bundler's documentation. For information on loading CSS in this manner, refer to the module bundler's documentation.
:marked :marked
### Template inline styles ### Template inline styles
@ -195,9 +193,8 @@ block module-bundlers
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import) For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
on the [MDN](https://developer.mozilla.org) site. on the [MDN](https://developer.mozilla.org) site.
block css-import-url :marked
:marked In this case, the URL is relative to the CSS file into which you're importing.
In this case, the URL is relative to the CSS file into which you're importing.
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)') +makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
@ -300,9 +297,8 @@ code-example(format="nocode").
Because these files are co-located with the component, Because these files are co-located with the component,
it would be nice to refer to them by name without also having to specify a path back to the root of the application. it would be nice to refer to them by name without also having to specify a path back to the root of the application.
block module-id :marked
:marked You can use a relative URL by prefixing your filenames with `./`:
You can use a relative URL by prefixing your filenames with `./`:
+makeExample('src/app/quest-summary.component.ts') +makeExample('src/app/quest-summary.component.ts')

View File

@ -1,6 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _thisDot = 'this.';
:marked :marked
**Dependency injection** is an important application design pattern. **Dependency injection** is an important application design pattern.
@ -62,7 +61,7 @@ block includes
What if the `Engine` class evolves and its constructor requires a parameter? What if the `Engine` class evolves and its constructor requires a parameter?
That would break the `Car` class and it would stay broken until you rewrote it along the lines of That would break the `Car` class and it would stay broken until you rewrote it along the lines of
`#{_thisDot}engine = new Engine(theNewParameter)`. `this.engine = new Engine(theNewParameter)`.
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`. The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
You may not anticipate them even now. You may not anticipate them even now.
But you'll *have* to start caring because But you'll *have* to start caring because
@ -108,11 +107,10 @@ block includes
The `Car` class no longer creates an `engine` or `tires`. The `Car` class no longer creates an `engine` or `tires`.
It just consumes them. It just consumes them.
block ctor-syntax .l-sub-section
.l-sub-section :marked
:marked This example leverages TypeScript's constructor syntax for declaring
This example leverages TypeScript's constructor syntax for declaring parameters and properties simultaneously.
parameters and properties simultaneously.
:marked :marked
Now you can create a car by passing the engine and tires to the constructor. Now you can create a car by passing the engine and tires to the constructor.
@ -133,8 +131,7 @@ block ctor-syntax
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
something like this: something like this:
- var stylePattern = { otl: /(new Car.*$)/gm }; +makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', { otl: /(new Car.*$)/gm })(format=".")
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
:marked :marked
The critical point is this: the `Car` class did not have to change. The critical point is this: the `Car` class did not have to change.
@ -146,8 +143,7 @@ block ctor-syntax
You can pass mocks to the constructor that do exactly what you want them to do You can pass mocks to the constructor that do exactly what you want them to do
during each test: during each test:
- var stylePattern = { otl: /(new Car.*$)/gm }; +makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', { otl: /(new Car.*$)/gm })(format=".")
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
:marked :marked
**You just learned what dependency injection is**. **You just learned what dependency injection is**.
@ -236,9 +232,8 @@ block ctor-syntax
Given that the service is a Given that the service is a
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns), [separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
consider writing the service code in its own file. consider writing the service code in its own file.
+ifDocsFor('ts')
:marked See [this note](#one-class-per-file) for details.
See [this note](#one-class-per-file) for details.
:marked :marked
The following `HeroService` exposes a `getHeroes` method that returns The following `HeroService` exposes a `getHeroes` method that returns
the same mock data as before, but none of its consumers need to know that. the same mock data as before, but none of its consumers need to know that.
@ -248,15 +243,14 @@ block ctor-syntax
:marked :marked
.l-sub-section .l-sub-section
:marked :marked
The `@Injectable()` #{_decorator} above the service class is The `@Injectable()` decorator above the service class is
covered [shortly](#injectable). covered [shortly](#injectable).
- var _perhaps = _docsFor == 'dart' ? '' : 'perhaps';
.l-sub-section .l-sub-section
:marked :marked
Of course, this isn't a real service. Of course, this isn't a real service.
If the app were actually getting data from a remote server, the API would have to be If the app were actually getting data from a remote server, the API would have to be
asynchronous, #{_perhaps} returning a !{_PromiseLinked}. asynchronous, perhaps returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
You'd also have to rewrite the way components consume the service. You'd also have to rewrite the way components consume the service.
This is important in general, but not in this example. This is important in general, but not in this example.
@ -279,18 +273,16 @@ a#injector-config
that create the services the application requires. that create the services the application requires.
This guide explains what [providers](#providers) are later. This guide explains what [providers](#providers) are later.
block register-provider-ngmodule :marked
:marked You can either register a provider within an [NgModule](ngmodule.html) or in application components.
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
a#register-providers-ngmodule a#register-providers-ngmodule
:marked :marked
### Registering providers in an _NgModule_ ### Registering providers in an _NgModule_
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider, Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
in its `providers` !{_array}. in its `providers` array.
- var app_module_ts = 'src/app/app.module.ts'; +makeExcerpt('src/app/app.module.ts (excerpt)', 'ngmodule', app_module_ts)
+makeExcerpt(app_module_ts + ' (excerpt)', 'ngmodule', app_module_ts)
:marked :marked
Because the `HeroService` is used _only_ within the `HeroesComponent` Because the `HeroService` is used _only_ within the `HeroesComponent`
and its subcomponents, the top-level `HeroesComponent` is the ideal and its subcomponents, the top-level `HeroesComponent` is the ideal
@ -300,31 +292,30 @@ a#register-providers-component
:marked :marked
### Registering providers in a component ### Registering providers in a component
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` !{_array}. Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array.
+makeExample('src/app/heroes/heroes.component.1.ts', 'full', 'src/app/heroes/heroes.component.ts', stylePattern)(format='.') +makeExample('src/app/heroes/heroes.component.1.ts', 'full', 'src/app/heroes/heroes.component.ts', stylePattern)(format='.')
block ngmodule-vs-component a#ngmodule-vs-comp
a#ngmodule-vs-comp :marked
### When to use _NgModule_ versus an application component
On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider
registered within an `NgModule` will be accessible in the _entire application_.
On the other hand, a provider registered in an application component is available only on
that component and all its children.
Here, the `APP_CONFIG` service needs to be available all across the application, so it's
registered in the `AppModule` `@NgModule` `providers` array.
But since the `HeroService` is only used within the *Heroes*
feature area and nowhere else, it makes sense to register it in
the `HeroesComponent`.
.l-sub-section
:marked :marked
### When to use _NgModule_ versus an application component Also see *"Should I add app-wide providers to the root `AppModule` or
the root `AppComponent`?"* in the [NgModule FAQ](../cookbook/ngmodule-faq.html#q-root-component-or-module).
On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider
registered within an `NgModule` will be accessible in the _entire application_.
On the other hand, a provider registered in an application component is available only on
that component and all its children.
Here, the `APP_CONFIG` service needs to be available all across the application, so it's
registered in the `AppModule` `@NgModule` `providers` !{_array}.
But since the `HeroService` is only used within the *Heroes*
feature area and nowhere else, it makes sense to register it in
the `HeroesComponent`.
.l-sub-section
:marked
Also see *"Should I add app-wide providers to the root `AppModule` or
the root `AppComponent`?"* in the [NgModule FAQ](../cookbook/ngmodule-faq.html#q-root-component-or-module).
a#prep-for-injection a#prep-for-injection
:marked :marked
@ -352,12 +343,12 @@ a#prep-for-injection
:marked :marked
Note that the constructor parameter has the type `HeroService`, and that Note that the constructor parameter has the type `HeroService`, and that
the `HeroListComponent` class has an `@Component` #{_decorator} the `HeroListComponent` class has an `@Component` decorator
(scroll up to confirm that fact). (scroll up to confirm that fact).
Also recall that the parent component (`HeroesComponent`) Also recall that the parent component (`HeroesComponent`)
has `providers` information for `HeroService`. has `providers` information for `HeroService`.
The constructor parameter type, the `@Component` #{_decorator}, The constructor parameter type, the `@Component` decorator,
and the parent's `providers` information combine to tell the and the parent's `providers` information combine to tell the
Angular injector to inject an instance of Angular injector to inject an instance of
`HeroService` whenever it creates a new `HeroListComponent`. `HeroService` whenever it creates a new `HeroListComponent`.
@ -432,37 +423,35 @@ a#service-needs-service
src/app/heroes/hero.service (v1)`) src/app/heroes/hero.service (v1)`)
:marked :marked
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `#{_priv}logger`. The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
You call that property within the `getHeroes()` method when anyone asks for heroes. You call that property within the `getHeroes()` method when anyone asks for heroes.
- var injUrl = '../api/core/index/Injectable-decorator.html';
a#injectable a#injectable
:marked :marked
### Why _@Injectable()_? ### Why _@Injectable()_?
**<a href="#{injUrl}">@Injectable()</a>** marks a class as available to an **<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>** marks a class as available to an
injector for instantiation. Generally speaking, an injector reports an injector for instantiation. Generally speaking, an injector reports an
error when trying to instantiate a class that is not marked as error when trying to instantiate a class that is not marked as
`@Injectable()`. `@Injectable()`.
block injectable-not-always-needed-in-ts .l-sub-section
.l-sub-section :marked
:marked As it happens, you could have omitted `@Injectable()` from the first
As it happens, you could have omitted `@Injectable()` from the first version of `HeroService` because it had no injected parameters.
version of `HeroService` because it had no injected parameters. But you must have it now that the service has an injected dependency.
But you must have it now that the service has an injected dependency. You need it because Angular requires constructor parameter metadata
You need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
in order to inject a `Logger`.
.callout.is-helpful .callout.is-helpful
header Suggestion: add @Injectable() to every service class header Suggestion: add @Injectable() to every service class
:marked :marked
Consider adding `@Injectable()` to every service class, even those that don't have dependencies Consider adding `@Injectable()` to every service class, even those that don't have dependencies
and, therefore, do not technically require it. Here's why: and, therefore, do not technically require it. Here's why:
ul(style="font-size:inherit") ul(style="font-size:inherit")
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when you add a dependency later. li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when you add a dependency later.
li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why #{_a} #{_decorator} is missing. li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why a decorator is missing.
:marked :marked
Injectors are also responsible for instantiating components Injectors are also responsible for instantiating components
@ -471,35 +460,33 @@ block injectable-not-always-needed-in-ts
You *can* add it if you really want to. It isn't necessary because the You *can* add it if you really want to. It isn't necessary because the
`HeroesComponent` is already marked with `@Component`, and this `HeroesComponent` is already marked with `@Component`, and this
!{_decorator} class (like `@Directive` and `@Pipe`, which you learn about later) decorator class (like `@Directive` and `@Pipe`, which you learn about later)
is a subtype of <a href="#{injUrl}">@Injectable()</a>. It is in is a subtype of <a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>. It is in
fact `@Injectable()` #{_decorator}s that fact `@Injectable()` decorators that
identify a class as a target for instantiation by an injector. identify a class as a target for instantiation by an injector.
+ifDocsFor('ts') .l-sub-section
.l-sub-section :marked
:marked At runtime, injectors can read class metadata in the transpiled JavaScript code
At runtime, injectors can read class metadata in the transpiled JavaScript code and use the constructor parameter type information
and use the constructor parameter type information to determine what things to inject.
to determine what things to inject.
Not every JavaScript class has metadata. Not every JavaScript class has metadata.
The TypeScript compiler discards metadata by default. The TypeScript compiler discards metadata by default.
If the `emitDecoratorMetadata` compiler option is true If the `emitDecoratorMetadata` compiler option is true
(as it should be in the `tsconfig.json`), (as it should be in the `tsconfig.json`),
the compiler adds the metadata to the generated JavaScript the compiler adds the metadata to the generated JavaScript
for _every class with at least one decorator_. for _every class with at least one decorator_.
While any decorator will trigger this effect, mark the service class with the While any decorator will trigger this effect, mark the service class with the
<a href="#{injUrl}">@Injectable()</a> #{_decorator} <a href="../api/core/index/Injectable-decorator.html">@Injectable()</a> decorator
to make the intent clear. to make the intent clear.
.callout.is-critical .callout.is-critical
header Always include the parentheses header Always include the parentheses
block always-include-paren :marked
:marked Always write `@Injectable()`, not just `@Injectable`.
Always write `@Injectable()`, not just `@Injectable`. The application will fail mysteriously if you forget the parentheses.
The application will fail mysteriously if you forget the parentheses.
.l-main-section#logger-service .l-main-section#logger-service
:marked :marked
@ -513,13 +500,10 @@ block injectable-not-always-needed-in-ts
+makeExample('src/app/logger.service.ts') +makeExample('src/app/logger.service.ts')
block real-logger
//- N/A
:marked :marked
You're likely to need the same logger service everywhere in your application, You're likely to need the same logger service everywhere in your application,
so put it in the project's `#{_appDir}` folder and so put it in the project's `app` folder and
register it in the `providers` #{_array} of the application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`. register it in the `providers` array of the application module, `AppModule`.
+makeExcerpt('src/app/providers.component.ts (excerpt)', 'providers-logger','src/app/app.module.ts') +makeExcerpt('src/app/providers.component.ts (excerpt)', 'providers-logger','src/app/app.module.ts')
@ -546,51 +530,44 @@ code-example(format="nocode").
You must register a service *provider* with the injector, or it won't know how to create the service. You must register a service *provider* with the injector, or it won't know how to create the service.
Earlier you registered the `Logger` service in the `providers` #{_array} of the metadata for the `AppModule` like this: Earlier you registered the `Logger` service in the `providers` array of the metadata for the `AppModule` like this:
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-logger') +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-logger')
- var implements = _docsFor == 'dart' ? 'implements' : 'looks and behaves like a '
- var objectlike = _docsFor == 'dart' ? '' : 'an object that behaves like '
- var loggerlike = _docsFor == 'dart' ? '' : 'You could provide a logger-like object. '
:marked :marked
There are many ways to *provide* something that #{implements} `Logger`. There are many ways to *provide* something that looks and behaves like a `Logger`.
The `Logger` class itself is an obvious and natural provider. The `Logger` class itself is an obvious and natural provider.
But it's not the only way. But it's not the only way.
You can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`. You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
You could provide a substitute class. #{loggerlike} You could provide a substitute class. You could provide a logger-like object.
You could give it a provider that calls a logger factory function. You could give it a provider that calls a logger factory function.
Any of these approaches might be a good choice under the right circumstances. Any of these approaches might be a good choice under the right circumstances.
What matters is that the injector has a provider to go to when it needs a `Logger`. What matters is that the injector has a provider to go to when it needs a `Logger`.
//- Dart limitation: the provide function isn't const so it cannot be used in an annotation. //- Dart limitation: the provide function isn't const so it cannot be used in an annotation.
- var _andProvideFn = _docsFor == 'dart' ? '' : 'and <i>provide</i> object literal';
#provide #provide
:marked :marked
### The *Provider* class !{_andProvideFn} ### The *Provider* class and _provide_ object literal
:marked :marked
You wrote the `providers` #{_array} like this: You wrote the `providers` array like this:
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-1') +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-1')
block provider-shorthand :marked
:marked This is actually a shorthand expression for a provider registration
This is actually a shorthand expression for a provider registration using a _provider_ object literal with two properties:
using a _provider_ object literal with two properties:
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3') +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3')
block provider-ctor-args
- var _secondParam = 'provider definition object';
:marked :marked
The first is the [token](#token) that serves as the key for both locating a dependency value The first is the [token](#token) that serves as the key for both locating a dependency value
and registering the provider. and registering the provider.
The second is a !{_secondParam}, The second is a provider definition object,
which you can think of as a *recipe* for creating the dependency value. which you can think of as a *recipe* for creating the dependency value.
There are many ways to create dependency values just as there are many ways to write a recipe. There are many ways to create dependency values just as there are many ways to write a recipe.
@ -604,9 +581,6 @@ block provider-ctor-args
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-4') +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-4')
block dart-diff-const-metadata
//- N/A
a#class-provider-dependencies a#class-provider-dependencies
:marked :marked
### Class provider with dependencies ### Class provider with dependencies
@ -653,9 +627,6 @@ a#value-provider
:marked :marked
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
block dart-diff-const-metadata-ctor
//- N/A
+makeExample('dependency-injection/ts/src/app/providers.component.ts','silent-logger')(format=".") +makeExample('dependency-injection/ts/src/app/providers.component.ts','silent-logger')(format=".")
:marked :marked
@ -720,19 +691,17 @@ block dart-diff-const-metadata-ctor
The `useFactory` field tells Angular that the provider is a factory function The `useFactory` field tells Angular that the provider is a factory function
whose implementation is the `heroServiceFactory`. whose implementation is the `heroServiceFactory`.
The `deps` property is #{_an} #{_array} of [provider tokens](#token). The `deps` property is an array of [provider tokens](#token).
The `Logger` and `UserService` classes serve as tokens for their own class providers. The `Logger` and `UserService` classes serve as tokens for their own class providers.
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters. The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
- var exportedvar = _docsFor == 'dart' ? 'constant' : 'exported variable'
- var variable = _docsFor == 'dart' ? 'constant' : 'variable'
:marked :marked
Notice that you captured the factory provider in #{_an} #{exportedvar}, `heroServiceProvider`. Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
This extra step makes the factory provider reusable. This extra step makes the factory provider reusable.
You can register the `HeroService` with this #{variable} wherever you need it. You can register the `HeroService` with this variable wherever you need it.
In this sample, you need it only in the `HeroesComponent`, In this sample, you need it only in the `HeroesComponent`,
where it replaces the previous `HeroService` registration in the metadata `providers` #{_array}. where it replaces the previous `HeroService` registration in the metadata `providers` array.
Here you see the new and the old implementation side-by-side: Here you see the new and the old implementation side-by-side:
+makeTabs( +makeTabs(
@ -774,14 +743,12 @@ a#non-class-dependencies
### Non-class dependencies ### Non-class dependencies
p p
| What if the dependency value isn't a class? Sometimes the thing you want to inject is a | What if the dependency value isn't a class? Sometimes the thing you want to inject is a
block non-class-dep-eg | span string, function, or object.
| span string, function, or object.
p p
| Applications often define configuration objects with lots of small facts | Applications often define configuration objects with lots of small facts
| (like the title of the application or the address of a web API endpoint) | (like the title of the application or the address of a web API endpoint)
block config-obj-maps | but these configuration objects aren't always instances of a class.
| but these configuration objects aren't always instances of a class. | They can be object literals such as this one:
| They can be object literals such as this one:
+makeExample('dependency-injection/ts/src/app/app.config.ts','config','src/app/app-config.ts (excerpt)')(format='.') +makeExample('dependency-injection/ts/src/app/app.config.ts','config','src/app/app-config.ts (excerpt)')(format='.')
@ -789,36 +756,33 @@ p
What if you'd like to make this configuration object available for injection? What if you'd like to make this configuration object available for injection?
You know you can register an object with a [value provider](#value-provider). You know you can register an object with a [value provider](#value-provider).
block what-should-we-use-as-token :marked
But what should you use as the token?
You don't have a class to serve as a token.
There is no `AppConfig` class.
.l-sub-section#interface
:marked :marked
But what should you use as the token? ### TypeScript interfaces aren't valid tokens
You don't have a class to serve as a token.
There is no `AppConfig` class.
.l-sub-section#interface The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you
:marked cannot use a TypeScript interface as a token:
### TypeScript interfaces aren't valid tokens +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9-interface')(format=".")
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-9-ctor-interface')(format=".")
:marked
That seems strange if you're used to dependency injection in strongly typed languages, where
an interface is the preferred dependency lookup key.
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you It's not Angular's doing. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
cannot use a TypeScript interface as a token: The TypeScript interface disappears from the generated JavaScript.
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9-interface')(format=".") There is no interface type information left for Angular to find at runtime.
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-9-ctor-interface')(format=".")
:marked
That seems strange if you're used to dependency injection in strongly typed languages, where
an interface is the preferred dependency lookup key.
It's not Angular's doing. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
The TypeScript interface disappears from the generated JavaScript.
There is no interface type information left for Angular to find at runtime.
//- FIXME update once https://github.com/dart-lang/angular2/issues/16 is addressed.
- var opaquetoken = _docsFor == 'dart' ? '<b>OpaqueToken</b>' : '<a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>'
a#opaquetoken a#opaquetoken
:marked :marked
### _OpaqueToken_ ### _OpaqueToken_
One solution to choosing a provider token for non-class dependencies is One solution to choosing a provider token for non-class dependencies is
to define and use an !{opaquetoken}. to define and use an <a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>.
The definition looks like this: The definition looks like this:
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.') +makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
@ -830,19 +794,17 @@ a#opaquetoken
:marked :marked
Now you can inject the configuration object into any constructor that needs it, with Now you can inject the configuration object into any constructor that needs it, with
the help of an `@Inject` #{_decorator}: the help of an `@Inject` decorator:
+makeExample('dependency-injection/ts/src/app/app.component.2.ts','ctor')(format=".") +makeExample('dependency-injection/ts/src/app/app.component.2.ts','ctor')(format=".")
- var configType = _docsFor == 'dart' ? '<code>Map</code>' : '<code>AppConfig</code>'
.l-sub-section .l-sub-section
:marked :marked
Although the !{configType} interface plays no role in dependency injection, Although the `AppConfig` interface plays no role in dependency injection,
it supports typing of the configuration object within the class. it supports typing of the configuration object within the class.
block dart-map-alternative :marked
:marked Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
+makeExcerpt('src/app/app.module.ts','ngmodule-providers') +makeExcerpt('src/app/app.module.ts','ngmodule-providers')
@ -855,8 +817,7 @@ block dart-map-alternative
You can tell Angular that the dependency is optional by annotating the You can tell Angular that the dependency is optional by annotating the
constructor argument with `@Optional()`: constructor argument with `@Optional()`:
+ifDocsFor('ts') +makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
+makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-10-ctor', '')(format='.') +makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-10-ctor', '')(format='.')
:marked :marked
@ -915,23 +876,22 @@ block dart-map-alternative
Framework developers may take this approach when they Framework developers may take this approach when they
must acquire services generically and dynamically. must acquire services generically and dynamically.
+ifDocsFor('ts') .l-main-section#one-class-per-file
.l-main-section#one-class-per-file :marked
## Appendix: Why have one class per file
Having multiple classes in the same file is confusing and best avoided.
Developers expect one class per file. Keep them happy.
If you combine the `HeroService` class with
the `HeroesComponent` in the same file,
**define the component last**.
If you define the component before the service,
you'll get a runtime null reference error.
.l-sub-section
:marked :marked
## Appendix: Why have one class per file You actually can define the component first with the help of the `forwardRef()` method as explained
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
Having multiple classes in the same file is confusing and best avoided. But why flirt with trouble?
Developers expect one class per file. Keep them happy. Avoid the problem altogether by defining components and services in separate files.
If you combine the `HeroService` class with
the `HeroesComponent` in the same file,
**define the component last**.
If you define the component before the service,
you'll get a runtime null reference error.
.l-sub-section
:marked
You actually can define the component first with the help of the `forwardRef()` method as explained
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
But why flirt with trouble?
Avoid the problem altogether by defining components and services in separate files.

View File

@ -156,7 +156,7 @@ a#node-modules
Practice with this sample before attempting these techniques on your application. Practice with this sample before attempting these techniques on your application.
1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project 1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project
named <ngio-ex path="simple-deployment"></ngio-ex>. named <code>simple-deployment</code>.
1. Add the "Simple deployment" sample files shown above. 1. Add the "Simple deployment" sample files shown above.

View File

@ -1,7 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
- var _boolean = 'truthy/falsy';
:marked :marked
You can display data by binding controls in an HTML template to properties of an Angular component. You can display data by binding controls in an HTML template to properties of an Angular component.
@ -19,7 +17,7 @@ figure.image-display
# Contents # Contents
* [Showing component properties with interpolation](#interpolation). * [Showing component properties with interpolation](#interpolation).
* [Showing !{_an} !{_array} property with NgFor](#ngFor). * [Showing an array property with NgFor](#ngFor).
* [Conditional display with NgIf](#ngIf). * [Conditional display with NgIf](#ngIf).
.l-sub-section .l-sub-section
@ -35,9 +33,9 @@ figure.image-display
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`. With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
Follow the [setup](setup.html) instructions for creating a new project Follow the [setup](setup.html) instructions for creating a new project
named <ngio-ex path="displaying-data"></ngio-ex>. named <code>displaying-data</code>.
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by Then modify the <code>app.component.ts</code> file by
changing the template and the body of the component. changing the template and the body of the component.
When you're done, it should look like this: When you're done, it should look like this:
@ -52,13 +50,12 @@ figure.image-display
+makeExcerpt('src/app/app.component.1.ts', 'template', '') +makeExcerpt('src/app/app.component.1.ts', 'template', '')
+ifDocsFor('ts') .l-sub-section
.l-sub-section :marked
:marked The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>). The backtick (<code>\`</code>)&mdash;which is *not* the same character as a single
The backtick (<code>\`</code>)&mdash;which is *not* the same character as a single quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
quote (`'`)&mdash;allows you to compose a string over several lines, which makes the HTML more readable.
HTML more readable.
:marked :marked
Angular automatically pulls the value of the `title` and `myHero` properties from the component and Angular automatically pulls the value of the `title` and `myHero` properties from the component and
@ -74,13 +71,13 @@ figure.image-display
Notice that you don't call **new** to create an instance of the `AppComponent` class. Notice that you don't call **new** to create an instance of the `AppComponent` class.
Angular is creating an instance for you. How? Angular is creating an instance for you. How?
The CSS `selector` in the `@Component` !{_decorator} specifies an element named `<my-app>`. The CSS `selector` in the `@Component` decorator specifies an element named `<my-app>`.
That element is a placeholder in the body of your `index.html` file: That element is a placeholder in the body of your `index.html` file:
+makeExcerpt('src/index.html', 'body') +makeExcerpt('src/index.html', 'body')
:marked :marked
When you bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), Angular looks for a `<my-app>` When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<my-app>`
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
inside the `<my-app>` tag. inside the `<my-app>` tag.
@ -88,9 +85,8 @@ figure.image-display
figure.image-display figure.image-display
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero") img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
+ifDocsFor('ts') :marked
:marked The next few sections review some of the coding choices in the app.
The next few sections review some of the coding choices in the app.
:marked :marked
## Template inline or template file? ## Template inline or template file?
@ -98,7 +94,7 @@ figure.image-display
You can store your component's template in one of two places. You can store your component's template in one of two places.
You can define it *inline* using the `template` property, or you can define You can define it *inline* using the `template` property, or you can define
the template in a separate HTML file and link to it in the template in a separate HTML file and link to it in
the component metadata using the `@Component` !{_decorator}'s `templateUrl` property. the component metadata using the `@Component` decorator's `templateUrl` property.
The choice between inline and separate HTML is a matter of taste, The choice between inline and separate HTML is a matter of taste,
circumstances, and organization policy. circumstances, and organization policy.
@ -107,22 +103,21 @@ figure.image-display
In either style, the template data bindings have the same access to the component's properties. In either style, the template data bindings have the same access to the component's properties.
+ifDocsFor('ts') :marked
:marked ## Constructor or variable initialization?
## Constructor or variable initialization?
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor: Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
+makeExcerpt('src/app/app-ctor.component.ts', 'class') +makeExcerpt('src/app/app-ctor.component.ts', 'class')
:marked :marked
This app uses more terse "variable assignment" style simply for brevity. This app uses more terse "variable assignment" style simply for brevity.
.l-main-section#ngFor .l-main-section#ngFor
:marked :marked
## Showing !{_an} !{_array} property with ***ngFor** ## Showing an array property with ***ngFor**
To display a list of heroes, begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}. To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
+makeExcerpt('src/app/app.component.2.ts', 'class') +makeExcerpt('src/app/app.component.2.ts', 'class')
@ -156,8 +151,8 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can In this case, `ngFor` is displaying an array, but `ngFor` can
repeat items for any [iterable](!{_iterableUrl}) object. repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
:marked :marked
Now the heroes appear in an unordered list. Now the heroes appear in an unordered list.
@ -171,38 +166,37 @@ figure.image-display
The app's code defines the data directly inside the component, which isn't best practice. The app's code defines the data directly inside the component, which isn't best practice.
In a simple demo, however, it's fine. In a simple demo, however, it's fine.
At the moment, the binding is to !{_an} !{_array} of strings. At the moment, the binding is to an array of strings.
In real applications, most bindings are to more specialized objects. In real applications, most bindings are to more specialized objects.
To convert this binding to use specialized objects, turn the !{_array} To convert this binding to use specialized objects, turn the array
of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `Hero` class. of hero names into an array of `Hero` objects. For that you'll need a `Hero` class.
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code: Create a new file in the `app` folder called `hero.ts` with the following code:
+makeExcerpt('src/app/hero.ts') +makeExcerpt('src/app/hero.ts')
block hero-class :marked
:marked You've defined a class with a constructor and two properties: `id` and `name`.
You've defined a class with a constructor and two properties: `id` and `name`.
It might not look like the class has properties, but it does. It might not look like the class has properties, but it does.
The declaration of the constructor parameters takes advantage of a TypeScript shortcut. The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
Consider the first parameter: Consider the first parameter:
+makeExcerpt('src/app/hero.ts ()', 'id') +makeExcerpt('src/app/hero.ts ()', 'id')
:marked :marked
That brief syntax does a lot: That brief syntax does a lot:
* Declares a constructor parameter and its type. * Declares a constructor parameter and its type.
* Declares a public property of the same name. * Declares a public property of the same name.
* Initializes that property with the corresponding argument when creating an instance of the class. * Initializes that property with the corresponding argument when creating an instance of the class.
.l-main-section .l-main-section
:marked :marked
## Using the Hero class ## Using the Hero class
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ !{_array} After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
of `Hero` objects: of `Hero` objects:
+makeExcerpt('src/app/app.component.3.ts', 'heroes') +makeExcerpt('src/app/app.component.3.ts', 'heroes')
@ -225,7 +219,7 @@ block hero-class
Let's change the example to display a message if there are more than three heroes. Let's change the example to display a message if there are more than three heroes.
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition. The Angular `ngIf` directive inserts or removes an element based on a _truthy/falsy_ condition.
To see it in action, add the following paragraph at the bottom of the template: To see it in action, add the following paragraph at the bottom of the template:
+makeExcerpt('src/app/app.component.ts', 'message') +makeExcerpt('src/app/app.component.ts', 'message')
@ -237,7 +231,7 @@ block hero-class
:marked :marked
The template expression inside the double quotes, The template expression inside the double quotes,
`*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}. `*ngIf="heros.length > 3"`, looks and behaves much like TypeScript.
When the component's list of heroes has more than three items, Angular adds the paragraph When the component's list of heroes has more than three items, Angular adds the paragraph
to the DOM and the message appears. If there are three or fewer items, Angular omits the to the DOM and the message appears. If there are three or fewer items, Angular omits the
paragraph, so no message appears. For more information, paragraph, so no message appears. For more information,
@ -250,8 +244,8 @@ block hero-class
big chunks of HTML with many data bindings. big chunks of HTML with many data bindings.
:marked :marked
Try it out. Because the !{_array} has four items, the message should appear. Try it out. Because the array has four items, the message should appear.
Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}. Go back into <code>app.component.ts"</code> and delete or comment out one of the elements from the hero array.
The browser should refresh automatically and the message should disappear. The browser should refresh automatically and the message should disappear.
.l-main-section .l-main-section
@ -259,16 +253,15 @@ block hero-class
## Summary ## Summary
Now you know how to use: Now you know how to use:
- **Interpolation** with double curly braces to display a component property. - **Interpolation** with double curly braces to display a component property.
- **ngFor** to display !{_an} !{_array} of items. - **ngFor** to display an array of items.
- A !{_Lang} class to shape the **model data** for your component and display properties of that model. - A TypeScript class to shape the **model data** for your component and display properties of that model.
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression. - **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
Here's the final code: Here's the final code:
block final-code +makeTabs(`displaying-data/ts/src/app/app.component.ts,
+makeTabs(`displaying-data/ts/src/app/app.component.ts, displaying-data/ts/src/app/hero.ts,
displaying-data/ts/src/app/hero.ts, displaying-data/ts/src/app/app.module.ts,
displaying-data/ts/src/app/app.module.ts, displaying-data/ts/src/main.ts`,
displaying-data/ts/src/main.ts`, 'final,,,',
'final,,,', 'src/app/app.component.ts, src/app/hero.ts, src/app/app.module.ts, main.ts')
'src/app/app.component.ts, src/app/hero.ts, src/app/app.module.ts, main.ts')

View File

@ -83,7 +83,7 @@ figure.image-display
## Setup ## Setup
Follow the [setup](setup.html) instructions for creating a new project Follow the [setup](setup.html) instructions for creating a new project
named <span ngio-ex>angular-forms</span>. named angular-forms.
## Create the Hero model class ## Create the Hero model class
@ -94,7 +94,7 @@ figure.image-display
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`) That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
and one optional field (`alterEgo`). and one optional field (`alterEgo`).
In the `!{_appDir}` directory, create the following file with the given content: In the `app` directory, create the following file with the given content:
+makeExample('src/app/hero.ts') +makeExample('src/app/hero.ts')

View File

@ -70,7 +70,7 @@ figure.image-display
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree. It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat. If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
All requests bubble up to the root <span if-docs="ts"><code>NgModule</code></span> injector that you configured with the `!{_bootstrapModule}` method. All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
.l-main-section .l-main-section
:marked :marked

View File

@ -1,8 +1,6 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var top="vertical-align:top"
figure figure
img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px") img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px")
@ -17,6 +15,7 @@ figure
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views. A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
<br class="l-clear-both"> <br class="l-clear-both">
<<<<<<< 93d3db1fd4f61c9c6078b2000dea164c87fa071c
+ifDocsFor('ts|js') +ifDocsFor('ts|js')
:marked :marked
## Contents ## Contents
@ -38,6 +37,28 @@ figure
* [Content projection](#content-projection) * [Content projection](#content-projection)
* [AfterContent hooks](#aftercontent-hooks) * [AfterContent hooks](#aftercontent-hooks)
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries) * [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
=======
:marked
## Contents
* [Component lifecycle hooks overview](#hooks-overview)
* [Lifecycle sequence](#hooks-purpose-timing)
* [Interfaces are optional (technically)](#interface-optional)
* [Other Angular lifecycle hooks](#other-lifecycle-hooks)
* [Lifecycle examples](#the-sample)
* [Peek-a-boo: all hooks](#peek-a-boo)
* [Spying OnInit and OnDestroy](#spy)
* [OnInit](#oninit)
* [OnDestroy](#ondestroy)
* [OnChanges](#onchanges)
* [DoCheck](#docheck)
* [AfterView](#afterview)
* [Abide by the unidirectional data flow rule](#wait-a-tick)
* [AfterContent](#aftercontent)
* [Content projection](#content-projection)
* [AfterContent hooks](#aftercontent-hooks)
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
>>>>>>> chore: remove dart remains
:marked :marked
Try the <live-example></live-example>. Try the <live-example></live-example>.
@ -72,7 +93,7 @@ table(width="100%")
th Hook th Hook
th Purpose and Timing th Purpose and Timing
tr(style=top) tr(style=vertical-align:top)
td <code>ngOnChanges()</code> td <code>ngOnChanges()</code>
td td
:marked :marked
@ -81,7 +102,7 @@ table(width="100%")
Called before `ngOnInit()` and whenever one or more data-bound input properties change. Called before `ngOnInit()` and whenever one or more data-bound input properties change.
tr(style=top) tr(style=vertical-align:top)
td <code>ngOnInit()</code> td <code>ngOnInit()</code>
td td
:marked :marked
@ -90,7 +111,7 @@ table(width="100%")
Called _once_, after the _first_ `ngOnChanges()`. Called _once_, after the _first_ `ngOnChanges()`.
tr(style=top) tr(style=vertical-align:top)
td <code>ngDoCheck()</code> td <code>ngDoCheck()</code>
td td
:marked :marked
@ -98,7 +119,7 @@ table(width="100%")
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`. Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
tr(style=top) tr(style=vertical-align:top)
td <code>ngAfterContentInit()</code> td <code>ngAfterContentInit()</code>
td td
:marked :marked
@ -108,7 +129,7 @@ table(width="100%")
_A component-only hook_. _A component-only hook_.
tr(style=top) tr(style=vertical-align:top)
td <code>ngAfterContentChecked()</code> td <code>ngAfterContentChecked()</code>
td td
:marked :marked
@ -118,7 +139,7 @@ table(width="100%")
_A component-only hook_. _A component-only hook_.
tr(style=top) tr(style=vertical-align:top)
td <code>ngAfterViewInit()</code> td <code>ngAfterViewInit()</code>
td td
:marked :marked
@ -128,7 +149,7 @@ table(width="100%")
_A component-only hook_. _A component-only hook_.
tr(style=top) tr(style=vertical-align:top)
td <code>ngAfterViewChecked()</code> td <code>ngAfterViewChecked()</code>
td td
:marked :marked
@ -138,7 +159,7 @@ table(width="100%")
_A component-only hook_. _A component-only hook_.
tr(style=top) tr(style=vertical-align:top)
td <code>ngOnDestroy</code> td <code>ngOnDestroy</code>
td td
:marked :marked
@ -147,24 +168,23 @@ table(width="100%")
Called _just before_ Angular destroys the directive/component. Called _just before_ Angular destroys the directive/component.
+ifDocsFor('ts|js') a#interface-optional
a#interface-optional .l-main-section
.l-main-section :marked
:marked ## Interfaces are optional (technically)
## Interfaces are optional (technically)
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective. The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
The JavaScript language doesn't have interfaces. The JavaScript language doesn't have interfaces.
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript. Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
Fortunately, they aren't necessary. Fortunately, they aren't necessary.
You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves. You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves.
Angular instead inspects directive and component classes and calls the hook methods *if they are defined*. Angular instead inspects directive and component classes and calls the hook methods *if they are defined*.
Angular finds and calls methods like `ngOnInit()`, with or without the interfaces. Angular finds and calls methods like `ngOnInit()`, with or without the interfaces.
Nonetheless, it's good practice to add interfaces to TypeScript directive classes Nonetheless, it's good practice to add interfaces to TypeScript directive classes
in order to benefit from strong typing and editor tooling. in order to benefit from strong typing and editor tooling.
a#other-lifecycle-hooks a#other-lifecycle-hooks
.l-main-section .l-main-section
@ -173,9 +193,6 @@ a#other-lifecycle-hooks
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks. Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
block other-angular-subsystems
//- N/A for TS.
:marked :marked
3rd party libraries might implement their hooks as well in order to give developers more 3rd party libraries might implement their hooks as well in order to give developers more
control over how these libraries are used. control over how these libraries are used.
@ -199,13 +216,13 @@ table(width="100%")
tr tr
th Component th Component
th Description th Description
tr(style=top) tr(style=vertical-align:top)
td <a href="#peek-a-boo">Peek-a-boo</a> td <a href="#peek-a-boo">Peek-a-boo</a>
td td
:marked :marked
Demonstrates every lifecycle hook. Demonstrates every lifecycle hook.
Each hook method writes to the on-screen log. Each hook method writes to the on-screen log.
tr(style=top) tr(style=vertical-align:top)
td <a href="#spy">Spy</a> td <a href="#spy">Spy</a>
td td
:marked :marked
@ -215,33 +232,33 @@ table(width="100%")
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
managed by the parent `SpyComponent`. managed by the parent `SpyComponent`.
tr(style=top) tr(style=vertical-align:top)
td <a href="#onchanges">OnChanges</a> td <a href="#onchanges">OnChanges</a>
td td
:marked :marked
See how Angular calls the `ngOnChanges()` hook with a `changes` object See how Angular calls the `ngOnChanges()` hook with a `changes` object
every time one of the component input properties changes. every time one of the component input properties changes.
Shows how to interpret the `changes` object. Shows how to interpret the `changes` object.
tr(style=top) tr(style=vertical-align:top)
td <a href="#docheck">DoCheck</a> td <a href="#docheck">DoCheck</a>
td td
:marked :marked
Implements an `ngDoCheck()` method with custom change detection. Implements an `ngDoCheck()` method with custom change detection.
See how often Angular calls this hook and watch it post changes to a log. See how often Angular calls this hook and watch it post changes to a log.
tr(style=top) tr(style=vertical-align:top)
td <a href="#afterview">AfterView</a> td <a href="#afterview">AfterView</a>
td td
:marked :marked
Shows what Angular means by a *view*. Shows what Angular means by a *view*.
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks. Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
tr(style=top) tr(style=vertical-align:top)
td <a href="#aftercontent">AfterContent</a> td <a href="#aftercontent">AfterContent</a>
td td
:marked :marked
Shows how to project external content into a component and Shows how to project external content into a component and
how to distinguish projected content from a component's view children. how to distinguish projected content from a component's view children.
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks. Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
tr(style=top) tr(style=vertical-align:top)
td Counter td Counter
td td
:marked :marked
@ -479,10 +496,8 @@ a#wait-a-tick
Both of these hooks fire _after_ the component's view has been composed. Both of these hooks fire _after_ the component's view has been composed.
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!). Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
block tick-methods The `LoggerService.tick_then()` postpones the log update
:marked for one turn of the browser's JavaScript cycle and that's just long enough.
The `LoggerService.tick_then()` postpones the log update
for one turn of the browser's JavaScript cycle and that's just long enough.
:marked :marked
Here's *AfterView* in action: Here's *AfterView* in action:

View File

@ -142,8 +142,8 @@ figure.image-display
* There will be one additional argument to the `transform` method for each parameter passed to the pipe. * There will be one additional argument to the `transform` method for each parameter passed to the pipe.
Your pipe has one such parameter: the `exponent`. Your pipe has one such parameter: the `exponent`.
* To tell Angular that this is a pipe, you apply the * To tell Angular that this is a pipe, you apply the
`@Pipe` #{_decorator}, which you import from the core Angular library. `@Pipe` decorator, which you import from the core Angular library.
* The `@Pipe` #{_decorator} allows you to define the * The `@Pipe` decorator allows you to define the
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier. pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
Your pipe's name is `exponentialStrength`. Your pipe's name is `exponentialStrength`.
@ -161,16 +161,14 @@ figure.image-display
figure.image-display figure.image-display
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster") img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
- var _decls = _docsFor == 'dart' ? 'pipes' : 'declarations';
- var _appMod = _docsFor == 'dart' ? '@Component' : 'AppModule';
:marked :marked
Note the following: Note the following:
* You use your custom pipe the same way you use built-in pipes. * You use your custom pipe the same way you use built-in pipes.
* You must include your pipe in the `!{_decls}` #{_array} of the `!{_appMod}`. * You must include your pipe in the `declarations` array of the `AppModule`.
.callout.is-helpful .callout.is-helpful
header Remember the !{_decls} #{_array} header Remember the declarations array
:marked :marked
You must manually register custom pipes. You must manually register custom pipes.
If you don't, Angular reports an error. If you don't, Angular reports an error.
@ -206,17 +204,17 @@ a#change-detection
### No pipe ### No pipe
In the next example, the component uses the default, aggressive change detection strategy to monitor and update In the next example, the component uses the default, aggressive change detection strategy to monitor and update
its display of every hero in the `heroes` #{_array}. Here's the template: its display of every hero in the `heroes` array. Here's the template:
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.') +makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
:marked :marked
The companion component class provides heroes, adds heroes into the #{_array}, and can reset the #{_array}. The companion component class provides heroes, adds heroes into the array, and can reset the array.
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.') +makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
:marked :marked
You can add heroes and Angular updates the display when you do. You can add heroes and Angular updates the display when you do.
If you click the `reset` button, Angular replaces `heroes` with a new #{_array} of the original heroes and updates the display. If you click the `reset` button, Angular replaces `heroes` with a new array of the original heroes and updates the display.
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well. If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
### Flying-heroes pipe ### Flying-heroes pipe
@ -237,15 +235,15 @@ a#change-detection
Notice how a hero is added: Notice how a hero is added:
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.') +makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
:marked :marked
You add the hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed. You add the hero into the `heroes` array. The reference to the array hasn't changed.
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*. It's the same array. That's all Angular cares about. From its perspective, *same array, no change, no display update*.
To fix that, create an #{_array} with the new hero appended and assign that to `heroes`. To fix that, create an array with the new hero appended and assign that to `heroes`.
This time Angular detects that the #{_array} reference has changed. This time Angular detects that the array reference has changed.
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero. It executes the pipe and updates the display with the new array, which includes the new flying hero.
If you *mutate* the #{_array}, no pipe is invoked and the display isn't updated; If you *mutate* the array, no pipe is invoked and the display isn't updated;
if you *replace* the #{_array}, the pipe executes and the display is updated. if you *replace* the array, the pipe executes and the display is updated.
The Flying Heroes application extends the The Flying Heroes application extends the
code with checkbox switches and additional displays to help you experience these effects. code with checkbox switches and additional displays to help you experience these effects.
@ -253,8 +251,8 @@ figure.image-display
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes") img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
:marked :marked
Replacing the #{_array} is an efficient way to signal Angular to update the display. Replacing the array is an efficient way to signal Angular to update the display.
When do you replace the #{_array}? When the data change. When do you replace the array? When the data change.
That's an easy rule to follow in *this* example That's an easy rule to follow in *this* example
where the only way to change the data is by adding a hero. where the only way to change the data is by adding a hero.
@ -289,7 +287,7 @@ figure.image-display
or a changed object reference (`Date`, `Array`, `Function`, `Object`). or a changed object reference (`Date`, `Array`, `Function`, `Object`).
Angular ignores changes within (composite) objects. Angular ignores changes within (composite) objects.
It won't call a pure pipe if you change an input month, add to an input #{_array}, or update an input object property. It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.
This may seem restrictive but it's also fast. This may seem restrictive but it's also fast.
An object reference check is fast&mdash;much faster than a deep check for An object reference check is fast&mdash;much faster than a deep check for
@ -335,28 +333,25 @@ figure.image-display
:marked :marked
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`. You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html'; +makeExcerpt('src/app/flying-heroes-impure.component.html (excerpt)', 'template-flying-heroes')
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
+makeExcerpt('src/app/flying-heroes' + _fnSuffix + ' (excerpt)', _region)
:marked :marked
The only substantive change is the pipe in the template. The only substantive change is the pipe in the template.
You can confirm in the <live-example></live-example> that the _flying heroes_ You can confirm in the <live-example></live-example> that the _flying heroes_
display updates as you add heroes, even when you mutate the `heroes` #{_array}. display updates as you add heroes, even when you mutate the `heroes` array.
- var _dollar = _docsFor === 'ts' ? '$' : '';
h3#async-pipe The impure #[i AsyncPipe] h3#async-pipe The impure #[i AsyncPipe]
:marked :marked
The Angular `AsyncPipe` is an interesting example of an impure pipe. The Angular `AsyncPipe` is an interesting example of an impure pipe.
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input The `AsyncPipe` accepts a `Promise` or `Observable` as input
and subscribes to the input automatically, eventually returning the emitted values. and subscribes to the input automatically, eventually returning the emitted values.
The `AsyncPipe` is also stateful. The `AsyncPipe` is also stateful.
The pipe maintains a subscription to the input `#{_Observable}` and The pipe maintains a subscription to the input `Observable` and
keeps delivering values from that `#{_Observable}` as they arrive. keeps delivering values from that `Observable` as they arrive.
This next example binds an `#{_Observable}` of message strings This next example binds an `Observable` of message strings
(`message#{_dollar}`) to a view with the `async` pipe. (`message$`) to a view with the `async` pipe.
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts') +makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
@ -375,7 +370,7 @@ h3#async-pipe The impure #[i AsyncPipe]
If you're not careful, this pipe will punish the server with requests. If you're not careful, this pipe will punish the server with requests.
In the following code, the pipe only calls the server when the request URL changes and it caches the server response. In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
The code<span if-docs="ts"> uses the [Angular http](server-communication.html) client to retrieve data</span>: The code uses the [Angular http](server-communication.html) client to retrieve data</span>:
+makeExample('src/app/fetch-json.pipe.ts') +makeExample('src/app/fetch-json.pipe.ts')
:marked :marked

View File

@ -1543,8 +1543,8 @@ a#milestone-4
Begin by imitating the heroes feature: Begin by imitating the heroes feature:
- Delete the placeholder crisis center file. - Delete the placeholder crisis center file.
- Create !{_an} `!{_appDir}/crisis-center` folder. - Create an `app/crisis-center` folder.
- Copy the files from `!{_appDir}/heroes` into the new crisis center folder. - Copy the files from `app/heroes` into the new crisis center folder.
- In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises". - In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes: You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes:
@ -1560,7 +1560,7 @@ a#milestone-4
:marked :marked
In keeping with the In keeping with the
<a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" target="_blank" title="Separation of Concerns">*Separation of Concerns* principle</a>, <a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" target="_blank" title="Separation of Concerns">*Separation of Concerns* principle</a>,
changes to the *Crisis Center* won't affect the `!{_AppModuleVsAppComp}` or changes to the *Crisis Center* won't affect the `AppModule` or
any other feature's component. any other feature's component.
a#crisis-child-routes a#crisis-child-routes
:marked :marked
@ -2288,7 +2288,7 @@ a#CanDeactivate
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '') +makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
:marked :marked
Add the `Guard` to the main `AppRoutingModule` `providers` !{_array} so the Add the `Guard` to the main `AppRoutingModule` `providers` array so the
`Router` can inject it during the navigation process. `Router` can inject it during the navigation process.
+makeExample('src/app/app-routing.module.4.ts', '', '') +makeExample('src/app/app-routing.module.4.ts', '', '')
@ -2350,7 +2350,7 @@ a#fetch-before-navigating
Import this resolver in the `crisis-center-routing.module.ts` Import this resolver in the `crisis-center-routing.module.ts`
and add a `resolve` object to the `CrisisDetailComponent` route configuration. and add a `resolve` object to the `CrisisDetailComponent` route configuration.
Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` !{_array}. Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` array.
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.4.ts (resolver)', 'crisis-detail-resolver') +makeExcerpt('src/app/crisis-center/crisis-center-routing.module.4.ts (resolver)', 'crisis-detail-resolver')
@ -2549,7 +2549,7 @@ a#can-load-guard
The `checkLogin()` method redirects to that URL once the user has logged in. The `checkLogin()` method redirects to that URL once the user has logged in.
Now import the `AuthGuard` into the `AppRoutingModule` and add the `AuthGuard` to the `canLoad` Now import the `AuthGuard` into the `AppRoutingModule` and add the `AuthGuard` to the `canLoad`
!{_array} for the `admin` route. array for the `admin` route.
The completed admin route looks like this: The completed admin route looks like this:
+makeExample('router/ts/src/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)') +makeExample('router/ts/src/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)')

View File

@ -7,18 +7,17 @@ block includes
this user?_) and authorization (_What can this user do?_). this user?_) and authorization (_What can this user do?_).
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project). For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
.l-main-section
:marked
# Contents
+ifDocsFor('ts') * [Reporting vulnerabilities](#report-issues).
.l-main-section * [Best practices](#best-practices).
:marked * [Preventing cross-site scripting (XSS)](#xss).
# Contents * [Trusting safe values](#bypass-security-apis).
* [HTTP-Level vulnerabilities](#http).
* [Reporting vulnerabilities](#report-issues). * [Auditing Angular applications](#code-review).
* [Best practices](#best-practices).
* [Preventing cross-site scripting (XSS)](#xss).
* [Trusting safe values](#bypass-security-apis).
* [HTTP-Level vulnerabilities](#http).
* [Auditing Angular applications](#code-review).
:marked :marked
You can run the <live-example></live-example> in Plunker and download the code from there. You can run the <live-example></live-example> in Plunker and download the code from there.
@ -108,14 +107,13 @@ h2#xss Preventing cross-site scripting (XSS)
+makeExcerpt('src/app/inner-html-binding.component.ts', 'class') +makeExcerpt('src/app/inner-html-binding.component.ts', 'class')
block html-sanitization :marked
:marked Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>` tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
figure.image-display figure.image-display
img(src='/resources/images/devguide/security/binding-inner-html.png' img(src='/resources/images/devguide/security/binding-inner-html.png'
alt='A screenshot showing interpolated and bound HTML values') alt='A screenshot showing interpolated and bound HTML values')
:marked :marked
### Avoid direct use of the DOM APIs ### Avoid direct use of the DOM APIs
@ -152,138 +150,136 @@ block html-sanitization
the server. Don't generate Angular templates on the server side using a templating language; doing this the server. Don't generate Angular templates on the server side using a templating language; doing this
carries a high risk of introducing template-injection vulnerabilities. carries a high risk of introducing template-injection vulnerabilities.
block bypass-security-apis .l-main-section
.l-main-section h2#bypass-security-apis Trusting safe values
h2#bypass-security-apis Trusting safe values :marked
:marked Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these situations, you can tell Angular that you inspected a value, checked how it was generated, and made
situations, you can tell Angular that you inspected a value, checked how it was generated, and made sure it will always be secure. But *be careful*. If you trust a value that might be malicious, you
sure it will always be secure. But *be careful*. If you trust a value that might be malicious, you are introducing a security vulnerability into your application. If in doubt, find a professional
are introducing a security vulnerability into your application. If in doubt, find a professional security reviewer.
security reviewer.
To mark a value as trusted, inject `DomSanitizer` and call one of the To mark a value as trusted, inject `DomSanitizer` and call one of the
following methods: following methods:
* `bypassSecurityTrustHtml` * `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript` * `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle` * `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl` * `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl` * `bypassSecurityTrustResourceUrl`
Remember, whether a value is safe depends on context, so choose the right context for Remember, whether a value is safe depends on context, so choose the right context for
your intended use of the value. Imagine that the following template needs to bind a URL to a your intended use of the value. Imagine that the following template needs to bind a URL to a
`javascript:alert(...)` call: `javascript:alert(...)` call:
+makeExcerpt('src/app/bypass-security.component.html', 'URL') +makeExcerpt('src/app/bypass-security.component.html', 'URL')
:marked :marked
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
in development mode, logs this action to the console. To prevent in development mode, logs this action to the console. To prevent
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call: this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url') +makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url')
figure.image-display figure.image-display
img(src='/resources/images/devguide/security/bypass-security-component.png' img(src='/resources/images/devguide/security/bypass-security-component.png'
alt='A screenshot showing an alert box created from a trusted URL') alt='A screenshot showing an alert box created from a trusted URL')
:marked :marked
If you need to convert user input into a trusted value, use a If you need to convert user input into a trusted value, use a
controller method. The following template allows users to enter a YouTube video ID and load the controller method. The following template allows users to enter a YouTube video ID and load the
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users
could execute. So call a method on the controller to construct a trusted video URL, which causes could execute. So call a method on the controller to construct a trusted video URL, which causes
Angular to allow binding into `<iframe src>`: Angular to allow binding into `<iframe src>`:
+makeExcerpt('src/app/bypass-security.component.html', 'iframe') +makeExcerpt('src/app/bypass-security.component.html', 'iframe')
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url') +makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
block http .l-main-section
.l-main-section h2#http HTTP-level vulnerabilities
h2#http HTTP-level vulnerabilities :marked
:marked Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily on the server side, but Angular provides helpers to make integration on the client side easier.
on the server side, but Angular provides helpers to make integration on the client side easier.
h3#xsrf Cross-site request forgery h3#xsrf Cross-site request forgery
:marked :marked
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
to the application's web server (such as `example-bank.com`). to the application's web server (such as `example-bank.com`).
Assume the user is logged into the application at `example-bank.com`. Assume the user is logged into the application at `example-bank.com`.
The user opens an email and clicks a link to `evil.com`, which opens in a new tab. The user opens an email and clicks a link to `evil.com`, which opens in a new tab.
The `evil.com` page immediately sends a malicious request to `example-bank.com`. The `evil.com` page immediately sends a malicious request to `example-bank.com`.
Perhaps it's a request to transfer money from the user's account to the attacker's account. Perhaps it's a request to transfer money from the user's account to the attacker's account.
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request. The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate
request from the application and the forged request from `evil.com`. request from the application and the forged request from `evil.com`.
To prevent this, the application must ensure that a user request originates from the real To prevent this, the application must ensure that a user request originates from the real
application, not from a different site. application, not from a different site.
The server and client must cooperate to thwart this attack. The server and client must cooperate to thwart this attack.
In a common anti-XSRF technique, the application server sends a randomly In a common anti-XSRF technique, the application server sends a randomly
generated authentication token in a cookie. generated authentication token in a cookie.
The client code reads the cookie and adds a custom request header with the token in all subsequent requests. The client code reads the cookie and adds a custom request header with the token in all subsequent requests.
The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match. The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
This technique is effective because all browsers implement the _same origin policy_. Only code from the website This technique is effective because all browsers implement the _same origin policy_. Only code from the website
on which cookies are set can read the cookies from that site and set custom headers on requests to that site. on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't. That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`. Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
The default `CookieXSRFStrategy` is turned on automatically. The default `CookieXSRFStrategy` is turned on automatically.
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
sets a header named `X-XSRF-TOKEN` with the value of that cookie. sets a header named `X-XSRF-TOKEN` with the value of that cookie.
The server must do its part by setting the The server must do its part by setting the
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header. includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
cryptographically secure random number generator, and expire in a day or two. cryptographically secure random number generator, and expire in a day or two.
Your server may use a different cookie or header name for this purpose. Your server may use a different cookie or header name for this purpose.
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values. An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
code-example(language="typescript"). code-example(language="typescript").
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') } { provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
:marked :marked
Or you can implement and provide an entirely custom `XSRFStrategy`: Or you can implement and provide an entirely custom `XSRFStrategy`:
code-example(language="typescript"). code-example(language="typescript").
{ provide: XSRFStrategy, useClass: MyXSRFStrategy } { provide: XSRFStrategy, useClass: MyXSRFStrategy }
:marked :marked
For information about CSRF at the Open Web Application Security Project (OWASP), see For information about CSRF at the Open Web Application Security Project (OWASP), see
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and
<a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>. <a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
The Stanford University paper The Stanford University paper
<a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail. <a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail.
See also Dave Smith's easy-to-understand See also Dave Smith's easy-to-understand
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>. <a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
h3#xssi Cross-site script inclusion (XSSI) h3#xssi Cross-site script inclusion (XSSI)
:marked :marked
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
read data from a JSON API. The attack works on older browsers by overriding native JavaScript read data from a JSON API. The attack works on older browsers by overriding native JavaScript
object constructors, and then including an API URL using a `<script>` tag. object constructors, and then including an API URL using a `<script>` tag.
This attack is only successful if the returned JSON is executable as JavaScript. Servers can This attack is only successful if the returned JSON is executable as JavaScript. Servers can
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
well-known string `")]}',\n"`. well-known string `")]}',\n"`.
Angular's `Http` library recognizes this convention and automatically strips the string Angular's `Http` library recognizes this convention and automatically strips the string
`")]}',\n"` from all responses before further parsing. `")]}',\n"` from all responses before further parsing.
For more information, see the XSSI section of this [Google web security blog For more information, see the XSSI section of this [Google web security blog
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html). post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
.l-main-section .l-main-section
h2#code-review Auditing Angular applications h2#code-review Auditing Angular applications

View File

@ -1,8 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _Http = 'Http'; // Angular `Http` library name.
- var _Angular_Http = 'Angular <code>Http</code>'
- var _Angular_http_library = 'Angular HTTP library'
:marked :marked
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication. [HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
@ -16,7 +13,7 @@ block includes
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support [JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
The !{_Angular_http_library} simplifies application programming with the **XHR** and **JSONP** APIs. The Angular HTTP library simplifies application programming with the **XHR** and **JSONP** APIs.
# Contents # Contents
* [Demos](#demos) * [Demos](#demos)
@ -24,7 +21,7 @@ block includes
* [The Tour of Heroes *HTTP* client demo](#http-client) * [The Tour of Heroes *HTTP* client demo](#http-client)
- [The `HeroListComponent` class](#HeroListComponent) - [The `HeroListComponent` class](#HeroListComponent)
* [Fetch data with `http.get()`](#fetch-data) * [Fetch data with `http.get()`](#fetch-data)
<li if-docs="ts"> [RxJS library](#rxjs-library) <li>[RxJS library](#rxjs-library)
<ul> <ul>
<li> [Enable RxJS operators](#enable-rxjs-operators)</li> <li> [Enable RxJS operators](#enable-rxjs-operators)</li>
</ul> </ul>
@ -38,16 +35,16 @@ block includes
- [Headers](#headers) - [Headers](#headers)
- [JSON results](#json-results) - [JSON results](#json-results)
<ul><li if-docs="ts"> [Fall back to promises](#promises)</ul> <ul><li> [Fall back to promises](#promises)</ul>
* [Cross-Origin Requests: Wikipedia example](#cors) * [Cross-Origin Requests: Wikipedia example](#cors)
<ul if-docs="ts"> <ul>
<li> [Search Wikipedia](#search-wikipedia)</li> <li> [Search Wikipedia](#search-wikipedia)</li>
<li> [Search parameters](#search-parameters)</li> <li> [Search parameters](#search-parameters)</li>
<li> [The WikiComponent](#wikicomponent)</li> <li> [The WikiComponent](#wikicomponent)</li>
</ul> </ul>
* [A wasteful app](#wasteful-app) * [A wasteful app](#wasteful-app)
<li if-docs="ts"> [More fun with Observables](#more-observables) <li> [More fun with Observables](#more-observables)
<ul> <ul>
<li> [Create a stream of search terms](#create-stream)</li> <li> [Create a stream of search terms](#create-stream)</li>
<li> [Listen for search terms](#listen-for-search-terms)</li> <li> [Listen for search terms](#listen-for-search-terms)</li>
@ -69,7 +66,7 @@ a#demos
block demos-list block demos-list
:marked :marked
- [The Tour of Heroes *HTTP* client demo](#http-client). - [The Tour of Heroes *HTTP* client demo](#http-client).
- [Fall back to !{_Promise}s](#promises). - [Fall back to Promises](#promises).
- [Cross-Origin Requests: Wikipedia example](#cors). - [Cross-Origin Requests: Wikipedia example](#cors).
- [More fun with Observables](#more-observables). - [More fun with Observables](#more-observables).
@ -83,18 +80,17 @@ block demos-list
First, configure the application to use server communication facilities. First, configure the application to use server communication facilities.
The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol. The Angular <code>Http</code> client communicates with the server using a familiar HTTP request/response protocol.
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}. The `Http` client is one of a family of services in the Angular HTTP library.
+ifDocsFor('ts') .l-sub-section
.l-sub-section :marked
:marked When importing from the `@angular/http` module, SystemJS knows how to load services from
When importing from the `@angular/http` module, SystemJS knows how to load services from the Angular HTTP library
the !{_Angular_http_library} because the `systemjs.config.js` file maps to that module name.
because the `systemjs.config.js` file maps to that module name.
:marked :marked
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system. Before you can use the `Http` client, you need to register it as a service provider with the dependency injection system.
.l-sub-section .l-sub-section
:marked :marked
@ -108,7 +104,7 @@ block demos-list
block http-providers block http-providers
:marked :marked
Begin by importing the necessary members. Begin by importing the necessary members.
The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement. The newcomers are the `HttpModule` and the `JsonpModule` from the Angular HTTP library. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`. To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
.l-sub-section .l-sub-section
@ -123,7 +119,7 @@ block http-providers
The first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application. The first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server. This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
The app uses the !{_Angular_Http} client to communicate via **XMLHttpRequest (XHR)**. The app uses the Angular <code>Http</code> client to communicate via **XMLHttpRequest (XHR)**.
It works like this: It works like this:
figure.image-display figure.image-display
@ -152,7 +148,7 @@ a#HeroListComponent
Angular [injects](dependency-injection.html) a `HeroService` into the constructor Angular [injects](dependency-injection.html) a `HeroService` into the constructor
and the component calls that service to fetch and save data. and the component calls that service to fetch and save data.
The component **does not talk directly to the !{_Angular_Http} client**. The component **does not talk directly to the Angular <code>Http</code> client**.
The component doesn't know or care how it gets the data. The component doesn't know or care how it gets the data.
It delegates to the `HeroService`. It delegates to the `HeroService`.
@ -169,7 +165,7 @@ a#HeroListComponent
(especially calling a remote server) is handled in a separate method. (especially calling a remote server) is handled in a separate method.
block getheroes-and-create block getheroes-and-create
:marked :marked
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server. The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the Angular <code>Http</code> client fetched from the server.
Think of an `Observable` as a stream of events published by some source. Think of an `Observable` as a stream of events published by some source.
To listen for events in this stream, ***subscribe*** to the `Observable`. To listen for events in this stream, ***subscribe*** to the `Observable`.
@ -188,15 +184,15 @@ a#HeroService
returning mock heroes in a service like this one: returning mock heroes in a service like this one:
+makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".") +makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".")
:marked :marked
You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service: You can revise that `HeroService` to get the heroes from the server using the Angular <code>Http</code> client service:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'v1', 'src/app/toh/hero.service.ts (revised)') +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'v1', 'src/app/toh/hero.service.ts (revised)')
:marked :marked
Notice that the !{_Angular_Http} client service is Notice that the Angular <code>Http</code> client service is
[injected](dependency-injection.html) into the `HeroService` constructor. [injected](dependency-injection.html) into the `HeroService` constructor.
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor') +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor')
:marked :marked
Look closely at how to call `!{_priv}http.get`: Look closely at how to call `http.get`:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'http-get', 'src/app/toh/hero.service.ts (getHeroes)')(format=".") +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'http-get', 'src/app/toh/hero.service.ts (getHeroes)')(format=".")
:marked :marked
You pass the resource URL to `get` and it calls the server which returns heroes. You pass the resource URL to `get` and it calls the server which returns heroes.
@ -208,46 +204,45 @@ a#HeroService
Alternatively, you can temporarily target a JSON file by changing the endpoint URL: Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".") +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".")
+ifDocsFor('ts') :marked
:marked <a id="rxjs"></a>
<a id="rxjs"></a> If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a
If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>.
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>. You'd expect to chain a call to `then()` and extract the heroes.
You'd expect to chain a call to `then()` and extract the heroes. Instead you're calling a `map()` method.
Instead you're calling a `map()` method. Clearly this is not a promise.
Clearly this is not a promise.
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
and `map()` is one of the RxJS *operators*. and `map()` is one of the RxJS *operators*.
a#rxjs-library a#rxjs-library
.l-main-section .l-main-section
:marked :marked
## RxJS library ## RxJS library
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a> <a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
is a third party library, endorsed by Angular, that implements the is a third party library, endorsed by Angular, that implements the
<a href="https://www.youtube.com/watch?v=VLGCCpOWFFw" target="_blank" title="Video: Rob Wormald on Observables"><b>asynchronous Observable</b></a> pattern. <a href="https://www.youtube.com/watch?v=VLGCCpOWFFw" target="_blank" title="Video: Rob Wormald on Observables"><b>asynchronous Observable</b></a> pattern.
All of the Developer Guide samples have installed the RxJS npm package All of the Developer Guide samples have installed the RxJS npm package
because Observables are used widely in Angular applications. because Observables are used widely in Angular applications.
_This_ app needs it when working with the HTTP client. _This_ app needs it when working with the HTTP client.
But you must take a critical extra step to make RxJS Observables usable: But you must take a critical extra step to make RxJS Observables usable:
_you must import the RxJS operators individually_. _you must import the RxJS operators individually_.
### Enable RxJS operators ### Enable RxJS operators
The RxJS library is large. The RxJS library is large.
Size matters when building a production application and deploying it to mobile devices. Size matters when building a production application and deploying it to mobile devices.
You should include only necessary features. You should include only necessary features.
Each code file should add the operators it needs by importing from an RxJS library. Each code file should add the operators it needs by importing from an RxJS library.
The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this. The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this.
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".") +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
.l-main-section .l-main-section
a#extract-data a#extract-data
:marked :marked
## Process the response object ## Process the response object
Remember that the `getHeroes()` method used an `!{_priv}extractData()` helper method to map the `!{_priv}http.get` response object to heroes: Remember that the `getHeroes()` method used an `extractData()` helper method to map the `http.get` response object to heroes:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'extract-data', 'src/app/toh/hero.service.ts (excerpt)')(format=".") +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'extract-data', 'src/app/toh/hero.service.ts (excerpt)')(format=".")
:marked :marked
The `response` object doesn't hold the data in a form the app can use directly. The `response` object doesn't hold the data in a form the app can use directly.
@ -271,7 +266,7 @@ block parse-json
.l-sub-section .l-sub-section
:marked :marked
Don't expect the decoded JSON to be the heroes !{_array} directly. Don't expect the decoded JSON to be the heroes array directly.
This server always wraps JSON results in an object with a `data` This server always wraps JSON results in an object with a `data`
property. You have to unwrap it to get the heroes. property. You have to unwrap it to get the heroes.
This is conventional web API behavior, driven by This is conventional web API behavior, driven by
@ -291,14 +286,13 @@ a#no-return-response-object
The component that calls the `HeroService` only wants heroes and is kept separate The component that calls the `HeroService` only wants heroes and is kept separate
from getting them, the code dealing with where they come from, and the response object. from getting them, the code dealing with where they come from, and the response object.
+ifDocsFor('ts') .callout.is-important
.callout.is-important header HTTP GET is delayed
header HTTP GET is delayed :marked
:marked The `http.get` does **not send the request just yet.** This Observable is
The `!{_priv}http.get` does **not send the request just yet.** This Observable is [*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables),
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables), which means that the request won't go out until something *subscribes* to the Observable.
which means that the request won't go out until something *subscribes* to the Observable. That *something* is the [HeroListComponent](#subscribe).
That *something* is the [HeroListComponent](#subscribe).
a#error-handling a#error-handling
:marked :marked
@ -324,7 +318,7 @@ a#hero-list-component
h3 #[b HeroListComponent] error handling h3 #[b HeroListComponent] error handling
block hlc-error-handling block hlc-error-handling
:marked :marked
Back in the `HeroListComponent`, in `!{_priv}heroService.getHeroes()`, Back in the `HeroListComponent`, in `heroService.getHeroes()`,
the `subscribe` function has a second function parameter to handle the error message. the `subscribe` function has a second function parameter to handle the error message.
It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template. It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template.
@ -369,8 +363,7 @@ code-example(format="." language="javascript").
Now that you know how the API works, implement `create()` as follows: Now that you know how the API works, implement `create()` as follows:
+ifDocsFor('ts') +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
+makeExcerpt('src/app/toh/hero.service.ts', 'create') +makeExcerpt('src/app/toh/hero.service.ts', 'create')
a#headers a#headers
@ -379,19 +372,18 @@ a#headers
In the `headers` object, the `Content-Type` specifies that the body represents JSON. In the `headers` object, the `Content-Type` specifies that the body represents JSON.
+ifDocsFor('ts') :marked
:marked Next, the `headers` object is used to configure the `options` object. The `options`
Next, the `headers` object is used to configure the `options` object. The `options` object is a new instance of `RequestOptions`, a class that allows you to specify
object is a new instance of `RequestOptions`, a class that allows you to specify certain settings when instantiating a request. In this way, [headers](../api/http/index/Headers-class.html) is one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
certain settings when instantiating a request. In this way, [headers](../api/http/index/Headers-class.html) is one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
In the `return` statement, `options` is the *third* argument of the `post()` method, as shown above. In the `return` statement, `options` is the *third* argument of the `post()` method, as shown above.
a#json-results a#json-results
:marked :marked
### JSON results ### JSON results
As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](#extract-data) As with `getHeroes()`, use the `extractData()` helper to [extract the data](#extract-data)
from the response. from the response.
block hero-list-comp-add-hero block hero-list-comp-add-hero
@ -400,63 +392,62 @@ block hero-list-comp-add-hero
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user. When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
+makeExample('server-communication/ts/src/app/toh/hero-list.component.ts', 'addHero', 'src/app/toh/hero-list.component.ts (addHero)')(format=".") +makeExample('server-communication/ts/src/app/toh/hero-list.component.ts', 'addHero', 'src/app/toh/hero-list.component.ts (addHero)')(format=".")
+ifDocsFor('ts') h2#promises Fall back to promises
h2#promises Fall back to promises :marked
Although the Angular `http` client API returns an `Observable<Response>` you can turn it into a
[`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
It's easy to do, and in simple cases, a Promise-based version looks much
like the Observable-based version.
.l-sub-section
:marked :marked
Although the Angular `http` client API returns an `Observable<Response>` you can turn it into a While Promises may be more familiar, Observables have many advantages.
[`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
It's easy to do, and in simple cases, a Promise-based version looks much
like the Observable-based version.
.l-sub-section
:marked
While Promises may be more familiar, Observables have many advantages.
:marked
Here is a comparison of the `HeroService` using Promises versus Observables,
highlighting just the parts that are different.
+makeTabs(
'server-communication/ts/src/app/toh/hero.service.promise.ts,server-communication/ts/src/app/toh/hero.service.ts',
'methods, methods',
'src/app/toh/hero.service.promise.ts (promise-based), src/app/toh/hero.service.ts (observable-based)')
:marked
You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in
this example.
Alternatively, you can call `toPromise(success, fail)`. The Observable's `map` callback moves to the
first *success* parameter and its `catch` callback to the second *fail* parameter
in this pattern: `.toPromise(this.extractData, this.handleError)`.
The `errorHandler` forwards an error message as a failed `Promise` instead of a failed `Observable`.
The diagnostic *log to console* is just one more `then()` in the Promise chain.
You have to adjust the calling component to expect a `Promise` instead of an `Observable`:
+makeTabs(
'server-communication/ts/src/app/toh/hero-list.component.promise.ts, server-communication/ts/src/app/toh/hero-list.component.ts',
'methods, methods',
'src/app/toh/hero-list.component.promise.ts (promise-based), src/app/toh/hero-list.component.ts (observable-based)')
:marked
The only obvious difference is that you call `then()` on the returned Promise instead of `subscribe`.
Both methods take the same functional arguments.
.l-sub-section
:marked :marked
Here is a comparison of the `HeroService` using Promises versus Observables, The less obvious but critical difference is that these two methods return very different results.
highlighting just the parts that are different.
+makeTabs(
'server-communication/ts/src/app/toh/hero.service.promise.ts,server-communication/ts/src/app/toh/hero.service.ts',
'methods, methods',
'src/app/toh/hero.service.promise.ts (promise-based), src/app/toh/hero.service.ts (observable-based)')
:marked
You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in
this example.
Alternatively, you can call `toPromise(success, fail)`. The Observable's `map` callback moves to the The Promise-based `then()` returns another Promise. You can keep chaining
first *success* parameter and its `catch` callback to the second *fail* parameter more `then()` and `catch()` calls, getting a new promise each time.
in this pattern: `.toPromise(this.extractData, this.handleError)`.
The `errorHandler` forwards an error message as a failed `Promise` instead of a failed `Observable`. The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`.
It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again.
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
The diagnostic *log to console* is just one more `then()` in the Promise chain. To understand the implications and consequences of subscriptions,
watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE)
You have to adjust the calling component to expect a `Promise` instead of an `Observable`: or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
+makeTabs(
'server-communication/ts/src/app/toh/hero-list.component.promise.ts, server-communication/ts/src/app/toh/hero-list.component.ts',
'methods, methods',
'src/app/toh/hero-list.component.promise.ts (promise-based), src/app/toh/hero-list.component.ts (observable-based)')
:marked
The only obvious difference is that you call `then()` on the returned Promise instead of `subscribe`.
Both methods take the same functional arguments.
.l-sub-section
:marked
The less obvious but critical difference is that these two methods return very different results.
The Promise-based `then()` returns another Promise. You can keep chaining
more `then()` and `catch()` calls, getting a new promise each time.
The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`.
It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again.
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
To understand the implications and consequences of subscriptions,
watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE)
or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
h2#cors Cross-Origin Requests: Wikipedia example h2#cors Cross-Origin Requests: Wikipedia example
:marked :marked
You just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service. You just learned how to make `XMLHttpRequests` using the Angular <code>Http</code> service.
This is the most common approach to server communication, but it doesn't work in all scenarios. This is the most common approach to server communication, but it doesn't work in all scenarios.
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page. For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
@ -488,7 +479,7 @@ figure.image-display
block wikipedia-jsonp+ block wikipedia-jsonp+
:marked :marked
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter. Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter.
The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts you to `GET` requests. The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts you to `GET` requests.
All other HTTP methods throw an error because `JSONP` is a read-only facility. All other HTTP methods throw an error because `JSONP` is a read-only facility.
As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
@ -536,8 +527,8 @@ block wikipedia-jsonp+
The template presents an `<input>` element *search box* to gather search terms from the user, The template presents an `<input>` element *search box* to gather search terms from the user,
and calls a `search(term)` method after each `keyup` event. and calls a `search(term)` method after each `keyup` event.
The component's `search(term)` method delegates to the `WikipediaService`, which returns an The component's `search(term)` method delegates to the `WikipediaService`, which returns an
Observable !{_array} of string results (`Observable<string[]>`). Observable array of string results (`Observable<string[]>`).
Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`, Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`,
the app forwards the Observable result to the template (via `items`) where the `async` pipe the app forwards the Observable result to the template (via `items`) where the `async` pipe
in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe) in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe)
@ -706,8 +697,8 @@ a#in-mem-web-api
## Appendix: Tour of Heroes _in-memory web api_ ## Appendix: Tour of Heroes _in-memory web api_
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file: If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
- var _heroesJsonPath = (_docsFor == 'dart' ? 'web' : 'src/app') + '/heroes.json';
+makeJson('server-communication/' + _docsFor + '/' + _heroesJsonPath, null, _heroesJsonPath)(format=".") +makeJson('server-communication/ts/src/app/heroes.json', null, 'src/app/heroes.json')(format=".")
.l-sub-section .l-sub-section
:marked :marked
You wrap the heroes array in an object with a `data` property for the same reason that a data server does: You wrap the heroes array in an object with a `data` property for the same reason that a data server does:
@ -717,7 +708,6 @@ a#in-mem-web-api
You'd set the endpoint to the JSON file like this: You'd set the endpoint to the JSON file like this:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json', 'src/app/toh/hero.service.ts')(format=".") +makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json', 'src/app/toh/hero.service.ts')(format=".")
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
:marked :marked
The *get heroes* scenario would work, The *get heroes* scenario would work,
but since the app can't save changes to a JSON file, it needs a web API server. but since the app can't save changes to a JSON file, it needs a web API server.
@ -736,9 +726,9 @@ a#in-mem-web-api
for configuration options, default behaviors, and limitations. for configuration options, default behaviors, and limitations.
:marked :marked
The in-memory web API gets its data from !{_a_ca_class_with} a `createDb()` The in-memory web API gets its data from a custom application class with a `createDb()`
method that returns a map whose keys are collection names and whose values method that returns a map whose keys are collection names and whose values
are !{_array}s of objects in those collections. are arrays of objects in those collections.
Here's the class for this sample, based on the JSON data: Here's the class for this sample, based on the JSON data:
+makeExample('server-communication/ts/src/app/hero-data.ts', null, 'src/app/hero-data.ts')(format=".") +makeExample('server-communication/ts/src/app/hero-data.ts', null, 'src/app/hero-data.ts')(format=".")
@ -766,7 +756,7 @@ block redirect-to-web-api
The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_, The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
while setting the metadata for the root `AppModule`. Don't call it again. while setting the metadata for the root `AppModule`. Don't call it again.
:marked :marked
Here is the final, revised version of <span ngio-ex>src/app/app.module.ts</span>, demonstrating these steps. Here is the final, revised version of <code>src/app/app.module.ts</code>, demonstrating these steps.
+makeExcerpt('src/app/app.module.ts') +makeExcerpt('src/app/app.module.ts')
.alert.is-important .alert.is-important

View File

@ -1,32 +1,24 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _prereq = 'node and npm';
- var _playground = 'playground';
- var _Install = 'Install';
//- npm commands
- var _install = 'install';
- var _start = 'start';
a#develop-locally a#develop-locally
:marked :marked
## Setup a local development environment ## Setup a local development environment
<span if-docs="ts">
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_. The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
It's not where you'd develop a real application. It's not where you'd develop a real application.
You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular. You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
</span>
Setting up a new project on your machine is quick and easy with the **QuickStart seed**, Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
maintained [on github](!{_qsRepo} "Install the github QuickStart repo"). maintained [on github](https://github.com/angular/quickstart "Install the github QuickStart repo").
:marked :marked
Make sure you have [!{_prereq} installed](#install-prerequisites "What if you don't have !{_prereq}?"). Make sure you have [node and npm installed](#install-prerequisites "What if you don't have node and npm?").
Then ... Then ...
1. Create a project folder (you can call it `quickstart` and rename it later). 1. Create a project folder (you can call it `quickstart` and rename it later).
1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder. 1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder.
1. !{_Install} [!{_npm}](#install-prerequisites "What if you don't have !{_prereq}?") packages. 1. Install [npm](#install-prerequisites "What if you don't have node and npm?") packages.
1. Run `!{_npm} !{_start}` to launch the sample application. 1. Run `npm start` to launch the sample application.
a#clone a#clone
:marked :marked
@ -35,10 +27,10 @@ a#clone
Perform the _clone-to-launch_ steps with these terminal commands. Perform the _clone-to-launch_ steps with these terminal commands.
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
git clone !{_qsRepo}.git quickstart git clone https://github.com/angular/quickstart.git quickstart
cd quickstart cd quickstart
!{_npm} !{_install} npm install
!{_npm} !{_start} npm start
.alert.is-important .alert.is-important
:marked :marked
@ -47,13 +39,13 @@ code-example(language="sh" class="code-shell").
a#download a#download
:marked :marked
### Download ### Download
<a href="!{_qsRepoZip}" title="Download the QuickStart seed repository">Download the QuickStart seed</a> <a href="https://github.com/angular/quickstart/archive/master.zip" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
and unzip it into your project folder. Then perform the remaining steps with these terminal commands. and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
cd quickstart cd quickstart
!{_npm} !{_install} npm install
!{_npm} !{_start} npm start
.alert.is-important .alert.is-important
:marked :marked
@ -144,13 +136,13 @@ table(width="100%")
th File th File
th Purpose th Purpose
tr tr
td <ngio-ex>app/app.component.ts</ngio-ex> td <code>app/app.component.ts</code>
td td
:marked :marked
Defines the same `AppComponent` as the one in the QuickStart !{_playground}. Defines the same `AppComponent` as the one in the QuickStart playground.
It is the **root** component of what will become a tree of nested components It is the **root** component of what will become a tree of nested components
as the application evolves. as the application evolves.
tr(if-docs="ts") tr
td <code>app/app.module.ts</code> td <code>app/app.module.ts</code>
td td
:marked :marked
@ -158,7 +150,7 @@ table(width="100%")
Right now it declares only the `AppComponent`. Right now it declares only the `AppComponent`.
Soon there will be more components to declare. Soon there will be more components to declare.
tr tr
td <ngio-ex>main.ts</ngio-ex> td <code>main.ts</code>
td td
:marked :marked
Compiles the application with the [JIT compiler](../glossary.html#jit) and Compiles the application with the [JIT compiler](../glossary.html#jit) and
@ -180,7 +172,7 @@ br
a#install-prerequisites a#install-prerequisites
.l-main-section .l-main-section
:marked :marked
## Appendix: !{_prereq} ## Appendix: node and npm
block install-tooling block install-tooling
:marked :marked
Node.js and npm are essential to modern web development with Angular and other platforms. Node.js and npm are essential to modern web development with Angular and other platforms.
@ -198,32 +190,31 @@ block install-tooling
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
use other versions of node and npm. use other versions of node and npm.
+ifDocsFor('ts') a#why-locally
a#why-locally .l-main-section
.l-main-section :marked
:marked ## Appendix: Why develop locally
## Appendix: Why develop locally
<live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular. <live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular.
Links on almost every documentation page open completed samples in the browser. Links on almost every documentation page open completed samples in the browser.
You can play with the sample code, share your changes with friends, and download and run the code on your own machine. You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
The [QuickStart](../quickstart.html "Angular QuickStart Playground") shows just the `AppComponent` file. The [QuickStart](../quickstart.html "Angular QuickStart Playground") shows just the `AppComponent` file.
It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_. It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_.
so the reader can discover Angular without distraction. so the reader can discover Angular without distraction.
The other samples are based on the QuickStart seed. The other samples are based on the QuickStart seed.
As much fun as this is ... As much fun as this is ...
* you can't ship your app in plunker * you can't ship your app in plunker
* you aren't always online when writing code * you aren't always online when writing code
* transpiling TypeScript in the browser is slow * transpiling TypeScript in the browser is slow
* the type support, refactoring, and code completion only work in your local IDE * the type support, refactoring, and code completion only work in your local IDE
Use the <live-example title="QuickStart Seed in Plunker"><i>live coding</i></live-example> environment as a _playground_, Use the <live-example title="QuickStart Seed in Plunker"><i>live coding</i></live-example> environment as a _playground_,
a place to try the documentation samples and experiment on your own. a place to try the documentation samples and experiment on your own.
It's the perfect place to reproduce a bug when you want to It's the perfect place to reproduce a bug when you want to
<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or <a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>. <a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>.
For real development, we strongly recommend [developing locally](#develop-locally). For real development, we strongly recommend [developing locally](#develop-locally).

View File

@ -566,7 +566,7 @@ a#unless
+makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs') +makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs')
:marked :marked
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}. Add this directive to the `declarations` array of the AppModule.
Then create some HTML to try it. Then create some HTML to try it.

View File

@ -1,10 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _JavaScript = 'JavaScript';
//- Double underscore means don't escape var, use !{__var}.
- var __chaining_op = '<code>;</code> or <code>,</code>';
- var __new_op = '<code>new</code>';
- var __objectAsMap = 'object';
//- The docs standard h4 style uppercases, making code terms unreadable. Override it. //- The docs standard h4 style uppercases, making code terms unreadable. Override it.
style. style.
@ -133,19 +128,19 @@ a#template-expressions
In the [property binding](#property-binding) section below, In the [property binding](#property-binding) section below,
a template expression appears in quotes to the right of the&nbsp;`=` symbol as in `[property]="expression"`. a template expression appears in quotes to the right of the&nbsp;`=` symbol as in `[property]="expression"`.
You write these template expressions in a language that looks like #{_JavaScript}. You write these template expressions in a language that looks like JavaScript.
Many #{_JavaScript} expressions are legal template expressions, but not all. Many JavaScript expressions are legal template expressions, but not all.
#{_JavaScript} expressions that have or promote side effects are prohibited, JavaScript expressions that have or promote side effects are prohibited,
including: including:
* assignments (`=`, `+=`, `-=`, ...) * assignments (`=`, `+=`, `-=`, ...)
* !{__new_op} * <code>new</code>
* chaining expressions with !{__chaining_op} * chaining expressions with <code>;</code> or <code>,</code>
* increment and decrement operators (`++` and `--`) * increment and decrement operators (`++` and `--`)
:marked :marked
Other notable differences from #{_JavaScript} syntax include: Other notable differences from JavaScript syntax include:
* no support for the bitwise operators `|` and `&` * no support for the bitwise operators `|` and `&`
* new [template expression operators](#expression-operators), such as `|` and `?.` * new [template expression operators](#expression-operators), such as `|` and `?.`
@ -238,7 +233,7 @@ a#expression-guidelines
Dependent values should not change during a single turn of the event loop. Dependent values should not change during a single turn of the event loop.
If an idempotent expression returns a string or a number, it returns the same string or number If an idempotent expression returns a string or a number, it returns the same string or number
when called twice in a row. If the expression returns an object (including #{_an} `#{_Array}`), when called twice in a row. If the expression returns an object (including an `array`),
it returns the same object *reference* when called twice in a row. it returns the same object *reference* when called twice in a row.
a(href="#toc") back to top a(href="#toc") back to top
@ -263,14 +258,14 @@ a#template-statements
Responding to events is the other side of Angular's "unidirectional data flow". Responding to events is the other side of Angular's "unidirectional data flow".
You're free to change anything, anywhere, during this turn of the event loop. You're free to change anything, anywhere, during this turn of the event loop.
Like template expressions, template *statements* use a language that looks like #{_JavaScript}. Like template expressions, template *statements* use a language that looks like JavaScript.
The template statement parser differs from the template expression parser and The template statement parser differs from the template expression parser and
specifically supports both basic assignment (`=`) and chaining expressions specifically supports both basic assignment (`=`) and chaining expressions
(with !{__chaining_op}). (with <code>;</code> or <code>,</code>).
However, certain #{_JavaScript} syntax is not allowed: However, certain JavaScript syntax is not allowed:
* !{__new_op} * <code>new</code>
* increment and decrement operators, `++` and `--` * increment and decrement operators, `++` and `--`
* operator assignment, such as `+=` and `-=` * operator assignment, such as `+=` and `-=`
* the bitwise operators `|` and `&` * the bitwise operators `|` and `&`
@ -808,8 +803,8 @@ a(href="#toc") back to top
:marked :marked
Finally, you can bind to a specific class name. Finally, you can bind to a specific class name.
Angular adds the class when the template expression evaluates to #{_truthy}. Angular adds the class when the template expression evaluates to truthy.
It removes the class when the expression is #{_falsy}. It removes the class when the expression is falsy.
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".") +makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
@ -1127,7 +1122,7 @@ a#ngClass
:marked :marked
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice. To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
Try binding `ngClass` to a key:value control !{__objectAsMap}. Try binding `ngClass` to a key:value control object.
Each key of the object is a CSS class name; its value is `true` if the class should be added, Each key of the object is a CSS class name; its value is `true` if the class should be added,
`false` if it should be removed. `false` if it should be removed.
@ -1164,7 +1159,7 @@ a#ngStyle
:marked :marked
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice. To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice.
Try binding `ngStyle` to a key:value control !{__objectAsMap}. Try binding `ngStyle` to a key:value control object.
Each key of the object is a style name; its value is whatever is appropriate for that style. Each key of the object is a style name; its value is whatever is appropriate for that style.
Consider a `setCurrentStyles` component method that sets a component property, `currentStyles` Consider a `setCurrentStyles` component method that sets a component property, `currentStyles`
@ -1195,18 +1190,17 @@ a#ngModel
+makeExcerpt('src/app/app.component.html', 'NgModel-1', '') +makeExcerpt('src/app/app.component.html', 'NgModel-1', '')
+ifDocsFor('ts|js') :marked
:marked #### _FormsModule_ is required to use _ngModel_
#### _FormsModule_ is required to use _ngModel_
Before using the `ngModel` directive in a two-way data binding, Before using the `ngModel` directive in a two-way data binding,
you must import the `FormsModule` and add it to the Angular module's `imports` list. you must import the `FormsModule` and add it to the Angular module's `imports` list.
Learn more about the `FormsModule` and `ngModel` in the Learn more about the `FormsModule` and `ngModel` in the
[Forms](../guide/forms.html#ngModel) guide. [Forms](../guide/forms.html#ngModel) guide.
Here's how to import the `FormsModule` to make `[(ngModel)]` available. Here's how to import the `FormsModule` to make `[(ngModel)]` available.
+makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '') +makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '')
:marked :marked
#### Inside <span class="syntax">[(ngModel)]</span> #### Inside <span class="syntax">[(ngModel)]</span>
@ -1317,8 +1311,8 @@ a#ngIf
Don't forget the asterisk (`*`) in front of `ngIf`. Don't forget the asterisk (`*`) in front of `ngIf`.
:marked :marked
When the `isActive` expression returns a #{_truthy} value, `NgIf` adds the `HeroDetailComponent` to the DOM. When the `isActive` expression returns a truthy value, `NgIf` adds the `HeroDetailComponent` to the DOM.
When the expression is #{_falsy}, `NgIf` removes the `HeroDetailComponent` When the expression is falsy, `NgIf` removes the `HeroDetailComponent`
from the DOM, destroying that component and all of its sub-components. from the DOM, destroying that component and all of its sub-components.
#### Show/hide is not the same thing #### Show/hide is not the same thing
@ -1397,7 +1391,7 @@ a#microsyntax
It's a *microsyntax* &mdash; a little language of its own that Angular interprets. It's a *microsyntax* &mdash; a little language of its own that Angular interprets.
The string `"let hero of heroes"` means: The string `"let hero of heroes"` means:
> *Take each hero in the `heroes` #{_array}, store it in the local `hero` looping variable, and > *Take each hero in the `heroes` array, store it in the local `hero` looping variable, and
make it available to the templated HTML for each iteration.* make it available to the templated HTML for each iteration.*
Angular translates this instruction into a `<template>` around the host element, Angular translates this instruction into a `<template>` around the host element,
@ -1412,8 +1406,8 @@ a#template-input-variables
### Template input variables ### Template input variables
The `let` keyword before `hero` creates a _template input variable_ called `hero`. The `let` keyword before `hero` creates a _template input variable_ called `hero`.
The `ngFor` directive iterates over the `heroes` #{_array} returned by the parent component's `heroes` property The `ngFor` directive iterates over the `heroes` array returned by the parent component's `heroes` property
and sets `hero` to the current item from the #{_array} during each iteration. and sets `hero` to the current item from the array during each iteration.
You reference the `hero` input variable within the `ngFor` host element You reference the `hero` input variable within the `ngFor` host element
(and within its descendents) to access the hero's properties. (and within its descendents) to access the hero's properties.
@ -1655,13 +1649,13 @@ a#inputs-outputs
.l-sub-section .l-sub-section
:marked :marked
Alternatively, you can identify members in the `inputs` and `outputs` #{_array}s Alternatively, you can identify members in the `inputs` and `outputs` arrays
of the directive metadata, as in this example: of the directive metadata, as in this example:
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".") +makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
:marked :marked
You can specify an input/output property either with a decorator or in a metadata #{_array}. You can specify an input/output property either with a decorator or in a metadata array.
Don't do both! Don't do both!
:marked :marked
@ -1709,7 +1703,7 @@ h3#aliasing-io Aliasing input/output properties
.l-sub-section .l-sub-section
:marked :marked
You can also alias property names in the `inputs` and `outputs` #{_array}s. You can also alias property names in the `inputs` and `outputs` arrays.
You write a colon-delimited (`:`) string with You write a colon-delimited (`:`) string with
the directive property name on the *left* and the public alias on the *right*: the directive property name on the *left* and the public alias on the *right*:
@ -1722,7 +1716,7 @@ a#expression-operators
:marked :marked
## Template expression operators ## Template expression operators
The template expression language employs a subset of #{_JavaScript} syntax supplemented with a few special operators The template expression language employs a subset of JavaScript syntax supplemented with a few special operators
for specific scenarios. The next sections cover two of these operators: _pipe_ and _safe navigation operator_. for specific scenarios. The next sections cover two of these operators: _pipe_ and _safe navigation operator_.
a#pipe a#pipe
@ -1818,12 +1812,11 @@ code-example(format="nocode").
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".") +makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".")
block safe-op-alt :marked
:marked You could try to chain parts of the property path with `&&`, knowing that the expression bails out
You could try to chain parts of the property path with `&&`, knowing that the expression bails out when it encounters the first null.
when it encounters the first null.
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
:marked :marked
These approaches have merit but can be cumbersome, especially if the property path is long. These approaches have merit but can be cumbersome, especially if the property path is long.

View File

@ -1,10 +1,5 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _JavaScript = 'JavaScript';
//- Double underscore means don't escape var, use !{__var}.
- var __chaining_op = '<code>;</code> or <code>,</code>';
- var __new_op = '<code>new</code>';
- var __objectAsMap = 'object';
:marked :marked
This guide offers tips and techniques for testing Angular applications. This guide offers tips and techniques for testing Angular applications.

View File

@ -2,19 +2,19 @@
- var vers = current.path[2] - var vers = current.path[2]
.clearfix .clearfix
a.card.c4(href="/docs/#{lang}/#{vers}/quickstart.html") a.card.c4(href="/docs/ts/latest/quickstart.html")
h2.text-headline.text-uppercase Quickstart h2.text-headline.text-uppercase Quickstart
p A short beginner guide explaining the basic concepts of Angular p A short beginner guide explaining the basic concepts of Angular
footer View Quickstart footer View Quickstart
a.card.c4(href="/docs/#{lang}/#{vers}/guide/") a.card.c4(href="/docs/ts/latest/guide/")
h2.text-headline.text-uppercase Developer Guide h2.text-headline.text-uppercase Developer Guide
p An intermediate development guide covering all major features of Angular p An intermediate development guide covering all major features of Angular
footer View Guide footer View Guide
a.card.c4(href="/docs/#{lang}/#{vers}/api/") a.card.c4(href="/docs/ts/latest/api/")
h2.text-headline.text-uppercase API Reference h2.text-headline.text-uppercase API Reference
p An advanced reference of all Angular Classes, Methods, etc. p An advanced reference of all Angular Classes, Methods, etc.
@ -24,31 +24,31 @@
h4 Advanced Documentation h4 Advanced Documentation
ul ul
li li
a(href="/docs/#{lang}/#{vers}/guide/animations.html") Animations a(href="/docs/ts/latest/guide/animations.html") Animations
li li
a(href="/docs/#{lang}/#{vers}/guide/attribute-directives.html") Attribute Directives a(href="/docs/ts/latest/guide/attribute-directives.html") Attribute Directives
li li
a(href="/docs/#{lang}/#{vers}/guide/browser-support.html") Browser Support a(href="/docs/ts/latest/guide/browser-support.html") Browser Support
li li
a(href="/docs/#{lang}/#{vers}/guide/component-styles.html") Component Styles a(href="/docs/ts/latest/guide/component-styles.html") Component Styles
li li
a(href="/docs/#{lang}/#{vers}/guide/deployment.html") Deployment a(href="/docs/ts/latest/guide/deployment.html") Deployment
li li
a(href="/docs/#{lang}/#{vers}/guide/animations.html") View All... a(href="/docs/ts/latest/guide/animations.html") View All...
.c4.secondary-content-list .c4.secondary-content-list
h4 Cookbook h4 Cookbook
ul ul
li li
a(href="/docs/#{lang}/#{vers}/cookbook/aot-compiler.html") Ahead-of-time Compilation a(href="/docs/ts/latest/cookbook/aot-compiler.html") Ahead-of-time Compilation
li li
a(href="/docs/#{lang}/#{vers}/cookbook/ajs-quick-reference.html") AngularJS to Angular a(href="/docs/ts/latest/cookbook/ajs-quick-reference.html") AngularJS to Angular
li li
a(href="/docs/#{lang}/#{vers}/cookbook/component-communication.html") Component Interaction a(href="/docs/ts/latest/cookbook/component-communication.html") Component Interaction
li li
a(href="/docs/#{lang}/#{vers}/cookbook/dependency-injection.html") Dependency Injection a(href="/docs/ts/latest/cookbook/dependency-injection.html") Dependency Injection
li li
a(href="/docs/#{lang}/#{vers}/cookbook/") View All... a(href="/docs/ts/latest/cookbook/") View All...
.c4.secondary-content-list .c4.secondary-content-list
h4 Tools & Libraries h4 Tools & Libraries

View File

@ -15,9 +15,8 @@ block qs-src-online-and-local
and prepare for development of a real Angular application. and prepare for development of a real Angular application.
:marked :marked
Every component begins with an `@Component` [!{_decorator}](glossary.html#!{_decorator} '"!{_decorator}" explained') Every component begins with an `@Component` [decorator](glossary.html#decorator '"decorator" explained')
<span if-docs="ts">function</span> that function that takes a _metadata_ object. The metadata object describes how the HTML template and component class work together.
<span if-docs="ts">takes a _metadata_ object. The metadata object</span> describes how the HTML template and component class work together.
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`. The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
+makeExample('src/index.html','my-app','index.html (inside <body>)')(format='.') +makeExample('src/index.html','my-app','index.html (inside <body>)')(format='.')
@ -28,19 +27,18 @@ block qs-src-online-and-local
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property. At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
Interpolation binding is one of many Angular features you'll discover in this documentation. Interpolation binding is one of many Angular features you'll discover in this documentation.
+ifDocsFor('ts') :marked
:marked In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
.callout.is-helpful .callout.is-helpful
header A word about TypeScript header A word about TypeScript
p. p.
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how. uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how.
.l-sub-section .l-sub-section
:marked :marked
### Next step ### Next step
Start [**learning Angular**](guide/learning-angular.html "Learning Angular"). Start [**learning Angular**](guide/learning-angular.html "Learning Angular").

View File

@ -3,7 +3,7 @@ include ../_util-fns
:marked :marked
## Setup to develop locally ## Setup to develop locally
Follow the [setup](../guide/setup.html) instructions for creating a new project Follow the [setup](../guide/setup.html) instructions for creating a new project
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>. named <code>angular-tour-of-heroes</code>.
The file structure should look like this: The file structure should look like this:

View File

@ -10,7 +10,7 @@ include ../_util-fns
and makes it easy to unit-test components with a mock service. and makes it easy to unit-test components with a mock service.
Because data services are invariably asynchronous, Because data services are invariably asynchronous,
you'll finish the page with a *!{_Promise}*-based version of the data service. you'll finish the page with a *Promise*-based version of the data service.
When you're done with this page, the app should look like this <live-example></live-example>. When you're done with this page, the app should look like this <live-example></live-example>.
@ -229,7 +229,7 @@ code-example(format="nocode").
<a id="async"></a> <a id="async"></a>
:marked :marked
## Async services and !{_Promise}s ## Async services and Promises
The `HeroService` returns a list of mock heroes immediately; The `HeroService` returns a list of mock heroes immediately;
its `getHeroes()` signature is synchronous. its `getHeroes()` signature is synchronous.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".") +makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
@ -240,12 +240,12 @@ code-example(format="nocode").
:marked :marked
To coordinate the view with the response, To coordinate the view with the response,
you can use *!{_Promise}s*, which is an asynchronous you can use *Promises*, which is an asynchronous
technique that changes the signature of the `getHeroes()` method. technique that changes the signature of the `getHeroes()` method.
### The hero service makes a !{_Promise} ### The hero service makes a Promise
A *!{_Promise}* essentially promises to call back when the results are ready. A *Promise* essentially promises to call back when the results are ready.
You ask an asynchronous service to do some work and give it a callback function. You ask an asynchronous service to do some work and give it a callback function.
The service does that work and eventually calls the function with the results or an error. The service does that work and eventually calls the function with the results or an error.
.l-sub-section .l-sub-section
@ -255,22 +255,22 @@ code-example(format="nocode").
[Exploring ES6](http://http://exploringjs.com/es6.html). [Exploring ES6](http://http://exploringjs.com/es6.html).
:marked :marked
Update the `HeroService` with this !{_Promise}-returning `getHeroes()` method: Update the `HeroService` with this Promise-returning `getHeroes()` method:
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".") +makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".")
:marked :marked
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server, You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
by returning an *immediately resolved !{_Promise}* with the mock heroes as the result. by returning an *immediately resolved Promise* with the mock heroes as the result.
### Act on the !{_Promise} ### Act on the Promise
As a result of the change to `HeroService`, `this.heroes` is now set to a !{_Promise} rather than an array of heroes. As a result of the change to `HeroService`, `this.heroes` is now set to a `Promise` rather than an array of heroes.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".") +makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".")
:marked :marked
You have to change the implementation to *act on the !{_Promise} when it resolves*. You have to change the implementation to *act on the `Promise` when it resolves*.
When the !{_Promise} resolves successfully, you'll have heroes to display. When the `Promise` resolves successfully, you'll have heroes to display.
Pass the callback function as an argument to the !{_Promise}'s `then()` method: Pass the callback function as an argument to the Promise's `then()` method:
+makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".") +makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".")
.l-sub-section .l-sub-section
:marked :marked
@ -329,7 +329,7 @@ code-example(format="nocode").
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates. * You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
* You defined the `HeroService` as a provider for the `AppComponent`. * You defined the `HeroService` as a provider for the `AppComponent`.
* You created mock hero data and imported them into the service. * You created mock hero data and imported them into the service.
* You designed the service to return a !{_Promise} and the component to get the data from the !{_Promise}. * You designed the service to return a Promise and the component to get the data from the Promise.
Your app should look like this <live-example></live-example>. Your app should look like this <live-example></live-example>.
@ -348,8 +348,8 @@ code-example(format="nocode").
import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`. import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`.
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".") +makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".")
:marked :marked
Like `getHeroes()`, it also returns a !{_Promise}. Like `getHeroes()`, it also returns a `Promise`.
But this !{_Promise} waits two seconds before resolving the !{_Promise} with mock heroes. But this Promise waits two seconds before resolving the Promise with mock heroes.
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()` Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
and see how the app behaves. and see how the app behaves.

View File

@ -1,11 +1,5 @@
- var _example = 'toh-5';
block includes block includes
include ../_util-fns include ../_util-fns
- var _appRoutingTsVsAppComp = 'app.module.ts'
- var _RoutesVsAtRouteConfig = 'Routes'
- var _RouterModuleVsRouterDirectives = 'RouterModule'
- var _redirect = 'redirect'
:marked :marked
There are new requirements for the Tour of Heroes app: There are new requirements for the Tour of Heroes app:
@ -30,40 +24,38 @@ figure.image-display
:marked :marked
When you're done with this page, the app should look like this <live-example></live-example>. When you're done with this page, the app should look like this <live-example></live-example>.
+ifDocsFor('ts|js') include ../../../_includes/_see-addr-bar
include ../../../_includes/_see-addr-bar
.l-main-section .l-main-section
:marked :marked
## Where you left off ## Where you left off
Before continuing with the Tour of Heroes, verify that you have the following structure. Before continuing with the Tour of Heroes, verify that you have the following structure.
block intro-file-tree
.filetree
.file angular-tour-of-heroes
.children
.file src
.children
.file app
.children
.file app.component.ts
.file app.module.ts
.file hero.service.ts
.file hero.ts
.file hero-detail.component.ts
.file mock-heroes.ts
.file main.ts
.file index.html
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file package.json
block keep-app-running .filetree
:marked .file angular-tour-of-heroes
## Keep the app transpiling and running .children
Enter the following command in the terminal window: .file src
.children
.file app
.children
.file app.component.ts
.file app.module.ts
.file hero.service.ts
.file hero.ts
.file hero-detail.component.ts
.file mock-heroes.ts
.file main.ts
.file index.html
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file package.json
:marked
## Keep the app transpiling and running
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
npm start npm start
@ -108,11 +100,11 @@ block keep-app-running
and create a separate `AppComponent` shell. and create a separate `AppComponent` shell.
Do the following: Do the following:
* Rename the <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>. * Rename the <code>app.component.ts</code> file to <code>heroes.component.ts</code>.
* Rename the `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file). * Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file).
* Rename the selector `my-app` to `my-heroes`. * Rename the selector `my-app` as `my-heroes`.
+makeExcerpt('src/app/heroes.component.ts (showing renamings only)', 'renaming') +makeExample('toh-5/ts/src/app/heroes.component.ts', 'renaming', 'src/app/heroes.component.ts (showing renamings only)')
:marked :marked
### Create *AppComponent* ### Create *AppComponent*
@ -122,27 +114,26 @@ block keep-app-running
Perform these steps: Perform these steps:
* Create the file <span ngio-ex>src/app/app.component.ts</span>. * Create the file <code>src/app/app.component.ts</code>.
* Define an <span if-docs="ts">exported</span> `AppComponent` class. * Define an exported `AppComponent` class.
* Add an `@Component` !{_decorator} above the class with a `my-app` selector. * Add an `@Component` decorator above the class with a `my-app` selector.
* Move the following from `HeroesComponent` to `AppComponent`: * Move the following from `HeroesComponent` to `AppComponent`:
* `title` class property. * `title` class property.
* `@Component` template `<h1>` element, which contains a binding to `title`. * `@Component` template `<h1>` element, which contains a binding to `title`.
* Add a `<my-heroes>` element to the app template just below the heading so you still see the heroes. * Add a `<my-heroes>` element to the app template just below the heading so you still see the heroes.
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags. * Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `<my-heroes>` tags.
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because you'll need it in every other view. * Add `HeroService` to the `providers` array of `AppModule` because you'll need it in every other view.
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it was promoted. * Remove `HeroService` from the `HeroesComponent` `providers` array since it was promoted.
* Add the supporting `import` statements for `AppComponent`. * Add the supporting `import` statements for `AppComponent`.
The first draft looks like this: The first draft looks like this:
block app-comp-v1 +makeTabs(
+makeTabs( `toh-5/ts/src/app/app.component.1.ts,
`toh-5/ts/src/app/app.component.1.ts, toh-5/ts/src/app/app.module.1.ts`,
toh-5/ts/src/app/app.module.1.ts`, ',',
',', `src/app/app.component.ts (v1),
`src/app/app.component.ts (v1), src/app/app.module.ts (v1)`)
src/app/app.module.ts (v1)`)
:marked :marked
The app still runs and displays heroes. The app still runs and displays heroes.
@ -155,12 +146,11 @@ block app-comp-v1
Use the Angular router to enable navigation. Use the Angular router to enable navigation.
block angular-router :marked
:marked The Angular router is an external, optional Angular NgModule called `RouterModule`.
The Angular router is an external, optional Angular NgModule called `RouterModule`. The router is a combination of multiple provided services (`RouterModule`),
The router is a combination of multiple provided services (`RouterModule`), multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`), and a configuration (`Routes`). You'll configure the routes first.
and a configuration (`Routes`). You'll configure the routes first.
:marked :marked
### *&lt;base href>* ### *&lt;base href>*
@ -169,7 +159,7 @@ block angular-router
(or a script that dynamically sets this element) (or a script that dynamically sets this element)
at the top of the `<head>` section. at the top of the `<head>` section.
+makeExcerpt('src/index.html', 'base-href') +makeExample('toh-5/ts/src/index.html', 'base-href', 'src/index.html (base-href)')
.callout.is-important .callout.is-important
header base href is essential header base href is essential
@ -179,11 +169,10 @@ block angular-router
a#configure-routes a#configure-routes
block router-config-intro :marked
:marked ### Configure routes
### Configure routes
Create a configuration file for the app routes. Create a configuration file for the app routes.
:marked :marked
*Routes* tell the router which views to display when a user clicks a link or *Routes* tell the router which views to display when a user clicks a link or
@ -191,49 +180,42 @@ block router-config-intro
Define the first route as a route to the heroes component. Define the first route as a route to the heroes component.
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts' +makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)')
+makeExcerpt('src/app/' + _file + ' (heroes route)', 'heroes')
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
:marked :marked
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*. The `Routes` are an array of *route definitions*.
This route definition has the following parts: This route definition has the following parts:
- *Path*: The router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}heroes`). - *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`).
<li if-docs="dart"> *Name*: The official name of the route;
it must begin with a capital letter to avoid confusion with the path (`Heroes`).</li>
- *Component*: The component that the router should create when navigating to this route (`HeroesComponent`). - *Component*: The component that the router should create when navigating to this route (`HeroesComponent`).
.l-sub-section .l-sub-section
:marked :marked
Read more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing & Navigation](../guide/router.html) page. Read more about defining routes with `Routes` in the [Routing & Navigation](../guide/router.html) page.
+ifDocsFor('ts|js') :marked
### Make the router available
Import the `RouterModule` and add it to the `AppModule` imports array.
+makeExample('toh-5/ts/src/app/app.module.2.ts', '', 'src/app/app.module.ts (app routing)')
.l-sub-section
:marked :marked
### Make the router available The `forRoot()` method is called because a configured router is provided at the app's root.
The `forRoot()` method supplies the Router service providers and directives needed for routing, and
performs the initial navigation based on the current browser URL.
Import the `RouterModule` and add it to the `AppModule` imports !{_array}.
+makeExcerpt('src/app/app.module.2.ts (app routing)', '')
.l-sub-section
:marked
The `forRoot()` method is called because a configured router is provided at the app's root.
The `forRoot()` method supplies the Router service providers and directives needed for routing, and
performs the initial navigation based on the current browser URL.
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
:marked :marked
### Router outlet ### Router outlet
If you paste the path, `/heroes`, into the browser address bar at the end of the URL, If you paste the path, `/heroes`, into the browser address bar at the end of the URL,
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`. the router should match it to the `heroes` route and display the `HeroesComponent`.
However, you have to tell the router where to display the component. However, you have to tell the router where to display the component.
To do this, you can add a `<router-outlet>` element at the end of the template. To do this, you can add a `<router-outlet>` element at the end of the template.
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`. `RouterOutlet` is one of the directives provided by the `RouterModule`.
The router displays each component immediately below the `<router-outlet>` as users navigate through the app. The router displays each component immediately below the `<router-outlet>` as users navigate through the app.
### Router links ### Router links
@ -243,21 +225,20 @@ block router-config-intro
The revised template looks like this: The revised template looks like this:
+makeExcerpt('src/app/app.component.1.ts', 'template-v2') +makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v2', 'src/app/app.component.ts (template-v2)')
block routerLink :marked
Note the `routerLink` binding in the anchor tag.
The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string
that tells the router where to navigate when the user clicks the link.
Since the link is not dynamic, a routing instruction is defined with a one-time binding to the route path.
Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
.l-sub-section
:marked :marked
Note the `routerLink` binding in the anchor tag. Read more about dynamic router links and the link parameters array
The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the
that tells the router where to navigate when the user clicks the link. [Routing & Navigation](../guide/router.html#) page.
Since the link is not dynamic, a routing instruction is defined with a one-time binding to the route path.
Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
.l-sub-section
:marked
Read more about dynamic router links and the link parameters array
in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the
[Routing & Navigation](../guide/router.html#) page.
:marked :marked
Refresh the browser. The browser displays the app title and heroes link, but not the heroes list. Refresh the browser. The browser displays the app title and heroes link, but not the heroes list.
@ -274,7 +255,7 @@ block routerLink
`AppComponent` now looks like this: `AppComponent` now looks like this:
+makeExample('src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)') +makeExample('toh-5/ts/src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)')
:marked :marked
The *AppComponent* is now attached to a router and displays routed views. The *AppComponent* is now attached to a router and displays routed views.
@ -287,25 +268,23 @@ block routerLink
Routing only makes sense when multiple views exist. Routing only makes sense when multiple views exist.
To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from. To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from.
+makeExcerpt('src/app/dashboard.component.1.ts (v1)', '') +makeExample('toh-5/ts/src/app/dashboard.component.1.ts', '', 'src/app/dashboard.component.ts (v1)')
:marked :marked
You'll make this component more useful later. You'll make this component more useful later.
### Configure the dashboard route ### Configure the dashboard route
To teach `!{_appRoutingTsVsAppComp}` to navigate to the dashboard, To teach `app.module.ts` to navigate to the dashboard,
import the dashboard component and import the dashboard component and
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions. add the following route definition to the `Routes` array of definitions.
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'src/app/app.module.3.ts' +makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)')
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
+ifDocsFor('ts|js') :marked
:marked Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
+makeExcerpt('src/app/app.module.ts', 'dashboard') +makeExample('toh-5/ts/src/app/app.module.ts', 'dashboard', 'src/app/app.module.ts (dashboard)')
:marked :marked
### Add a !{_redirect} route ### Add a !{_redirect} route
@ -314,25 +293,23 @@ block routerLink
When the app starts, it should show the dashboard and When the app starts, it should show the dashboard and
display a `/dashboard` URL in the browser address bar. display a `/dashboard` URL in the browser address bar.
block redirect-vs-use-as-default :marked
To make this happen, use a redirect route. Add the following
to the array of route definitions:
+makeExample('toh-5/ts/src/app/app.module.3.ts','redirect', 'src/app/app.module.ts (redirect)')
.l-sub-section
:marked :marked
To make this happen, use a redirect route. Add the following Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section
to the array of route definitions: of the [Routing & Navigation](../guide/router.html#) page.
+makeExcerpt('src/app/app.module.3.ts','redirect')
.l-sub-section
:marked
Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section
of the [Routing & Navigation](../guide/router.html#) page.
:marked :marked
### Add navigation to the template ### Add navigation to the template
Add a dashboard navigation link to the template, just above the *Heroes* link. Add a dashboard navigation link to the template, just above the *Heroes* link.
- var _vers = _docsFor == 'dart' ? '' : '.1' +makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)')
+makeExcerpt('src/app/app.component' + _vers + '.ts', 'template-v3')
.l-sub-section .l-sub-section
:marked :marked
@ -349,15 +326,12 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new Replace the `template` metadata with a `templateUrl` property that points to a new
template file. template file.
+makeExcerpt('src/app/dashboard.component.ts', 'metadata') +makeExample('toh-5/ts/src/app/dashboard.component.ts', 'metadata', 'src/app/dashboard.component.ts (metadata)')
block templateUrl-path-resolution
//- N/A for TS
:marked :marked
Create that file with this content: Create that file with this content:
+makeExample('src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html') +makeExample('toh-5/ts/src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html')
:marked :marked
`*ngFor` is used again to iterate over a list of heroes and display their names. `*ngFor` is used again to iterate over a list of heroes and display their names.
@ -365,32 +339,32 @@ block templateUrl-path-resolution
### Sharing the *HeroService* ### Sharing the *HeroService*
To populate the component's `heroes` !{_array}, you can re-use the `HeroService`. To populate the component's `heroes` array, you can re-use the `HeroService`.
Earlier, you removed the `HeroService` from the `providers` !{_array} of `HeroesComponent` Earlier, you removed the `HeroService` from the `providers` array of `HeroesComponent`
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`. and added it to the `providers` array of `AppModule`.
That move created a singleton `HeroService` instance, available to all components of the app. That move created a singleton `HeroService` instance, available to all components of the app.
Angular injects `HeroService` and you can use it in the `DashboardComponent`. Angular injects `HeroService` and you can use it in the `DashboardComponent`.
### Get heroes ### Get heroes
In <span ngio-ex>dashboard.component.ts</span>, add the following `import` statements. In <code>dashboard.component.ts</code>, add the following `import` statements.
+makeExcerpt('src/app/dashboard.component.ts','imports') +makeExample('toh-5/ts/src/app/dashboard.component.ts','imports', 'src/app/dashboard.component.ts (imports)')
:marked :marked
Now create the `DashboardComponent` class like this: Now create the `DashboardComponent` class like this:
+makeExcerpt('src/app/dashboard.component.ts (class)', 'class') +makeExample('toh-5/ts/src/app/dashboard.component.ts', 'class', 'src/app/dashboard.component.ts (class)')
:marked :marked
This kind of logic is also used in the `HeroesComponent`: This kind of logic is also used in the `HeroesComponent`:
* Define a `heroes` !{_array} property. * Define a `heroes` array property.
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field. * Inject the `HeroService` in the constructor and hold it in a private `heroService` field.
* Call the service to get heroes inside the Angular `ngOnInit()` lifecycle hook. * Call the service to get heroes inside the Angular `ngOnInit()` lifecycle hook.
In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice()` method</span>. In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th) with the `Array.slice` method.
Refresh the browser to see four hero names in the new dashboard. Refresh the browser to see four hero names in the new dashboard.
@ -407,7 +381,7 @@ block templateUrl-path-resolution
### Routing to a hero detail ### Routing to a hero detail
You can add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}`, where the other routes are configured. You can add a route to the `HeroDetailComponent` in `app.module.ts`, where the other routes are configured.
The new route is unusual in that you must tell the `HeroDetailComponent` which hero to show. The new route is unusual in that you must tell the `HeroDetailComponent` which hero to show.
You didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything. You didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
@ -437,17 +411,15 @@ code-example(format="nocode").
Use the following *route definition*. Use the following *route definition*.
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts' +makeExample('toh-5/ts/src/app/app.module.3.ts','hero-detail', 'src/app/app.module.ts (hero detail)')
+makeExcerpt(_file + ' (hero detail)','hero-detail')
:marked :marked
The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id` The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id`
when navigating to the `HeroDetailComponent`. when navigating to the `HeroDetailComponent`.
+ifDocsFor('dart') .l-sub-section
.l-sub-section :marked
:marked Be sure to import the hero detail component before creating this route.
Be sure to import the hero detail component before creating this route.
:marked :marked
You're finished with the app routes. You're finished with the app routes.
@ -471,68 +443,59 @@ code-example(format="nocode").
The template won't change. Hero names will display the same way. The template won't change. Hero names will display the same way.
The major changes are driven by how you get hero names. The major changes are driven by how you get hero names.
block route-params :marked
:marked You'll no longer receive the hero in a parent component property binding.
You'll no longer receive the hero in a parent component property binding. The new `HeroDetailComponent` should take the `id` parameter from the `params` Observable
The new `HeroDetailComponent` should take the `id` parameter from the `params` Observable in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
:marked :marked
Add the following imports: Add the following imports:
- var _vers = _docsFor == 'dart' ? '' : '.1' +makeExample('toh-5/ts/src/app/hero-detail.component.1.ts', 'added-imports', 'src/app/hero-detail.component.ts')
+makeExcerpt('src/app/hero-detail.component' + _vers + '.ts', 'added-imports', 'src/app/hero-detail.component')
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
:marked :marked
Inject the `!{_ActivatedRoute}`, `HeroService`, and `Location` services Inject the `ActivatedRoute`, `HeroService`, and `Location` services
into the constructor, saving their values in private fields: into the constructor, saving their values in private fields:
+makeExcerpt('src/app/hero-detail.component.ts (constructor)', 'ctor') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ctor', 'src/app/hero-detail.component.ts (constructor)')
+ifDocsFor('ts') :marked
:marked Import the `switchMap` operator to use later with the route parameters `Observable`.
Import the `switchMap` operator to use later with the route parameters `Observable`.
+makeExcerpt('src/app/hero-detail.component.ts (switchMap import)', 'rxjs-import') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'rxjs-import', 'src/app/hero-detail.component.ts (switchMap import)')
:marked :marked
Tell the class to implement the `OnInit` interface. Tell the class to implement the `OnInit` interface.
+makeExcerpt('src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts')
block ngOnInit :marked
:marked Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to extract the `id` parameter value from the `ActivatedRoute` service
extract the `id` parameter value from the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
and use the `HeroService` to fetch the hero with that `id`.
+makeExcerpt('src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts')
:marked
The `switchMap` operator maps the `id` in the Observable route parameters
to a new `Observable`, the result of the `HeroService.getHero()` method.
block extract-id If a user re-navigates to this component while a `getHero` request is still processing,
:marked `switchMap` cancels the old request and then calls `HeroService.getHero()` again.
The `switchMap` operator maps the `id` in the Observable route parameters
to a new `Observable`, the result of the `HeroService.getHero()` method.
If a user re-navigates to this component while a `getHero` request is still processing,
`switchMap` cancels the old request and then calls `HeroService.getHero()` again.
- var _str2int = _docsFor == 'dart' ? '<code>int.parse()</code> static method' : 'JavaScript (+) operator'
:marked :marked
The hero `id` is a number. Route parameters are always strings. The hero `id` is a number. Route parameters are always strings.
So the route parameter value is converted to a number with the !{_str2int}. So the route parameter value is converted to a number with the JavaScript (+) operator.
+ifDocsFor('ts') .l-sub-section
.l-sub-section :marked
:marked ### Do you need to unsubscribe?
### Do you need to unsubscribe?
As described in the [ActivatedRoute: the one-stop-shop for route information](../guide/router.html#activated-route) As described in the [ActivatedRoute: the one-stop-shop for route information](../guide/router.html#activated-route)
section of the [Routing & Navigation](../guide/router.html) page, section of the [Routing & Navigation](../guide/router.html) page,
the `Router` manages the observables it provides and localizes the `Router` manages the observables it provides and localizes
the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against
memory leaks, so you don't need to unsubscribe from the route `params` `Observable`. memory leaks, so you don't need to unsubscribe from the route `params` `Observable`.
:marked :marked
### Add *HeroService.getHero()* ### Add *HeroService.getHero()*
@ -540,7 +503,7 @@ block extract-id
In the previous code snippet, `HeroService` doesn't have a `getHero()` method. To fix this issue, In the previous code snippet, `HeroService` doesn't have a `getHero()` method. To fix this issue,
open `HeroService` and add a `getHero()` method that filters the heroes list from `getHeroes()` by `id`. open `HeroService` and add a `getHero()` method that filters the heroes list from `getHeroes()` by `id`.
+makeExcerpt('src/app/hero.service.ts', 'getHero') +makeExample('toh-5/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts (getHero)')
:marked :marked
### Find the way back ### Find the way back
@ -551,31 +514,30 @@ block extract-id
Now add a third option, a `goBack()` method that navigates backward one step in the browser's history stack Now add a third option, a `goBack()` method that navigates backward one step in the browser's history stack
using the `Location` service you injected previously. using the `Location` service you injected previously.
+makeExcerpt('src/app/hero-detail.component.ts', 'goBack') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'goBack', 'src/app/hero-detail.component.ts (goBack)')
- var _CanDeactivateGuard = _docsFor == 'dart' ? '<em>routerCanDeactivate()</em> hook' : '<em>CanDeactivate</em> guard'
.l-sub-section .l-sub-section
:marked :marked
Going back too far could take users out of the app. Going back too far could take users out of the app.
In a real app, you can prevent this issue with the !{_CanDeactivateGuard}. In a real app, you can prevent this issue with the <em>CanDeactivate</em> guard.
Read more on the [CanDeactivate](../api/router/index/CanDeactivate-interface.html) page. Read more on the [CanDeactivate](../api/router/index/CanDeactivate-interface.html) page.
:marked :marked
You'll wire this method with an event binding to a *Back* button that you'll add to the component template. You'll wire this method with an event binding to a *Back* button that you'll add to the component template.
+makeExcerpt('src/app/hero-detail.component.html', 'back-button', '') +makeExample('toh-5/ts/src/app/hero-detail.component.html', 'back-button', '')
:marked :marked
Migrate the template to its own file Migrate the template to its own file
called <span ngio-ex>hero-detail.component.html</span>: called <code>hero-detail.component.html</code>:
+makeExample('src/app/hero-detail.component.html') +makeExample('toh-5/ts/src/app/hero-detail.component.html', 'src/app/hero-detail.component.html')
:marked :marked
Update the component metadata with a `templateUrl` pointing to the template file that you just created. Update the component metadata with a `templateUrl` pointing to the template file that you just created.
+makeExcerpt('src/app/hero-detail.component.ts', 'metadata') +makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'metadata', 'src/app/hero-detail.component.ts (metadata)')
:marked :marked
Refresh the browser and see the results. Refresh the browser and see the results.
@ -593,70 +555,67 @@ block extract-id
To achieve this effect, reopen `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags To achieve this effect, reopen `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
with `<a>` tags. Change the opening `<a>` tag to the following: with `<a>` tags. Change the opening `<a>` tag to the following:
+makeExample('src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)') +makeExample('toh-5/ts/src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)')
- var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
:marked :marked
Notice the `[routerLink]` binding. Notice the `[routerLink]` binding.
As described in the [Router links](#router-links) section of this page, As described in the [Router links](#router-links) section of this page,
top-level navigation in the `AppComponent` template has router links set to fixed !{_pathVsName}s of the top-level navigation in the `AppComponent` template has router links set to fixed paths of the
destination routes, "/dashboard" and "/heroes". destination routes, "/dashboard" and "/heroes".
This time, you're binding to an expression containing a *link parameters !{_array}*. This time, you're binding to an expression containing a *link parameters array*.
The !{_array} has two elements: the *!{_pathVsName}* of The array has two elements: the *path* of
the destination route and a *route parameter* set to the value of the current hero's `id`. the destination route and a *route parameter* set to the value of the current hero's `id`.
The two !{_array} items align with the *!{_pathVsName}* and *:id* The two array items align with the *path* and *:id*
token in the parameterized hero detail route definition that you added to token in the parameterized hero detail route definition that you added to
`!{_appRoutingTsVsAppComp}` earlier: `app.module.ts` earlier:
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts' +makeExample('toh-5/ts/src/app/app.module.3.ts', 'hero-detail', 'src/app/app.module.ts (hero detail)')
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
:marked :marked
Refresh the browser and select a hero from the dashboard; the app navigates to that heros details. Refresh the browser and select a hero from the dashboard; the app navigates to that heros details.
+ifDocsFor('ts') .l-main-section
.l-main-section :marked
:marked ## Refactor routes to a _Routing Module_
## Refactor routes to a _Routing Module_
Almost 20 lines of `AppModule` are devoted to configuring four routes. Almost 20 lines of `AppModule` are devoted to configuring four routes.
Most applications have many more routes and they add guard services Most applications have many more routes and they add guard services
to protect against unwanted or unauthorized navigations. to protect against unwanted or unauthorized navigations.
(Read more about guard services in the [Route Guards](../guide/router.html#guards) (Read more about guard services in the [Route Guards](../guide/router.html#guards)
section of the [Routing & Navigation](../guide/router.html) page.) section of the [Routing & Navigation](../guide/router.html) page.)
Routing considerations could quickly dominate this module and obscure its primary purpose, which is to Routing considerations could quickly dominate this module and obscure its primary purpose, which is to
establish key facts about the entire app for the Angular compiler. establish key facts about the entire app for the Angular compiler.
It's a good idea to refactor the routing configuration into its own class. It's a good idea to refactor the routing configuration into its own class.
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`, The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`,
a class dedicated to routing should be a *routing module*. a class dedicated to routing should be a *routing module*.
For more information, see the [Milestone #2: The Routing Module](../guide/router.html#routing-module) For more information, see the [Milestone #2: The Routing Module](../guide/router.html#routing-module)
section of the [Routing & Navigation](../guide/router.html) page. section of the [Routing & Navigation](../guide/router.html) page.
By convention, a routing module name contains the word "Routing" and By convention, a routing module name contains the word "Routing" and
aligns with the name of the module that declares the components navigated to. aligns with the name of the module that declares the components navigated to.
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`. Create an `app-routing.module.ts` file as a sibling to `app.module.ts`.
Give it the following contents, extracted from the `AppModule` class: Give it the following contents, extracted from the `AppModule` class:
+makeExample('src/app/app-routing.module.ts') +makeExample('toh-5/ts/src/app/app-routing.module.ts', null, 'src/app/app-routing.module.ts')
:marked :marked
The following points are typical of routing modules: The following points are typical of routing modules:
* The Routing Module pulls the routes into a variable. The variable clarifies the * The Routing Module pulls the routes into a variable. The variable clarifies the
routing module pattern in case you export the module in the future. routing module pattern in case you export the module in the future.
* The Routing Module adds `RouterModule.forRoot(routes)` to `imports`. * The Routing Module adds `RouterModule.forRoot(routes)` to `imports`.
* The Routing Module adds `RouterModule` to `exports` so that the * The Routing Module adds `RouterModule` to `exports` so that the
components in the companion module have access to Router declarables, components in the companion module have access to Router declarables,
such as `RouterLink` and `RouterOutlet`. such as `RouterLink` and `RouterOutlet`.
* There are no `declarations`. Declarations are the responsibility of the companion module. * There are no `declarations`. Declarations are the responsibility of the companion module.
* If you have guard services, the Routing Module adds module `providers`. (There are none in this example.) * If you have guard services, the Routing Module adds module `providers`. (There are none in this example.)
### Update *AppModule* ### Update *AppModule*
Delete the routing configuration from `AppModule` and import the `AppRoutingModule`. Delete the routing configuration from `AppModule` and import the `AppRoutingModule`.
Use an ES `import` statement *and* add it to the `NgModule.imports` list. Use an ES `import` statement *and* add it to the `NgModule.imports` list.
Here is the revised `AppModule`, compared to its pre-refactor state: Here is the revised `AppModule`, compared to its pre-refactor state:
@ -692,7 +651,7 @@ block extract-id
Add the following HTML fragment at the bottom of the template where the `<hero-detail>` used to be: Add the following HTML fragment at the bottom of the template where the `<hero-detail>` used to be:
+makeExcerpt('src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts') +makeExample('toh-5/ts/src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts')
:marked :marked
@ -707,7 +666,7 @@ figure.image-display
The hero's name is displayed in capital letters because of the `uppercase` pipe The hero's name is displayed in capital letters because of the `uppercase` pipe
that's included in the interpolation binding, right after the pipe operator ( | ). that's included in the interpolation binding, right after the pipe operator ( | ).
+makeExcerpt('src/app/heroes.component.html', 'pipe', '') +makeExample('toh-5/ts/src/app/heroes.component.html', 'pipe', '')
:marked :marked
Pipes are a good way to format strings, currency amounts, dates and other display data. Pipes are a good way to format strings, currency amounts, dates and other display data.
@ -729,10 +688,10 @@ figure.image-display
Before making any more changes, migrate the template and styles to their own files. Before making any more changes, migrate the template and styles to their own files.
First, move the template contents from `heroes.component.ts` First, move the template contents from `heroes.component.ts`
into a new <span ngio-ex>heroes.component.html</span> file. into a new <code>heroes.component.html</code> file.
Don't copy the backticks. As for `heroes.component.ts`, you'll Don't copy the backticks. As for `heroes.component.ts`, you'll
come back to it in a minute. Next, move the come back to it in a minute. Next, move the
styles contents into a new <span ngio-ex>heroes.component.css</span> file. styles contents into a new <code>heroes.component.css</code> file.
The two new files should look like this: The two new files should look like this:
@ -747,14 +706,11 @@ figure.image-display
`templateUrl` and `styleUrls` respectively. `templateUrl` and `styleUrls` respectively.
Set their properties to refer to the new files. Set their properties to refer to the new files.
block heroes-component-cleanup +makeExample('toh-5/ts/src/app/heroes.component.ts', 'metadata', 'src/app/heroes.component.ts (revised metadata)')
//- Only relevant for Dart.
+makeExcerpt('src/app/heroes.component.ts (revised metadata)', 'metadata')
.l-sub-section .l-sub-section
:marked :marked
The `styleUrls` property is !{_an} !{_array} of style file names (with paths). The `styleUrls` property is an array of style file names (with paths).
You could list multiple style files from different locations if you needed them. You could list multiple style files from different locations if you needed them.
:marked :marked
@ -770,16 +726,16 @@ block heroes-component-cleanup
1. Inject the `router` in the constructor, along with the `HeroService`. 1. Inject the `router` in the constructor, along with the `HeroService`.
1. Implement `gotoDetail()` by calling the router `navigate()` method. 1. Implement `gotoDetail()` by calling the router `navigate()` method.
+makeExcerpt('src/app/heroes.component.ts', 'gotoDetail') +makeExample('toh-5/ts/src/app/heroes.component.ts', 'gotoDetail', 'src/app/heroes.component.ts (gotoDetail)')
:marked :marked
Note that you're passing a two-element *link parameters !{_array}*&mdash;a Note that you're passing a two-element *link parameters array*&mdash;a
!{_pathVsName} and the route parameter&mdash;to path and the route parameter&mdash;to
the router `navigate()` method, just as you did in the `[routerLink]` binding the router `navigate()` method, just as you did in the `[routerLink]` binding
back in the `DashboardComponent`. back in the `DashboardComponent`.
Here's the revised `HeroesComponent` class: Here's the revised `HeroesComponent` class:
+makeExcerpt('src/app/heroes.component.ts', 'class') +makeExample('toh-5/ts/src/app/heroes.component.ts', 'class', 'src/app/heroes.component.ts (class)')
:marked :marked
Refresh the browser and start clicking. Refresh the browser and start clicking.
@ -800,21 +756,21 @@ block heroes-component-cleanup
would obscure the component logic. would obscure the component logic.
Instead, edit the CSS in a separate `*.css` file. Instead, edit the CSS in a separate `*.css` file.
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference Add a <code>dashboard.component.css</code> file to the `app` folder and reference
that file in the component metadata's `styleUrls` !{_array} property like this: that file in the component metadata's `styleUrls` array property like this:
+makeExcerpt('src/app/dashboard.component.ts (styleUrls)', 'css') +makeExample('toh-5/ts/src/app/dashboard.component.ts', 'css', 'src/app/dashboard.component.ts (styleUrls)')
:marked :marked
### Add stylish hero details ### Add stylish hero details
You've also been provided with CSS styles specifically for the `HeroDetailComponent`. You've also been provided with CSS styles specifically for the `HeroDetailComponent`.
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}` Add a <code>hero-detail.component.css</code> to the `app`
folder and refer to that file inside folder and refer to that file inside
the `styleUrls` !{_array} as you did for `DashboardComponent`. the `styleUrls` array as you did for `DashboardComponent`.
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` !{_decorator} Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` decorator
<span if-docs="ts">and its import</span>. and its import.
Here's the content for the component CSS files. Here's the content for the component CSS files.
@ -831,25 +787,24 @@ block heroes-component-cleanup
The provided CSS makes the navigation links in the `AppComponent` look more like selectable buttons. The provided CSS makes the navigation links in the `AppComponent` look more like selectable buttons.
You'll surround those links in `<nav>` tags. You'll surround those links in `<nav>` tags.
Add an <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content. Add an <code>app.component.css</code> file to the `app` folder with the following content.
+makeExcerpt('src/app/app.component.css (navigation styles)', '') +makeExample('toh-5/ts/src/app/app.component.css', '', 'src/app/app.component.css (navigation styles)')
.l-sub-section .l-sub-section
block router-link-active :marked
:marked **The *routerLinkActive* directive**
**The *routerLinkActive* directive**
The Angular router provides a `routerLinkActive` directive you can use to The Angular router provides a `routerLinkActive` directive you can use to
add a class to the HTML navigation element whose route matches the active route. add a class to the HTML navigation element whose route matches the active route.
All you have to do is define the style for it. All you have to do is define the style for it.
+makeExcerpt('src/app/app.component.ts (active router links)', 'template') +makeExample('toh-5/ts/src/app/app.component.ts', 'template', 'src/app/app.component.ts (active router links)')
:marked :marked
Add a `styleUrls` property that refers to this CSS file as follows: Add a `styleUrls` property that refers to this CSS file as follows:
+makeExcerpt('src/app/app.component.ts','styleUrls') +makeExample('toh-5/ts/src/app/app.component.ts','styleUrls')
:marked :marked
@ -865,16 +820,14 @@ block heroes-component-cleanup
These correspond to the full set of master styles that you installed earlier during [setup](../guide/setup.html). These correspond to the full set of master styles that you installed earlier during [setup](../guide/setup.html).
Here's an excerpt: Here's an excerpt:
+makeExcerpt('src/styles.css (excerpt)', 'toh') +makeExample('toh-5/ts/src/styles.css', 'toh', 'src/styles.css (excerpt)')
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css'
:marked :marked
Create the file <span ngio-ex>styles.css</span>. Create the file <code>styles.css</code>.
Ensure that the file contains the [master styles provided here](!{styles_css}). Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css).
Also edit <span ngio-ex>index.html</span> to refer to this stylesheet. Also edit <code>index.html</code> to refer to this stylesheet.
+makeExcerpt('src/index.html (link ref)', 'css') +makeExample('toh-5/ts/src/index.html', 'css', 'src/index.html (link ref)')
:marked :marked
Look at the app now. The dashboard, heroes, and navigation links are styled. Look at the app now. The dashboard, heroes, and navigation links are styled.
@ -889,37 +842,36 @@ figure.image-display
Review the sample source code in the <live-example></live-example> for this page. Review the sample source code in the <live-example></live-example> for this page.
Verify that you have the following structure: Verify that you have the following structure:
block file-tree-end .filetree
.filetree .file angular-tour-of-heroes
.file angular-tour-of-heroes .children
.file src
.children .children
.file src .file app
.children .children
.file app .file app.component.css
.children .file app.component.ts
.file app.component.css .file app.module.ts
.file app.component.ts .file app-routing.module.ts
.file app.module.ts .file dashboard.component.css
.file app-routing.module.ts .file dashboard.component.html
.file dashboard.component.css .file dashboard.component.ts
.file dashboard.component.html .file hero.service.ts
.file dashboard.component.ts .file hero.ts
.file hero.service.ts .file hero-detail.component.css
.file hero.ts .file hero-detail.component.html
.file hero-detail.component.css .file hero-detail.component.ts
.file hero-detail.component.html .file heroes.component.css
.file hero-detail.component.ts .file heroes.component.html
.file heroes.component.css .file heroes.component.ts
.file heroes.component.html .file mock-heroes.ts
.file heroes.component.ts .file main.ts
.file mock-heroes.ts .file index.html
.file main.ts .file styles.css
.file index.html .file systemjs.config.js
.file styles.css .file tsconfig.json
.file systemjs.config.js .file node_modules ...
.file tsconfig.json .file package.json
.file node_modules ...
.file package.json
.l-main-section .l-main-section
:marked :marked

View File

@ -1,15 +1,5 @@
- var _example = 'toh-6';
block includes block includes
include ../_util-fns include ../_util-fns
- var _Http = 'Http'; // Angular `Http` library name.
- var _Angular_Http = 'Angular <code>Http</code>'
- var _Angular_http_library = 'Angular HTTP library'
- var _HttpModule = 'HttpModule'
- var _JSON_stringify = 'JSON.stringify'
//- Shared var definitions
- var _promise = _Promise.toLowerCase()
:marked :marked
In this page, you'll make the following improvements. In this page, you'll make the following improvements.
@ -29,10 +19,10 @@ block includes
In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list, In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list,
editing a selected hero along the way. editing a selected hero along the way.
That's the starting point for this page. That's the starting point for this page.
block start-server-and-watch
:marked :marked
## Keep the app transpiling and running ## Keep the app transpiling and running
Enter the following command in the terminal window: Enter the following command in the terminal window:
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
npm start npm start
@ -46,77 +36,76 @@ block start-server-and-watch
.l-main-section#http-providers .l-main-section#http-providers
h1 Providing HTTP Services h1 Providing HTTP Services
block http-library
:marked
The `HttpModule` is not a core Angular module.
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
and is shipped in a separate script file as part of the Angular npm package.
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it. :marked
The `HttpModule` is not a core Angular module.
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
and is shipped in a separate script file as part of the Angular npm package.
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it.
:marked :marked
## Register for HTTP services ## Register for HTTP services
block http-providers :marked
:marked The app will depend on the Angular `http` service, which itself depends on other supporting services.
The app will depend on the Angular `http` service, which itself depends on other supporting services. The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
To allow access to these services from anywhere in the app, To allow access to these services from anywhere in the app,
add `HttpModule` to the `imports` list of the `AppModule`. add `HttpModule` to the `imports` list of the `AppModule`.
+makeExample('src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)') +makeExample('toh-6/ts/src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
:marked :marked
Notice that you also supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`. Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`.
.l-main-section .l-main-section
:marked :marked
## Simulate the web API ## Simulate the web API
We recommend registering app-wide services in the root
`AppModule` *providers*.
Until you have a web server that can handle requests for hero data, Until you have a web server that can handle requests for hero data,
the HTTP client will fetch and save data from the HTTP client will fetch and save data from
a mock service, the *in-memory web API*. a mock service, the *in-memory web API*.
Update <span ngio-ex>!{_appModuleTsVsMainTs}</span> with this version, which uses the mock service: Update <code>src/app/app.module.ts</code> with this version, which uses the mock service:
+makeExcerpt(_appModuleTsVsMainTs, 'v2') +makeExample('toh-6/ts/src/app/app.module.ts', 'v2', 'src/app/app.module.ts (v2)')
block backend :marked
:marked Rather than require a real API server, this example simulates communication with the remote server by adding the
Rather than require a real API server, this example simulates communication with the remote server by adding the <a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a> to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
+makeExcerpt(_appModuleTsVsMainTs, 'in-mem-web-api', '') +makeExample('toh-6/ts/src/app/app.module.ts', 'in-mem-web-api', '')
:marked :marked
The `forRoot()` configuration method takes an `InMemoryDataService` class The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database. that primes the in-memory database.
Add the file `in-memory-data.service.ts` in `!{_appDir}` with the following content: Add the file `in-memory-data.service.ts` in `app` with the following content:
+makeExample('src/app/in-memory-data.service.ts', 'init')(format='.') +makeExample('toh-6/ts/src/app/in-memory-data.service.ts', 'init', 'src/app/in-memory-data.service.ts')(format='.')
:marked :marked
This file replaces `mock-heroes.ts`, which is now safe to delete. This file replaces `mock-heroes.ts`, which is now safe to delete.
block dont-be-distracted-by-backend-subst .alert.is-helpful
.alert.is-helpful :marked
:marked The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes. Don't worry about the details of this backend substitution; you can
Don't worry about the details of this backend substitution; you can skip it when you have a real web API server.
skip it when you have a real web API server.
Read more about the in-memory web API in the Read more about the in-memory web API in the
[Appendix: Tour of Heroes in-memory web api](../guide/server-communication.html#in-mem-web-api) [Appendix: Tour of Heroes in-memory web api](../guide/server-communication.html#in-mem-web-api)
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page. section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
.l-main-section .l-main-section
:marked :marked
## Heroes and HTTP ## Heroes and HTTP
In the current `HeroService` implementation, a !{_Promise} resolved with mock heroes is returned. In the current `HeroService` implementation, a Promise resolved with mock heroes is returned.
+makeExcerpt('toh-4/ts/src/app/hero.service.ts (old getHeroes)', 'get-heroes') +makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (old getHeroes)')
:marked :marked
This was implemented in anticipation of ultimately This was implemented in anticipation of ultimately
@ -124,56 +113,53 @@ block dont-be-distracted-by-backend-subst
Now convert `getHeroes()` to use HTTP. Now convert `getHeroes()` to use HTTP.
+makeExcerpt('src/app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes') +makeExample('toh-6/ts/src/app/hero.service.ts', 'getHeroes', 'src/app/hero.service.ts (updated getHeroes and new class members)')
:marked :marked
Update the import statements as follows: Update the import statements as follows:
+makeExcerpt('src/app/hero.service.ts (updated imports)', 'imports') +makeExample('toh-6/ts/src/app/hero.service.ts', 'imports', 'src/app/hero.service.ts (updated imports)')
- var _h3id = `http-${_promise}`
:marked :marked
Refresh the browser. The hero data should successfully load from the Refresh the browser. The hero data should successfully load from the
mock server. mock server.
<h3 id="!{_h3id}">HTTP !{_Promise}</h3> <h3 id="http-promise">HTTP Promise</h3>
:marked
The Angular `http.get` returns an RxJS `Observable`.
*Observables* are a powerful way to manage asynchronous data flows.
You'll read about [Observables](#observables) later in this page.
block get-heroes-details For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-promise', '')
:marked
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
To use those capabilities, you have to add the operators themselves.
That's as easy as importing them from the RxJS library like this:
+makeExample('toh-6/ts/src/app/hero.service.ts', 'rxjs', '')
.l-sub-section
:marked :marked
The Angular `http.get` returns an RxJS `Observable`. You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
*Observables* are a powerful way to manage asynchronous data flows.
You'll read about [Observables](#observables) later in this page.
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator. :marked
### Extracting the data in the *then* callback
+makeExcerpt('src/app/hero.service.ts', 'to-promise', '') In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
data within the response.
:marked +makeExample('toh-6/ts/src/app/hero.service.ts', 'to-data', '')
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
To use those capabilities, you have to add the operators themselves.
That's as easy as importing them from the RxJS library like this:
+makeExcerpt('src/app/hero.service.ts', 'rxjs', '')
.l-sub-section
:marked
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
:marked
### Extracting the data in the *then* callback
In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
data within the response.
+makeExcerpt('src/app/hero.service.ts', 'to-data', '')
:marked :marked
The response JSON has a single `data` property, which The response JSON has a single `data` property, which
holds the !{_array} of heroes that the caller wants. holds the array of heroes that the caller wants.
So you grab that !{_array} and return it as the resolved !{_Promise} value. So you grab that array and return it as the resolved Promise value.
.alert.is-important .alert.is-important
:marked :marked
@ -183,27 +169,26 @@ block get-heroes-details
:marked :marked
The caller is unaware that you fetched the heroes from the (mock) server. The caller is unaware that you fetched the heroes from the (mock) server.
It receives a !{_Promise} of *heroes* just as it did before. It receives a Promise of *heroes* just as it did before.
### Error Handling ### Error Handling
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler. At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
+makeExcerpt('src/app/hero.service.ts', 'catch', '') +makeExample('toh-6/ts/src/app/hero.service.ts', 'catch', '')
:marked :marked
This is a critical step. This is a critical step.
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control. You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
+makeExcerpt('src/app/hero.service.ts', 'handleError', '') +makeExample('toh-6/ts/src/app/hero.service.ts', 'handleError', '')
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
:marked :marked
This demo service logs the error to the console; in real life, This demo service logs the error to the console; in real life,
you would handle the error in code. For a demo, this works. you would handle the error in code. For a demo, this works.
The code also includes an error to The code also includes an error to
the caller in a !{rejected_promise}, so that the caller can display a proper error message to the user. the caller in a rejected promise, so that the caller can display a proper error message to the user.
### Get hero by id ### Get hero by id
@ -215,19 +200,19 @@ block get-heroes-details
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`). Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
Update the `HeroService.getHero()` method to make a _get-by-id_ request: Update the `HeroService.getHero()` method to make a _get-by-id_ request:
+makeExcerpt('src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts') +makeExample('toh-6/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
:marked :marked
This request is almost the same as `getHeroes()`. This request is almost the same as `getHeroes()`.
The hero id in the URL identifies which hero the server should update. The hero id in the URL identifies which hero the server should update.
Also, the `data` in the response is a single hero object rather than !{_an} !{_array}. Also, the `data` in the response is a single hero object rather than an array.
### Unchanged _getHeroes_ API ### Unchanged _getHeroes_ API
Although you made significant internal changes to `getHeroes()` and `getHero()`, Although you made significant internal changes to `getHeroes()` and `getHero()`,
the public signatures didn't change. the public signatures didn't change.
You still return a !{_Promise} from both methods. You still return a Promise from both methods.
You won't have to update any of the components that call them. You won't have to update any of the components that call them.
Now it's time to add the ability to create and delete heroes. Now it's time to add the ability to create and delete heroes.
@ -251,13 +236,13 @@ block get-heroes-details
At the end of the hero detail template, add a save button with a `click` event At the end of the hero detail template, add a save button with a `click` event
binding that invokes a new component method named `save()`. binding that invokes a new component method named `save()`.
+makeExcerpt('src/app/hero-detail.component.html', 'save') +makeExample('toh-6/ts/src/app/hero-detail.component.html', 'save', 'src/app/hero-detail.component.html (save)')
:marked :marked
Add the following `save()` method, which persists hero name changes using the hero service Add the following `save()` method, which persists hero name changes using the hero service
`update()` method and then navigates back to the previous view. `update()` method and then navigates back to the previous view.
+makeExcerpt('src/app/hero-detail.component.ts', 'save') +makeExample('toh-6/ts/src/app/hero-detail.component.ts', 'save', 'src/app/hero-detail.component.ts (save)')
:marked :marked
### Add a hero service _update()_ method ### Add a hero service _update()_ method
@ -266,12 +251,12 @@ block get-heroes-details
`getHeroes()`, but it uses an HTTP `put()` to persist server-side changes. `getHeroes()`, but it uses an HTTP `put()` to persist server-side changes.
+makeExcerpt('src/app/hero.service.ts', 'update') +makeExample('toh-6/ts/src/app/hero.service.ts', 'update', 'src/app/hero.service.ts (update)')
:marked :marked
To identify which hero the server should update, the hero `id` is encoded in To identify which hero the server should update, the hero `id` is encoded in
the URL. The `put()` body is the JSON string encoding of the hero, obtained by the URL. The `put()` body is the JSON string encoding of the hero, obtained by
calling `!{_JSON_stringify}`. The body content type calling `JSON.stringify`. The body content type
(`application/json`) is identified in the request header. (`application/json`) is identified in the request header.
Refresh the browser, change a hero name, save your change, Refresh the browser, change a hero name, save your change,
@ -287,20 +272,20 @@ block get-heroes-details
Insert the following into the heroes component HTML, just after Insert the following into the heroes component HTML, just after
the heading: the heading:
+makeExcerpt('src/app/heroes.component.html', 'add') +makeExample('toh-6/ts/src/app/heroes.component.html', 'add', 'src/app/heroes.component.html (add)')
:marked :marked
In response to a click event, call the component's click handler and then In response to a click event, call the component's click handler and then
clear the input field so that it's ready for another name. clear the input field so that it's ready for another name.
+makeExcerpt('src/app/heroes.component.ts', 'add') +makeExample('toh-6/ts/src/app/heroes.component.ts', 'add', 'src/app/heroes.component.ts (add)')
:marked :marked
When the given name is non-blank, the handler delegates creation of the When the given name is non-blank, the handler delegates creation of the
named hero to the hero service, and then adds the new hero to the !{_array}. named hero to the hero service, and then adds the new hero to the array.
Implement the `create()` method in the `HeroService` class. Implement the `create()` method in the `HeroService` class.
+makeExcerpt('src/app/hero.service.ts', 'create') +makeExample('toh-6/ts/src/app/hero.service.ts', 'create', 'src/app/hero.service.ts (create)')
:marked :marked
Refresh the browser and create some heroes. Refresh the browser and create some heroes.
@ -314,12 +299,12 @@ block get-heroes-details
Add the following button element to the heroes component HTML, after the hero Add the following button element to the heroes component HTML, after the hero
name in the repeated `<li>` element. name in the repeated `<li>` element.
+makeExcerpt('src/app/heroes.component.html', 'delete', '') +makeExample('toh-6/ts/src/app/heroes.component.html', 'delete', '')
:marked :marked
The `<li>` element should now look like this: The `<li>` element should now look like this:
+makeExcerpt('src/app/heroes.component.html', 'li-element') +makeExample('toh-6/ts/src/app/heroes.component.html', 'li-element', 'src/app/heroes.component.html (li-element)')
:marked :marked
In addition to calling the component's `delete()` method, the delete button's In addition to calling the component's `delete()` method, the delete button's
@ -329,61 +314,60 @@ block get-heroes-details
The logic of the `delete()` handler is a bit trickier: The logic of the `delete()` handler is a bit trickier:
+makeExcerpt('src/app/heroes.component.ts', 'delete') +makeExample('toh-6/ts/src/app/heroes.component.ts', 'delete', 'src/app/heroes.component.ts (delete)')
:marked :marked
Of course you delegate hero deletion to the hero service, but the component Of course you delegate hero deletion to the hero service, but the component
is still responsible for updating the display: it removes the deleted hero is still responsible for updating the display: it removes the deleted hero
from the !{_array} and resets the selected hero, if necessary. from the array and resets the selected hero, if necessary.
:marked :marked
To place the delete button at the far right of the hero entry, To place the delete button at the far right of the hero entry,
add this CSS: add this CSS:
+makeExcerpt('src/app/heroes.component.css', 'additions') +makeExample('toh-6/ts/src/app/heroes.component.css', 'additions', 'src/app/heroes.component.css (additions)')
:marked :marked
### Hero service _delete()_ method ### Hero service _delete()_ method
Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server: Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server:
+makeExcerpt('src/app/hero.service.ts', 'delete') +makeExample('toh-6/ts/src/app/hero.service.ts', 'delete', 'src/app/hero.service.ts (delete)')
:marked :marked
Refresh the browser and try the new delete functionality. Refresh the browser and try the new delete functionality.
#observables #observables
:marked :marked
## !{_Observable}s ## Observables
block observables-section-intro :marked
:marked Each `Http` service method returns an `Observable` of HTTP `Response` objects.
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller. The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
This section shows you how, when, and why to return the `Observable` directly. This section shows you how, when, and why to return the `Observable` directly.
### Background ### Background
An *Observable* is a stream of events that you can process with array-like operators. An *Observable* is a stream of events that you can process with array-like operators.
Angular core has basic support for observables. Angular core has basic support for observables.
Developers augment that support with operators and extensions from the Developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>. <a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
You'll see how shortly. You'll see how shortly.
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`. Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`.
That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller. That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller.
Converting to a Promise is often a good choice. You typically ask `http.get()` to fetch a single chunk of data. Converting to a Promise is often a good choice. You typically ask `http.get()` to fetch a single chunk of data.
When you receive the data, you're done. When you receive the data, you're done.
The calling component can easily consume a single result in the form of a Promise. The calling component can easily consume a single result in the form of a Promise.
:marked :marked
But requests aren't always done only once. But requests aren't always done only once.
You may start one request, You may start one request,
cancel it, and make a different request before the server has responded to the first request. cancel it, and make a different request before the server has responded to the first request.
A *request-cancel-new-request* sequence is difficult to implement with *!{_Promise}s*, but A *request-cancel-new-request* sequence is difficult to implement with *Promises*, but
easy with *!{_Observable}s*. easy with *Observables*.
### Add the ability to search by name ### Add the ability to search by name
You're going to add a *hero search* feature to the Tour of Heroes. You're going to add a *hero search* feature to the Tour of Heroes.
@ -391,13 +375,13 @@ block observables-section-intro
Start by creating `HeroSearchService` that sends search queries to the server's web API. Start by creating `HeroSearchService` that sends search queries to the server's web API.
+makeExample('src/app/hero-search.service.ts') +makeExample('toh-6/ts/src/app/hero-search.service.ts', null, 'src/app/hero-search.service.ts')
:marked :marked
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one The `http.get()` call in `HeroSearchService` is similar to the one
in the `HeroService`, although the URL now has a query string. in the `HeroService`, although the URL now has a query string.
<span if-docs="ts">More importantly, you no longer call `toPromise()`. More importantly, you no longer call `toPromise()`.
Instead you return the *Observable* from the the `htttp.get()`, Instead you return the *Observable* from the the `htttp.get()`,
after chaining it to another RxJS operator, <code>map()</code>, after chaining it to another RxJS operator, <code>map()</code>,
to extract heroes from the response data. to extract heroes from the response data.
@ -411,121 +395,117 @@ block observables-section-intro
The component template is simple&mdash;just a text box and a list of matching search results. The component template is simple&mdash;just a text box and a list of matching search results.
+makeExample('src/app/hero-search.component.html') +makeExample('toh-6/ts/src/app/hero-search.component.html', null, 'src/app/hero-search.component.html')
:marked :marked
Also, add styles for the new component. Also, add styles for the new component.
+makeExample('src/app/hero-search.component.css') +makeExample('toh-6/ts/src/app/hero-search.component.css', null, 'src/app/hero-search.component.css')
:marked :marked
As the user types in the search box, a *keyup* event binding calls the component's `search()` As the user types in the search box, a *keyup* event binding calls the component's `search()`
method with the new search box value. method with the new search box value.
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property. As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
But as you'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}. But as you'll soon see, the `heroes` property is now an *Observable* of hero arrays, rather than just a hero array.
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until you route it through the `async` pipe (`AsyncPipe`). The `*ngFor` can't do anything with an `Observable` until you route it through the `async` pipe (`AsyncPipe`).
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`. The `async` pipe subscribes to the `Observable` and produces the array of heroes to `*ngFor`.
Create the `HeroSearchComponent` class and metadata. Create the `HeroSearchComponent` class and metadata.
+makeExample('src/app/hero-search.component.ts') +makeExample('toh-6/ts/src/app/hero-search.component.ts', null, 'src/app/hero-search.component.ts')
:marked :marked
#### Search terms #### Search terms
Focus on `!{_priv}searchTerms`: Focus on the `searchTerms`:
+makeExcerpt('src/app/hero-search.component.ts', 'searchTerms', '') +makeExample('toh-6/ts/src/app/hero-search.component.ts', 'searchTerms', '')
block search-criteria-intro :marked
:marked A `Subject` is a producer of an _observable_ event stream;
A `Subject` is a producer of an _observable_ event stream; `searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`. Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`.
:marked :marked
<a id="ngoninit"></a> <a id="ngoninit"></a>
#### Initialize the *heroes* property (*ngOnInit*) #### Initialize the *heroes* property (*ngOnInit*)
<span if-docs="ts">A `Subject` is also an `Observable`.</span> A `Subject` is also an `Observable`.
You can turn the stream You can turn the stream
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property. of search terms into a stream of `Hero` arrays and assign the result to the `heroes` property.
+makeExcerpt('src/app/hero-search.component.ts', 'search', '') +makeExample('toh-6/ts/src/app/hero-search.component.ts', 'search', '')
:marked :marked
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests, Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
taxing server resources and burning through the cellular network data plan. taxing server resources and burning through the cellular network data plan.
block observable-transformers :marked
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`.
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
before passing along the latest string. You'll never make requests more frequently than 300ms.
* `distinctUntilChanged` ensures that a request is sent only if the filter text changed.
* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`.
It cancels and discards previous search observables, returning only the latest search service observable.
.l-sub-section
:marked :marked
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`. With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how: (formerly known as `flatMapLatest`),
every qualifying key event can trigger an `http()` method call.
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
and they may not return in the order sent.
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds `switchMap()` preserves the original request order while returning
before passing along the latest string. You'll never make requests more frequently than 300ms. only the observable from the most recent `http` method call.
* `distinctUntilChanged()` ensures that a request is sent only if the filter text changed. Results from prior calls are canceled and discarded.
* `switchMap()` calls the search service for each search term that makes it through `debounceTime()` and `distinctUntilChanged()`.
It cancels and discards previous search observables, returning only the latest search service observable.
.l-sub-section If the search text is empty, the `http()` method call is also short circuited
:marked and an observable containing an empty array is returned.
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
(formerly known as `flatMapLatest`),
every qualifying key event can trigger an `http()` method call.
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
and they may not return in the order sent.
`switchMap()` preserves the original request order while returning Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
only the observable from the most recent `http` method call. doesn't actually abort a pending HTTP request.
Results from prior calls are canceled and discarded. For now, unwanted results are discarded.
:marked
* `catch` intercepts a failed observable.
The simple example prints the error to the console; a real life app would do better.
Then to clear the search result, you return an observable containing an empty array.
If the search text is empty, the `http()` method call is also short circuited a#rxjs-imports
and an observable containing an empty array is returned. :marked
### Import RxJS operators
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable Most RxJS operators are not included in Angular's base `Observable` implementation.
doesn't actually abort a pending HTTP request. The base implementation includes only what Angular itself requires.
For now, unwanted results are discarded.
:marked
* `catch` intercepts a failed observable.
The simple example prints the error to the console; a real life app would do better.
Then to clear the search result, you return an observable containing an empty array.
a#rxjs-imports When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
:marked Here are all the RxJS imports that _this_ component needs:
### Import RxJS operators
Most RxJS operators are not included in Angular's base `Observable` implementation. +makeExample('toh-6/ts/src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
The base implementation includes only what Angular itself requires.
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined. :marked
Here are all the RxJS imports that _this_ component needs: The `import 'rxjs/add/...'` syntax may be unfamiliar.
It's missing the usual list of symbols between the braces: `{...}`.
+makeExample('src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.') You don't need the operator symbols themselves.
In each case, the mere act of importing the library
:marked loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
The `import 'rxjs/add/...'` syntax may be unfamiliar.
It's missing the usual list of symbols between the braces: `{...}`.
You don't need the operator symbols themselves.
In each case, the mere act of importing the library
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
:marked :marked
### Add the search component to the dashboard ### Add the search component to the dashboard
Add the hero search HTML element to the bottom of the `DashboardComponent` template. Add the hero search HTML element to the bottom of the `DashboardComponent` template.
+makeExample('src/app/dashboard.component.html')(format='.') +makeExample('toh-6/ts/src/app/dashboard.component.html', null, 'src/app/dashboard.component.html')(format='.')
- var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
- var declFile = _docsFor == 'dart' ? 'src/app/dashboard.component.ts' : 'src/app/app.module.ts'
:marked :marked
Finally, import `HeroSearchComponent` from Finally, import `HeroSearchComponent` from
<span ngio-ex>hero-search.component.ts</span> <code>hero-search.component.ts</code>
and add it to the `!{_declarations}` !{_array}. and add it to the `declarations` array.
+makeExcerpt(declFile, 'search') +makeExample('toh-6/ts/src/app/app.module.ts', 'search', 'src/app/app.module.ts (search)')
:marked :marked
Run the app again. In the Dashboard, enter some text in the search box. Run the app again. In the Dashboard, enter some text in the search box.
@ -541,41 +521,40 @@ figure.image-display
Review the sample source code in the <live-example></live-example> for this page. Review the sample source code in the <live-example></live-example> for this page.
Verify that you have the following structure: Verify that you have the following structure:
block filetree .filetree
.filetree .file angular-tour-of-heroes
.file angular-tour-of-heroes .children
.file src
.children .children
.file src .file app
.children .children
.file app .file app.component.ts
.children .file app.component.css
.file app.component.ts .file app.module.ts
.file app.component.css .file app-routing.module.ts
.file app.module.ts .file dashboard.component.css
.file app-routing.module.ts .file dashboard.component.html
.file dashboard.component.css .file dashboard.component.ts
.file dashboard.component.html .file hero.ts
.file dashboard.component.ts .file hero-detail.component.css
.file hero.ts .file hero-detail.component.html
.file hero-detail.component.css .file hero-detail.component.ts
.file hero-detail.component.html .file hero-search.component.html (new)
.file hero-detail.component.ts .file hero-search.component.css (new)
.file hero-search.component.html (new) .file hero-search.component.ts (new)
.file hero-search.component.css (new) .file hero-search.service.ts (new)
.file hero-search.component.ts (new) .file hero.service.ts
.file hero-search.service.ts (new) .file heroes.component.css
.file hero.service.ts .file heroes.component.html
.file heroes.component.css .file heroes.component.ts
.file heroes.component.html .file in-memory-data.service.ts (new)
.file heroes.component.ts .file main.ts
.file in-memory-data.service.ts (new) .file index.html
.file main.ts .file styles.css
.file index.html .file systemjs.config.js
.file styles.css .file tsconfig.json
.file systemjs.config.js .file node_modules ...
.file tsconfig.json .file package.json
.file node_modules ...
.file package.json
.l-main-section .l-main-section
:marked :marked
@ -587,44 +566,43 @@ block filetree
- You extended `HeroService` to support `post()`, `put()`, and `delete()` methods. - You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
- You updated the components to allow adding, editing, and deleting of heroes. - You updated the components to allow adding, editing, and deleting of heroes.
- You configured an in-memory web API. - You configured an in-memory web API.
- You learned how to use !{_Observable}s. - You learned how to use Observables.
Here are the files you added or changed in this page. Here are the files you added or changed in this page.
block file-summary +makeTabs(
+makeTabs( `toh-6/ts/src/app/app.component.ts,
`toh-6/ts/src/app/app.component.ts, toh-6/ts/src/app/app.module.ts,
toh-6/ts/src/app/app.module.ts, toh-6/ts/src/app/heroes.component.ts,
toh-6/ts/src/app/heroes.component.ts, toh-6/ts/src/app/heroes.component.html,
toh-6/ts/src/app/heroes.component.html, toh-6/ts/src/app/heroes.component.css,
toh-6/ts/src/app/heroes.component.css, toh-6/ts/src/app/hero-detail.component.ts,
toh-6/ts/src/app/hero-detail.component.ts, toh-6/ts/src/app/hero-detail.component.html,
toh-6/ts/src/app/hero-detail.component.html, toh-6/ts/src/app/hero.service.ts,
toh-6/ts/src/app/hero.service.ts, toh-6/ts/src/app/in-memory-data.service.ts`,
toh-6/ts/src/app/in-memory-data.service.ts`, ',,,,,,,,',
',,,,,,,,', `app.comp...ts,
`app.comp...ts, app.mod...ts,
app.mod...ts, heroes.comp...ts,
heroes.comp...ts, heroes.comp...html,
heroes.comp...html, heroes.comp...css,
heroes.comp...css, hero-detail.comp...ts,
hero-detail.comp...ts, hero-detail.comp...html,
hero-detail.comp...html, hero.service.ts,
hero.service.ts, in-memory-data.service.ts`
in-memory-data.service.ts` )
)
+makeTabs( +makeTabs(
`toh-6/ts/src/app/hero-search.service.ts, `toh-6/ts/src/app/hero-search.service.ts,
toh-6/ts/src/app/hero-search.component.ts, toh-6/ts/src/app/hero-search.component.ts,
toh-6/ts/src/app/hero-search.component.html, toh-6/ts/src/app/hero-search.component.html,
toh-6/ts/src/app/hero-search.component.css`, toh-6/ts/src/app/hero-search.component.css`,
null, null,
`hero-search.service.ts, `hero-search.service.ts,
hero-search.component.ts, hero-search.component.ts,
hero-search.component.html, hero-search.component.html,
hero-search.component.css` hero-search.component.css`
) )
.l-sub-section .l-sub-section
:marked :marked

View File

@ -1,38 +0,0 @@
/*
* Angular.io Example File Path Directive
*
* Usage:
* <span ngio-ex [lang="ts|dart"]>some_path</span>
* <ngio-ex path="some_path" [lang="ts|dart"]></ngio-ex>
*
* The latter gets treated as a block tag in markdown when at the start of a line.
*
* Yields
* <code>some_path_possibly_adjusted</code>
*
* The given path is assumed to be a TS app directory or
* source file path. When this directive is used in Dart docs
* it adjusts the path to conform to Dart directory and file
* name conventions. See NgIoUtil.adjustTsExamplePathForDart()
* for details.
*/
angularIO.directive('ngioEx', ['$location', function ($location) {
return {
restrict: 'AE',
compile: function (tElement, attrs) {
var examplePath = attrs.path || tElement.text();
if (NgIoUtil.isDoc($location, 'dart') || attrs.lang === 'dart') {
examplePath = NgIoUtil.adjustTsExamplePathForDart(examplePath);
}
var template = '<code>' + examplePath + '</code>';
// UPDATE ELEMENT WITH NEW TEMPLATE
tElement.html(template);
// RETURN ELEMENT
return function (scope, element, attrs) { };
}
};
}]);