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/if-docs.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_
to use the ES `import` and `export` statements that Rollup requires.
Rollup then preserves the parts of `RxJS` referenced by the application
in the final bundle. Using it is straigthforward. Add the following to
the `plugins` !{_array} in `rollup-config.js`:
Rollup then preserves the parts of `RxJS` referenced by the application
in the final bundle. Using it is straigthforward. Add the following to
the `plugins` array in `rollup-config.js`:
+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*
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.
Add the following to the `plugins` !{_array}:
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
Add the following to the `plugins` array:
+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
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
Angular has its own vocabulary.
Most Angular terms are common English words
@ -29,22 +22,21 @@ a#aot
.l-sub-section
:marked
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
to a<span if-docs="ts"> module</span> factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
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.
+ifDocsFor('ts')
:marked
## Angular module
.l-sub-section
:marked
## Angular module
.l-sub-section
: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`.
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
called `AppModule` and resides in a file named `app.module.ts`.
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`.
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
## Annotation
@ -64,50 +56,49 @@ a#attribute-directives
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
+ifDocsFor('ts|js')
:marked
## Barrel
.l-sub-section
:marked
## Barrel
.l-sub-section
: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.
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:
code-example.
// heroes/hero.component.ts
export class HeroComponent {}
For example, imagine three ES2015 modules in a `heroes` folder:
code-example.
// heroes/hero.component.ts
export class HeroComponent {}
// heroes/hero.model.ts
export class Hero {}
// heroes/hero.model.ts
export class Hero {}
// heroes/hero.service.ts
export class HeroService {}
:marked
Without a barrel, a consumer needs three import statements:
code-example.
import { HeroComponent } from '../heroes/hero.component.ts';
import { Hero } from '../heroes/hero.model.ts';
import { HeroService } from '../heroes/hero.service.ts';
:marked
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
code-example.
export * from './hero.model.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
:marked
Now a consumer can import what it needs from the barrel.
code-example.
import { Hero, HeroService } from '../heroes'; // index is implied
:marked
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
// heroes/hero.service.ts
export class HeroService {}
:marked
Without a barrel, a consumer needs three import statements:
code-example.
import { HeroComponent } from '../heroes/hero.component.ts';
import { Hero } from '../heroes/hero.model.ts';
import { HeroService } from '../heroes/hero.service.ts';
:marked
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
code-example.
export * from './hero.model.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
:marked
Now a consumer can import what it needs from the barrel.
code-example.
import { Hero, HeroService } from '../heroes'; // index is implied
:marked
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
.alert.is-important
:marked
You can often achieve the same result using [Angular modules](#angular-module) instead.
.alert.is-important
:marked
You can often achieve the same result using [Angular modules](#angular-module) instead.
:marked
## Binding
@ -126,7 +117,7 @@ a#attribute-directives
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),
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.
@ -153,7 +144,7 @@ a#component
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).
Apply the `!{_at}Component` !{_decoratorLink} to
Apply the `@Component` [decorator](#decorator) to
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
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 (`-`).
This form is also known as kebab-case.
[Directive](#directive) selectors (like `my-app`) <span if-docs="ts">and
the root of filenames (such as `hero-list.component.ts`)</span> are often
[Directive](#directive) selectors (like `my-app`) and
the root of filenames (such as `hero-list.component.ts`) are often
spelled in dash-case.
:marked
@ -189,14 +180,14 @@ a#component
Angular has a rich data-binding framework with a variety of data-binding
operations and supporting declaration syntax.
Read about the following forms of binding in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page:
* [Interpolation](!{docsLatest}/guide/template-syntax.html#interpolation).
* [Property binding](!{docsLatest}/guide/template-syntax.html#property-binding).
* [Event binding](!{docsLatest}/guide/template-syntax.html#event-binding).
* [Attribute binding](!{docsLatest}/guide/template-syntax.html#attribute-binding).
* [Class binding](!{docsLatest}/guide/template-syntax.html#class-binding).
* [Style binding](!{docsLatest}/guide/template-syntax.html#style-binding).
* [Two-way data binding with ngModel](!{docsLatest}/guide/template-syntax.html#ngModel).
Read about the following forms of binding in the [Template Syntax](./template-syntax.html) page:
* [Interpolation](./template-syntax.html#interpolation).
* [Property binding](./template-syntax.html#property-binding).
* [Event binding](./template-syntax.html#event-binding).
* [Attribute binding](./template-syntax.html#attribute-binding).
* [Class binding](./template-syntax.html#class-binding).
* [Style binding](./template-syntax.html#style-binding).
* [Two-way data binding with ngModel](./template-syntax.html#ngModel).
a#decorator
a#decoration
@ -280,7 +271,7 @@ a#decoration
Angular registers some of its own providers with every injector.
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#directives
@ -370,11 +361,11 @@ a#H
.l-sub-section
:marked
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
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
## Interpolation
@ -390,8 +381,8 @@ a#H
<label>My current hero is {{hero.name}}</label>
:marked
Read more about [interpolation](!{docsLatest}/guide/template-syntax.html#interpolation) in the
[Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
Read more about [interpolation](./template-syntax.html#interpolation) in the
[Template Syntax](./template-syntax.html) page.
.l-main-section#J
@ -400,7 +391,7 @@ a#jit
## Just-in-time (JIT) compilation
.l-sub-section
: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.
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.
* `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
@ -446,7 +437,7 @@ a#jit
:marked
Angular has the following types of modules:
- [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.
:marked
@ -477,29 +468,28 @@ a#jit
a#N
.l-main-section#O
+ifDocsFor('ts|js')
:marked
## Observable
.l-sub-section
:marked
## Observable
.l-sub-section
:marked
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.
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).
Observables are a proposed feature for ES2016, the next version of JavaScript.
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.
:marked
## Output
.l-sub-section
:marked
A directive property that can be the *target* of event binding
(read more in the [event binding](!{docsLatest}/guide/template-syntax.html#event-binding)
section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page).
(read more in the [event binding](./template-syntax.html#event-binding)
section of the [Template Syntax](./template-syntax.html) page).
Events stream *out* of this property to the receiver identified
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
@ -527,7 +517,7 @@ a#N
:marked
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
## 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
of views.
+ifDocsFor('ts|js')
:marked
In most cases, components become attached to a router by means
of a `RouterConfig` that defines routes to views.
In most cases, components become attached to a router by means
of a `RouterConfig` that defines routes to views.
A [routing component's](#routing-component) template has a `RouterOutlet` element
where it can display views produced by the router.
A [routing component's](#routing-component) template has a `RouterOutlet` element
where it can display views produced by the router.
Other views in the application likely have anchor tags or buttons with `RouterLink`
directives that users can click to navigate.
Other views in the application likely have anchor tags or buttons with `RouterLink`
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
## Router module
@ -585,7 +574,7 @@ a#Q
:marked
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
## Routing component
@ -593,7 +582,7 @@ a#Q
:marked
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
@ -629,7 +618,7 @@ a#Q
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
:marked
@ -650,7 +639,7 @@ a#structural-directives
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.
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
: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.
Read about how to build template-driven forms
in the [Forms](!{docsLatest}/guide/forms.html) page.
in the [Forms](./forms.html) page.
:marked
## Template expression
.l-sub-section
:marked
A !{_Lang}-like syntax that Angular evaluates within
A TypeScript-like syntax that Angular evaluates within
a [data binding](#data-binding).
Read about how to write template expressions
in the [Template expressions](!{docsLatest}/guide/template-syntax.html#template-expressions) section
of the [Template Syntax](!{docsLatest}/guide/template-syntax.html#) page.
in the [Template expressions](./template-syntax.html#template-expressions) section
of the [Template Syntax](./template-syntax.html#) page.
:marked
## Transpile

View File

@ -172,7 +172,7 @@ figure
* Enter: `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.
+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
include ../_util-fns
- var _library_module = 'library module'
- var _at_angular = '@angular'
block angular-intro
:marked
Angular is a framework for building client applications in HTML and
either JavaScript or a language like TypeScript that compiles to JavaScript.
:marked
Angular is a framework for building client applications in HTML and
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
You write Angular applications by composing HTML *templates* with Angularized markup,
@ -48,78 +45,77 @@ figure
figure
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
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.
This page introduces modules; the [Angular modules](ngmodule.html) page covers them in depth.
* `exports` - the subset of declarations that should be visible and usable in the component [templates](#templates) of other modules.
<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
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
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.
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.
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/main.ts', '', 'src/main.ts')(format='.')
+makeExample('src/app/app.module.ts', 'imports', '')(format='.')
+makeExample('src/app/app.module.ts', 'export', '')(format='.')
.l-sub-section
:marked
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
: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.
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
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
### Angular libraries
@ -127,32 +123,31 @@ block angular-modules
figure
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
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
:marked
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.
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
<br class="l-clear-both"><br>
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.
<br class="l-clear-both"><br>
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
:marked
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
: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.
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
:marked
In this way you're using both the Angular and JavaScript module systems _together_.
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
:marked
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
: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.
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
:marked
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".
Hang in there. The confusion yields to clarity with time and experience.
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.
.l-sub-section
:marked
Learn more from the [Angular modules](ngmodule.html) page.
.l-sub-section
:marked
Learn more from the [Angular modules](ngmodule.html) page.
.l-hr
@ -176,7 +171,7 @@ figure
The class interacts with the view through an API of properties and methods.
<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.
`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.
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**.
In TypeScript, you attach metadata by using a **decorator**.
Here's some metadata for `HeroListComponent`:
+makeExcerpt('src/app/hero-list.component.ts', 'metadata')
: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.
block ts-decorator
:marked
The `@Component` decorator takes a required configuration object with the
information Angular needs to create and present the component and its view.
:marked
The `@Component` decorator takes a required configuration object with the
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
- `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).
block dart-directives
: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`
so it can get the list of heroes to display.
@ -277,8 +269,8 @@ figure
The template, metadata, and component together describe a view.
Apply other metadata !{_decorator}s in a similar fashion to guide Angular behavior.
`@Injectable`, `@Input`, and `@Output` are a few of the more popular !{_decorator}s.
Apply other metadata decorators in a similar fashion to guide Angular behavior.
`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.
<br class="l-clear-both">
:marked
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
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` !{_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">
.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.
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
block dart-bool
:marked
**Attribute** directives alter the appearance or behavior of an existing element.
In templates they look like regular HTML attributes, hence the name.
@ -430,7 +420,7 @@ figure
+makeExcerpt('src/app/logger.service.ts', 'class')
: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.
+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.
A provider is something that can create or return a service, typically the service class itself.
block registering-providers
:marked
You can register providers in modules or in components.
In general, add providers to the [root module](#module) so that
the same instance of a service is available everywhere.
:marked
You can register providers in modules or in components.
In general, add providers to the [root module](#module) so that
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
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
application and never leave the browser.
block angular-feature-testing
:marked
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
using the _Angular Testing Platform_.
:marked
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
using the _Angular Testing Platform_.

View File

@ -56,7 +56,7 @@ block includes
### Write the directive code
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:
@ -97,7 +97,7 @@ block includes
:marked
After the `@Directive` metadata comes the directive's controller class,
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
each matching element, injecting an Angular `ElementRef`
@ -113,7 +113,7 @@ block includes
applies the directive as an attribute to a paragraph (`<p>`) element.
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:
+makeExample('src/app/app.component.1.html')
@ -176,12 +176,12 @@ figure.image-display
:marked
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', '')
: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.
.l-sub-section
@ -194,7 +194,7 @@ figure.image-display
1. Talking to DOM API directly isn't a best practice.
: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.
+makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor')
@ -226,7 +226,7 @@ a#input
:marked
### 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.
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
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', '')
@ -345,7 +345,7 @@ figure.image-display
:marked
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.
@ -400,7 +400,7 @@ figure.image-display
+makeExcerpt('src/app/highlight.directive.ts', 'color', '')
: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.
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.
The component and its template trust each other implicitly.
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.
The properties of a component or directive are hidden from binding by default.
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.
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 (=),
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 (=),
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:
@ -435,8 +435,8 @@ figure.image-display
:marked
* The `color` property in the expression on the right belongs to the template's component.
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`,
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
include ../_util-fns
- var _at_angular = '@angular'
:marked
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.
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:
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
@ -134,8 +134,8 @@ a(id='loading-styles')
### Styles in metadata
You can add a `styles` #{_array} property to the `@Component` #{_decorator}.
Each string in the #{_array} (usually just one string) defines the CSS.
You can add a `styles` array property to the `@Component` decorator.
Each string in the array (usually just one string) defines the CSS.
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
@ -143,32 +143,30 @@ a(id='loading-styles')
### Style URLs in metadata
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')
block style-url
.alert.is-important
:marked
The URL is relative to the *application root*, which is usually the
location of the `index.html` web page that hosts the application.
The style file URL is *not* relative to the component file.
That's why the example URL begins `src/app/`.
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
.alert.is-important
:marked
The URL is relative to the *application root*, which is usually the
location of the `index.html` web page that hosts the application.
The style file URL is *not* relative to the component file.
That's why the example URL begins `src/app/`.
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
block module-bundlers
.l-sub-section
:marked
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:
.l-sub-section
:marked
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:
`styles: [require('my.component.css')]`
`styles: [require('my.component.css')]`
Set the `styles` property, not the `styleUrls` property. The module
bundler loads the CSS strings, not Angular.
Angular sees the CSS strings only after the bundler loads them.
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.
Set the `styles` property, not the `styleUrls` property. The module
bundler loads the CSS strings, not Angular.
Angular sees the CSS strings only after the bundler loads them.
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.
:marked
### Template inline styles
@ -195,9 +193,8 @@ block module-bundlers
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
on the [MDN](https://developer.mozilla.org) site.
block css-import-url
:marked
In this case, the URL is relative to the CSS file into which you're importing.
:marked
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)')
@ -300,9 +297,8 @@ code-example(format="nocode").
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.
block module-id
:marked
You can use a relative URL by prefixing your filenames with `./`:
:marked
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
include ../_util-fns
- var _thisDot = 'this.';
:marked
**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?
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`.
You may not anticipate them even now.
But you'll *have* to start caring because
@ -108,11 +107,10 @@ block includes
The `Car` class no longer creates an `engine` or `tires`.
It just consumes them.
block ctor-syntax
.l-sub-section
:marked
This example leverages TypeScript's constructor syntax for declaring
parameters and properties simultaneously.
.l-sub-section
:marked
This example leverages TypeScript's constructor syntax for declaring
parameters and properties simultaneously.
:marked
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
something like this:
- var stylePattern = { otl: /(new Car.*$)/gm };
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', { otl: /(new Car.*$)/gm })(format=".")
:marked
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
during each test:
- var stylePattern = { otl: /(new Car.*$)/gm };
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', { otl: /(new Car.*$)/gm })(format=".")
:marked
**You just learned what dependency injection is**.
@ -236,9 +232,8 @@ block ctor-syntax
Given that the service is a
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
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
The following `HeroService` exposes a `getHeroes` method that returns
the same mock data as before, but none of its consumers need to know that.
@ -248,15 +243,14 @@ block ctor-syntax
:marked
.l-sub-section
:marked
The `@Injectable()` #{_decorator} above the service class is
The `@Injectable()` decorator above the service class is
covered [shortly](#injectable).
- var _perhaps = _docsFor == 'dart' ? '' : 'perhaps';
.l-sub-section
:marked
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
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.
This is important in general, but not in this example.
@ -279,18 +273,16 @@ a#injector-config
that create the services the application requires.
This guide explains what [providers](#providers) are later.
block register-provider-ngmodule
:marked
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
:marked
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
a#register-providers-ngmodule
:marked
### Registering providers in an _NgModule_
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
in its `providers` !{_array}.
a#register-providers-ngmodule
:marked
### Registering providers in an _NgModule_
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
in its `providers` array.
- var app_module_ts = 'src/app/app.module.ts';
+makeExcerpt(app_module_ts + ' (excerpt)', 'ngmodule', app_module_ts)
+makeExcerpt('src/app/app.module.ts (excerpt)', 'ngmodule', app_module_ts)
:marked
Because the `HeroService` is used _only_ within the `HeroesComponent`
and its subcomponents, the top-level `HeroesComponent` is the ideal
@ -300,31 +292,30 @@ a#register-providers-component
:marked
### 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='.')
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
### 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
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).
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
:marked
@ -352,12 +343,12 @@ a#prep-for-injection
:marked
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).
Also recall that the parent component (`HeroesComponent`)
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
Angular injector to inject an instance of
`HeroService` whenever it creates a new `HeroListComponent`.
@ -432,37 +423,35 @@ a#service-needs-service
src/app/heroes/hero.service (v1)`)
: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.
- var injUrl = '../api/core/index/Injectable-decorator.html';
a#injectable
:marked
### 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
error when trying to instantiate a class that is not marked as
`@Injectable()`.
block injectable-not-always-needed-in-ts
.l-sub-section
:marked
As it happens, you could have omitted `@Injectable()` from the first
version of `HeroService` because it had no injected parameters.
But you must have it now that the service has an injected dependency.
You need it because Angular requires constructor parameter metadata
in order to inject a `Logger`.
.l-sub-section
:marked
As it happens, you could have omitted `@Injectable()` from the first
version of `HeroService` because it had no injected parameters.
But you must have it now that the service has an injected dependency.
You need it because Angular requires constructor parameter metadata
in order to inject a `Logger`.
.callout.is-helpful
header Suggestion: add @Injectable() to every service class
:marked
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
and, therefore, do not technically require it. Here's why:
.callout.is-helpful
header Suggestion: add @Injectable() to every service class
:marked
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
and, therefore, do not technically require it. Here's why:
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>Consistency:</b> All services follow the same rules, and you don't have to wonder why #{_a} #{_decorator} is missing.
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>Consistency:</b> All services follow the same rules, and you don't have to wonder why a decorator is missing.
:marked
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
`HeroesComponent` is already marked with `@Component`, and this
!{_decorator} class (like `@Directive` and `@Pipe`, which you learn about later)
is a subtype of <a href="#{injUrl}">@Injectable()</a>. It is in
fact `@Injectable()` #{_decorator}s that
decorator class (like `@Directive` and `@Pipe`, which you learn about later)
is a subtype of <a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>. It is in
fact `@Injectable()` decorators that
identify a class as a target for instantiation by an injector.
+ifDocsFor('ts')
.l-sub-section
:marked
At runtime, injectors can read class metadata in the transpiled JavaScript code
and use the constructor parameter type information
to determine what things to inject.
.l-sub-section
:marked
At runtime, injectors can read class metadata in the transpiled JavaScript code
and use the constructor parameter type information
to determine what things to inject.
Not every JavaScript class has metadata.
The TypeScript compiler discards metadata by default.
If the `emitDecoratorMetadata` compiler option is true
(as it should be in the `tsconfig.json`),
the compiler adds the metadata to the generated JavaScript
for _every class with at least one decorator_.
Not every JavaScript class has metadata.
The TypeScript compiler discards metadata by default.
If the `emitDecoratorMetadata` compiler option is true
(as it should be in the `tsconfig.json`),
the compiler adds the metadata to the generated JavaScript
for _every class with at least one decorator_.
While any decorator will trigger this effect, mark the service class with the
<a href="#{injUrl}">@Injectable()</a> #{_decorator}
to make the intent clear.
While any decorator will trigger this effect, mark the service class with the
<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a> decorator
to make the intent clear.
.callout.is-critical
header Always include the parentheses
block always-include-paren
:marked
Always write `@Injectable()`, not just `@Injectable`.
The application will fail mysteriously if you forget the parentheses.
:marked
Always write `@Injectable()`, not just `@Injectable`.
The application will fail mysteriously if you forget the parentheses.
.l-main-section#logger-service
:marked
@ -513,13 +500,10 @@ block injectable-not-always-needed-in-ts
+makeExample('src/app/logger.service.ts')
block real-logger
//- N/A
:marked
You're likely to need the same logger service everywhere in your application,
so put it in the project's `#{_appDir}` folder and
register it in the `providers` #{_array} of the application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`.
so put it in the project's `app` folder and
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')
@ -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.
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')
- 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
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.
But it's not the only way.
You can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
You could provide a substitute class. #{loggerlike}
You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
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.
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`.
//- 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
:marked
### The *Provider* class !{_andProvideFn}
### The *Provider* class and _provide_ object literal
: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')
block provider-shorthand
:marked
This is actually a shorthand expression for a provider registration
using a _provider_ object literal with two properties:
:marked
This is actually a shorthand expression for a provider registration
using a _provider_ object literal with two properties:
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3')
block provider-ctor-args
- var _secondParam = 'provider definition object';
:marked
The first is the [token](#token) that serves as the key for both locating a dependency value
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.
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')
block dart-diff-const-metadata
//- N/A
a#class-provider-dependencies
:marked
### Class provider with dependencies
@ -653,9 +627,6 @@ a#value-provider
:marked
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=".")
:marked
@ -720,19 +691,17 @@ block dart-diff-const-metadata-ctor
The `useFactory` field tells Angular that the provider is a factory function
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 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
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.
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`,
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:
+makeTabs(
@ -774,14 +743,12 @@ a#non-class-dependencies
### Non-class dependencies
p
| 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
| Applications often define configuration objects with lots of small facts
| (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.
| They can be object literals such as this one:
| but these configuration objects aren't always instances of a class.
| 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='.')
@ -789,36 +756,33 @@ p
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).
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
But what should you use as the token?
You don't have a class to serve as a token.
There is no `AppConfig` class.
### TypeScript interfaces aren't valid tokens
.l-sub-section#interface
:marked
### TypeScript interfaces aren't valid tokens
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you
cannot use a TypeScript interface as a token:
+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
cannot use a TypeScript interface as a token:
+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.
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.
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
:marked
### _OpaqueToken_
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:
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
@ -830,19 +794,17 @@ a#opaquetoken
:marked
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=".")
- var configType = _docsFor == 'dart' ? '<code>Map</code>' : '<code>AppConfig</code>'
.l-sub-section
: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.
block dart-map-alternative
:marked
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
:marked
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
+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
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='.')
:marked
@ -915,23 +876,22 @@ block dart-map-alternative
Framework developers may take this approach when they
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
## 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
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.
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.
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.

View File

@ -1,7 +1,5 @@
block includes
include ../_util-fns
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
- var _boolean = 'truthy/falsy';
:marked
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
* [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).
.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}}`.
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.
When you're done, it should look like this:
@ -52,13 +50,12 @@ figure.image-display
+makeExcerpt('src/app/app.component.1.ts', 'template', '')
+ifDocsFor('ts')
.l-sub-section
:marked
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
quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
HTML more readable.
.l-sub-section
:marked
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
quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
HTML more readable.
:marked
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.
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:
+makeExcerpt('src/index.html', 'body')
: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
inside the `<my-app>` tag.
@ -88,9 +85,8 @@ figure.image-display
figure.image-display
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
+ifDocsFor('ts')
:marked
The next few sections review some of the coding choices in the app.
:marked
The next few sections review some of the coding choices in the app.
:marked
## 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 define it *inline* using the `template` property, or you can define
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,
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.
+ifDocsFor('ts')
:marked
## Constructor or variable initialization?
:marked
## 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
This app uses more terse "variable assignment" style simply for brevity.
:marked
This app uses more terse "variable assignment" style simply for brevity.
.l-main-section#ngFor
: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')
@ -156,8 +151,8 @@ figure.image-display
.l-sub-section
:marked
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
repeat items for any [iterable](!{_iterableUrl}) object.
In this case, `ngFor` is displaying an array, but `ngFor` can
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
:marked
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.
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.
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.
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.
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')
block hero-class
:marked
You've defined a class with a constructor and two properties: `id` and `name`.
:marked
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.
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
It might not look like the class has properties, but it does.
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
That brief syntax does a lot:
* Declares a constructor parameter and its type.
* Declares a public property of the same name.
* Initializes that property with the corresponding argument when creating an instance of the class.
:marked
That brief syntax does a lot:
* Declares a constructor parameter and its type.
* Declares a public property of the same name.
* Initializes that property with the corresponding argument when creating an instance of the class.
.l-main-section
:marked
## 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:
+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.
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:
+makeExcerpt('src/app/app.component.ts', 'message')
@ -237,7 +231,7 @@ block hero-class
:marked
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
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,
@ -250,8 +244,8 @@ block hero-class
big chunks of HTML with many data bindings.
:marked
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}.
Try it out. Because the array has four items, the message should appear.
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.
.l-main-section
@ -259,16 +253,15 @@ block hero-class
## Summary
Now you know how to use:
- **Interpolation** with double curly braces to display a component property.
- **ngFor** to display !{_an} !{_array} of items.
- A !{_Lang} class to shape the **model data** for your component and display properties of that model.
- **ngFor** to display an array of items.
- 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.
Here's the final code:
block final-code
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
displaying-data/ts/src/app/hero.ts,
displaying-data/ts/src/app/app.module.ts,
displaying-data/ts/src/main.ts`,
'final,,,',
'src/app/app.component.ts, src/app/hero.ts, src/app/app.module.ts, main.ts')
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
displaying-data/ts/src/app/hero.ts,
displaying-data/ts/src/app/app.module.ts,
displaying-data/ts/src/main.ts`,
'final,,,',
'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
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
@ -94,7 +94,7 @@ figure.image-display
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
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')

View File

@ -70,7 +70,7 @@ figure.image-display
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.
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
:marked

View File

@ -1,8 +1,6 @@
block includes
include ../_util-fns
- var top="vertical-align:top"
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")
@ -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.
<br class="l-clear-both">
<<<<<<< 93d3db1fd4f61c9c6078b2000dea164c87fa071c
+ifDocsFor('ts|js')
:marked
## Contents
@ -38,6 +37,28 @@ figure
* [Content projection](#content-projection)
* [AfterContent hooks](#aftercontent-hooks)
* [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
Try the <live-example></live-example>.
@ -72,7 +93,7 @@ table(width="100%")
th Hook
th Purpose and Timing
tr(style=top)
tr(style=vertical-align:top)
td <code>ngOnChanges()</code>
td
:marked
@ -81,7 +102,7 @@ table(width="100%")
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
:marked
@ -90,7 +111,7 @@ table(width="100%")
Called _once_, after the _first_ `ngOnChanges()`.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngDoCheck()</code>
td
:marked
@ -98,7 +119,7 @@ table(width="100%")
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngAfterContentInit()</code>
td
:marked
@ -108,7 +129,7 @@ table(width="100%")
_A component-only hook_.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngAfterContentChecked()</code>
td
:marked
@ -118,7 +139,7 @@ table(width="100%")
_A component-only hook_.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngAfterViewInit()</code>
td
:marked
@ -128,7 +149,7 @@ table(width="100%")
_A component-only hook_.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngAfterViewChecked()</code>
td
:marked
@ -138,7 +159,7 @@ table(width="100%")
_A component-only hook_.
tr(style=top)
tr(style=vertical-align:top)
td <code>ngOnDestroy</code>
td
:marked
@ -147,24 +168,23 @@ table(width="100%")
Called _just before_ Angular destroys the directive/component.
+ifDocsFor('ts|js')
a#interface-optional
.l-main-section
:marked
## Interfaces are optional (technically)
a#interface-optional
.l-main-section
:marked
## Interfaces are optional (technically)
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
The JavaScript language doesn't have interfaces.
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
The JavaScript language doesn't have interfaces.
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
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.
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.
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 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.
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
in order to benefit from strong typing and editor tooling.
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
in order to benefit from strong typing and editor tooling.
a#other-lifecycle-hooks
.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.
block other-angular-subsystems
//- N/A for TS.
:marked
3rd party libraries might implement their hooks as well in order to give developers more
control over how these libraries are used.
@ -199,13 +216,13 @@ table(width="100%")
tr
th Component
th Description
tr(style=top)
tr(style=vertical-align:top)
td <a href="#peek-a-boo">Peek-a-boo</a>
td
:marked
Demonstrates every lifecycle hook.
Each hook method writes to the on-screen log.
tr(style=top)
tr(style=vertical-align:top)
td <a href="#spy">Spy</a>
td
:marked
@ -215,33 +232,33 @@ table(width="100%")
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
managed by the parent `SpyComponent`.
tr(style=top)
tr(style=vertical-align:top)
td <a href="#onchanges">OnChanges</a>
td
:marked
See how Angular calls the `ngOnChanges()` hook with a `changes` object
every time one of the component input properties changes.
Shows how to interpret the `changes` object.
tr(style=top)
tr(style=vertical-align:top)
td <a href="#docheck">DoCheck</a>
td
:marked
Implements an `ngDoCheck()` method with custom change detection.
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
:marked
Shows what Angular means by a *view*.
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
tr(style=top)
tr(style=vertical-align:top)
td <a href="#aftercontent">AfterContent</a>
td
:marked
Shows how to project external content into a component and
how to distinguish projected content from a component's view children.
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
tr(style=top)
tr(style=vertical-align:top)
td Counter
td
:marked
@ -479,10 +496,8 @@ a#wait-a-tick
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!).
block tick-methods
:marked
The `LoggerService.tick_then()` postpones the log update
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
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.
Your pipe has one such parameter: the `exponent`.
* To tell Angular that this is a pipe, you apply the
`@Pipe` #{_decorator}, which you import from the core Angular library.
* The `@Pipe` #{_decorator} allows you to define the
`@Pipe` decorator, which you import from the core Angular library.
* The `@Pipe` decorator allows you to define the
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
Your pipe's name is `exponentialStrength`.
@ -161,16 +161,14 @@ figure.image-display
figure.image-display
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
Note the following:
* 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
header Remember the !{_decls} #{_array}
header Remember the declarations array
:marked
You must manually register custom pipes.
If you don't, Angular reports an error.
@ -206,17 +204,17 @@ a#change-detection
### No pipe
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='.')
: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='.')
:marked
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.
### Flying-heroes pipe
@ -237,15 +235,15 @@ a#change-detection
Notice how a hero is added:
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
:marked
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*.
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*.
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.
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero.
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.
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 *replace* the #{_array}, the pipe executes and the display is 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.
The Flying Heroes application extends the
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")
:marked
Replacing the #{_array} is an efficient way to signal Angular to update the display.
When do you replace the #{_array}? When the data change.
Replacing the array is an efficient way to signal Angular to update the display.
When do you replace the array? When the data change.
That's an easy rule to follow in *this* example
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`).
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.
An object reference check is fast&mdash;much faster than a deep check for
@ -335,28 +333,25 @@ figure.image-display
:marked
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html';
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
+makeExcerpt('src/app/flying-heroes' + _fnSuffix + ' (excerpt)', _region)
+makeExcerpt('src/app/flying-heroes-impure.component.html (excerpt)', 'template-flying-heroes')
:marked
The only substantive change is the pipe in the template.
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]
:marked
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.
The `AsyncPipe` is also stateful.
The pipe maintains a subscription to the input `#{_Observable}` and
keeps delivering values from that `#{_Observable}` as they arrive.
The pipe maintains a subscription to the input `Observable` and
keeps delivering values from that `Observable` as they arrive.
This next example binds an `#{_Observable}` of message strings
(`message#{_dollar}`) to a view with the `async` pipe.
This next example binds an `Observable` of message strings
(`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')
@ -375,7 +370,7 @@ h3#async-pipe The impure #[i AsyncPipe]
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.
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')
:marked

View File

@ -1543,8 +1543,8 @@ a#milestone-4
Begin by imitating the heroes feature:
- Delete the placeholder crisis center file.
- Create !{_an} `!{_appDir}/crisis-center` folder.
- Copy the files from `!{_appDir}/heroes` into the new crisis center folder.
- Create an `app/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".
You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes:
@ -1560,7 +1560,7 @@ a#milestone-4
:marked
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>,
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.
a#crisis-child-routes
:marked
@ -2288,7 +2288,7 @@ a#CanDeactivate
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
: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.
+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`
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')
@ -2549,7 +2549,7 @@ a#can-load-guard
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`
!{_array} for the `admin` route.
array for the `admin` route.
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)')

View File

@ -7,18 +7,17 @@ block includes
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).
.l-main-section
:marked
# Contents
+ifDocsFor('ts')
.l-main-section
:marked
# Contents
* [Reporting vulnerabilities](#report-issues).
* [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).
* [Reporting vulnerabilities](#report-issues).
* [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
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')
block html-sanitization
:marked
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.
:marked
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.
figure.image-display
img(src='/resources/images/devguide/security/binding-inner-html.png'
alt='A screenshot showing interpolated and bound HTML values')
figure.image-display
img(src='/resources/images/devguide/security/binding-inner-html.png'
alt='A screenshot showing interpolated and bound HTML values')
:marked
### 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
carries a high risk of introducing template-injection vulnerabilities.
block bypass-security-apis
.l-main-section
h2#bypass-security-apis Trusting safe values
:marked
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
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
are introducing a security vulnerability into your application. If in doubt, find a professional
security reviewer.
.l-main-section
h2#bypass-security-apis Trusting safe values
:marked
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
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
are introducing a security vulnerability into your application. If in doubt, find a professional
security reviewer.
To mark a value as trusted, inject `DomSanitizer` and call one of the
following methods:
To mark a value as trusted, inject `DomSanitizer` and call one of the
following methods:
* `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl`
* `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl`
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
`javascript:alert(...)` call:
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
`javascript:alert(...)` call:
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
:marked
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
in development mode, logs this action to the console. To prevent
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
:marked
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
in development mode, logs this action to the console. To prevent
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
img(src='/resources/images/devguide/security/bypass-security-component.png'
alt='A screenshot showing an alert box created from a trusted URL')
figure.image-display
img(src='/resources/images/devguide/security/bypass-security-component.png'
alt='A screenshot showing an alert box created from a trusted URL')
:marked
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
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
could execute. So call a method on the controller to construct a trusted video URL, which causes
Angular to allow binding into `<iframe src>`:
:marked
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
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
could execute. So call a method on the controller to construct a trusted video URL, which causes
Angular to allow binding into `<iframe src>`:
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
block http
.l-main-section
h2#http HTTP-level vulnerabilities
:marked
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
on the server side, but Angular provides helpers to make integration on the client side easier.
.l-main-section
h2#http HTTP-level vulnerabilities
:marked
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
on the server side, but Angular provides helpers to make integration on the client side easier.
h3#xsrf Cross-site request forgery
:marked
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
to the application's web server (such as `example-bank.com`).
h3#xsrf Cross-site request forgery
:marked
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
to the application's web server (such as `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.
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 `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.
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
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.
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
request from the application and the forged request from `evil.com`.
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`.
To prevent this, the application must ensure that a user request originates from the real
application, not from a different site.
The server and client must cooperate to thwart this attack.
To prevent this, the application must ensure that a user request originates from the real
application, not from a different site.
The server and client must cooperate to thwart this attack.
In a common anti-XSRF technique, the application server sends a randomly
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 server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
In a common anti-XSRF technique, the application server sends a randomly
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 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
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.
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.
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`.
The default `CookieXSRFStrategy` is turned on automatically.
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.
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.
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.
The server must do its part by setting the
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
The server must do its part by setting the
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
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
cryptographically secure random number generator, and expire in a day or two.
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.
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.
code-example(language="typescript").
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
:marked
Or you can implement and provide an entirely custom `XSRFStrategy`:
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.
code-example(language="typescript").
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
:marked
Or you can implement and provide an entirely custom `XSRFStrategy`:
code-example(language="typescript").
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
code-example(language="typescript").
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
:marked
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/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
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.
:marked
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/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
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.
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>.
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>.
h3#xssi Cross-site script inclusion (XSSI)
:marked
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
object constructors, and then including an API URL using a `<script>` tag.
h3#xssi Cross-site script inclusion (XSSI)
:marked
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
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
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
well-known string `")]}',\n"`.
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
well-known string `")]}',\n"`.
Angular's `Http` library recognizes this convention and automatically strips the string
`")]}',\n"` from all responses before further parsing.
Angular's `Http` library recognizes this convention and automatically strips the string
`")]}',\n"` from all responses before further parsing.
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).
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).
.l-main-section
h2#code-review Auditing Angular applications

View File

@ -1,8 +1,5 @@
block includes
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
[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
[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
* [Demos](#demos)
@ -24,7 +21,7 @@ block includes
* [The Tour of Heroes *HTTP* client demo](#http-client)
- [The `HeroListComponent` class](#HeroListComponent)
* [Fetch data with `http.get()`](#fetch-data)
<li if-docs="ts"> [RxJS library](#rxjs-library)
<li>[RxJS library](#rxjs-library)
<ul>
<li> [Enable RxJS operators](#enable-rxjs-operators)</li>
</ul>
@ -38,16 +35,16 @@ block includes
- [Headers](#headers)
- [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)
<ul if-docs="ts">
<ul>
<li> [Search Wikipedia](#search-wikipedia)</li>
<li> [Search parameters](#search-parameters)</li>
<li> [The WikiComponent](#wikicomponent)</li>
</ul>
* [A wasteful app](#wasteful-app)
<li if-docs="ts"> [More fun with Observables](#more-observables)
<li> [More fun with Observables](#more-observables)
<ul>
<li> [Create a stream of search terms](#create-stream)</li>
<li> [Listen for search terms](#listen-for-search-terms)</li>
@ -69,7 +66,7 @@ a#demos
block demos-list
:marked
- [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).
- [More fun with Observables](#more-observables).
@ -83,18 +80,17 @@ block demos-list
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 `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
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.
+ifDocsFor('ts')
.l-sub-section
:marked
When importing from the `@angular/http` module, SystemJS knows how to load services from
the !{_Angular_http_library}
because the `systemjs.config.js` file maps to that module name.
.l-sub-section
:marked
When importing from the `@angular/http` module, SystemJS knows how to load services from
the Angular HTTP library
because the `systemjs.config.js` file maps to that module name.
: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
:marked
@ -108,7 +104,7 @@ block demos-list
block http-providers
:marked
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`.
.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.
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:
figure.image-display
@ -152,7 +148,7 @@ a#HeroListComponent
Angular [injects](dependency-injection.html) a `HeroService` into the constructor
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.
It delegates to the `HeroService`.
@ -169,7 +165,7 @@ a#HeroListComponent
(especially calling a remote server) is handled in a separate method.
block getheroes-and-create
: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.
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:
+makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".")
: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)')
: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.
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor')
: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=".")
:marked
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:
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".")
+ifDocsFor('ts')
:marked
<a id="rxjs"></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>.
You'd expect to chain a call to `then()` and extract the heroes.
Instead you're calling a `map()` method.
Clearly this is not a promise.
:marked
<a id="rxjs"></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>.
You'd expect to chain a call to `then()` and extract the heroes.
Instead you're calling a `map()` method.
Clearly this is not a promise.
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*.
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*.
a#rxjs-library
.l-main-section
:marked
## RxJS library
<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
<a href="https://www.youtube.com/watch?v=VLGCCpOWFFw" target="_blank" title="Video: Rob Wormald on Observables"><b>asynchronous Observable</b></a> pattern.
a#rxjs-library
.l-main-section
:marked
## RxJS library
<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
<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
because Observables are used widely in Angular applications.
_This_ app needs it when working with the HTTP client.
But you must take a critical extra step to make RxJS Observables usable:
_you must import the RxJS operators individually_.
All of the Developer Guide samples have installed the RxJS npm package
because Observables are used widely in Angular applications.
_This_ app needs it when working with the HTTP client.
But you must take a critical extra step to make RxJS Observables usable:
_you must import the RxJS operators individually_.
### Enable RxJS operators
The RxJS library is large.
Size matters when building a production application and deploying it to mobile devices.
You should include only necessary features.
### Enable RxJS operators
The RxJS library is large.
Size matters when building a production application and deploying it to mobile devices.
You should include only necessary features.
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.
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
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.
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
.l-main-section
a#extract-data
:marked
## 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=".")
:marked
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
: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`
property. You have to unwrap it to get the heroes.
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
from getting them, the code dealing with where they come from, and the response object.
+ifDocsFor('ts')
.callout.is-important
header HTTP GET is delayed
:marked
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),
which means that the request won't go out until something *subscribes* to the Observable.
That *something* is the [HeroListComponent](#subscribe).
.callout.is-important
header HTTP GET is delayed
:marked
The `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),
which means that the request won't go out until something *subscribes* to the Observable.
That *something* is the [HeroListComponent](#subscribe).
a#error-handling
:marked
@ -324,7 +318,7 @@ a#hero-list-component
h3 #[b HeroListComponent] error handling
block hlc-error-handling
: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.
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:
+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')
a#headers
@ -379,19 +372,18 @@ a#headers
In the `headers` object, the `Content-Type` specifies that the body represents JSON.
+ifDocsFor('ts')
:marked
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
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).
:marked
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
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
:marked
### 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.
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.
+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
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
While Promises may be more familiar, Observables have many advantages.
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
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.
The less obvious but critical difference is that these two methods return very different results.
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 Promise-based `then()` returns another Promise. You can keep chaining
more `then()` and `catch()` calls, getting a new promise each time.
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.
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
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).
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
: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.
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+
:marked
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.
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,
and calls a `search(term)` method after each `keyup` event.
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
Observable !{_array} of string results (`Observable<string[]>`).
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
Observable array of string results (`Observable<string[]>`).
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
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_
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
:marked
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:
+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
The *get heroes* scenario would work,
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.
: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
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:
+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_,
while setting the metadata for the root `AppModule`. Don't call it again.
: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')
.alert.is-important

View File

@ -1,32 +1,24 @@
block includes
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
:marked
## Setup a local development environment
<span if-docs="ts">
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.
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**,
maintained [on github](!{_qsRepo} "Install the github QuickStart repo").
maintained [on github](https://github.com/angular/quickstart "Install the github QuickStart repo").
: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 ...
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. !{_Install} [!{_npm}](#install-prerequisites "What if you don't have !{_prereq}?") packages.
1. Run `!{_npm} !{_start}` to launch the sample application.
1. Install [npm](#install-prerequisites "What if you don't have node and npm?") packages.
1. Run `npm start` to launch the sample application.
a#clone
:marked
@ -35,10 +27,10 @@ a#clone
Perform the _clone-to-launch_ steps with these terminal commands.
code-example(language="sh" class="code-shell").
git clone !{_qsRepo}.git quickstart
git clone https://github.com/angular/quickstart.git quickstart
cd quickstart
!{_npm} !{_install}
!{_npm} !{_start}
npm install
npm start
.alert.is-important
:marked
@ -47,13 +39,13 @@ code-example(language="sh" class="code-shell").
a#download
:marked
### 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.
code-example(language="sh" class="code-shell").
cd quickstart
!{_npm} !{_install}
!{_npm} !{_start}
npm install
npm start
.alert.is-important
:marked
@ -144,13 +136,13 @@ table(width="100%")
th File
th Purpose
tr
td <ngio-ex>app/app.component.ts</ngio-ex>
td <code>app/app.component.ts</code>
td
: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
as the application evolves.
tr(if-docs="ts")
tr
td <code>app/app.module.ts</code>
td
:marked
@ -158,7 +150,7 @@ table(width="100%")
Right now it declares only the `AppComponent`.
Soon there will be more components to declare.
tr
td <ngio-ex>main.ts</ngio-ex>
td <code>main.ts</code>
td
:marked
Compiles the application with the [JIT compiler](../glossary.html#jit) and
@ -180,7 +172,7 @@ br
a#install-prerequisites
.l-main-section
:marked
## Appendix: !{_prereq}
## Appendix: node and npm
block install-tooling
:marked
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
use other versions of node and npm.
+ifDocsFor('ts')
a#why-locally
.l-main-section
:marked
## Appendix: Why develop locally
a#why-locally
.l-main-section
:marked
## 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.
You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
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.
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_.
so the reader can discover Angular without distraction.
The other samples are based on the QuickStart seed.
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_.
so the reader can discover Angular without distraction.
The other samples are based on the QuickStart seed.
As much fun as this is ...
* you can't ship your app in plunker
* you aren't always online when writing code
* transpiling TypeScript in the browser is slow
* the type support, refactoring, and code completion only work in your local IDE
As much fun as this is ...
* you can't ship your app in plunker
* you aren't always online when writing code
* transpiling TypeScript in the browser is slow
* 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_,
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
<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>.
For real development, we strongly recommend [developing locally](#develop-locally).
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.
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/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).

View File

@ -566,7 +566,7 @@ a#unless
+makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs')
: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.

View File

@ -1,10 +1,5 @@
block includes
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.
style.
@ -133,19 +128,19 @@ a#template-expressions
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"`.
You write these template expressions in a language that looks like #{_JavaScript}.
Many #{_JavaScript} expressions are legal template expressions, but not all.
You write these template expressions in a language that looks like JavaScript.
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:
* assignments (`=`, `+=`, `-=`, ...)
* !{__new_op}
* chaining expressions with !{__chaining_op}
* <code>new</code>
* chaining expressions with <code>;</code> or <code>,</code>
* increment and decrement operators (`++` and `--`)
:marked
Other notable differences from #{_JavaScript} syntax include:
Other notable differences from JavaScript syntax include:
* no support for the bitwise operators `|` 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.
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.
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".
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
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 `--`
* operator assignment, such as `+=` and `-=`
* the bitwise operators `|` and `&`
@ -808,8 +803,8 @@ a(href="#toc") back to top
:marked
Finally, you can bind to a specific class name.
Angular adds the class when the template expression evaluates to #{_truthy}.
It removes the class when the expression is #{_falsy}.
Angular adds the class when the template expression evaluates to truthy.
It removes the class when the expression is falsy.
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
@ -1127,7 +1122,7 @@ a#ngClass
:marked
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,
`false` if it should be removed.
@ -1164,7 +1159,7 @@ a#ngStyle
:marked
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.
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', '')
+ifDocsFor('ts|js')
:marked
#### _FormsModule_ is required to use _ngModel_
:marked
#### _FormsModule_ is required to use _ngModel_
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.
Learn more about the `FormsModule` and `ngModel` in the
[Forms](../guide/forms.html#ngModel) guide.
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.
Learn more about the `FormsModule` and `ngModel` in the
[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
#### Inside <span class="syntax">[(ngModel)]</span>
@ -1317,8 +1311,8 @@ a#ngIf
Don't forget the asterisk (`*`) in front of `ngIf`.
:marked
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 `isActive` expression returns a truthy value, `NgIf` adds the `HeroDetailComponent` to the DOM.
When the expression is falsy, `NgIf` removes the `HeroDetailComponent`
from the DOM, destroying that component and all of its sub-components.
#### 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.
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.*
Angular translates this instruction into a `<template>` around the host element,
@ -1412,8 +1406,8 @@ a#template-input-variables
### Template input variables
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
and sets `hero` to the current item from the #{_array} during each iteration.
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.
You reference the `hero` input variable within the `ngFor` host element
(and within its descendents) to access the hero's properties.
@ -1655,13 +1649,13 @@ a#inputs-outputs
.l-sub-section
: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:
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
: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!
:marked
@ -1709,7 +1703,7 @@ h3#aliasing-io Aliasing input/output properties
.l-sub-section
: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
the directive property name on the *left* and the public alias on the *right*:
@ -1722,7 +1716,7 @@ a#expression-operators
:marked
## 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_.
a#pipe
@ -1818,12 +1812,11 @@ code-example(format="nocode").
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".")
block safe-op-alt
:marked
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
when it encounters the first null.
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
:marked
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
when it encounters the first null.
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
:marked
These approaches have merit but can be cumbersome, especially if the property path is long.

View File

@ -1,10 +1,5 @@
block includes
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
This guide offers tips and techniques for testing Angular applications.

View File

@ -2,19 +2,19 @@
- var vers = current.path[2]
.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
p A short beginner guide explaining the basic concepts of Angular
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
p An intermediate development guide covering all major features of Angular
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
p An advanced reference of all Angular Classes, Methods, etc.
@ -24,31 +24,31 @@
h4 Advanced Documentation
ul
li
a(href="/docs/#{lang}/#{vers}/guide/animations.html") Animations
a(href="/docs/ts/latest/guide/animations.html") Animations
li
a(href="/docs/#{lang}/#{vers}/guide/attribute-directives.html") Attribute Directives
a(href="/docs/ts/latest/guide/attribute-directives.html") Attribute Directives
li
a(href="/docs/#{lang}/#{vers}/guide/browser-support.html") Browser Support
a(href="/docs/ts/latest/guide/browser-support.html") Browser Support
li
a(href="/docs/#{lang}/#{vers}/guide/component-styles.html") Component Styles
a(href="/docs/ts/latest/guide/component-styles.html") Component Styles
li
a(href="/docs/#{lang}/#{vers}/guide/deployment.html") Deployment
a(href="/docs/ts/latest/guide/deployment.html") Deployment
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
h4 Cookbook
ul
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
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
a(href="/docs/#{lang}/#{vers}/cookbook/component-communication.html") Component Interaction
a(href="/docs/ts/latest/cookbook/component-communication.html") Component Interaction
li
a(href="/docs/#{lang}/#{vers}/cookbook/dependency-injection.html") Dependency Injection
a(href="/docs/ts/latest/cookbook/dependency-injection.html") Dependency Injection
li
a(href="/docs/#{lang}/#{vers}/cookbook/") View All...
a(href="/docs/ts/latest/cookbook/") View All...
.c4.secondary-content-list
h4 Tools & Libraries

View File

@ -15,9 +15,8 @@ block qs-src-online-and-local
and prepare for development of a real Angular application.
:marked
Every component begins with an `@Component` [!{_decorator}](glossary.html#!{_decorator} '"!{_decorator}" explained')
<span if-docs="ts">function</span> that
<span if-docs="ts">takes a _metadata_ object. The metadata object</span> describes how the HTML template and component class work together.
Every component begins with an `@Component` [decorator](glossary.html#decorator '"decorator" explained')
function that takes a _metadata_ object. The metadata object 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`.
+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.
Interpolation binding is one of many Angular features you'll discover in this documentation.
+ifDocsFor('ts')
:marked
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
:marked
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
.callout.is-helpful
header A word about TypeScript
p.
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.
.callout.is-helpful
header A word about TypeScript
p.
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.
.l-sub-section
:marked
### Next step
:marked
### 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
## Setup to develop locally
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:

View File

@ -10,7 +10,7 @@ include ../_util-fns
and makes it easy to unit-test components with a mock service.
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>.
@ -229,7 +229,7 @@ code-example(format="nocode").
<a id="async"></a>
:marked
## Async services and !{_Promise}s
## Async services and Promises
The `HeroService` returns a list of mock heroes immediately;
its `getHeroes()` signature is synchronous.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
@ -240,12 +240,12 @@ code-example(format="nocode").
:marked
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.
### 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.
The service does that work and eventually calls the function with the results or an error.
.l-sub-section
@ -255,22 +255,22 @@ code-example(format="nocode").
[Exploring ES6](http://http://exploringjs.com/es6.html).
: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=".")
:marked
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=".")
:marked
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.
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.
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=".")
.l-sub-section
: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 defined the `HeroService` as a provider for the `AppComponent`.
* 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>.
@ -348,8 +348,8 @@ code-example(format="nocode").
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=".")
:marked
Like `getHeroes()`, it also returns a !{_Promise}.
But this !{_Promise} waits two seconds before resolving the !{_Promise} with mock heroes.
Like `getHeroes()`, it also returns a `Promise`.
But this Promise waits two seconds before resolving the Promise with mock heroes.
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
and see how the app behaves.

View File

@ -1,11 +1,5 @@
- var _example = 'toh-5';
block includes
include ../_util-fns
- var _appRoutingTsVsAppComp = 'app.module.ts'
- var _RoutesVsAtRouteConfig = 'Routes'
- var _RouterModuleVsRouterDirectives = 'RouterModule'
- var _redirect = 'redirect'
:marked
There are new requirements for the Tour of Heroes app:
@ -30,40 +24,38 @@ figure.image-display
:marked
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
:marked
## Where you left off
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
:marked
## Keep the app transpiling and running
Enter the following command in the terminal window:
.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
:marked
## Keep the app transpiling and running
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell").
npm start
@ -108,11 +100,11 @@ block keep-app-running
and create a separate `AppComponent` shell.
Do the following:
* Rename the <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>.
* Rename the `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file).
* Rename the selector `my-app` to `my-heroes`.
* Rename the <code>app.component.ts</code> file to <code>heroes.component.ts</code>.
* Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file).
* 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
### Create *AppComponent*
@ -122,27 +114,26 @@ block keep-app-running
Perform these steps:
* Create the file <span ngio-ex>src/app/app.component.ts</span>.
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
* Create the file <code>src/app/app.component.ts</code>.
* Define an exported `AppComponent` class.
* Add an `@Component` decorator above the class with a `my-app` selector.
* Move the following from `HeroesComponent` to `AppComponent`:
* `title` class property.
* `@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 `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because you'll need it in every other view.
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it was promoted.
* Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `<my-heroes>` tags.
* 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.
* Add the supporting `import` statements for `AppComponent`.
The first draft looks like this:
block app-comp-v1
+makeTabs(
`toh-5/ts/src/app/app.component.1.ts,
toh-5/ts/src/app/app.module.1.ts`,
',',
`src/app/app.component.ts (v1),
src/app/app.module.ts (v1)`)
+makeTabs(
`toh-5/ts/src/app/app.component.1.ts,
toh-5/ts/src/app/app.module.1.ts`,
',',
`src/app/app.component.ts (v1),
src/app/app.module.ts (v1)`)
:marked
The app still runs and displays heroes.
@ -155,12 +146,11 @@ block app-comp-v1
Use the Angular router to enable navigation.
block angular-router
:marked
The Angular router is an external, optional Angular NgModule called `RouterModule`.
The router is a combination of multiple provided services (`RouterModule`),
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
and a configuration (`Routes`). You'll configure the routes first.
:marked
The Angular router is an external, optional Angular NgModule called `RouterModule`.
The router is a combination of multiple provided services (`RouterModule`),
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
and a configuration (`Routes`). You'll configure the routes first.
:marked
### *&lt;base href>*
@ -169,7 +159,7 @@ block angular-router
(or a script that dynamically sets this element)
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
header base href is essential
@ -179,11 +169,10 @@ block angular-router
a#configure-routes
block router-config-intro
:marked
### Configure routes
:marked
### Configure routes
Create a configuration file for the app routes.
Create a configuration file for the app routes.
:marked
*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.
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts'
+makeExcerpt('src/app/' + _file + ' (heroes route)', 'heroes')
+makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)')
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
:marked
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
The `Routes` are an array of *route definitions*.
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`).
<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>
- *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`).
- *Component*: The component that the router should create when navigating to this route (`HeroesComponent`).
.l-sub-section
: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
### 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
### Router outlet
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.
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.
### Router links
@ -243,21 +225,20 @@ block router-config-intro
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
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
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.
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
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:
+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
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.
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
You'll make this component more useful later.
### 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
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'
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)')
+ifDocsFor('ts|js')
:marked
Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
:marked
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
### Add a !{_redirect} route
@ -314,25 +293,23 @@ block routerLink
When the app starts, it should show the dashboard and
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
To make this happen, use a redirect route. Add the following
to the array of route definitions:
+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.
Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section
of the [Routing & Navigation](../guide/router.html#) page.
:marked
### Add navigation to the template
Add a dashboard navigation link to the template, just above the *Heroes* link.
- var _vers = _docsFor == 'dart' ? '' : '.1'
+makeExcerpt('src/app/app.component' + _vers + '.ts', 'template-v3')
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)')
.l-sub-section
:marked
@ -349,15 +326,12 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new
template file.
+makeExcerpt('src/app/dashboard.component.ts', 'metadata')
block templateUrl-path-resolution
//- N/A for TS
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'metadata', 'src/app/dashboard.component.ts (metadata)')
:marked
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
`*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*
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`
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
Earlier, you removed the `HeroService` from the `providers` array of `HeroesComponent`
and added it to the `providers` array of `AppModule`.
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`.
### 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
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
This kind of logic is also used in the `HeroesComponent`:
* Define a `heroes` !{_array} property.
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
* Define a `heroes` array property.
* 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.
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.
@ -407,7 +381,7 @@ block templateUrl-path-resolution
### 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.
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*.
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
+makeExcerpt(_file + ' (hero detail)','hero-detail')
+makeExample('toh-5/ts/src/app/app.module.3.ts','hero-detail', 'src/app/app.module.ts (hero detail)')
:marked
The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id`
when navigating to the `HeroDetailComponent`.
+ifDocsFor('dart')
.l-sub-section
:marked
Be sure to import the hero detail component before creating this route.
.l-sub-section
:marked
Be sure to import the hero detail component before creating this route.
:marked
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 major changes are driven by how you get hero names.
block route-params
:marked
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
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
:marked
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
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
:marked
Add the following imports:
- var _vers = _docsFor == 'dart' ? '' : '.1'
+makeExcerpt('src/app/hero-detail.component' + _vers + '.ts', 'added-imports', 'src/app/hero-detail.component')
+makeExample('toh-5/ts/src/app/hero-detail.component.1.ts', 'added-imports', 'src/app/hero-detail.component.ts')
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
:marked
Inject the `!{_ActivatedRoute}`, `HeroService`, and `Location` services
Inject the `ActivatedRoute`, `HeroService`, and `Location` services
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
Import the `switchMap` operator to use later with the route parameters `Observable`.
:marked
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
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
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
extract the `id` parameter value from the `ActivatedRoute` service
and use the `HeroService` to fetch the hero with that `id`.
:marked
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
extract the `id` parameter value from the `ActivatedRoute` service
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
:marked
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.
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
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
:marked
### Do you need to unsubscribe?
.l-sub-section
:marked
### Do you need to unsubscribe?
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,
the `Router` manages the observables it provides and localizes
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`.
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,
the `Router` manages the observables it provides and localizes
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`.
:marked
### 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,
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
### 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
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
:marked
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.
:marked
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
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
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
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
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
Notice the `[routerLink]` binding.
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".
This time, you're binding to an expression containing a *link parameters !{_array}*.
The !{_array} has two elements: the *!{_pathVsName}* of
This time, you're binding to an expression containing a *link parameters array*.
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 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
`!{_appRoutingTsVsAppComp}` earlier:
`app.module.ts` earlier:
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'hero-detail', 'src/app/app.module.ts (hero detail)')
:marked
Refresh the browser and select a hero from the dashboard; the app navigates to that heros details.
+ifDocsFor('ts')
.l-main-section
:marked
## Refactor routes to a _Routing Module_
.l-main-section
:marked
## Refactor routes to a _Routing Module_
Almost 20 lines of `AppModule` are devoted to configuring four routes.
Most applications have many more routes and they add guard services
to protect against unwanted or unauthorized navigations.
(Read more about guard services in the [Route Guards](../guide/router.html#guards)
section of the [Routing & Navigation](../guide/router.html) page.)
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.
Almost 20 lines of `AppModule` are devoted to configuring four routes.
Most applications have many more routes and they add guard services
to protect against unwanted or unauthorized navigations.
(Read more about guard services in the [Route Guards](../guide/router.html#guards)
section of the [Routing & Navigation](../guide/router.html) page.)
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.
It's a good idea to refactor the routing configuration into its own class.
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`,
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)
section of the [Routing & Navigation](../guide/router.html) page.
It's a good idea to refactor the routing configuration into its own class.
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`,
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)
section of the [Routing & Navigation](../guide/router.html) page.
By convention, a routing module name contains the word "Routing" and
aligns with the name of the module that declares the components navigated to.
By convention, a routing module name contains the word "Routing" and
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`.
Give it the following contents, extracted from the `AppModule` class:
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`.
Give it the following contents, extracted from the `AppModule` class:
+makeExample('src/app/app-routing.module.ts')
:marked
The following points are typical of routing modules:
* 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.
* The Routing Module adds `RouterModule.forRoot(routes)` to `imports`.
* The Routing Module adds `RouterModule` to `exports` so that the
components in the companion module have access to Router declarables,
such as `RouterLink` and `RouterOutlet`.
* 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.)
+makeExample('toh-5/ts/src/app/app-routing.module.ts', null, 'src/app/app-routing.module.ts')
:marked
The following points are typical of routing modules:
* 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.
* The Routing Module adds `RouterModule.forRoot(routes)` to `imports`.
* The Routing Module adds `RouterModule` to `exports` so that the
components in the companion module have access to Router declarables,
such as `RouterLink` and `RouterOutlet`.
* 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.)
### Update *AppModule*
### Update *AppModule*
Delete the routing configuration from `AppModule` and import the `AppRoutingModule`.
Use an ES `import` statement *and* add it to the `NgModule.imports` list.
Delete the routing configuration from `AppModule` and import the `AppRoutingModule`.
Use an ES `import` statement *and* add it to the `NgModule.imports` list.
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:
+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
@ -707,7 +666,7 @@ figure.image-display
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 ( | ).
+makeExcerpt('src/app/heroes.component.html', 'pipe', '')
+makeExample('toh-5/ts/src/app/heroes.component.html', 'pipe', '')
:marked
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.
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
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:
@ -747,14 +706,11 @@ figure.image-display
`templateUrl` and `styleUrls` respectively.
Set their properties to refer to the new files.
block heroes-component-cleanup
//- Only relevant for Dart.
+makeExcerpt('src/app/heroes.component.ts (revised metadata)', 'metadata')
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'metadata', 'src/app/heroes.component.ts (revised metadata)')
.l-sub-section
: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.
:marked
@ -770,16 +726,16 @@ block heroes-component-cleanup
1. Inject the `router` in the constructor, along with the `HeroService`.
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
Note that you're passing a two-element *link parameters !{_array}*&mdash;a
!{_pathVsName} and the route parameter&mdash;to
Note that you're passing a two-element *link parameters array*&mdash;a
path and the route parameter&mdash;to
the router `navigate()` method, just as you did in the `[routerLink]` binding
back in the `DashboardComponent`.
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
Refresh the browser and start clicking.
@ -800,21 +756,21 @@ block heroes-component-cleanup
would obscure the component logic.
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
that file in the component metadata's `styleUrls` !{_array} property like this:
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:
+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
### Add stylish hero details
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
the `styleUrls` !{_array} as you did for `DashboardComponent`.
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` !{_decorator}
<span if-docs="ts">and its import</span>.
the `styleUrls` array as you did for `DashboardComponent`.
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` decorator
and its import.
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.
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
block router-link-active
:marked
**The *routerLinkActive* directive**
:marked
**The *routerLinkActive* directive**
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.
All you have to do is define the style for it.
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.
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
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
@ -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).
Here's an excerpt:
+makeExcerpt('src/styles.css (excerpt)', 'toh')
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css'
+makeExample('toh-5/ts/src/styles.css', 'toh', 'src/styles.css (excerpt)')
:marked
Create the file <span ngio-ex>styles.css</span>.
Ensure that the file contains the [master styles provided here](!{styles_css}).
Also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
Create the file <code>styles.css</code>.
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 <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
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.
Verify that you have the following structure:
block file-tree-end
.filetree
.file angular-tour-of-heroes
.filetree
.file angular-tour-of-heroes
.children
.file src
.children
.file src
.file app
.children
.file app
.children
.file app.component.css
.file app.component.ts
.file app.module.ts
.file app-routing.module.ts
.file dashboard.component.css
.file dashboard.component.html
.file dashboard.component.ts
.file hero.service.ts
.file hero.ts
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file heroes.component.css
.file heroes.component.html
.file heroes.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
.file app.component.css
.file app.component.ts
.file app.module.ts
.file app-routing.module.ts
.file dashboard.component.css
.file dashboard.component.html
.file dashboard.component.ts
.file hero.service.ts
.file hero.ts
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file heroes.component.css
.file heroes.component.html
.file heroes.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
.l-main-section
:marked

View File

@ -1,15 +1,5 @@
- var _example = 'toh-6';
block includes
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
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,
editing a selected hero along the way.
That's the starting point for this page.
block start-server-and-watch
:marked
## Keep the app transpiling and running
Enter the following command in the terminal window:
:marked
## Keep the app transpiling and running
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell").
npm start
@ -46,77 +36,76 @@ block start-server-and-watch
.l-main-section#http-providers
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
## Register for HTTP services
block http-providers
:marked
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.
:marked
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.
To allow access to these services from anywhere in the app,
add `HttpModule` to the `imports` list of the `AppModule`.
To allow access to these services from anywhere in the app,
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
Notice that you also supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
:marked
Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`.
.l-main-section
:marked
## 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,
the HTTP client will fetch and save data from
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
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>
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
:marked
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>
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
The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
Add the file `in-memory-data.service.ts` in `!{_appDir}` with the following content:
:marked
The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
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
This file replaces `mock-heroes.ts`, which is now safe to delete.
block dont-be-distracted-by-backend-subst
.alert.is-helpful
:marked
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
skip it when you have a real web API server.
.alert.is-helpful
:marked
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
skip it when you have a real web API server.
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)
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
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)
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
.l-main-section
:marked
## 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
This was implemented in anticipation of ultimately
@ -124,56 +113,53 @@ block dont-be-distracted-by-backend-subst
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
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
Refresh the browser. The hero data should successfully load from the
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
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.
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
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
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', '')
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-data', '')
:marked
The response JSON has a single `data` property, which
holds the !{_array} of heroes that the caller wants.
So you grab that !{_array} and return it as the resolved !{_Promise} value.
holds the array of heroes that the caller wants.
So you grab that array and return it as the resolved Promise value.
.alert.is-important
:marked
@ -183,27 +169,26 @@ block get-heroes-details
:marked
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
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
This is a critical step.
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
This demo service logs the error to the console; in real life,
you would handle the error in code. For a demo, this works.
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
@ -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`).
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
This request is almost the same as `getHeroes()`.
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
Although you made significant internal changes to `getHeroes()` and `getHero()`,
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.
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
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
Add the following `save()` method, which persists hero name changes using the hero service
`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
### 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.
+makeExcerpt('src/app/hero.service.ts', 'update')
+makeExample('toh-6/ts/src/app/hero.service.ts', 'update', 'src/app/hero.service.ts (update)')
:marked
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
calling `!{_JSON_stringify}`. The body content type
calling `JSON.stringify`. The body content type
(`application/json`) is identified in the request header.
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
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
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.
+makeExcerpt('src/app/heroes.component.ts', 'add')
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'add', 'src/app/heroes.component.ts (add)')
:marked
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.
+makeExcerpt('src/app/hero.service.ts', 'create')
+makeExample('toh-6/ts/src/app/hero.service.ts', 'create', 'src/app/hero.service.ts (create)')
:marked
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
name in the repeated `<li>` element.
+makeExcerpt('src/app/heroes.component.html', 'delete', '')
+makeExample('toh-6/ts/src/app/heroes.component.html', 'delete', '')
:marked
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
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:
+makeExcerpt('src/app/heroes.component.ts', 'delete')
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'delete', 'src/app/heroes.component.ts (delete)')
:marked
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
from the !{_array} and resets the selected hero, if necessary.
from the array and resets the selected hero, if necessary.
:marked
To place the delete button at the far right of the hero entry,
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
### Hero service _delete()_ method
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
Refresh the browser and try the new delete functionality.
#observables
:marked
## !{_Observable}s
## Observables
block observables-section-intro
:marked
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
:marked
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.
This section shows you how, when, and why to return the `Observable` directly.
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.
### Background
An *Observable* is a stream of events that you can process with array-like operators.
### Background
An *Observable* is a stream of events that you can process with array-like operators.
Angular core has basic support for observables.
Developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
You'll see how shortly.
Angular core has basic support for observables.
Developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
You'll see how shortly.
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.
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.
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.
The calling component can easily consume a single result in the form of a Promise.
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.
The calling component can easily consume a single result in the form of a Promise.
:marked
But requests aren't always done only once.
You may start one 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
easy with *!{_Observable}s*.
A *request-cancel-new-request* sequence is difficult to implement with *Promises*, but
easy with *Observables*.
### Add the ability to search by name
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.
+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
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.
<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()`,
after chaining it to another RxJS operator, <code>map()</code>,
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.
+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
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
As the user types in the search box, a *keyup* event binding calls the component's `search()`
method with the new search box value.
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}.
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`.
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 `async` pipe subscribes to the `Observable` and produces the array of heroes to `*ngFor`.
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
#### 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
A `Subject` is a producer of an _observable_ event stream;
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
:marked
A `Subject` is a producer of an _observable_ event stream;
`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
<a id="ngoninit"></a>
#### 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
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
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.
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
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:
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.
* `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 `debounceTime()` and `distinctUntilChanged()`.
It cancels and discards previous search observables, returning only the latest search service observable.
`switchMap()` preserves the original request order while returning
only the observable from the most recent `http` method call.
Results from prior calls are canceled and discarded.
.l-sub-section
:marked
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.
If the search text is empty, the `http()` method call is also short circuited
and an observable containing an empty array is returned.
`switchMap()` preserves the original request order while returning
only the observable from the most recent `http` method call.
Results from prior calls are canceled and discarded.
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
doesn't actually abort a pending HTTP request.
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
and an observable containing an empty array is returned.
a#rxjs-imports
:marked
### Import RxJS operators
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
doesn't actually abort a pending HTTP request.
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.
Most RxJS operators are not included in Angular's base `Observable` implementation.
The base implementation includes only what Angular itself requires.
a#rxjs-imports
:marked
### Import RxJS operators
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
Here are all the RxJS imports that _this_ component needs:
Most RxJS operators are not included in Angular's base `Observable` implementation.
The base implementation includes only what Angular itself requires.
+makeExample('toh-6/ts/src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
Here are all the RxJS imports that _this_ component needs:
:marked
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='.')
:marked
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.
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
### Add the search component to the dashboard
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
Finally, import `HeroSearchComponent` from
<span ngio-ex>hero-search.component.ts</span>
and add it to the `!{_declarations}` !{_array}.
<code>hero-search.component.ts</code>
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
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.
Verify that you have the following structure:
block filetree
.filetree
.file angular-tour-of-heroes
.filetree
.file angular-tour-of-heroes
.children
.file src
.children
.file src
.file app
.children
.file app
.children
.file app.component.ts
.file app.component.css
.file app.module.ts
.file app-routing.module.ts
.file dashboard.component.css
.file dashboard.component.html
.file dashboard.component.ts
.file hero.ts
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file hero-search.component.html (new)
.file hero-search.component.css (new)
.file hero-search.component.ts (new)
.file hero-search.service.ts (new)
.file hero.service.ts
.file heroes.component.css
.file heroes.component.html
.file heroes.component.ts
.file in-memory-data.service.ts (new)
.file main.ts
.file index.html
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file package.json
.file app.component.ts
.file app.component.css
.file app.module.ts
.file app-routing.module.ts
.file dashboard.component.css
.file dashboard.component.html
.file dashboard.component.ts
.file hero.ts
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file hero-search.component.html (new)
.file hero-search.component.css (new)
.file hero-search.component.ts (new)
.file hero-search.service.ts (new)
.file hero.service.ts
.file heroes.component.css
.file heroes.component.html
.file heroes.component.ts
.file in-memory-data.service.ts (new)
.file main.ts
.file index.html
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file package.json
.l-main-section
:marked
@ -587,44 +566,43 @@ block filetree
- You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
- You updated the components to allow adding, editing, and deleting of heroes.
- 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.
block file-summary
+makeTabs(
`toh-6/ts/src/app/app.component.ts,
toh-6/ts/src/app/app.module.ts,
toh-6/ts/src/app/heroes.component.ts,
toh-6/ts/src/app/heroes.component.html,
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.html,
toh-6/ts/src/app/hero.service.ts,
toh-6/ts/src/app/in-memory-data.service.ts`,
',,,,,,,,',
`app.comp...ts,
app.mod...ts,
heroes.comp...ts,
heroes.comp...html,
heroes.comp...css,
hero-detail.comp...ts,
hero-detail.comp...html,
hero.service.ts,
in-memory-data.service.ts`
)
+makeTabs(
`toh-6/ts/src/app/app.component.ts,
toh-6/ts/src/app/app.module.ts,
toh-6/ts/src/app/heroes.component.ts,
toh-6/ts/src/app/heroes.component.html,
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.html,
toh-6/ts/src/app/hero.service.ts,
toh-6/ts/src/app/in-memory-data.service.ts`,
',,,,,,,,',
`app.comp...ts,
app.mod...ts,
heroes.comp...ts,
heroes.comp...html,
heroes.comp...css,
hero-detail.comp...ts,
hero-detail.comp...html,
hero.service.ts,
in-memory-data.service.ts`
)
+makeTabs(
`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.html,
toh-6/ts/src/app/hero-search.component.css`,
null,
`hero-search.service.ts,
hero-search.component.ts,
hero-search.component.html,
hero-search.component.css`
)
+makeTabs(
`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.html,
toh-6/ts/src/app/hero-search.component.css`,
null,
`hero-search.service.ts,
hero-search.component.ts,
hero-search.component.html,
hero-search.component.css`
)
.l-sub-section
: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) { };
}
};
}]);