chore: remove dart remains (#3468)
This commit is contained in:
parent
93d3db1fd4
commit
f7c89d07b1
|
@ -36,5 +36,4 @@ script(src="/resources/js/directives/code-pane.js")
|
||||||
script(src="/resources/js/directives/code-example.js")
|
script(src="/resources/js/directives/code-example.js")
|
||||||
script(src="/resources/js/directives/if-docs.js")
|
script(src="/resources/js/directives/if-docs.js")
|
||||||
script(src="/resources/js/directives/live-example.js")
|
script(src="/resources/js/directives/live-example.js")
|
||||||
script(src="/resources/js/directives/ngio-ex-path.js")
|
script(src="/resources/js/directives/scroll-y-offset-element.js")
|
||||||
script(src="/resources/js/directives/scroll-y-offset-element.js")
|
|
||||||
|
|
|
@ -283,9 +283,9 @@ a#rollup-plugins
|
||||||
|
|
||||||
Luckily, there is a Rollup plugin that modifies _RxJs_
|
Luckily, there is a Rollup plugin that modifies _RxJs_
|
||||||
to use the ES `import` and `export` statements that Rollup requires.
|
to use the ES `import` and `export` statements that Rollup requires.
|
||||||
Rollup then preserves the parts of `RxJS` referenced by the application
|
Rollup then preserves the parts of `RxJS` referenced by the application
|
||||||
in the final bundle. Using it is straigthforward. Add the following to
|
in the final bundle. Using it is straigthforward. Add the following to
|
||||||
the `plugins` !{_array} in `rollup-config.js`:
|
the `plugins` array in `rollup-config.js`:
|
||||||
|
|
||||||
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
||||||
|
|
||||||
|
@ -293,8 +293,8 @@ a#rollup-plugins
|
||||||
*Minification*
|
*Minification*
|
||||||
|
|
||||||
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
|
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
|
||||||
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
|
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
|
||||||
Add the following to the `plugins` !{_array}:
|
Add the following to the `plugins` array:
|
||||||
|
|
||||||
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
block includes
|
block includes
|
||||||
include _util-fns
|
include _util-fns
|
||||||
|
|
||||||
//- current.path = ['docs', lang, 'latest', ...]
|
|
||||||
- var lang = current.path[1]
|
|
||||||
- var docsPath='/' + current.path[0]
|
|
||||||
- var docsLatest='/' + current.path.slice(0,3).join('/');
|
|
||||||
- var _at = lang === 'js' ? '' : '@'
|
|
||||||
- var _decoratorLink = '<a href="#' + _decorator + '">' + _decorator + '</a>'
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular has its own vocabulary.
|
Angular has its own vocabulary.
|
||||||
Most Angular terms are common English words
|
Most Angular terms are common English words
|
||||||
|
@ -29,22 +22,21 @@ a#aot
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
You can compile Angular applications at build time.
|
You can compile Angular applications at build time.
|
||||||
By compiling your application<span if-docs="ts"> using the compiler-cli, `ngc`</span>, you can bootstrap directly
|
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
||||||
to a<span if-docs="ts"> module</span> factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
|
## Angular module
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
## Angular module
|
Helps you organize an application into cohesive blocks of functionality.
|
||||||
.l-sub-section
|
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||||
:marked
|
|
||||||
Helps you organize an application into cohesive blocks of functionality.
|
|
||||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
|
||||||
|
|
||||||
Every Angular application has an application root-module class. By convention, the class is
|
Every Angular application has an application root-module class. By convention, the class is
|
||||||
called `AppModule` and resides in a file named `app.module.ts`.
|
called `AppModule` and resides in a file named `app.module.ts`.
|
||||||
|
|
||||||
For details and examples, see the [Angular Modules (NgModule)](!{docsLatest}/guide/ngmodule.html) page.
|
For details and examples, see the [Angular Modules (NgModule)](./ngmodule.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Annotation
|
## Annotation
|
||||||
|
@ -64,50 +56,49 @@ a#attribute-directives
|
||||||
|
|
||||||
For example, you can use the `ngClass` directive to add and remove CSS class names.
|
For example, you can use the `ngClass` directive to add and remove CSS class names.
|
||||||
|
|
||||||
Learn about them in the [_Attribute Directives_](!{docsLatest}/guide/attribute-directives.html) guide.
|
Learn about them in the [_Attribute Directives_](./attribute-directives.html) guide.
|
||||||
|
|
||||||
.l-main-section#B
|
.l-main-section#B
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
:marked
|
||||||
|
## Barrel
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
## Barrel
|
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
||||||
.l-sub-section
|
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
||||||
:marked
|
|
||||||
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
|
||||||
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
|
||||||
|
|
||||||
For example, imagine three ES2015 modules in a `heroes` folder:
|
For example, imagine three ES2015 modules in a `heroes` folder:
|
||||||
code-example.
|
code-example.
|
||||||
// heroes/hero.component.ts
|
// heroes/hero.component.ts
|
||||||
export class HeroComponent {}
|
export class HeroComponent {}
|
||||||
|
|
||||||
// heroes/hero.model.ts
|
// heroes/hero.model.ts
|
||||||
export class Hero {}
|
export class Hero {}
|
||||||
|
|
||||||
// heroes/hero.service.ts
|
// heroes/hero.service.ts
|
||||||
export class HeroService {}
|
export class HeroService {}
|
||||||
:marked
|
:marked
|
||||||
Without a barrel, a consumer needs three import statements:
|
Without a barrel, a consumer needs three import statements:
|
||||||
code-example.
|
code-example.
|
||||||
import { HeroComponent } from '../heroes/hero.component.ts';
|
import { HeroComponent } from '../heroes/hero.component.ts';
|
||||||
import { Hero } from '../heroes/hero.model.ts';
|
import { Hero } from '../heroes/hero.model.ts';
|
||||||
import { HeroService } from '../heroes/hero.service.ts';
|
import { HeroService } from '../heroes/hero.service.ts';
|
||||||
:marked
|
:marked
|
||||||
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
|
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
|
||||||
code-example.
|
code-example.
|
||||||
export * from './hero.model.ts'; // re-export all of its exports
|
export * from './hero.model.ts'; // re-export all of its exports
|
||||||
export * from './hero.service.ts'; // re-export all of its exports
|
export * from './hero.service.ts'; // re-export all of its exports
|
||||||
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
||||||
:marked
|
:marked
|
||||||
Now a consumer can import what it needs from the barrel.
|
Now a consumer can import what it needs from the barrel.
|
||||||
code-example.
|
code-example.
|
||||||
import { Hero, HeroService } from '../heroes'; // index is implied
|
import { Hero, HeroService } from '../heroes'; // index is implied
|
||||||
:marked
|
:marked
|
||||||
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
|
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
You can often achieve the same result using [Angular modules](#angular-module) instead.
|
You can often achieve the same result using [Angular modules](#angular-module) instead.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Binding
|
## Binding
|
||||||
|
@ -126,7 +117,7 @@ a#attribute-directives
|
||||||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
||||||
Bootstrapping identifies an application's top level "root" [component](#component),
|
Bootstrapping identifies an application's top level "root" [component](#component),
|
||||||
which is the first component that is loaded for the application.
|
which is the first component that is loaded for the application.
|
||||||
For more information, see the [Setup](!{docsLatest}/guide/setup.html) page.
|
For more information, see the [Setup](./setup.html) page.
|
||||||
|
|
||||||
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
||||||
|
|
||||||
|
@ -153,7 +144,7 @@ a#component
|
||||||
The *component* is one of the most important building blocks in the Angular system.
|
The *component* is one of the most important building blocks in the Angular system.
|
||||||
It is, in fact, an Angular [directive](#directive) with a companion [template](#template).
|
It is, in fact, an Angular [directive](#directive) with a companion [template](#template).
|
||||||
|
|
||||||
Apply the `!{_at}Component` !{_decoratorLink} to
|
Apply the `@Component` [decorator](#decorator) to
|
||||||
the component class, thereby attaching to the class the essential component metadata
|
the component class, thereby attaching to the class the essential component metadata
|
||||||
that Angular needs to create a component instance and render the component with its template
|
that Angular needs to create a component instance and render the component with its template
|
||||||
as a view.
|
as a view.
|
||||||
|
@ -169,8 +160,8 @@ a#component
|
||||||
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
||||||
This form is also known as kebab-case.
|
This form is also known as kebab-case.
|
||||||
|
|
||||||
[Directive](#directive) selectors (like `my-app`) <span if-docs="ts">and
|
[Directive](#directive) selectors (like `my-app`) and
|
||||||
the root of filenames (such as `hero-list.component.ts`)</span> are often
|
the root of filenames (such as `hero-list.component.ts`) are often
|
||||||
spelled in dash-case.
|
spelled in dash-case.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -189,14 +180,14 @@ a#component
|
||||||
Angular has a rich data-binding framework with a variety of data-binding
|
Angular has a rich data-binding framework with a variety of data-binding
|
||||||
operations and supporting declaration syntax.
|
operations and supporting declaration syntax.
|
||||||
|
|
||||||
Read about the following forms of binding in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page:
|
Read about the following forms of binding in the [Template Syntax](./template-syntax.html) page:
|
||||||
* [Interpolation](!{docsLatest}/guide/template-syntax.html#interpolation).
|
* [Interpolation](./template-syntax.html#interpolation).
|
||||||
* [Property binding](!{docsLatest}/guide/template-syntax.html#property-binding).
|
* [Property binding](./template-syntax.html#property-binding).
|
||||||
* [Event binding](!{docsLatest}/guide/template-syntax.html#event-binding).
|
* [Event binding](./template-syntax.html#event-binding).
|
||||||
* [Attribute binding](!{docsLatest}/guide/template-syntax.html#attribute-binding).
|
* [Attribute binding](./template-syntax.html#attribute-binding).
|
||||||
* [Class binding](!{docsLatest}/guide/template-syntax.html#class-binding).
|
* [Class binding](./template-syntax.html#class-binding).
|
||||||
* [Style binding](!{docsLatest}/guide/template-syntax.html#style-binding).
|
* [Style binding](./template-syntax.html#style-binding).
|
||||||
* [Two-way data binding with ngModel](!{docsLatest}/guide/template-syntax.html#ngModel).
|
* [Two-way data binding with ngModel](./template-syntax.html#ngModel).
|
||||||
|
|
||||||
a#decorator
|
a#decorator
|
||||||
a#decoration
|
a#decoration
|
||||||
|
@ -280,7 +271,7 @@ a#decoration
|
||||||
Angular registers some of its own providers with every injector.
|
Angular registers some of its own providers with every injector.
|
||||||
You can register your own providers.
|
You can register your own providers.
|
||||||
|
|
||||||
Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page.
|
Read more in the [Dependency Injection](./dependency-injection.html) page.
|
||||||
|
|
||||||
a#directive
|
a#directive
|
||||||
a#directives
|
a#directives
|
||||||
|
@ -370,11 +361,11 @@ a#H
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A directive property that can be the *target* of a
|
A directive property that can be the *target* of a
|
||||||
[property binding](!{docsLatest}/guide/template-syntax.html#property-binding) (explained in detail in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page).
|
[property binding](./template-syntax.html#property-binding) (explained in detail in the [Template Syntax](./template-syntax.html) page).
|
||||||
Data values flow *into* this property from the data source identified
|
Data values flow *into* this property from the data source identified
|
||||||
in the template expression to the right of the equal sign.
|
in the template expression to the right of the equal sign.
|
||||||
|
|
||||||
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Interpolation
|
## Interpolation
|
||||||
|
@ -390,8 +381,8 @@ a#H
|
||||||
<label>My current hero is {{hero.name}}</label>
|
<label>My current hero is {{hero.name}}</label>
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Read more about [interpolation](!{docsLatest}/guide/template-syntax.html#interpolation) in the
|
Read more about [interpolation](./template-syntax.html#interpolation) in the
|
||||||
[Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
[Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
.l-main-section#J
|
.l-main-section#J
|
||||||
|
|
||||||
|
@ -400,7 +391,7 @@ a#jit
|
||||||
## Just-in-time (JIT) compilation
|
## Just-in-time (JIT) compilation
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A bootstrapping method of compiling components<span if-docs="ts"> and modules</span> in the browser
|
A bootstrapping method of compiling components and modules in the browser
|
||||||
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
||||||
Consider using the [ahead-of-time](#aot) mode for production apps.
|
Consider using the [ahead-of-time](#aot) mode for production apps.
|
||||||
|
|
||||||
|
@ -435,7 +426,7 @@ a#jit
|
||||||
* `ngAfterViewChecked`: after every check of a component's views.
|
* `ngAfterViewChecked`: after every check of a component's views.
|
||||||
* `ngOnDestroy`: just before the directive is destroyed.
|
* `ngOnDestroy`: just before the directive is destroyed.
|
||||||
|
|
||||||
Read more in the [Lifecycle Hooks](!{docsLatest}/guide/lifecycle-hooks.html) page.
|
Read more in the [Lifecycle Hooks](./lifecycle-hooks.html) page.
|
||||||
|
|
||||||
.l-main-section#M
|
.l-main-section#M
|
||||||
|
|
||||||
|
@ -446,7 +437,7 @@ a#jit
|
||||||
:marked
|
:marked
|
||||||
Angular has the following types of modules:
|
Angular has the following types of modules:
|
||||||
- [Angular modules](#angular-module).
|
- [Angular modules](#angular-module).
|
||||||
For details and examples, see the [Angular Modules](!{docsLatest}/guide/ngmodule.html) page.
|
For details and examples, see the [Angular Modules](./ngmodule.html) page.
|
||||||
- ES2015 modules, as described in this section.
|
- ES2015 modules, as described in this section.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -477,29 +468,28 @@ a#jit
|
||||||
a#N
|
a#N
|
||||||
.l-main-section#O
|
.l-main-section#O
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
:marked
|
||||||
|
## Observable
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
## Observable
|
An array whose items arrive asynchronously over time.
|
||||||
.l-sub-section
|
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||||
:marked
|
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
||||||
An array whose items arrive asynchronously over time.
|
|
||||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
|
||||||
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
|
||||||
|
|
||||||
To use observables, Angular uses a third-party library called Reactive Extensions (RxJS).
|
To use observables, Angular uses a third-party library called Reactive Extensions (RxJS).
|
||||||
Observables are a proposed feature for ES2016, the next version of JavaScript.
|
Observables are a proposed feature for ES2016, the next version of JavaScript.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Output
|
## Output
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A directive property that can be the *target* of event binding
|
A directive property that can be the *target* of event binding
|
||||||
(read more in the [event binding](!{docsLatest}/guide/template-syntax.html#event-binding)
|
(read more in the [event binding](./template-syntax.html#event-binding)
|
||||||
section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page).
|
section of the [Template Syntax](./template-syntax.html) page).
|
||||||
Events stream *out* of this property to the receiver identified
|
Events stream *out* of this property to the receiver identified
|
||||||
in the template expression to the right of the equal sign.
|
in the template expression to the right of the equal sign.
|
||||||
|
|
||||||
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
.l-main-section#P
|
.l-main-section#P
|
||||||
|
|
||||||
|
@ -527,7 +517,7 @@ a#N
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You can also write your own custom pipes.
|
You can also write your own custom pipes.
|
||||||
Read more in the page on [pipes](!{docsLatest}/guide/pipes.html).
|
Read more in the page on [pipes](./pipes.html).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Provider
|
## Provider
|
||||||
|
@ -566,18 +556,17 @@ a#Q
|
||||||
|
|
||||||
The Angular component router is a richly featured mechanism for configuring and managing the entire view navigation process, including the creation and destruction
|
The Angular component router is a richly featured mechanism for configuring and managing the entire view navigation process, including the creation and destruction
|
||||||
of views.
|
of views.
|
||||||
+ifDocsFor('ts|js')
|
|
||||||
:marked
|
In most cases, components become attached to a router by means
|
||||||
In most cases, components become attached to a router by means
|
of a `RouterConfig` that defines routes to views.
|
||||||
of a `RouterConfig` that defines routes to views.
|
|
||||||
|
|
||||||
A [routing component's](#routing-component) template has a `RouterOutlet` element
|
A [routing component's](#routing-component) template has a `RouterOutlet` element
|
||||||
where it can display views produced by the router.
|
where it can display views produced by the router.
|
||||||
|
|
||||||
Other views in the application likely have anchor tags or buttons with `RouterLink`
|
Other views in the application likely have anchor tags or buttons with `RouterLink`
|
||||||
directives that users can click to navigate.
|
directives that users can click to navigate.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
For more information, see the [Routing & Navigation](./router.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Router module
|
## Router module
|
||||||
|
@ -585,7 +574,7 @@ a#Q
|
||||||
:marked
|
:marked
|
||||||
A separate [Angular module](#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
A separate [Angular module](#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
For more information, see the [Routing & Navigation](./router.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Routing component
|
## Routing component
|
||||||
|
@ -593,7 +582,7 @@ a#Q
|
||||||
:marked
|
:marked
|
||||||
An Angular [component](#component) with a `RouterOutlet` that displays views based on router navigations.
|
An Angular [component](#component) with a `RouterOutlet` that displays views based on router navigations.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
For more information, see the [Routing & Navigation](./router.html) page.
|
||||||
|
|
||||||
.l-main-section#S
|
.l-main-section#S
|
||||||
|
|
||||||
|
@ -629,7 +618,7 @@ a#Q
|
||||||
|
|
||||||
Applications often require services such as a data service or a logging service.
|
Applications often require services such as a data service or a logging service.
|
||||||
|
|
||||||
For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial.
|
For more information, see the [Services](.ial/toh-pt4.html) page of the [Tour of Heroes](.ial/) tutorial.
|
||||||
|
|
||||||
a#snake-case
|
a#snake-case
|
||||||
:marked
|
:marked
|
||||||
|
@ -650,7 +639,7 @@ a#structural-directives
|
||||||
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
||||||
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
||||||
|
|
||||||
Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page.
|
Read more in the [Structural Directives](./structural-directives.html) page.
|
||||||
|
|
||||||
.l-main-section#T
|
.l-main-section#T
|
||||||
:marked
|
:marked
|
||||||
|
@ -677,18 +666,18 @@ a#structural-directives
|
||||||
Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios.
|
Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios.
|
||||||
|
|
||||||
Read about how to build template-driven forms
|
Read about how to build template-driven forms
|
||||||
in the [Forms](!{docsLatest}/guide/forms.html) page.
|
in the [Forms](./forms.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Template expression
|
## Template expression
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A !{_Lang}-like syntax that Angular evaluates within
|
A TypeScript-like syntax that Angular evaluates within
|
||||||
a [data binding](#data-binding).
|
a [data binding](#data-binding).
|
||||||
|
|
||||||
Read about how to write template expressions
|
Read about how to write template expressions
|
||||||
in the [Template expressions](!{docsLatest}/guide/template-syntax.html#template-expressions) section
|
in the [Template expressions](./template-syntax.html#template-expressions) section
|
||||||
of the [Template Syntax](!{docsLatest}/guide/template-syntax.html#) page.
|
of the [Template Syntax](./template-syntax.html#) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Transpile
|
## Transpile
|
||||||
|
|
|
@ -172,7 +172,7 @@ figure
|
||||||
* Enter: `void => *`
|
* Enter: `void => *`
|
||||||
* Leave: `* => void`
|
* Leave: `* => void`
|
||||||
|
|
||||||
For example, in the `animations` !{_array} below there are two transitions that use
|
For example, in the `animations` array below there are two transitions that use
|
||||||
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
||||||
+makeExample('animations/ts/src/app/hero-list-enter-leave.component.ts', 'animationdef', 'hero-list-enter-leave.component.ts (excerpt)')(format=".")
|
+makeExample('animations/ts/src/app/hero-list-enter-leave.component.ts', 'animationdef', 'hero-list-enter-leave.component.ts (excerpt)')(format=".")
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _library_module = 'library module'
|
|
||||||
- var _at_angular = '@angular'
|
|
||||||
|
|
||||||
block angular-intro
|
:marked
|
||||||
:marked
|
Angular is a framework for building client applications in HTML and
|
||||||
Angular is a framework for building client applications in HTML and
|
either JavaScript or a language like TypeScript that compiles to JavaScript.
|
||||||
either JavaScript or a language like TypeScript that compiles to JavaScript.
|
|
||||||
|
|
||||||
The framework consists of several libraries, some of them core and some optional.
|
The framework consists of several libraries, some of them core and some optional.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You write Angular applications by composing HTML *templates* with Angularized markup,
|
You write Angular applications by composing HTML *templates* with Angularized markup,
|
||||||
|
@ -48,78 +45,77 @@ figure
|
||||||
figure
|
figure
|
||||||
img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
||||||
|
|
||||||
block angular-modules
|
:marked
|
||||||
|
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
|
||||||
|
|
||||||
|
_Angular modules_ are a big deal.
|
||||||
|
This page introduces modules; the [Angular modules](ngmodule.html) page covers them in depth.
|
||||||
|
|
||||||
|
<br class="l-clear-both"><br>
|
||||||
|
:marked
|
||||||
|
Every Angular app has at least one Angular module class, [the _root module_](appmodule.html "AppModule: the root module"),
|
||||||
|
conventionally named `AppModule`.
|
||||||
|
|
||||||
|
While the _root module_ may be the only module in a small application, most apps have many more
|
||||||
|
_feature modules_, each a cohesive block of code dedicated to an application domain,
|
||||||
|
a workflow, or a closely related set of capabilities.
|
||||||
|
|
||||||
|
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
|
Decorators are functions that modify JavaScript classes.
|
||||||
|
Angular has many decorators that attach metadata to classes so that it knows
|
||||||
|
what those classes mean and how they should work.
|
||||||
|
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0" target="_blank">
|
||||||
|
Learn more</a> about decorators on the web.
|
||||||
|
:marked
|
||||||
|
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
|
||||||
|
The most important properties are:
|
||||||
|
* `declarations` - the _view classes_ that belong to this module.
|
||||||
|
Angular has three kinds of view classes: [components](#components), [directives](#directives), and [pipes](pipes.html).
|
||||||
|
|
||||||
_Angular modules_ are a big deal.
|
* `exports` - the subset of declarations that should be visible and usable in the component [templates](#templates) of other modules.
|
||||||
This page introduces modules; the [Angular modules](ngmodule.html) page covers them in depth.
|
|
||||||
|
|
||||||
<br class="l-clear-both"><br>
|
* `imports` - other modules whose exported classes are needed by component templates declared in _this_ module.
|
||||||
|
|
||||||
|
* `providers` - creators of [services](#services) that this module contributes to
|
||||||
|
the global collection of services; they become accessible in all parts of the app.
|
||||||
|
|
||||||
|
* `bootstrap` - the main application view, called the _root component_,
|
||||||
|
that hosts all other app views. Only the _root module_ should set this `bootstrap` property.
|
||||||
|
|
||||||
|
Here's a simple root module:
|
||||||
|
+makeExample('src/app/mini-app.ts', 'module', 'src/app/app.module.ts')(format='.')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Every Angular app has at least one Angular module class, [the _root module_](appmodule.html "AppModule: the root module"),
|
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
|
||||||
conventionally named `AppModule`.
|
:marked
|
||||||
|
Launch an application by _bootstrapping_ its root module.
|
||||||
While the _root module_ may be the only module in a small application, most apps have many more
|
During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
|
||||||
_feature modules_, each a cohesive block of code dedicated to an application domain,
|
|
||||||
a workflow, or a closely related set of capabilities.
|
|
||||||
|
|
||||||
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
Decorators are functions that modify JavaScript classes.
|
|
||||||
Angular has many decorators that attach metadata to classes so that it knows
|
|
||||||
what those classes mean and how they should work.
|
|
||||||
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0" target="_blank">
|
|
||||||
Learn more</a> about decorators on the web.
|
|
||||||
:marked
|
|
||||||
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
|
|
||||||
The most important properties are:
|
|
||||||
* `declarations` - the _view classes_ that belong to this module.
|
|
||||||
Angular has three kinds of view classes: [components](#components), [directives](#directives), and [pipes](pipes.html).
|
|
||||||
|
|
||||||
* `exports` - the subset of declarations that should be visible and usable in the component [templates](#templates) of other modules.
|
|
||||||
|
|
||||||
* `imports` - other modules whose exported classes are needed by component templates declared in _this_ module.
|
|
||||||
|
|
||||||
* `providers` - creators of [services](#services) that this module contributes to
|
|
||||||
the global collection of services; they become accessible in all parts of the app.
|
|
||||||
|
|
||||||
* `bootstrap` - the main application view, called the _root component_,
|
|
||||||
that hosts all other app views. Only the _root module_ should set this `bootstrap` property.
|
|
||||||
|
|
||||||
Here's a simple root module:
|
|
||||||
+makeExample('src/app/mini-app.ts', 'module', 'src/app/app.module.ts')(format='.')
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
|
|
||||||
:marked
|
|
||||||
Launch an application by _bootstrapping_ its root module.
|
|
||||||
During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
|
|
||||||
|
|
||||||
+makeExample('src/main.ts', '', 'src/main.ts')(format='.')
|
|
||||||
|
|
||||||
:marked
|
|
||||||
### Angular modules vs. JavaScript modules
|
|
||||||
|
|
||||||
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
|
||||||
|
|
||||||
JavaScript also has its own module system for managing collections of JavaScript objects.
|
|
||||||
It's completely different and unrelated to the Angular module system.
|
|
||||||
|
|
||||||
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
+makeExample('src/main.ts', '', 'src/main.ts')(format='.')
|
||||||
The module declares some objects to be public by marking them with the `export` key word.
|
|
||||||
Other JavaScript modules use *import statements* to access public objects from other modules.
|
|
||||||
|
|
||||||
+makeExample('src/app/app.module.ts', 'imports', '')(format='.')
|
:marked
|
||||||
+makeExample('src/app/app.module.ts', 'export', '')(format='.')
|
### Angular modules vs. JavaScript modules
|
||||||
|
|
||||||
.l-sub-section
|
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
||||||
:marked
|
|
||||||
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
|
JavaScript also has its own module system for managing collections of JavaScript objects.
|
||||||
|
It's completely different and unrelated to the Angular module system.
|
||||||
|
|
||||||
|
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
||||||
|
The module declares some objects to be public by marking them with the `export` key word.
|
||||||
|
Other JavaScript modules use *import statements* to access public objects from other modules.
|
||||||
|
|
||||||
|
+makeExample('src/app/app.module.ts', 'imports', '')(format='.')
|
||||||
|
+makeExample('src/app/app.module.ts', 'export', '')(format='.')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
These are two different and _complementary_ module systems. Use them both to write your apps.
|
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
|
||||||
|
:marked
|
||||||
|
These are two different and _complementary_ module systems. Use them both to write your apps.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Angular libraries
|
### Angular libraries
|
||||||
|
@ -127,32 +123,31 @@ block angular-modules
|
||||||
figure
|
figure
|
||||||
img(src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
img(src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
||||||
|
|
||||||
block angular-libraries
|
:marked
|
||||||
:marked
|
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
|
||||||
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
|
|
||||||
|
|
||||||
Each Angular library name begins with the `!{_at_angular}` prefix.
|
Each Angular library name begins with the `@angular` prefix.
|
||||||
|
|
||||||
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
|
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
|
||||||
<br class="l-clear-both"><br>
|
<br class="l-clear-both"><br>
|
||||||
|
|
||||||
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
|
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
|
||||||
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
|
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
|
||||||
:marked
|
:marked
|
||||||
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
||||||
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
|
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
|
||||||
:marked
|
:marked
|
||||||
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||||
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
|
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
|
||||||
:marked
|
:marked
|
||||||
In this way you're using both the Angular and JavaScript module systems _together_.
|
In this way you're using both the Angular and JavaScript module systems _together_.
|
||||||
|
|
||||||
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
|
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
|
||||||
Hang in there. The confusion yields to clarity with time and experience.
|
Hang in there. The confusion yields to clarity with time and experience.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Learn more from the [Angular modules](ngmodule.html) page.
|
Learn more from the [Angular modules](ngmodule.html) page.
|
||||||
|
|
||||||
.l-hr
|
.l-hr
|
||||||
|
|
||||||
|
@ -176,7 +171,7 @@ figure
|
||||||
The class interacts with the view through an API of properties and methods.
|
The class interacts with the view through an API of properties and methods.
|
||||||
|
|
||||||
<a id="component-code"></a>
|
<a id="component-code"></a>
|
||||||
For example, this `HeroListComponent` has a `heroes` property that returns !{_an} !{_array} of heroes
|
For example, this `HeroListComponent` has a `heroes` property that returns an array of heroes
|
||||||
that it acquires from a service.
|
that it acquires from a service.
|
||||||
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||||
|
|
||||||
|
@ -238,21 +233,20 @@ figure
|
||||||
|
|
||||||
To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class.
|
To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class.
|
||||||
|
|
||||||
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**.
|
In TypeScript, you attach metadata by using a **decorator**.
|
||||||
Here's some metadata for `HeroListComponent`:
|
Here's some metadata for `HeroListComponent`:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-list.component.ts', 'metadata')
|
+makeExcerpt('src/app/hero-list.component.ts', 'metadata')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Here is the `@Component` !{_decorator}, which identifies the class
|
Here is the `@Component` decorator, which identifies the class
|
||||||
immediately below it as a component class.
|
immediately below it as a component class.
|
||||||
|
|
||||||
block ts-decorator
|
:marked
|
||||||
:marked
|
The `@Component` decorator takes a required configuration object with the
|
||||||
The `@Component` decorator takes a required configuration object with the
|
information Angular needs to create and present the component and its view.
|
||||||
information Angular needs to create and present the component and its view.
|
|
||||||
|
|
||||||
Here are a few of the most useful `@Component` configuration options:
|
Here are a few of the most useful `@Component` configuration options:
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
- `selector`: CSS selector that tells Angular to create and insert an instance of this component
|
- `selector`: CSS selector that tells Angular to create and insert an instance of this component
|
||||||
|
@ -262,10 +256,8 @@ block ts-decorator
|
||||||
|
|
||||||
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
|
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
|
||||||
|
|
||||||
block dart-directives
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
- `providers`: !{_array} of **dependency injection providers** for services that the component requires.
|
- `providers`: array of **dependency injection providers** for services that the component requires.
|
||||||
This is one way to tell Angular that the component's constructor requires a `HeroService`
|
This is one way to tell Angular that the component's constructor requires a `HeroService`
|
||||||
so it can get the list of heroes to display.
|
so it can get the list of heroes to display.
|
||||||
|
|
||||||
|
@ -277,8 +269,8 @@ figure
|
||||||
|
|
||||||
The template, metadata, and component together describe a view.
|
The template, metadata, and component together describe a view.
|
||||||
|
|
||||||
Apply other metadata !{_decorator}s in a similar fashion to guide Angular behavior.
|
Apply other metadata decorators in a similar fashion to guide Angular behavior.
|
||||||
`@Injectable`, `@Input`, and `@Output` are a few of the more popular !{_decorator}s.
|
`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.
|
||||||
<br class="l-clear-both">
|
<br class="l-clear-both">
|
||||||
:marked
|
:marked
|
||||||
The architectural takeaway is that you must add metadata to your code
|
The architectural takeaway is that you must add metadata to your code
|
||||||
|
@ -352,9 +344,9 @@ figure
|
||||||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
||||||
according to the instructions given by **directives**.
|
according to the instructions given by **directives**.
|
||||||
|
|
||||||
A directive is a class with a `@Directive` !{_decorator}.
|
A directive is a class with a `@Directive` decorator.
|
||||||
A component is a *directive-with-a-template*;
|
A component is a *directive-with-a-template*;
|
||||||
a `@Component` !{_decorator} is actually a `@Directive` !{_decorator} extended with template-oriented features.
|
a `@Component` decorator is actually a `@Directive` decorator extended with template-oriented features.
|
||||||
<br class="l-clear-both">
|
<br class="l-clear-both">
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -377,8 +369,6 @@ figure
|
||||||
* [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
* [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||||
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
|
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
|
||||||
|
|
||||||
block dart-bool
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||||
In templates they look like regular HTML attributes, hence the name.
|
In templates they look like regular HTML attributes, hence the name.
|
||||||
|
@ -430,7 +420,7 @@ figure
|
||||||
+makeExcerpt('src/app/logger.service.ts', 'class')
|
+makeExcerpt('src/app/logger.service.ts', 'class')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Here's a `HeroService` that uses a !{_PromiseLinked} to fetch heroes.
|
Here's a `HeroService` that uses a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to fetch heroes.
|
||||||
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'class')
|
+makeExcerpt('src/app/hero.service.ts', 'class')
|
||||||
|
@ -491,14 +481,13 @@ figure
|
||||||
In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
|
In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
|
||||||
A provider is something that can create or return a service, typically the service class itself.
|
A provider is something that can create or return a service, typically the service class itself.
|
||||||
|
|
||||||
block registering-providers
|
:marked
|
||||||
:marked
|
You can register providers in modules or in components.
|
||||||
You can register providers in modules or in components.
|
|
||||||
|
In general, add providers to the [root module](#module) so that
|
||||||
In general, add providers to the [root module](#module) so that
|
the same instance of a service is available everywhere.
|
||||||
the same instance of a service is available everywhere.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.ts (module providers)', 'providers')
|
+makeExcerpt('src/app/app.module.ts (module providers)', 'providers')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
||||||
|
@ -573,7 +562,6 @@ block registering-providers
|
||||||
> [**Router**](router.html): Navigate from page to page within the client
|
> [**Router**](router.html): Navigate from page to page within the client
|
||||||
application and never leave the browser.
|
application and never leave the browser.
|
||||||
|
|
||||||
block angular-feature-testing
|
:marked
|
||||||
:marked
|
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
|
||||||
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
|
using the _Angular Testing Platform_.
|
||||||
using the _Angular Testing Platform_.
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ block includes
|
||||||
### Write the directive code
|
### Write the directive code
|
||||||
|
|
||||||
Follow the [setup](setup.html) instructions for creating a new local project
|
Follow the [setup](setup.html) instructions for creating a new local project
|
||||||
named <span ngio-ex>attribute-directives</span>.
|
named <code>attribute-directives</code>.
|
||||||
|
|
||||||
Create the following source file in the indicated folder:
|
Create the following source file in the indicated folder:
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ block includes
|
||||||
:marked
|
:marked
|
||||||
After the `@Directive` metadata comes the directive's controller class,
|
After the `@Directive` metadata comes the directive's controller class,
|
||||||
called `HighlightDirective`, which contains the logic for the directive.
|
called `HighlightDirective`, which contains the logic for the directive.
|
||||||
<span if-docs="ts">Exporting `HighlightDirective` makes it accessible to other components.</span>
|
Exporting `HighlightDirective` makes it accessible to other components.
|
||||||
|
|
||||||
Angular creates a new instance of the directive's controller class for
|
Angular creates a new instance of the directive's controller class for
|
||||||
each matching element, injecting an Angular `ElementRef`
|
each matching element, injecting an Angular `ElementRef`
|
||||||
|
@ -113,7 +113,7 @@ block includes
|
||||||
applies the directive as an attribute to a paragraph (`<p>`) element.
|
applies the directive as an attribute to a paragraph (`<p>`) element.
|
||||||
In Angular terms, the `<p>` element is the attribute **host**.
|
In Angular terms, the `<p>` element is the attribute **host**.
|
||||||
|
|
||||||
Put the template in its own <span ngio-ex>app.component.html</span>
|
Put the template in its own <code>app.component.html</code>
|
||||||
file that looks like this:
|
file that looks like this:
|
||||||
|
|
||||||
+makeExample('src/app/app.component.1.html')
|
+makeExample('src/app/app.component.1.html')
|
||||||
|
@ -176,12 +176,12 @@ figure.image-display
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||||
each adorned by the `HostListener` !{_decorator}.
|
each adorned by the `HostListener` decorator.
|
||||||
|
|
||||||
+makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '')
|
+makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `@HostListener` !{_decorator} lets you subscribe to events of the DOM
|
The `@HostListener` decorator lets you subscribe to events of the DOM
|
||||||
element that hosts an attribute directive, the `<p>` in this case.
|
element that hosts an attribute directive, the `<p>` in this case.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -194,7 +194,7 @@ figure.image-display
|
||||||
1. Talking to DOM API directly isn't a best practice.
|
1. Talking to DOM API directly isn't a best practice.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The handlers delegate to a helper method that sets the color on the DOM element, `#{_priv}el`,
|
The handlers delegate to a helper method that sets the color on the DOM element, `el`,
|
||||||
which you declare and initialize in the constructor.
|
which you declare and initialize in the constructor.
|
||||||
|
|
||||||
+makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor')
|
+makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor')
|
||||||
|
@ -226,7 +226,7 @@ a#input
|
||||||
:marked
|
:marked
|
||||||
### Binding to an _@Input_ property
|
### Binding to an _@Input_ property
|
||||||
|
|
||||||
Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
Notice the `@Input` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
||||||
|
|
||||||
It's called an *input* property because data flows from the binding expression _into_ the directive.
|
It's called an *input* property because data flows from the binding expression _into_ the directive.
|
||||||
Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that.
|
Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that.
|
||||||
|
@ -299,7 +299,7 @@ a#input-alias
|
||||||
In this section, you'll turn `AppComponent` into a harness that
|
In this section, you'll turn `AppComponent` into a harness that
|
||||||
lets you pick the highlight color with a radio button and bind your color choice to the directive.
|
lets you pick the highlight color with a radio button and bind your color choice to the directive.
|
||||||
|
|
||||||
Update <span ngio-ex>app.component.html</span> as follows:
|
Update <code>app.component.html</code> as follows:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.html', 'v2', '')
|
+makeExcerpt('src/app/app.component.html', 'v2', '')
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ figure.image-display
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||||
because you made it _public_ with the `@Input` !{_decorator}.
|
because you made it _public_ with the `@Input` decorator.
|
||||||
|
|
||||||
Here's how the harness should work when you're done coding.
|
Here's how the harness should work when you're done coding.
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ figure.image-display
|
||||||
+makeExcerpt('src/app/highlight.directive.ts', 'color', '')
|
+makeExcerpt('src/app/highlight.directive.ts', 'color', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Either way, the `@Input` !{_decorator} tells Angular that this property is
|
Either way, the `@Input` decorator tells Angular that this property is
|
||||||
_public_ and available for binding by a parent component.
|
_public_ and available for binding by a parent component.
|
||||||
Without `@Input`, Angular refuses to bind to the property.
|
Without `@Input`, Angular refuses to bind to the property.
|
||||||
|
|
||||||
|
@ -411,22 +411,22 @@ figure.image-display
|
||||||
Angular treats a component's template as _belonging_ to the component.
|
Angular treats a component's template as _belonging_ to the component.
|
||||||
The component and its template trust each other implicitly.
|
The component and its template trust each other implicitly.
|
||||||
Therefore, the component's own template may bind to _any_ property of that component,
|
Therefore, the component's own template may bind to _any_ property of that component,
|
||||||
with or without the `@Input` !{_decorator}.
|
with or without the `@Input` decorator.
|
||||||
|
|
||||||
But a component or directive shouldn't blindly trust _other_ components and directives.
|
But a component or directive shouldn't blindly trust _other_ components and directives.
|
||||||
The properties of a component or directive are hidden from binding by default.
|
The properties of a component or directive are hidden from binding by default.
|
||||||
They are _private_ from an Angular binding perspective.
|
They are _private_ from an Angular binding perspective.
|
||||||
When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective.
|
When adorned with the `@Input` decorator, the property becomes _public_ from an Angular binding perspective.
|
||||||
Only then can it be bound by some other component or directive.
|
Only then can it be bound by some other component or directive.
|
||||||
|
|
||||||
You can tell if `@Input` is needed by the position of the property name in a binding.
|
You can tell if `@Input` is needed by the position of the property name in a binding.
|
||||||
|
|
||||||
* When it appears in the template expression to the ***right*** of the equals (=),
|
* When it appears in the template expression to the ***right*** of the equals (=),
|
||||||
it belongs to the template's component and does not require the `@Input` !{_decorator}.
|
it belongs to the template's component and does not require the `@Input` decorator.
|
||||||
|
|
||||||
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
|
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
|
||||||
the property belongs to some _other_ component or directive;
|
the property belongs to some _other_ component or directive;
|
||||||
that property must be adorned with the `@Input` !{_decorator}.
|
that property must be adorned with the `@Input` decorator.
|
||||||
|
|
||||||
Now apply that reasoning to the following example:
|
Now apply that reasoning to the following example:
|
||||||
|
|
||||||
|
@ -435,8 +435,8 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
* The `color` property in the expression on the right belongs to the template's component.
|
* The `color` property in the expression on the right belongs to the template's component.
|
||||||
The template and its component trust each other.
|
The template and its component trust each other.
|
||||||
The `color` property doesn't require the `@Input` !{_decorator}.
|
The `color` property doesn't require the `@Input` decorator.
|
||||||
|
|
||||||
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
|
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
|
||||||
not a property of the template's component. There are trust issues.
|
not a property of the template's component. There are trust issues.
|
||||||
Therefore, the directive property must carry the `@Input` !{_decorator}.
|
Therefore, the directive property must carry the `@Input` decorator.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _at_angular = '@angular'
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular supports most recent browsers. This includes the following specific versions:
|
Angular supports most recent browsers. This includes the following specific versions:
|
||||||
|
|
|
@ -31,7 +31,7 @@ block includes
|
||||||
specifying any selectors, rules, and media queries that you need.
|
specifying any selectors, rules, and media queries that you need.
|
||||||
|
|
||||||
One way to do this is to set the `styles` property in the component metadata.
|
One way to do this is to set the `styles` property in the component metadata.
|
||||||
The `styles` property takes #{_an} #{_array} of strings that contain CSS code.
|
The `styles` property takes an array of strings that contain CSS code.
|
||||||
Usually you give it one string, as in the following example:
|
Usually you give it one string, as in the following example:
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
|
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
|
||||||
|
@ -134,8 +134,8 @@ a(id='loading-styles')
|
||||||
|
|
||||||
### Styles in metadata
|
### Styles in metadata
|
||||||
|
|
||||||
You can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
You can add a `styles` array property to the `@Component` decorator.
|
||||||
Each string in the #{_array} (usually just one string) defines the CSS.
|
Each string in the array (usually just one string) defines the CSS.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
|
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
|
||||||
|
|
||||||
|
@ -143,32 +143,30 @@ a(id='loading-styles')
|
||||||
### Style URLs in metadata
|
### Style URLs in metadata
|
||||||
|
|
||||||
You can load styles from external CSS files by adding a `styleUrls` attribute
|
You can load styles from external CSS files by adding a `styleUrls` attribute
|
||||||
into a component's `@Component` #{_decorator}:
|
into a component's `@Component` decorator:
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
|
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
|
||||||
|
|
||||||
block style-url
|
.alert.is-important
|
||||||
.alert.is-important
|
:marked
|
||||||
:marked
|
The URL is relative to the *application root*, which is usually the
|
||||||
The URL is relative to the *application root*, which is usually the
|
location of the `index.html` web page that hosts the application.
|
||||||
location of the `index.html` web page that hosts the application.
|
The style file URL is *not* relative to the component file.
|
||||||
The style file URL is *not* relative to the component file.
|
That's why the example URL begins `src/app/`.
|
||||||
That's why the example URL begins `src/app/`.
|
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
|
||||||
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
|
|
||||||
|
|
||||||
block module-bundlers
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
||||||
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
to load styles from external files at build time. You could write:
|
||||||
to load styles from external files at build time. You could write:
|
|
||||||
|
|
||||||
`styles: [require('my.component.css')]`
|
`styles: [require('my.component.css')]`
|
||||||
|
|
||||||
Set the `styles` property, not the `styleUrls` property. The module
|
Set the `styles` property, not the `styleUrls` property. The module
|
||||||
bundler loads the CSS strings, not Angular.
|
bundler loads the CSS strings, not Angular.
|
||||||
Angular sees the CSS strings only after the bundler loads them.
|
Angular sees the CSS strings only after the bundler loads them.
|
||||||
To Angular, it's as if you wrote the `styles` array by hand.
|
To Angular, it's as if you wrote the `styles` array by hand.
|
||||||
For information on loading CSS in this manner, refer to the module bundler's documentation.
|
For information on loading CSS in this manner, refer to the module bundler's documentation.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Template inline styles
|
### Template inline styles
|
||||||
|
@ -195,9 +193,8 @@ block module-bundlers
|
||||||
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
||||||
on the [MDN](https://developer.mozilla.org) site.
|
on the [MDN](https://developer.mozilla.org) site.
|
||||||
|
|
||||||
block css-import-url
|
:marked
|
||||||
:marked
|
In this case, the URL is relative to the CSS file into which you're importing.
|
||||||
In this case, the URL is relative to the CSS file into which you're importing.
|
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
|
||||||
|
|
||||||
|
@ -300,9 +297,8 @@ code-example(format="nocode").
|
||||||
Because these files are co-located with the component,
|
Because these files are co-located with the component,
|
||||||
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
||||||
|
|
||||||
block module-id
|
:marked
|
||||||
:marked
|
You can use a relative URL by prefixing your filenames with `./`:
|
||||||
You can use a relative URL by prefixing your filenames with `./`:
|
|
||||||
|
|
||||||
+makeExample('src/app/quest-summary.component.ts')
|
+makeExample('src/app/quest-summary.component.ts')
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _thisDot = 'this.';
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**Dependency injection** is an important application design pattern.
|
**Dependency injection** is an important application design pattern.
|
||||||
|
@ -62,7 +61,7 @@ block includes
|
||||||
|
|
||||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||||
That would break the `Car` class and it would stay broken until you rewrote it along the lines of
|
That would break the `Car` class and it would stay broken until you rewrote it along the lines of
|
||||||
`#{_thisDot}engine = new Engine(theNewParameter)`.
|
`this.engine = new Engine(theNewParameter)`.
|
||||||
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
|
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
|
||||||
You may not anticipate them even now.
|
You may not anticipate them even now.
|
||||||
But you'll *have* to start caring because
|
But you'll *have* to start caring because
|
||||||
|
@ -108,11 +107,10 @@ block includes
|
||||||
The `Car` class no longer creates an `engine` or `tires`.
|
The `Car` class no longer creates an `engine` or `tires`.
|
||||||
It just consumes them.
|
It just consumes them.
|
||||||
|
|
||||||
block ctor-syntax
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
This example leverages TypeScript's constructor syntax for declaring
|
||||||
This example leverages TypeScript's constructor syntax for declaring
|
parameters and properties simultaneously.
|
||||||
parameters and properties simultaneously.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Now you can create a car by passing the engine and tires to the constructor.
|
Now you can create a car by passing the engine and tires to the constructor.
|
||||||
|
@ -133,8 +131,7 @@ block ctor-syntax
|
||||||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||||
something like this:
|
something like this:
|
||||||
|
|
||||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', { otl: /(new Car.*$)/gm })(format=".")
|
||||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The critical point is this: the `Car` class did not have to change.
|
The critical point is this: the `Car` class did not have to change.
|
||||||
|
@ -146,8 +143,7 @@ block ctor-syntax
|
||||||
You can pass mocks to the constructor that do exactly what you want them to do
|
You can pass mocks to the constructor that do exactly what you want them to do
|
||||||
during each test:
|
during each test:
|
||||||
|
|
||||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', { otl: /(new Car.*$)/gm })(format=".")
|
||||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**You just learned what dependency injection is**.
|
**You just learned what dependency injection is**.
|
||||||
|
@ -236,9 +232,8 @@ block ctor-syntax
|
||||||
Given that the service is a
|
Given that the service is a
|
||||||
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
||||||
consider writing the service code in its own file.
|
consider writing the service code in its own file.
|
||||||
+ifDocsFor('ts')
|
|
||||||
:marked
|
See [this note](#one-class-per-file) for details.
|
||||||
See [this note](#one-class-per-file) for details.
|
|
||||||
:marked
|
:marked
|
||||||
The following `HeroService` exposes a `getHeroes` method that returns
|
The following `HeroService` exposes a `getHeroes` method that returns
|
||||||
the same mock data as before, but none of its consumers need to know that.
|
the same mock data as before, but none of its consumers need to know that.
|
||||||
|
@ -248,15 +243,14 @@ block ctor-syntax
|
||||||
:marked
|
:marked
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `@Injectable()` #{_decorator} above the service class is
|
The `@Injectable()` decorator above the service class is
|
||||||
covered [shortly](#injectable).
|
covered [shortly](#injectable).
|
||||||
|
|
||||||
- var _perhaps = _docsFor == 'dart' ? '' : 'perhaps';
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Of course, this isn't a real service.
|
Of course, this isn't a real service.
|
||||||
If the app were actually getting data from a remote server, the API would have to be
|
If the app were actually getting data from a remote server, the API would have to be
|
||||||
asynchronous, #{_perhaps} returning a !{_PromiseLinked}.
|
asynchronous, perhaps returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||||
You'd also have to rewrite the way components consume the service.
|
You'd also have to rewrite the way components consume the service.
|
||||||
This is important in general, but not in this example.
|
This is important in general, but not in this example.
|
||||||
|
|
||||||
|
@ -279,18 +273,16 @@ a#injector-config
|
||||||
that create the services the application requires.
|
that create the services the application requires.
|
||||||
This guide explains what [providers](#providers) are later.
|
This guide explains what [providers](#providers) are later.
|
||||||
|
|
||||||
block register-provider-ngmodule
|
:marked
|
||||||
:marked
|
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
|
||||||
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
|
|
||||||
|
|
||||||
a#register-providers-ngmodule
|
a#register-providers-ngmodule
|
||||||
:marked
|
:marked
|
||||||
### Registering providers in an _NgModule_
|
### Registering providers in an _NgModule_
|
||||||
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
|
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
|
||||||
in its `providers` !{_array}.
|
in its `providers` array.
|
||||||
|
|
||||||
- var app_module_ts = 'src/app/app.module.ts';
|
+makeExcerpt('src/app/app.module.ts (excerpt)', 'ngmodule', app_module_ts)
|
||||||
+makeExcerpt(app_module_ts + ' (excerpt)', 'ngmodule', app_module_ts)
|
|
||||||
:marked
|
:marked
|
||||||
Because the `HeroService` is used _only_ within the `HeroesComponent`
|
Because the `HeroService` is used _only_ within the `HeroesComponent`
|
||||||
and its subcomponents, the top-level `HeroesComponent` is the ideal
|
and its subcomponents, the top-level `HeroesComponent` is the ideal
|
||||||
|
@ -300,31 +292,30 @@ a#register-providers-component
|
||||||
:marked
|
:marked
|
||||||
### Registering providers in a component
|
### Registering providers in a component
|
||||||
|
|
||||||
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` !{_array}.
|
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array.
|
||||||
|
|
||||||
+makeExample('src/app/heroes/heroes.component.1.ts', 'full', 'src/app/heroes/heroes.component.ts', stylePattern)(format='.')
|
+makeExample('src/app/heroes/heroes.component.1.ts', 'full', 'src/app/heroes/heroes.component.ts', stylePattern)(format='.')
|
||||||
|
|
||||||
block ngmodule-vs-component
|
a#ngmodule-vs-comp
|
||||||
a#ngmodule-vs-comp
|
:marked
|
||||||
|
### When to use _NgModule_ versus an application component
|
||||||
|
|
||||||
|
On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider
|
||||||
|
registered within an `NgModule` will be accessible in the _entire application_.
|
||||||
|
|
||||||
|
On the other hand, a provider registered in an application component is available only on
|
||||||
|
that component and all its children.
|
||||||
|
|
||||||
|
Here, the `APP_CONFIG` service needs to be available all across the application, so it's
|
||||||
|
registered in the `AppModule` `@NgModule` `providers` array.
|
||||||
|
But since the `HeroService` is only used within the *Heroes*
|
||||||
|
feature area and nowhere else, it makes sense to register it in
|
||||||
|
the `HeroesComponent`.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
### When to use _NgModule_ versus an application component
|
Also see *"Should I add app-wide providers to the root `AppModule` or
|
||||||
|
the root `AppComponent`?"* in the [NgModule FAQ](../cookbook/ngmodule-faq.html#q-root-component-or-module).
|
||||||
On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider
|
|
||||||
registered within an `NgModule` will be accessible in the _entire application_.
|
|
||||||
|
|
||||||
On the other hand, a provider registered in an application component is available only on
|
|
||||||
that component and all its children.
|
|
||||||
|
|
||||||
Here, the `APP_CONFIG` service needs to be available all across the application, so it's
|
|
||||||
registered in the `AppModule` `@NgModule` `providers` !{_array}.
|
|
||||||
But since the `HeroService` is only used within the *Heroes*
|
|
||||||
feature area and nowhere else, it makes sense to register it in
|
|
||||||
the `HeroesComponent`.
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
Also see *"Should I add app-wide providers to the root `AppModule` or
|
|
||||||
the root `AppComponent`?"* in the [NgModule FAQ](../cookbook/ngmodule-faq.html#q-root-component-or-module).
|
|
||||||
|
|
||||||
a#prep-for-injection
|
a#prep-for-injection
|
||||||
:marked
|
:marked
|
||||||
|
@ -352,12 +343,12 @@ a#prep-for-injection
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Note that the constructor parameter has the type `HeroService`, and that
|
Note that the constructor parameter has the type `HeroService`, and that
|
||||||
the `HeroListComponent` class has an `@Component` #{_decorator}
|
the `HeroListComponent` class has an `@Component` decorator
|
||||||
(scroll up to confirm that fact).
|
(scroll up to confirm that fact).
|
||||||
Also recall that the parent component (`HeroesComponent`)
|
Also recall that the parent component (`HeroesComponent`)
|
||||||
has `providers` information for `HeroService`.
|
has `providers` information for `HeroService`.
|
||||||
|
|
||||||
The constructor parameter type, the `@Component` #{_decorator},
|
The constructor parameter type, the `@Component` decorator,
|
||||||
and the parent's `providers` information combine to tell the
|
and the parent's `providers` information combine to tell the
|
||||||
Angular injector to inject an instance of
|
Angular injector to inject an instance of
|
||||||
`HeroService` whenever it creates a new `HeroListComponent`.
|
`HeroService` whenever it creates a new `HeroListComponent`.
|
||||||
|
@ -432,37 +423,35 @@ a#service-needs-service
|
||||||
src/app/heroes/hero.service (v1)`)
|
src/app/heroes/hero.service (v1)`)
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `#{_priv}logger`.
|
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
|
||||||
You call that property within the `getHeroes()` method when anyone asks for heroes.
|
You call that property within the `getHeroes()` method when anyone asks for heroes.
|
||||||
|
|
||||||
- var injUrl = '../api/core/index/Injectable-decorator.html';
|
|
||||||
a#injectable
|
a#injectable
|
||||||
:marked
|
:marked
|
||||||
### Why _@Injectable()_?
|
### Why _@Injectable()_?
|
||||||
|
|
||||||
**<a href="#{injUrl}">@Injectable()</a>** marks a class as available to an
|
**<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>** marks a class as available to an
|
||||||
injector for instantiation. Generally speaking, an injector reports an
|
injector for instantiation. Generally speaking, an injector reports an
|
||||||
error when trying to instantiate a class that is not marked as
|
error when trying to instantiate a class that is not marked as
|
||||||
`@Injectable()`.
|
`@Injectable()`.
|
||||||
|
|
||||||
block injectable-not-always-needed-in-ts
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
As it happens, you could have omitted `@Injectable()` from the first
|
||||||
As it happens, you could have omitted `@Injectable()` from the first
|
version of `HeroService` because it had no injected parameters.
|
||||||
version of `HeroService` because it had no injected parameters.
|
But you must have it now that the service has an injected dependency.
|
||||||
But you must have it now that the service has an injected dependency.
|
You need it because Angular requires constructor parameter metadata
|
||||||
You need it because Angular requires constructor parameter metadata
|
in order to inject a `Logger`.
|
||||||
in order to inject a `Logger`.
|
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Suggestion: add @Injectable() to every service class
|
header Suggestion: add @Injectable() to every service class
|
||||||
:marked
|
:marked
|
||||||
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
|
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||||
and, therefore, do not technically require it. Here's why:
|
and, therefore, do not technically require it. Here's why:
|
||||||
|
|
||||||
ul(style="font-size:inherit")
|
ul(style="font-size:inherit")
|
||||||
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when you add a dependency later.
|
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when you add a dependency later.
|
||||||
li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why #{_a} #{_decorator} is missing.
|
li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why a decorator is missing.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Injectors are also responsible for instantiating components
|
Injectors are also responsible for instantiating components
|
||||||
|
@ -471,35 +460,33 @@ block injectable-not-always-needed-in-ts
|
||||||
|
|
||||||
You *can* add it if you really want to. It isn't necessary because the
|
You *can* add it if you really want to. It isn't necessary because the
|
||||||
`HeroesComponent` is already marked with `@Component`, and this
|
`HeroesComponent` is already marked with `@Component`, and this
|
||||||
!{_decorator} class (like `@Directive` and `@Pipe`, which you learn about later)
|
decorator class (like `@Directive` and `@Pipe`, which you learn about later)
|
||||||
is a subtype of <a href="#{injUrl}">@Injectable()</a>. It is in
|
is a subtype of <a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>. It is in
|
||||||
fact `@Injectable()` #{_decorator}s that
|
fact `@Injectable()` decorators that
|
||||||
identify a class as a target for instantiation by an injector.
|
identify a class as a target for instantiation by an injector.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
At runtime, injectors can read class metadata in the transpiled JavaScript code
|
||||||
At runtime, injectors can read class metadata in the transpiled JavaScript code
|
and use the constructor parameter type information
|
||||||
and use the constructor parameter type information
|
to determine what things to inject.
|
||||||
to determine what things to inject.
|
|
||||||
|
|
||||||
Not every JavaScript class has metadata.
|
Not every JavaScript class has metadata.
|
||||||
The TypeScript compiler discards metadata by default.
|
The TypeScript compiler discards metadata by default.
|
||||||
If the `emitDecoratorMetadata` compiler option is true
|
If the `emitDecoratorMetadata` compiler option is true
|
||||||
(as it should be in the `tsconfig.json`),
|
(as it should be in the `tsconfig.json`),
|
||||||
the compiler adds the metadata to the generated JavaScript
|
the compiler adds the metadata to the generated JavaScript
|
||||||
for _every class with at least one decorator_.
|
for _every class with at least one decorator_.
|
||||||
|
|
||||||
While any decorator will trigger this effect, mark the service class with the
|
While any decorator will trigger this effect, mark the service class with the
|
||||||
<a href="#{injUrl}">@Injectable()</a> #{_decorator}
|
<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a> decorator
|
||||||
to make the intent clear.
|
to make the intent clear.
|
||||||
|
|
||||||
.callout.is-critical
|
.callout.is-critical
|
||||||
header Always include the parentheses
|
header Always include the parentheses
|
||||||
block always-include-paren
|
:marked
|
||||||
:marked
|
Always write `@Injectable()`, not just `@Injectable`.
|
||||||
Always write `@Injectable()`, not just `@Injectable`.
|
The application will fail mysteriously if you forget the parentheses.
|
||||||
The application will fail mysteriously if you forget the parentheses.
|
|
||||||
|
|
||||||
.l-main-section#logger-service
|
.l-main-section#logger-service
|
||||||
:marked
|
:marked
|
||||||
|
@ -513,13 +500,10 @@ block injectable-not-always-needed-in-ts
|
||||||
|
|
||||||
+makeExample('src/app/logger.service.ts')
|
+makeExample('src/app/logger.service.ts')
|
||||||
|
|
||||||
block real-logger
|
|
||||||
//- N/A
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You're likely to need the same logger service everywhere in your application,
|
You're likely to need the same logger service everywhere in your application,
|
||||||
so put it in the project's `#{_appDir}` folder and
|
so put it in the project's `app` folder and
|
||||||
register it in the `providers` #{_array} of the application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`.
|
register it in the `providers` array of the application module, `AppModule`.
|
||||||
|
|
||||||
+makeExcerpt('src/app/providers.component.ts (excerpt)', 'providers-logger','src/app/app.module.ts')
|
+makeExcerpt('src/app/providers.component.ts (excerpt)', 'providers-logger','src/app/app.module.ts')
|
||||||
|
|
||||||
|
@ -546,51 +530,44 @@ code-example(format="nocode").
|
||||||
|
|
||||||
You must register a service *provider* with the injector, or it won't know how to create the service.
|
You must register a service *provider* with the injector, or it won't know how to create the service.
|
||||||
|
|
||||||
Earlier you registered the `Logger` service in the `providers` #{_array} of the metadata for the `AppModule` like this:
|
Earlier you registered the `Logger` service in the `providers` array of the metadata for the `AppModule` like this:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-logger')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-logger')
|
||||||
|
|
||||||
- var implements = _docsFor == 'dart' ? 'implements' : 'looks and behaves like a '
|
|
||||||
- var objectlike = _docsFor == 'dart' ? '' : 'an object that behaves like '
|
|
||||||
- var loggerlike = _docsFor == 'dart' ? '' : 'You could provide a logger-like object. '
|
|
||||||
:marked
|
:marked
|
||||||
There are many ways to *provide* something that #{implements} `Logger`.
|
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||||
The `Logger` class itself is an obvious and natural provider.
|
The `Logger` class itself is an obvious and natural provider.
|
||||||
But it's not the only way.
|
But it's not the only way.
|
||||||
|
|
||||||
You can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
||||||
You could provide a substitute class. #{loggerlike}
|
You could provide a substitute class. You could provide a logger-like object.
|
||||||
You could give it a provider that calls a logger factory function.
|
You could give it a provider that calls a logger factory function.
|
||||||
Any of these approaches might be a good choice under the right circumstances.
|
Any of these approaches might be a good choice under the right circumstances.
|
||||||
|
|
||||||
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||||
|
|
||||||
//- Dart limitation: the provide function isn't const so it cannot be used in an annotation.
|
//- Dart limitation: the provide function isn't const so it cannot be used in an annotation.
|
||||||
- var _andProvideFn = _docsFor == 'dart' ? '' : 'and <i>provide</i> object literal';
|
|
||||||
#provide
|
#provide
|
||||||
:marked
|
:marked
|
||||||
### The *Provider* class !{_andProvideFn}
|
### The *Provider* class and _provide_ object literal
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You wrote the `providers` #{_array} like this:
|
You wrote the `providers` array like this:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-1')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-1')
|
||||||
|
|
||||||
block provider-shorthand
|
:marked
|
||||||
:marked
|
This is actually a shorthand expression for a provider registration
|
||||||
This is actually a shorthand expression for a provider registration
|
using a _provider_ object literal with two properties:
|
||||||
using a _provider_ object literal with two properties:
|
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3')
|
||||||
|
|
||||||
block provider-ctor-args
|
|
||||||
- var _secondParam = 'provider definition object';
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The first is the [token](#token) that serves as the key for both locating a dependency value
|
The first is the [token](#token) that serves as the key for both locating a dependency value
|
||||||
and registering the provider.
|
and registering the provider.
|
||||||
|
|
||||||
The second is a !{_secondParam},
|
The second is a provider definition object,
|
||||||
which you can think of as a *recipe* for creating the dependency value.
|
which you can think of as a *recipe* for creating the dependency value.
|
||||||
There are many ways to create dependency values just as there are many ways to write a recipe.
|
There are many ways to create dependency values just as there are many ways to write a recipe.
|
||||||
|
|
||||||
|
@ -604,9 +581,6 @@ block provider-ctor-args
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-4')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-4')
|
||||||
|
|
||||||
block dart-diff-const-metadata
|
|
||||||
//- N/A
|
|
||||||
|
|
||||||
a#class-provider-dependencies
|
a#class-provider-dependencies
|
||||||
:marked
|
:marked
|
||||||
### Class provider with dependencies
|
### Class provider with dependencies
|
||||||
|
@ -653,9 +627,6 @@ a#value-provider
|
||||||
:marked
|
:marked
|
||||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||||
|
|
||||||
block dart-diff-const-metadata-ctor
|
|
||||||
//- N/A
|
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','silent-logger')(format=".")
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','silent-logger')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -720,19 +691,17 @@ block dart-diff-const-metadata-ctor
|
||||||
The `useFactory` field tells Angular that the provider is a factory function
|
The `useFactory` field tells Angular that the provider is a factory function
|
||||||
whose implementation is the `heroServiceFactory`.
|
whose implementation is the `heroServiceFactory`.
|
||||||
|
|
||||||
The `deps` property is #{_an} #{_array} of [provider tokens](#token).
|
The `deps` property is an array of [provider tokens](#token).
|
||||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||||
|
|
||||||
- var exportedvar = _docsFor == 'dart' ? 'constant' : 'exported variable'
|
|
||||||
- var variable = _docsFor == 'dart' ? 'constant' : 'variable'
|
|
||||||
:marked
|
:marked
|
||||||
Notice that you captured the factory provider in #{_an} #{exportedvar}, `heroServiceProvider`.
|
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||||
This extra step makes the factory provider reusable.
|
This extra step makes the factory provider reusable.
|
||||||
You can register the `HeroService` with this #{variable} wherever you need it.
|
You can register the `HeroService` with this variable wherever you need it.
|
||||||
|
|
||||||
In this sample, you need it only in the `HeroesComponent`,
|
In this sample, you need it only in the `HeroesComponent`,
|
||||||
where it replaces the previous `HeroService` registration in the metadata `providers` #{_array}.
|
where it replaces the previous `HeroService` registration in the metadata `providers` array.
|
||||||
Here you see the new and the old implementation side-by-side:
|
Here you see the new and the old implementation side-by-side:
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
|
@ -774,14 +743,12 @@ a#non-class-dependencies
|
||||||
### Non-class dependencies
|
### Non-class dependencies
|
||||||
p
|
p
|
||||||
| What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
| What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
||||||
block non-class-dep-eg
|
| span string, function, or object.
|
||||||
| span string, function, or object.
|
|
||||||
p
|
p
|
||||||
| Applications often define configuration objects with lots of small facts
|
| Applications often define configuration objects with lots of small facts
|
||||||
| (like the title of the application or the address of a web API endpoint)
|
| (like the title of the application or the address of a web API endpoint)
|
||||||
block config-obj-maps
|
| but these configuration objects aren't always instances of a class.
|
||||||
| but these configuration objects aren't always instances of a class.
|
| They can be object literals such as this one:
|
||||||
| They can be object literals such as this one:
|
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/app.config.ts','config','src/app/app-config.ts (excerpt)')(format='.')
|
+makeExample('dependency-injection/ts/src/app/app.config.ts','config','src/app/app-config.ts (excerpt)')(format='.')
|
||||||
|
|
||||||
|
@ -789,36 +756,33 @@ p
|
||||||
What if you'd like to make this configuration object available for injection?
|
What if you'd like to make this configuration object available for injection?
|
||||||
You know you can register an object with a [value provider](#value-provider).
|
You know you can register an object with a [value provider](#value-provider).
|
||||||
|
|
||||||
block what-should-we-use-as-token
|
:marked
|
||||||
|
But what should you use as the token?
|
||||||
|
You don't have a class to serve as a token.
|
||||||
|
There is no `AppConfig` class.
|
||||||
|
|
||||||
|
.l-sub-section#interface
|
||||||
:marked
|
:marked
|
||||||
But what should you use as the token?
|
### TypeScript interfaces aren't valid tokens
|
||||||
You don't have a class to serve as a token.
|
|
||||||
There is no `AppConfig` class.
|
|
||||||
|
|
||||||
.l-sub-section#interface
|
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you
|
||||||
:marked
|
cannot use a TypeScript interface as a token:
|
||||||
### TypeScript interfaces aren't valid tokens
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9-interface')(format=".")
|
||||||
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-9-ctor-interface')(format=".")
|
||||||
|
:marked
|
||||||
|
That seems strange if you're used to dependency injection in strongly typed languages, where
|
||||||
|
an interface is the preferred dependency lookup key.
|
||||||
|
|
||||||
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you
|
It's not Angular's doing. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
|
||||||
cannot use a TypeScript interface as a token:
|
The TypeScript interface disappears from the generated JavaScript.
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9-interface')(format=".")
|
There is no interface type information left for Angular to find at runtime.
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-9-ctor-interface')(format=".")
|
|
||||||
:marked
|
|
||||||
That seems strange if you're used to dependency injection in strongly typed languages, where
|
|
||||||
an interface is the preferred dependency lookup key.
|
|
||||||
|
|
||||||
It's not Angular's doing. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
|
|
||||||
The TypeScript interface disappears from the generated JavaScript.
|
|
||||||
There is no interface type information left for Angular to find at runtime.
|
|
||||||
|
|
||||||
//- FIXME update once https://github.com/dart-lang/angular2/issues/16 is addressed.
|
|
||||||
- var opaquetoken = _docsFor == 'dart' ? '<b>OpaqueToken</b>' : '<a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>'
|
|
||||||
a#opaquetoken
|
a#opaquetoken
|
||||||
:marked
|
:marked
|
||||||
### _OpaqueToken_
|
### _OpaqueToken_
|
||||||
|
|
||||||
One solution to choosing a provider token for non-class dependencies is
|
One solution to choosing a provider token for non-class dependencies is
|
||||||
to define and use an !{opaquetoken}.
|
to define and use an <a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>.
|
||||||
The definition looks like this:
|
The definition looks like this:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
|
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
|
||||||
|
@ -830,19 +794,17 @@ a#opaquetoken
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Now you can inject the configuration object into any constructor that needs it, with
|
Now you can inject the configuration object into any constructor that needs it, with
|
||||||
the help of an `@Inject` #{_decorator}:
|
the help of an `@Inject` decorator:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/src/app/app.component.2.ts','ctor')(format=".")
|
+makeExample('dependency-injection/ts/src/app/app.component.2.ts','ctor')(format=".")
|
||||||
|
|
||||||
- var configType = _docsFor == 'dart' ? '<code>Map</code>' : '<code>AppConfig</code>'
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Although the !{configType} interface plays no role in dependency injection,
|
Although the `AppConfig` interface plays no role in dependency injection,
|
||||||
it supports typing of the configuration object within the class.
|
it supports typing of the configuration object within the class.
|
||||||
|
|
||||||
block dart-map-alternative
|
:marked
|
||||||
:marked
|
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
|
||||||
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.ts','ngmodule-providers')
|
+makeExcerpt('src/app/app.module.ts','ngmodule-providers')
|
||||||
|
|
||||||
|
@ -855,8 +817,7 @@ block dart-map-alternative
|
||||||
You can tell Angular that the dependency is optional by annotating the
|
You can tell Angular that the dependency is optional by annotating the
|
||||||
constructor argument with `@Optional()`:
|
constructor argument with `@Optional()`:
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
|
|
||||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-10-ctor', '')(format='.')
|
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-10-ctor', '')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -915,23 +876,22 @@ block dart-map-alternative
|
||||||
Framework developers may take this approach when they
|
Framework developers may take this approach when they
|
||||||
must acquire services generically and dynamically.
|
must acquire services generically and dynamically.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-main-section#one-class-per-file
|
||||||
.l-main-section#one-class-per-file
|
:marked
|
||||||
|
## Appendix: Why have one class per file
|
||||||
|
|
||||||
|
Having multiple classes in the same file is confusing and best avoided.
|
||||||
|
Developers expect one class per file. Keep them happy.
|
||||||
|
|
||||||
|
If you combine the `HeroService` class with
|
||||||
|
the `HeroesComponent` in the same file,
|
||||||
|
**define the component last**.
|
||||||
|
If you define the component before the service,
|
||||||
|
you'll get a runtime null reference error.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
## Appendix: Why have one class per file
|
You actually can define the component first with the help of the `forwardRef()` method as explained
|
||||||
|
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||||
Having multiple classes in the same file is confusing and best avoided.
|
But why flirt with trouble?
|
||||||
Developers expect one class per file. Keep them happy.
|
Avoid the problem altogether by defining components and services in separate files.
|
||||||
|
|
||||||
If you combine the `HeroService` class with
|
|
||||||
the `HeroesComponent` in the same file,
|
|
||||||
**define the component last**.
|
|
||||||
If you define the component before the service,
|
|
||||||
you'll get a runtime null reference error.
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
You actually can define the component first with the help of the `forwardRef()` method as explained
|
|
||||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
|
||||||
But why flirt with trouble?
|
|
||||||
Avoid the problem altogether by defining components and services in separate files.
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ a#node-modules
|
||||||
Practice with this sample before attempting these techniques on your application.
|
Practice with this sample before attempting these techniques on your application.
|
||||||
|
|
||||||
1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project
|
1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project
|
||||||
named <ngio-ex path="simple-deployment"></ngio-ex>.
|
named <code>simple-deployment</code>.
|
||||||
|
|
||||||
1. Add the "Simple deployment" sample files shown above.
|
1. Add the "Simple deployment" sample files shown above.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
|
|
||||||
- var _boolean = 'truthy/falsy';
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You can display data by binding controls in an HTML template to properties of an Angular component.
|
You can display data by binding controls in an HTML template to properties of an Angular component.
|
||||||
|
@ -19,7 +17,7 @@ figure.image-display
|
||||||
# Contents
|
# Contents
|
||||||
|
|
||||||
* [Showing component properties with interpolation](#interpolation).
|
* [Showing component properties with interpolation](#interpolation).
|
||||||
* [Showing !{_an} !{_array} property with NgFor](#ngFor).
|
* [Showing an array property with NgFor](#ngFor).
|
||||||
* [Conditional display with NgIf](#ngIf).
|
* [Conditional display with NgIf](#ngIf).
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -35,9 +33,9 @@ figure.image-display
|
||||||
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||||
|
|
||||||
Follow the [setup](setup.html) instructions for creating a new project
|
Follow the [setup](setup.html) instructions for creating a new project
|
||||||
named <ngio-ex path="displaying-data"></ngio-ex>.
|
named <code>displaying-data</code>.
|
||||||
|
|
||||||
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
Then modify the <code>app.component.ts</code> file by
|
||||||
changing the template and the body of the component.
|
changing the template and the body of the component.
|
||||||
|
|
||||||
When you're done, it should look like this:
|
When you're done, it should look like this:
|
||||||
|
@ -52,13 +50,12 @@ figure.image-display
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.1.ts', 'template', '')
|
+makeExcerpt('src/app/app.component.1.ts', 'template', '')
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
|
||||||
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
|
The backtick (<code>\`</code>)—which is *not* the same character as a single
|
||||||
The backtick (<code>\`</code>)—which is *not* the same character as a single
|
quote (`'`)—allows you to compose a string over several lines, which makes the
|
||||||
quote (`'`)—allows you to compose a string over several lines, which makes the
|
HTML more readable.
|
||||||
HTML more readable.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
||||||
|
@ -74,13 +71,13 @@ figure.image-display
|
||||||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||||
Angular is creating an instance for you. How?
|
Angular is creating an instance for you. How?
|
||||||
|
|
||||||
The CSS `selector` in the `@Component` !{_decorator} specifies an element named `<my-app>`.
|
The CSS `selector` in the `@Component` decorator specifies an element named `<my-app>`.
|
||||||
That element is a placeholder in the body of your `index.html` file:
|
That element is a placeholder in the body of your `index.html` file:
|
||||||
|
|
||||||
+makeExcerpt('src/index.html', 'body')
|
+makeExcerpt('src/index.html', 'body')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When you bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), Angular looks for a `<my-app>`
|
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<my-app>`
|
||||||
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
||||||
inside the `<my-app>` tag.
|
inside the `<my-app>` tag.
|
||||||
|
|
||||||
|
@ -88,9 +85,8 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
|
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
The next few sections review some of the coding choices in the app.
|
||||||
The next few sections review some of the coding choices in the app.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Template inline or template file?
|
## Template inline or template file?
|
||||||
|
@ -98,7 +94,7 @@ figure.image-display
|
||||||
You can store your component's template in one of two places.
|
You can store your component's template in one of two places.
|
||||||
You can define it *inline* using the `template` property, or you can define
|
You can define it *inline* using the `template` property, or you can define
|
||||||
the template in a separate HTML file and link to it in
|
the template in a separate HTML file and link to it in
|
||||||
the component metadata using the `@Component` !{_decorator}'s `templateUrl` property.
|
the component metadata using the `@Component` decorator's `templateUrl` property.
|
||||||
|
|
||||||
The choice between inline and separate HTML is a matter of taste,
|
The choice between inline and separate HTML is a matter of taste,
|
||||||
circumstances, and organization policy.
|
circumstances, and organization policy.
|
||||||
|
@ -107,22 +103,21 @@ figure.image-display
|
||||||
|
|
||||||
In either style, the template data bindings have the same access to the component's properties.
|
In either style, the template data bindings have the same access to the component's properties.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
## Constructor or variable initialization?
|
||||||
## Constructor or variable initialization?
|
|
||||||
|
|
||||||
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
|
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app-ctor.component.ts', 'class')
|
+makeExcerpt('src/app/app-ctor.component.ts', 'class')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This app uses more terse "variable assignment" style simply for brevity.
|
This app uses more terse "variable assignment" style simply for brevity.
|
||||||
|
|
||||||
.l-main-section#ngFor
|
.l-main-section#ngFor
|
||||||
:marked
|
:marked
|
||||||
## Showing !{_an} !{_array} property with ***ngFor**
|
## Showing an array property with ***ngFor**
|
||||||
|
|
||||||
To display a list of heroes, begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}.
|
To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.2.ts', 'class')
|
+makeExcerpt('src/app/app.component.2.ts', 'class')
|
||||||
|
|
||||||
|
@ -156,8 +151,8 @@ figure.image-display
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
|
In this case, `ngFor` is displaying an array, but `ngFor` can
|
||||||
repeat items for any [iterable](!{_iterableUrl}) object.
|
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
|
||||||
:marked
|
:marked
|
||||||
Now the heroes appear in an unordered list.
|
Now the heroes appear in an unordered list.
|
||||||
|
|
||||||
|
@ -171,38 +166,37 @@ figure.image-display
|
||||||
The app's code defines the data directly inside the component, which isn't best practice.
|
The app's code defines the data directly inside the component, which isn't best practice.
|
||||||
In a simple demo, however, it's fine.
|
In a simple demo, however, it's fine.
|
||||||
|
|
||||||
At the moment, the binding is to !{_an} !{_array} of strings.
|
At the moment, the binding is to an array of strings.
|
||||||
In real applications, most bindings are to more specialized objects.
|
In real applications, most bindings are to more specialized objects.
|
||||||
|
|
||||||
To convert this binding to use specialized objects, turn the !{_array}
|
To convert this binding to use specialized objects, turn the array
|
||||||
of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `Hero` class.
|
of hero names into an array of `Hero` objects. For that you'll need a `Hero` class.
|
||||||
|
|
||||||
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code:
|
Create a new file in the `app` folder called `hero.ts` with the following code:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.ts')
|
+makeExcerpt('src/app/hero.ts')
|
||||||
|
|
||||||
block hero-class
|
:marked
|
||||||
:marked
|
You've defined a class with a constructor and two properties: `id` and `name`.
|
||||||
You've defined a class with a constructor and two properties: `id` and `name`.
|
|
||||||
|
|
||||||
It might not look like the class has properties, but it does.
|
It might not look like the class has properties, but it does.
|
||||||
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
|
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
|
||||||
|
|
||||||
Consider the first parameter:
|
Consider the first parameter:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.ts ()', 'id')
|
+makeExcerpt('src/app/hero.ts ()', 'id')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
That brief syntax does a lot:
|
That brief syntax does a lot:
|
||||||
* Declares a constructor parameter and its type.
|
* Declares a constructor parameter and its type.
|
||||||
* Declares a public property of the same name.
|
* Declares a public property of the same name.
|
||||||
* Initializes that property with the corresponding argument when creating an instance of the class.
|
* Initializes that property with the corresponding argument when creating an instance of the class.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Using the Hero class
|
## Using the Hero class
|
||||||
|
|
||||||
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ !{_array}
|
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
|
||||||
of `Hero` objects:
|
of `Hero` objects:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.3.ts', 'heroes')
|
+makeExcerpt('src/app/app.component.3.ts', 'heroes')
|
||||||
|
@ -225,7 +219,7 @@ block hero-class
|
||||||
|
|
||||||
Let's change the example to display a message if there are more than three heroes.
|
Let's change the example to display a message if there are more than three heroes.
|
||||||
|
|
||||||
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition.
|
The Angular `ngIf` directive inserts or removes an element based on a _truthy/falsy_ condition.
|
||||||
To see it in action, add the following paragraph at the bottom of the template:
|
To see it in action, add the following paragraph at the bottom of the template:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.ts', 'message')
|
+makeExcerpt('src/app/app.component.ts', 'message')
|
||||||
|
@ -237,7 +231,7 @@ block hero-class
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The template expression inside the double quotes,
|
The template expression inside the double quotes,
|
||||||
`*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}.
|
`*ngIf="heros.length > 3"`, looks and behaves much like TypeScript.
|
||||||
When the component's list of heroes has more than three items, Angular adds the paragraph
|
When the component's list of heroes has more than three items, Angular adds the paragraph
|
||||||
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
||||||
paragraph, so no message appears. For more information,
|
paragraph, so no message appears. For more information,
|
||||||
|
@ -250,8 +244,8 @@ block hero-class
|
||||||
big chunks of HTML with many data bindings.
|
big chunks of HTML with many data bindings.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Try it out. Because the !{_array} has four items, the message should appear.
|
Try it out. Because the array has four items, the message should appear.
|
||||||
Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}.
|
Go back into <code>app.component.ts"</code> and delete or comment out one of the elements from the hero array.
|
||||||
The browser should refresh automatically and the message should disappear.
|
The browser should refresh automatically and the message should disappear.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
|
@ -259,16 +253,15 @@ block hero-class
|
||||||
## Summary
|
## Summary
|
||||||
Now you know how to use:
|
Now you know how to use:
|
||||||
- **Interpolation** with double curly braces to display a component property.
|
- **Interpolation** with double curly braces to display a component property.
|
||||||
- **ngFor** to display !{_an} !{_array} of items.
|
- **ngFor** to display an array of items.
|
||||||
- A !{_Lang} class to shape the **model data** for your component and display properties of that model.
|
- A TypeScript class to shape the **model data** for your component and display properties of that model.
|
||||||
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||||
|
|
||||||
Here's the final code:
|
Here's the final code:
|
||||||
|
|
||||||
block final-code
|
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
|
||||||
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
|
displaying-data/ts/src/app/hero.ts,
|
||||||
displaying-data/ts/src/app/hero.ts,
|
displaying-data/ts/src/app/app.module.ts,
|
||||||
displaying-data/ts/src/app/app.module.ts,
|
displaying-data/ts/src/main.ts`,
|
||||||
displaying-data/ts/src/main.ts`,
|
'final,,,',
|
||||||
'final,,,',
|
'src/app/app.component.ts, src/app/hero.ts, src/app/app.module.ts, main.ts')
|
||||||
'src/app/app.component.ts, src/app/hero.ts, src/app/app.module.ts, main.ts')
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ figure.image-display
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
Follow the [setup](setup.html) instructions for creating a new project
|
Follow the [setup](setup.html) instructions for creating a new project
|
||||||
named <span ngio-ex>angular-forms</span>.
|
named angular-forms.
|
||||||
|
|
||||||
## Create the Hero model class
|
## Create the Hero model class
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ figure.image-display
|
||||||
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||||
and one optional field (`alterEgo`).
|
and one optional field (`alterEgo`).
|
||||||
|
|
||||||
In the `!{_appDir}` directory, create the following file with the given content:
|
In the `app` directory, create the following file with the given content:
|
||||||
|
|
||||||
+makeExample('src/app/hero.ts')
|
+makeExample('src/app/hero.ts')
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ figure.image-display
|
||||||
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
|
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
|
||||||
|
|
||||||
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||||
All requests bubble up to the root <span if-docs="ts"><code>NgModule</code></span> injector that you configured with the `!{_bootstrapModule}` method.
|
All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
|
|
||||||
- var top="vertical-align:top"
|
|
||||||
|
|
||||||
figure
|
figure
|
||||||
img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px")
|
img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px")
|
||||||
|
|
||||||
|
@ -17,6 +15,7 @@ figure
|
||||||
|
|
||||||
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||||
<br class="l-clear-both">
|
<br class="l-clear-both">
|
||||||
|
<<<<<<< 93d3db1fd4f61c9c6078b2000dea164c87fa071c
|
||||||
+ifDocsFor('ts|js')
|
+ifDocsFor('ts|js')
|
||||||
:marked
|
:marked
|
||||||
## Contents
|
## Contents
|
||||||
|
@ -38,6 +37,28 @@ figure
|
||||||
* [Content projection](#content-projection)
|
* [Content projection](#content-projection)
|
||||||
* [AfterContent hooks](#aftercontent-hooks)
|
* [AfterContent hooks](#aftercontent-hooks)
|
||||||
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
|
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
|
||||||
|
=======
|
||||||
|
|
||||||
|
:marked
|
||||||
|
## Contents
|
||||||
|
* [Component lifecycle hooks overview](#hooks-overview)
|
||||||
|
* [Lifecycle sequence](#hooks-purpose-timing)
|
||||||
|
* [Interfaces are optional (technically)](#interface-optional)
|
||||||
|
* [Other Angular lifecycle hooks](#other-lifecycle-hooks)
|
||||||
|
* [Lifecycle examples](#the-sample)
|
||||||
|
* [Peek-a-boo: all hooks](#peek-a-boo)
|
||||||
|
* [Spying OnInit and OnDestroy](#spy)
|
||||||
|
* [OnInit](#oninit)
|
||||||
|
* [OnDestroy](#ondestroy)
|
||||||
|
* [OnChanges](#onchanges)
|
||||||
|
* [DoCheck](#docheck)
|
||||||
|
* [AfterView](#afterview)
|
||||||
|
* [Abide by the unidirectional data flow rule](#wait-a-tick)
|
||||||
|
* [AfterContent](#aftercontent)
|
||||||
|
* [Content projection](#content-projection)
|
||||||
|
* [AfterContent hooks](#aftercontent-hooks)
|
||||||
|
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
|
||||||
|
>>>>>>> chore: remove dart remains
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Try the <live-example></live-example>.
|
Try the <live-example></live-example>.
|
||||||
|
@ -72,7 +93,7 @@ table(width="100%")
|
||||||
th Hook
|
th Hook
|
||||||
th Purpose and Timing
|
th Purpose and Timing
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngOnChanges()</code>
|
td <code>ngOnChanges()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -81,7 +102,7 @@ table(width="100%")
|
||||||
|
|
||||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngOnInit()</code>
|
td <code>ngOnInit()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -90,7 +111,7 @@ table(width="100%")
|
||||||
|
|
||||||
Called _once_, after the _first_ `ngOnChanges()`.
|
Called _once_, after the _first_ `ngOnChanges()`.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngDoCheck()</code>
|
td <code>ngDoCheck()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -98,7 +119,7 @@ table(width="100%")
|
||||||
|
|
||||||
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngAfterContentInit()</code>
|
td <code>ngAfterContentInit()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -108,7 +129,7 @@ table(width="100%")
|
||||||
|
|
||||||
_A component-only hook_.
|
_A component-only hook_.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngAfterContentChecked()</code>
|
td <code>ngAfterContentChecked()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -118,7 +139,7 @@ table(width="100%")
|
||||||
|
|
||||||
_A component-only hook_.
|
_A component-only hook_.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngAfterViewInit()</code>
|
td <code>ngAfterViewInit()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -128,7 +149,7 @@ table(width="100%")
|
||||||
|
|
||||||
_A component-only hook_.
|
_A component-only hook_.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngAfterViewChecked()</code>
|
td <code>ngAfterViewChecked()</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -138,7 +159,7 @@ table(width="100%")
|
||||||
|
|
||||||
_A component-only hook_.
|
_A component-only hook_.
|
||||||
|
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <code>ngOnDestroy</code>
|
td <code>ngOnDestroy</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -147,24 +168,23 @@ table(width="100%")
|
||||||
|
|
||||||
Called _just before_ Angular destroys the directive/component.
|
Called _just before_ Angular destroys the directive/component.
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
a#interface-optional
|
||||||
a#interface-optional
|
.l-main-section
|
||||||
.l-main-section
|
:marked
|
||||||
:marked
|
## Interfaces are optional (technically)
|
||||||
## Interfaces are optional (technically)
|
|
||||||
|
|
||||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||||
The JavaScript language doesn't have interfaces.
|
The JavaScript language doesn't have interfaces.
|
||||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||||
|
|
||||||
Fortunately, they aren't necessary.
|
Fortunately, they aren't necessary.
|
||||||
You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves.
|
You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves.
|
||||||
|
|
||||||
Angular instead inspects directive and component classes and calls the hook methods *if they are defined*.
|
Angular instead inspects directive and component classes and calls the hook methods *if they are defined*.
|
||||||
Angular finds and calls methods like `ngOnInit()`, with or without the interfaces.
|
Angular finds and calls methods like `ngOnInit()`, with or without the interfaces.
|
||||||
|
|
||||||
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
|
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
|
||||||
in order to benefit from strong typing and editor tooling.
|
in order to benefit from strong typing and editor tooling.
|
||||||
|
|
||||||
a#other-lifecycle-hooks
|
a#other-lifecycle-hooks
|
||||||
.l-main-section
|
.l-main-section
|
||||||
|
@ -173,9 +193,6 @@ a#other-lifecycle-hooks
|
||||||
|
|
||||||
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
||||||
|
|
||||||
block other-angular-subsystems
|
|
||||||
//- N/A for TS.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
3rd party libraries might implement their hooks as well in order to give developers more
|
3rd party libraries might implement their hooks as well in order to give developers more
|
||||||
control over how these libraries are used.
|
control over how these libraries are used.
|
||||||
|
@ -199,13 +216,13 @@ table(width="100%")
|
||||||
tr
|
tr
|
||||||
th Component
|
th Component
|
||||||
th Description
|
th Description
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#peek-a-boo">Peek-a-boo</a>
|
td <a href="#peek-a-boo">Peek-a-boo</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Demonstrates every lifecycle hook.
|
Demonstrates every lifecycle hook.
|
||||||
Each hook method writes to the on-screen log.
|
Each hook method writes to the on-screen log.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#spy">Spy</a>
|
td <a href="#spy">Spy</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -215,33 +232,33 @@ table(width="100%")
|
||||||
|
|
||||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||||
managed by the parent `SpyComponent`.
|
managed by the parent `SpyComponent`.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#onchanges">OnChanges</a>
|
td <a href="#onchanges">OnChanges</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
See how Angular calls the `ngOnChanges()` hook with a `changes` object
|
See how Angular calls the `ngOnChanges()` hook with a `changes` object
|
||||||
every time one of the component input properties changes.
|
every time one of the component input properties changes.
|
||||||
Shows how to interpret the `changes` object.
|
Shows how to interpret the `changes` object.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#docheck">DoCheck</a>
|
td <a href="#docheck">DoCheck</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Implements an `ngDoCheck()` method with custom change detection.
|
Implements an `ngDoCheck()` method with custom change detection.
|
||||||
See how often Angular calls this hook and watch it post changes to a log.
|
See how often Angular calls this hook and watch it post changes to a log.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#afterview">AfterView</a>
|
td <a href="#afterview">AfterView</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Shows what Angular means by a *view*.
|
Shows what Angular means by a *view*.
|
||||||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td <a href="#aftercontent">AfterContent</a>
|
td <a href="#aftercontent">AfterContent</a>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Shows how to project external content into a component and
|
Shows how to project external content into a component and
|
||||||
how to distinguish projected content from a component's view children.
|
how to distinguish projected content from a component's view children.
|
||||||
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||||||
tr(style=top)
|
tr(style=vertical-align:top)
|
||||||
td Counter
|
td Counter
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -479,10 +496,8 @@ a#wait-a-tick
|
||||||
Both of these hooks fire _after_ the component's view has been composed.
|
Both of these hooks fire _after_ the component's view has been composed.
|
||||||
|
|
||||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||||
block tick-methods
|
The `LoggerService.tick_then()` postpones the log update
|
||||||
:marked
|
for one turn of the browser's JavaScript cycle and that's just long enough.
|
||||||
The `LoggerService.tick_then()` postpones the log update
|
|
||||||
for one turn of the browser's JavaScript cycle and that's just long enough.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Here's *AfterView* in action:
|
Here's *AfterView* in action:
|
||||||
|
|
|
@ -142,8 +142,8 @@ figure.image-display
|
||||||
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
||||||
Your pipe has one such parameter: the `exponent`.
|
Your pipe has one such parameter: the `exponent`.
|
||||||
* To tell Angular that this is a pipe, you apply the
|
* To tell Angular that this is a pipe, you apply the
|
||||||
`@Pipe` #{_decorator}, which you import from the core Angular library.
|
`@Pipe` decorator, which you import from the core Angular library.
|
||||||
* The `@Pipe` #{_decorator} allows you to define the
|
* The `@Pipe` decorator allows you to define the
|
||||||
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
|
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
|
||||||
Your pipe's name is `exponentialStrength`.
|
Your pipe's name is `exponentialStrength`.
|
||||||
|
|
||||||
|
@ -161,16 +161,14 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||||||
|
|
||||||
- var _decls = _docsFor == 'dart' ? 'pipes' : 'declarations';
|
|
||||||
- var _appMod = _docsFor == 'dart' ? '@Component' : 'AppModule';
|
|
||||||
:marked
|
:marked
|
||||||
Note the following:
|
Note the following:
|
||||||
|
|
||||||
* You use your custom pipe the same way you use built-in pipes.
|
* You use your custom pipe the same way you use built-in pipes.
|
||||||
* You must include your pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
* You must include your pipe in the `declarations` array of the `AppModule`.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Remember the !{_decls} #{_array}
|
header Remember the declarations array
|
||||||
:marked
|
:marked
|
||||||
You must manually register custom pipes.
|
You must manually register custom pipes.
|
||||||
If you don't, Angular reports an error.
|
If you don't, Angular reports an error.
|
||||||
|
@ -206,17 +204,17 @@ a#change-detection
|
||||||
### No pipe
|
### No pipe
|
||||||
|
|
||||||
In the next example, the component uses the default, aggressive change detection strategy to monitor and update
|
In the next example, the component uses the default, aggressive change detection strategy to monitor and update
|
||||||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
its display of every hero in the `heroes` array. Here's the template:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The companion component class provides heroes, adds heroes into the #{_array}, and can reset the #{_array}.
|
The companion component class provides heroes, adds heroes into the array, and can reset the array.
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You can add heroes and Angular updates the display when you do.
|
You can add heroes and Angular updates the display when you do.
|
||||||
If you click the `reset` button, Angular replaces `heroes` with a new #{_array} of the original heroes and updates the display.
|
If you click the `reset` button, Angular replaces `heroes` with a new array of the original heroes and updates the display.
|
||||||
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
|
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
|
||||||
|
|
||||||
### Flying-heroes pipe
|
### Flying-heroes pipe
|
||||||
|
@ -237,15 +235,15 @@ a#change-detection
|
||||||
Notice how a hero is added:
|
Notice how a hero is added:
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
|
||||||
:marked
|
:marked
|
||||||
You add the hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
You add the hero into the `heroes` array. The reference to the array hasn't changed.
|
||||||
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
It's the same array. That's all Angular cares about. From its perspective, *same array, no change, no display update*.
|
||||||
|
|
||||||
To fix that, create an #{_array} with the new hero appended and assign that to `heroes`.
|
To fix that, create an array with the new hero appended and assign that to `heroes`.
|
||||||
This time Angular detects that the #{_array} reference has changed.
|
This time Angular detects that the array reference has changed.
|
||||||
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero.
|
It executes the pipe and updates the display with the new array, which includes the new flying hero.
|
||||||
|
|
||||||
If you *mutate* the #{_array}, no pipe is invoked and the display isn't updated;
|
If you *mutate* the array, no pipe is invoked and the display isn't updated;
|
||||||
if you *replace* the #{_array}, the pipe executes and the display is updated.
|
if you *replace* the array, the pipe executes and the display is updated.
|
||||||
The Flying Heroes application extends the
|
The Flying Heroes application extends the
|
||||||
code with checkbox switches and additional displays to help you experience these effects.
|
code with checkbox switches and additional displays to help you experience these effects.
|
||||||
|
|
||||||
|
@ -253,8 +251,8 @@ figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Replacing the #{_array} is an efficient way to signal Angular to update the display.
|
Replacing the array is an efficient way to signal Angular to update the display.
|
||||||
When do you replace the #{_array}? When the data change.
|
When do you replace the array? When the data change.
|
||||||
That's an easy rule to follow in *this* example
|
That's an easy rule to follow in *this* example
|
||||||
where the only way to change the data is by adding a hero.
|
where the only way to change the data is by adding a hero.
|
||||||
|
|
||||||
|
@ -289,7 +287,7 @@ figure.image-display
|
||||||
or a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
or a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||||||
|
|
||||||
Angular ignores changes within (composite) objects.
|
Angular ignores changes within (composite) objects.
|
||||||
It won't call a pure pipe if you change an input month, add to an input #{_array}, or update an input object property.
|
It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.
|
||||||
|
|
||||||
This may seem restrictive but it's also fast.
|
This may seem restrictive but it's also fast.
|
||||||
An object reference check is fast—much faster than a deep check for
|
An object reference check is fast—much faster than a deep check for
|
||||||
|
@ -335,28 +333,25 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
||||||
|
|
||||||
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html';
|
+makeExcerpt('src/app/flying-heroes-impure.component.html (excerpt)', 'template-flying-heroes')
|
||||||
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
|
|
||||||
+makeExcerpt('src/app/flying-heroes' + _fnSuffix + ' (excerpt)', _region)
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The only substantive change is the pipe in the template.
|
The only substantive change is the pipe in the template.
|
||||||
You can confirm in the <live-example></live-example> that the _flying heroes_
|
You can confirm in the <live-example></live-example> that the _flying heroes_
|
||||||
display updates as you add heroes, even when you mutate the `heroes` #{_array}.
|
display updates as you add heroes, even when you mutate the `heroes` array.
|
||||||
|
|
||||||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
|
||||||
h3#async-pipe The impure #[i AsyncPipe]
|
h3#async-pipe The impure #[i AsyncPipe]
|
||||||
:marked
|
:marked
|
||||||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||||||
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
The `AsyncPipe` accepts a `Promise` or `Observable` as input
|
||||||
and subscribes to the input automatically, eventually returning the emitted values.
|
and subscribes to the input automatically, eventually returning the emitted values.
|
||||||
|
|
||||||
The `AsyncPipe` is also stateful.
|
The `AsyncPipe` is also stateful.
|
||||||
The pipe maintains a subscription to the input `#{_Observable}` and
|
The pipe maintains a subscription to the input `Observable` and
|
||||||
keeps delivering values from that `#{_Observable}` as they arrive.
|
keeps delivering values from that `Observable` as they arrive.
|
||||||
|
|
||||||
This next example binds an `#{_Observable}` of message strings
|
This next example binds an `Observable` of message strings
|
||||||
(`message#{_dollar}`) to a view with the `async` pipe.
|
(`message$`) to a view with the `async` pipe.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
|
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
|
||||||
|
|
||||||
|
@ -375,7 +370,7 @@ h3#async-pipe The impure #[i AsyncPipe]
|
||||||
If you're not careful, this pipe will punish the server with requests.
|
If you're not careful, this pipe will punish the server with requests.
|
||||||
|
|
||||||
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
|
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
|
||||||
The code<span if-docs="ts"> uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
The code uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
||||||
|
|
||||||
+makeExample('src/app/fetch-json.pipe.ts')
|
+makeExample('src/app/fetch-json.pipe.ts')
|
||||||
:marked
|
:marked
|
||||||
|
|
|
@ -1543,8 +1543,8 @@ a#milestone-4
|
||||||
Begin by imitating the heroes feature:
|
Begin by imitating the heroes feature:
|
||||||
|
|
||||||
- Delete the placeholder crisis center file.
|
- Delete the placeholder crisis center file.
|
||||||
- Create !{_an} `!{_appDir}/crisis-center` folder.
|
- Create an `app/crisis-center` folder.
|
||||||
- Copy the files from `!{_appDir}/heroes` into the new crisis center folder.
|
- Copy the files from `app/heroes` into the new crisis center folder.
|
||||||
- In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
|
- In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
|
||||||
|
|
||||||
You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes:
|
You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes:
|
||||||
|
@ -1560,7 +1560,7 @@ a#milestone-4
|
||||||
:marked
|
:marked
|
||||||
In keeping with the
|
In keeping with the
|
||||||
<a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" target="_blank" title="Separation of Concerns">*Separation of Concerns* principle</a>,
|
<a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" target="_blank" title="Separation of Concerns">*Separation of Concerns* principle</a>,
|
||||||
changes to the *Crisis Center* won't affect the `!{_AppModuleVsAppComp}` or
|
changes to the *Crisis Center* won't affect the `AppModule` or
|
||||||
any other feature's component.
|
any other feature's component.
|
||||||
a#crisis-child-routes
|
a#crisis-child-routes
|
||||||
:marked
|
:marked
|
||||||
|
@ -2288,7 +2288,7 @@ a#CanDeactivate
|
||||||
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
|
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Add the `Guard` to the main `AppRoutingModule` `providers` !{_array} so the
|
Add the `Guard` to the main `AppRoutingModule` `providers` array so the
|
||||||
`Router` can inject it during the navigation process.
|
`Router` can inject it during the navigation process.
|
||||||
|
|
||||||
+makeExample('src/app/app-routing.module.4.ts', '', '')
|
+makeExample('src/app/app-routing.module.4.ts', '', '')
|
||||||
|
@ -2350,7 +2350,7 @@ a#fetch-before-navigating
|
||||||
Import this resolver in the `crisis-center-routing.module.ts`
|
Import this resolver in the `crisis-center-routing.module.ts`
|
||||||
and add a `resolve` object to the `CrisisDetailComponent` route configuration.
|
and add a `resolve` object to the `CrisisDetailComponent` route configuration.
|
||||||
|
|
||||||
Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` !{_array}.
|
Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` array.
|
||||||
|
|
||||||
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.4.ts (resolver)', 'crisis-detail-resolver')
|
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.4.ts (resolver)', 'crisis-detail-resolver')
|
||||||
|
|
||||||
|
@ -2549,7 +2549,7 @@ a#can-load-guard
|
||||||
The `checkLogin()` method redirects to that URL once the user has logged in.
|
The `checkLogin()` method redirects to that URL once the user has logged in.
|
||||||
|
|
||||||
Now import the `AuthGuard` into the `AppRoutingModule` and add the `AuthGuard` to the `canLoad`
|
Now import the `AuthGuard` into the `AppRoutingModule` and add the `AuthGuard` to the `canLoad`
|
||||||
!{_array} for the `admin` route.
|
array for the `admin` route.
|
||||||
The completed admin route looks like this:
|
The completed admin route looks like this:
|
||||||
|
|
||||||
+makeExample('router/ts/src/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)')
|
+makeExample('router/ts/src/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)')
|
||||||
|
|
|
@ -7,18 +7,17 @@ block includes
|
||||||
this user?_) and authorization (_What can this user do?_).
|
this user?_) and authorization (_What can this user do?_).
|
||||||
|
|
||||||
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
||||||
|
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
# Contents
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
* [Reporting vulnerabilities](#report-issues).
|
||||||
.l-main-section
|
* [Best practices](#best-practices).
|
||||||
:marked
|
* [Preventing cross-site scripting (XSS)](#xss).
|
||||||
# Contents
|
* [Trusting safe values](#bypass-security-apis).
|
||||||
|
* [HTTP-Level vulnerabilities](#http).
|
||||||
* [Reporting vulnerabilities](#report-issues).
|
* [Auditing Angular applications](#code-review).
|
||||||
* [Best practices](#best-practices).
|
|
||||||
* [Preventing cross-site scripting (XSS)](#xss).
|
|
||||||
* [Trusting safe values](#bypass-security-apis).
|
|
||||||
* [HTTP-Level vulnerabilities](#http).
|
|
||||||
* [Auditing Angular applications](#code-review).
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You can run the <live-example></live-example> in Plunker and download the code from there.
|
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||||
|
@ -108,14 +107,13 @@ h2#xss Preventing cross-site scripting (XSS)
|
||||||
|
|
||||||
+makeExcerpt('src/app/inner-html-binding.component.ts', 'class')
|
+makeExcerpt('src/app/inner-html-binding.component.ts', 'class')
|
||||||
|
|
||||||
block html-sanitization
|
:marked
|
||||||
:marked
|
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
|
||||||
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
|
tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
|
||||||
tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
|
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/security/binding-inner-html.png'
|
img(src='/resources/images/devguide/security/binding-inner-html.png'
|
||||||
alt='A screenshot showing interpolated and bound HTML values')
|
alt='A screenshot showing interpolated and bound HTML values')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Avoid direct use of the DOM APIs
|
### Avoid direct use of the DOM APIs
|
||||||
|
@ -152,138 +150,136 @@ block html-sanitization
|
||||||
the server. Don't generate Angular templates on the server side using a templating language; doing this
|
the server. Don't generate Angular templates on the server side using a templating language; doing this
|
||||||
carries a high risk of introducing template-injection vulnerabilities.
|
carries a high risk of introducing template-injection vulnerabilities.
|
||||||
|
|
||||||
block bypass-security-apis
|
.l-main-section
|
||||||
.l-main-section
|
h2#bypass-security-apis Trusting safe values
|
||||||
h2#bypass-security-apis Trusting safe values
|
:marked
|
||||||
:marked
|
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
|
||||||
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
|
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
|
||||||
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
|
situations, you can tell Angular that you inspected a value, checked how it was generated, and made
|
||||||
situations, you can tell Angular that you inspected a value, checked how it was generated, and made
|
sure it will always be secure. But *be careful*. If you trust a value that might be malicious, you
|
||||||
sure it will always be secure. But *be careful*. If you trust a value that might be malicious, you
|
are introducing a security vulnerability into your application. If in doubt, find a professional
|
||||||
are introducing a security vulnerability into your application. If in doubt, find a professional
|
security reviewer.
|
||||||
security reviewer.
|
|
||||||
|
|
||||||
To mark a value as trusted, inject `DomSanitizer` and call one of the
|
To mark a value as trusted, inject `DomSanitizer` and call one of the
|
||||||
following methods:
|
following methods:
|
||||||
|
|
||||||
* `bypassSecurityTrustHtml`
|
* `bypassSecurityTrustHtml`
|
||||||
* `bypassSecurityTrustScript`
|
* `bypassSecurityTrustScript`
|
||||||
* `bypassSecurityTrustStyle`
|
* `bypassSecurityTrustStyle`
|
||||||
* `bypassSecurityTrustUrl`
|
* `bypassSecurityTrustUrl`
|
||||||
* `bypassSecurityTrustResourceUrl`
|
* `bypassSecurityTrustResourceUrl`
|
||||||
|
|
||||||
Remember, whether a value is safe depends on context, so choose the right context for
|
Remember, whether a value is safe depends on context, so choose the right context for
|
||||||
your intended use of the value. Imagine that the following template needs to bind a URL to a
|
your intended use of the value. Imagine that the following template needs to bind a URL to a
|
||||||
`javascript:alert(...)` call:
|
`javascript:alert(...)` call:
|
||||||
|
|
||||||
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
|
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
|
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
|
||||||
in development mode, logs this action to the console. To prevent
|
in development mode, logs this action to the console. To prevent
|
||||||
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
||||||
|
|
||||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url')
|
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url')
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/security/bypass-security-component.png'
|
img(src='/resources/images/devguide/security/bypass-security-component.png'
|
||||||
alt='A screenshot showing an alert box created from a trusted URL')
|
alt='A screenshot showing an alert box created from a trusted URL')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
If you need to convert user input into a trusted value, use a
|
If you need to convert user input into a trusted value, use a
|
||||||
controller method. The following template allows users to enter a YouTube video ID and load the
|
controller method. The following template allows users to enter a YouTube video ID and load the
|
||||||
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
||||||
context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users
|
context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users
|
||||||
could execute. So call a method on the controller to construct a trusted video URL, which causes
|
could execute. So call a method on the controller to construct a trusted video URL, which causes
|
||||||
Angular to allow binding into `<iframe src>`:
|
Angular to allow binding into `<iframe src>`:
|
||||||
|
|
||||||
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
|
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
|
||||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
|
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
|
||||||
|
|
||||||
block http
|
.l-main-section
|
||||||
.l-main-section
|
h2#http HTTP-level vulnerabilities
|
||||||
h2#http HTTP-level vulnerabilities
|
:marked
|
||||||
:marked
|
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
|
||||||
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
|
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
|
||||||
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
|
on the server side, but Angular provides helpers to make integration on the client side easier.
|
||||||
on the server side, but Angular provides helpers to make integration on the client side easier.
|
|
||||||
|
|
||||||
h3#xsrf Cross-site request forgery
|
h3#xsrf Cross-site request forgery
|
||||||
:marked
|
:marked
|
||||||
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
||||||
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
|
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
|
||||||
to the application's web server (such as `example-bank.com`).
|
to the application's web server (such as `example-bank.com`).
|
||||||
|
|
||||||
Assume the user is logged into the application at `example-bank.com`.
|
Assume the user is logged into the application at `example-bank.com`.
|
||||||
The user opens an email and clicks a link to `evil.com`, which opens in a new tab.
|
The user opens an email and clicks a link to `evil.com`, which opens in a new tab.
|
||||||
|
|
||||||
The `evil.com` page immediately sends a malicious request to `example-bank.com`.
|
The `evil.com` page immediately sends a malicious request to `example-bank.com`.
|
||||||
Perhaps it's a request to transfer money from the user's account to the attacker's account.
|
Perhaps it's a request to transfer money from the user's account to the attacker's account.
|
||||||
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
|
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
|
||||||
|
|
||||||
If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate
|
If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate
|
||||||
request from the application and the forged request from `evil.com`.
|
request from the application and the forged request from `evil.com`.
|
||||||
|
|
||||||
To prevent this, the application must ensure that a user request originates from the real
|
To prevent this, the application must ensure that a user request originates from the real
|
||||||
application, not from a different site.
|
application, not from a different site.
|
||||||
The server and client must cooperate to thwart this attack.
|
The server and client must cooperate to thwart this attack.
|
||||||
|
|
||||||
In a common anti-XSRF technique, the application server sends a randomly
|
In a common anti-XSRF technique, the application server sends a randomly
|
||||||
generated authentication token in a cookie.
|
generated authentication token in a cookie.
|
||||||
The client code reads the cookie and adds a custom request header with the token in all subsequent requests.
|
The client code reads the cookie and adds a custom request header with the token in all subsequent requests.
|
||||||
The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
|
The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
|
||||||
|
|
||||||
This technique is effective because all browsers implement the _same origin policy_. Only code from the website
|
This technique is effective because all browsers implement the _same origin policy_. Only code from the website
|
||||||
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
|
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
|
||||||
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
|
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
|
||||||
|
|
||||||
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
|
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
|
||||||
The default `CookieXSRFStrategy` is turned on automatically.
|
The default `CookieXSRFStrategy` is turned on automatically.
|
||||||
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
|
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
|
||||||
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
|
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
|
||||||
|
|
||||||
The server must do its part by setting the
|
The server must do its part by setting the
|
||||||
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
|
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
|
||||||
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
|
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
|
||||||
|
|
||||||
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
|
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
|
||||||
cryptographically secure random number generator, and expire in a day or two.
|
cryptographically secure random number generator, and expire in a day or two.
|
||||||
|
|
||||||
Your server may use a different cookie or header name for this purpose.
|
Your server may use a different cookie or header name for this purpose.
|
||||||
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
|
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
|
||||||
code-example(language="typescript").
|
code-example(language="typescript").
|
||||||
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
|
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
|
||||||
:marked
|
:marked
|
||||||
Or you can implement and provide an entirely custom `XSRFStrategy`:
|
Or you can implement and provide an entirely custom `XSRFStrategy`:
|
||||||
|
|
||||||
code-example(language="typescript").
|
code-example(language="typescript").
|
||||||
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
|
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
||||||
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and
|
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and
|
||||||
<a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
|
<a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
|
||||||
The Stanford University paper
|
The Stanford University paper
|
||||||
<a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail.
|
<a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail.
|
||||||
|
|
||||||
See also Dave Smith's easy-to-understand
|
See also Dave Smith's easy-to-understand
|
||||||
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
|
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
|
||||||
|
|
||||||
h3#xssi Cross-site script inclusion (XSSI)
|
h3#xssi Cross-site script inclusion (XSSI)
|
||||||
:marked
|
:marked
|
||||||
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
|
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
|
||||||
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
|
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
|
||||||
object constructors, and then including an API URL using a `<script>` tag.
|
object constructors, and then including an API URL using a `<script>` tag.
|
||||||
|
|
||||||
This attack is only successful if the returned JSON is executable as JavaScript. Servers can
|
This attack is only successful if the returned JSON is executable as JavaScript. Servers can
|
||||||
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
|
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
|
||||||
well-known string `")]}',\n"`.
|
well-known string `")]}',\n"`.
|
||||||
|
|
||||||
Angular's `Http` library recognizes this convention and automatically strips the string
|
Angular's `Http` library recognizes this convention and automatically strips the string
|
||||||
`")]}',\n"` from all responses before further parsing.
|
`")]}',\n"` from all responses before further parsing.
|
||||||
|
|
||||||
For more information, see the XSSI section of this [Google web security blog
|
For more information, see the XSSI section of this [Google web security blog
|
||||||
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
|
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#code-review Auditing Angular applications
|
h2#code-review Auditing Angular applications
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _Http = 'Http'; // Angular `Http` library name.
|
|
||||||
- var _Angular_Http = 'Angular <code>Http</code>'
|
|
||||||
- var _Angular_http_library = 'Angular HTTP library'
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
|
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
|
||||||
|
@ -16,7 +13,7 @@ block includes
|
||||||
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
|
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
|
||||||
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
||||||
|
|
||||||
The !{_Angular_http_library} simplifies application programming with the **XHR** and **JSONP** APIs.
|
The Angular HTTP library simplifies application programming with the **XHR** and **JSONP** APIs.
|
||||||
# Contents
|
# Contents
|
||||||
|
|
||||||
* [Demos](#demos)
|
* [Demos](#demos)
|
||||||
|
@ -24,7 +21,7 @@ block includes
|
||||||
* [The Tour of Heroes *HTTP* client demo](#http-client)
|
* [The Tour of Heroes *HTTP* client demo](#http-client)
|
||||||
- [The `HeroListComponent` class](#HeroListComponent)
|
- [The `HeroListComponent` class](#HeroListComponent)
|
||||||
* [Fetch data with `http.get()`](#fetch-data)
|
* [Fetch data with `http.get()`](#fetch-data)
|
||||||
<li if-docs="ts"> [RxJS library](#rxjs-library)
|
<li>[RxJS library](#rxjs-library)
|
||||||
<ul>
|
<ul>
|
||||||
<li> [Enable RxJS operators](#enable-rxjs-operators)</li>
|
<li> [Enable RxJS operators](#enable-rxjs-operators)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -38,16 +35,16 @@ block includes
|
||||||
- [Headers](#headers)
|
- [Headers](#headers)
|
||||||
- [JSON results](#json-results)
|
- [JSON results](#json-results)
|
||||||
|
|
||||||
<ul><li if-docs="ts"> [Fall back to promises](#promises)</ul>
|
<ul><li> [Fall back to promises](#promises)</ul>
|
||||||
|
|
||||||
* [Cross-Origin Requests: Wikipedia example](#cors)
|
* [Cross-Origin Requests: Wikipedia example](#cors)
|
||||||
<ul if-docs="ts">
|
<ul>
|
||||||
<li> [Search Wikipedia](#search-wikipedia)</li>
|
<li> [Search Wikipedia](#search-wikipedia)</li>
|
||||||
<li> [Search parameters](#search-parameters)</li>
|
<li> [Search parameters](#search-parameters)</li>
|
||||||
<li> [The WikiComponent](#wikicomponent)</li>
|
<li> [The WikiComponent](#wikicomponent)</li>
|
||||||
</ul>
|
</ul>
|
||||||
* [A wasteful app](#wasteful-app)
|
* [A wasteful app](#wasteful-app)
|
||||||
<li if-docs="ts"> [More fun with Observables](#more-observables)
|
<li> [More fun with Observables](#more-observables)
|
||||||
<ul>
|
<ul>
|
||||||
<li> [Create a stream of search terms](#create-stream)</li>
|
<li> [Create a stream of search terms](#create-stream)</li>
|
||||||
<li> [Listen for search terms](#listen-for-search-terms)</li>
|
<li> [Listen for search terms](#listen-for-search-terms)</li>
|
||||||
|
@ -69,7 +66,7 @@ a#demos
|
||||||
block demos-list
|
block demos-list
|
||||||
:marked
|
:marked
|
||||||
- [The Tour of Heroes *HTTP* client demo](#http-client).
|
- [The Tour of Heroes *HTTP* client demo](#http-client).
|
||||||
- [Fall back to !{_Promise}s](#promises).
|
- [Fall back to Promises](#promises).
|
||||||
- [Cross-Origin Requests: Wikipedia example](#cors).
|
- [Cross-Origin Requests: Wikipedia example](#cors).
|
||||||
- [More fun with Observables](#more-observables).
|
- [More fun with Observables](#more-observables).
|
||||||
|
|
||||||
|
@ -83,18 +80,17 @@ block demos-list
|
||||||
|
|
||||||
First, configure the application to use server communication facilities.
|
First, configure the application to use server communication facilities.
|
||||||
|
|
||||||
The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol.
|
The Angular <code>Http</code> client communicates with the server using a familiar HTTP request/response protocol.
|
||||||
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
The `Http` client is one of a family of services in the Angular HTTP library.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
When importing from the `@angular/http` module, SystemJS knows how to load services from
|
||||||
When importing from the `@angular/http` module, SystemJS knows how to load services from
|
the Angular HTTP library
|
||||||
the !{_Angular_http_library}
|
because the `systemjs.config.js` file maps to that module name.
|
||||||
because the `systemjs.config.js` file maps to that module name.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system.
|
Before you can use the `Http` client, you need to register it as a service provider with the dependency injection system.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
|
@ -108,7 +104,7 @@ block demos-list
|
||||||
block http-providers
|
block http-providers
|
||||||
:marked
|
:marked
|
||||||
Begin by importing the necessary members.
|
Begin by importing the necessary members.
|
||||||
The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
|
The newcomers are the `HttpModule` and the `JsonpModule` from the Angular HTTP library. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
|
||||||
|
|
||||||
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
|
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -123,7 +119,7 @@ block http-providers
|
||||||
|
|
||||||
The first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
|
The first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
|
||||||
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
|
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
|
||||||
The app uses the !{_Angular_Http} client to communicate via **XMLHttpRequest (XHR)**.
|
The app uses the Angular <code>Http</code> client to communicate via **XMLHttpRequest (XHR)**.
|
||||||
|
|
||||||
It works like this:
|
It works like this:
|
||||||
figure.image-display
|
figure.image-display
|
||||||
|
@ -152,7 +148,7 @@ a#HeroListComponent
|
||||||
Angular [injects](dependency-injection.html) a `HeroService` into the constructor
|
Angular [injects](dependency-injection.html) a `HeroService` into the constructor
|
||||||
and the component calls that service to fetch and save data.
|
and the component calls that service to fetch and save data.
|
||||||
|
|
||||||
The component **does not talk directly to the !{_Angular_Http} client**.
|
The component **does not talk directly to the Angular <code>Http</code> client**.
|
||||||
The component doesn't know or care how it gets the data.
|
The component doesn't know or care how it gets the data.
|
||||||
It delegates to the `HeroService`.
|
It delegates to the `HeroService`.
|
||||||
|
|
||||||
|
@ -169,7 +165,7 @@ a#HeroListComponent
|
||||||
(especially calling a remote server) is handled in a separate method.
|
(especially calling a remote server) is handled in a separate method.
|
||||||
block getheroes-and-create
|
block getheroes-and-create
|
||||||
:marked
|
:marked
|
||||||
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
|
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the Angular <code>Http</code> client fetched from the server.
|
||||||
|
|
||||||
Think of an `Observable` as a stream of events published by some source.
|
Think of an `Observable` as a stream of events published by some source.
|
||||||
To listen for events in this stream, ***subscribe*** to the `Observable`.
|
To listen for events in this stream, ***subscribe*** to the `Observable`.
|
||||||
|
@ -188,15 +184,15 @@ a#HeroService
|
||||||
returning mock heroes in a service like this one:
|
returning mock heroes in a service like this one:
|
||||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".")
|
+makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".")
|
||||||
:marked
|
:marked
|
||||||
You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
|
You can revise that `HeroService` to get the heroes from the server using the Angular <code>Http</code> client service:
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'v1', 'src/app/toh/hero.service.ts (revised)')
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'v1', 'src/app/toh/hero.service.ts (revised)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Notice that the !{_Angular_Http} client service is
|
Notice that the Angular <code>Http</code> client service is
|
||||||
[injected](dependency-injection.html) into the `HeroService` constructor.
|
[injected](dependency-injection.html) into the `HeroService` constructor.
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor')
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor')
|
||||||
:marked
|
:marked
|
||||||
Look closely at how to call `!{_priv}http.get`:
|
Look closely at how to call `http.get`:
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'http-get', 'src/app/toh/hero.service.ts (getHeroes)')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'http-get', 'src/app/toh/hero.service.ts (getHeroes)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
You pass the resource URL to `get` and it calls the server which returns heroes.
|
You pass the resource URL to `get` and it calls the server which returns heroes.
|
||||||
|
@ -208,46 +204,45 @@ a#HeroService
|
||||||
Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
|
Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
<a id="rxjs"></a>
|
||||||
<a id="rxjs"></a>
|
If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a
|
||||||
If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a
|
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>.
|
||||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>.
|
You'd expect to chain a call to `then()` and extract the heroes.
|
||||||
You'd expect to chain a call to `then()` and extract the heroes.
|
Instead you're calling a `map()` method.
|
||||||
Instead you're calling a `map()` method.
|
Clearly this is not a promise.
|
||||||
Clearly this is not a promise.
|
|
||||||
|
|
||||||
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
|
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
|
||||||
and `map()` is one of the RxJS *operators*.
|
and `map()` is one of the RxJS *operators*.
|
||||||
|
|
||||||
a#rxjs-library
|
a#rxjs-library
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## RxJS library
|
## RxJS library
|
||||||
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
|
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
|
||||||
is a third party library, endorsed by Angular, that implements the
|
is a third party library, endorsed by Angular, that implements the
|
||||||
<a href="https://www.youtube.com/watch?v=VLGCCpOWFFw" target="_blank" title="Video: Rob Wormald on Observables"><b>asynchronous Observable</b></a> pattern.
|
<a href="https://www.youtube.com/watch?v=VLGCCpOWFFw" target="_blank" title="Video: Rob Wormald on Observables"><b>asynchronous Observable</b></a> pattern.
|
||||||
|
|
||||||
All of the Developer Guide samples have installed the RxJS npm package
|
All of the Developer Guide samples have installed the RxJS npm package
|
||||||
because Observables are used widely in Angular applications.
|
because Observables are used widely in Angular applications.
|
||||||
_This_ app needs it when working with the HTTP client.
|
_This_ app needs it when working with the HTTP client.
|
||||||
But you must take a critical extra step to make RxJS Observables usable:
|
But you must take a critical extra step to make RxJS Observables usable:
|
||||||
_you must import the RxJS operators individually_.
|
_you must import the RxJS operators individually_.
|
||||||
|
|
||||||
### Enable RxJS operators
|
### Enable RxJS operators
|
||||||
The RxJS library is large.
|
The RxJS library is large.
|
||||||
Size matters when building a production application and deploying it to mobile devices.
|
Size matters when building a production application and deploying it to mobile devices.
|
||||||
You should include only necessary features.
|
You should include only necessary features.
|
||||||
|
|
||||||
Each code file should add the operators it needs by importing from an RxJS library.
|
Each code file should add the operators it needs by importing from an RxJS library.
|
||||||
The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this.
|
The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this.
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a#extract-data
|
a#extract-data
|
||||||
:marked
|
:marked
|
||||||
## Process the response object
|
## Process the response object
|
||||||
Remember that the `getHeroes()` method used an `!{_priv}extractData()` helper method to map the `!{_priv}http.get` response object to heroes:
|
Remember that the `getHeroes()` method used an `extractData()` helper method to map the `http.get` response object to heroes:
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'extract-data', 'src/app/toh/hero.service.ts (excerpt)')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'extract-data', 'src/app/toh/hero.service.ts (excerpt)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
The `response` object doesn't hold the data in a form the app can use directly.
|
The `response` object doesn't hold the data in a form the app can use directly.
|
||||||
|
@ -271,7 +266,7 @@ block parse-json
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Don't expect the decoded JSON to be the heroes !{_array} directly.
|
Don't expect the decoded JSON to be the heroes array directly.
|
||||||
This server always wraps JSON results in an object with a `data`
|
This server always wraps JSON results in an object with a `data`
|
||||||
property. You have to unwrap it to get the heroes.
|
property. You have to unwrap it to get the heroes.
|
||||||
This is conventional web API behavior, driven by
|
This is conventional web API behavior, driven by
|
||||||
|
@ -291,14 +286,13 @@ a#no-return-response-object
|
||||||
The component that calls the `HeroService` only wants heroes and is kept separate
|
The component that calls the `HeroService` only wants heroes and is kept separate
|
||||||
from getting them, the code dealing with where they come from, and the response object.
|
from getting them, the code dealing with where they come from, and the response object.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.callout.is-important
|
||||||
.callout.is-important
|
header HTTP GET is delayed
|
||||||
header HTTP GET is delayed
|
:marked
|
||||||
:marked
|
The `http.get` does **not send the request just yet.** This Observable is
|
||||||
The `!{_priv}http.get` does **not send the request just yet.** This Observable is
|
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables),
|
||||||
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables),
|
which means that the request won't go out until something *subscribes* to the Observable.
|
||||||
which means that the request won't go out until something *subscribes* to the Observable.
|
That *something* is the [HeroListComponent](#subscribe).
|
||||||
That *something* is the [HeroListComponent](#subscribe).
|
|
||||||
|
|
||||||
a#error-handling
|
a#error-handling
|
||||||
:marked
|
:marked
|
||||||
|
@ -324,7 +318,7 @@ a#hero-list-component
|
||||||
h3 #[b HeroListComponent] error handling
|
h3 #[b HeroListComponent] error handling
|
||||||
block hlc-error-handling
|
block hlc-error-handling
|
||||||
:marked
|
:marked
|
||||||
Back in the `HeroListComponent`, in `!{_priv}heroService.getHeroes()`,
|
Back in the `HeroListComponent`, in `heroService.getHeroes()`,
|
||||||
the `subscribe` function has a second function parameter to handle the error message.
|
the `subscribe` function has a second function parameter to handle the error message.
|
||||||
It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template.
|
It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template.
|
||||||
|
|
||||||
|
@ -369,8 +363,7 @@ code-example(format="." language="javascript").
|
||||||
|
|
||||||
Now that you know how the API works, implement `create()` as follows:
|
Now that you know how the API works, implement `create()` as follows:
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
|
|
||||||
+makeExcerpt('src/app/toh/hero.service.ts', 'create')
|
+makeExcerpt('src/app/toh/hero.service.ts', 'create')
|
||||||
|
|
||||||
a#headers
|
a#headers
|
||||||
|
@ -379,19 +372,18 @@ a#headers
|
||||||
|
|
||||||
In the `headers` object, the `Content-Type` specifies that the body represents JSON.
|
In the `headers` object, the `Content-Type` specifies that the body represents JSON.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
Next, the `headers` object is used to configure the `options` object. The `options`
|
||||||
Next, the `headers` object is used to configure the `options` object. The `options`
|
object is a new instance of `RequestOptions`, a class that allows you to specify
|
||||||
object is a new instance of `RequestOptions`, a class that allows you to specify
|
certain settings when instantiating a request. In this way, [headers](../api/http/index/Headers-class.html) is one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
|
||||||
certain settings when instantiating a request. In this way, [headers](../api/http/index/Headers-class.html) is one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
|
|
||||||
|
|
||||||
In the `return` statement, `options` is the *third* argument of the `post()` method, as shown above.
|
In the `return` statement, `options` is the *third* argument of the `post()` method, as shown above.
|
||||||
|
|
||||||
a#json-results
|
a#json-results
|
||||||
:marked
|
:marked
|
||||||
### JSON results
|
### JSON results
|
||||||
|
|
||||||
As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](#extract-data)
|
As with `getHeroes()`, use the `extractData()` helper to [extract the data](#extract-data)
|
||||||
from the response.
|
from the response.
|
||||||
|
|
||||||
block hero-list-comp-add-hero
|
block hero-list-comp-add-hero
|
||||||
|
@ -400,63 +392,62 @@ block hero-list-comp-add-hero
|
||||||
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero-list.component.ts', 'addHero', 'src/app/toh/hero-list.component.ts (addHero)')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero-list.component.ts', 'addHero', 'src/app/toh/hero-list.component.ts (addHero)')(format=".")
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
h2#promises Fall back to promises
|
||||||
h2#promises Fall back to promises
|
:marked
|
||||||
|
Although the Angular `http` client API returns an `Observable<Response>` you can turn it into a
|
||||||
|
[`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||||
|
It's easy to do, and in simple cases, a Promise-based version looks much
|
||||||
|
like the Observable-based version.
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Although the Angular `http` client API returns an `Observable<Response>` you can turn it into a
|
While Promises may be more familiar, Observables have many advantages.
|
||||||
[`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
|
||||||
It's easy to do, and in simple cases, a Promise-based version looks much
|
|
||||||
like the Observable-based version.
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
While Promises may be more familiar, Observables have many advantages.
|
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Here is a comparison of the `HeroService` using Promises versus Observables,
|
||||||
|
highlighting just the parts that are different.
|
||||||
|
+makeTabs(
|
||||||
|
'server-communication/ts/src/app/toh/hero.service.promise.ts,server-communication/ts/src/app/toh/hero.service.ts',
|
||||||
|
'methods, methods',
|
||||||
|
'src/app/toh/hero.service.promise.ts (promise-based), src/app/toh/hero.service.ts (observable-based)')
|
||||||
|
:marked
|
||||||
|
You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in
|
||||||
|
this example.
|
||||||
|
|
||||||
|
Alternatively, you can call `toPromise(success, fail)`. The Observable's `map` callback moves to the
|
||||||
|
first *success* parameter and its `catch` callback to the second *fail* parameter
|
||||||
|
in this pattern: `.toPromise(this.extractData, this.handleError)`.
|
||||||
|
|
||||||
|
The `errorHandler` forwards an error message as a failed `Promise` instead of a failed `Observable`.
|
||||||
|
|
||||||
|
The diagnostic *log to console* is just one more `then()` in the Promise chain.
|
||||||
|
|
||||||
|
You have to adjust the calling component to expect a `Promise` instead of an `Observable`:
|
||||||
|
|
||||||
|
+makeTabs(
|
||||||
|
'server-communication/ts/src/app/toh/hero-list.component.promise.ts, server-communication/ts/src/app/toh/hero-list.component.ts',
|
||||||
|
'methods, methods',
|
||||||
|
'src/app/toh/hero-list.component.promise.ts (promise-based), src/app/toh/hero-list.component.ts (observable-based)')
|
||||||
|
:marked
|
||||||
|
The only obvious difference is that you call `then()` on the returned Promise instead of `subscribe`.
|
||||||
|
Both methods take the same functional arguments.
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Here is a comparison of the `HeroService` using Promises versus Observables,
|
The less obvious but critical difference is that these two methods return very different results.
|
||||||
highlighting just the parts that are different.
|
|
||||||
+makeTabs(
|
|
||||||
'server-communication/ts/src/app/toh/hero.service.promise.ts,server-communication/ts/src/app/toh/hero.service.ts',
|
|
||||||
'methods, methods',
|
|
||||||
'src/app/toh/hero.service.promise.ts (promise-based), src/app/toh/hero.service.ts (observable-based)')
|
|
||||||
:marked
|
|
||||||
You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in
|
|
||||||
this example.
|
|
||||||
|
|
||||||
Alternatively, you can call `toPromise(success, fail)`. The Observable's `map` callback moves to the
|
The Promise-based `then()` returns another Promise. You can keep chaining
|
||||||
first *success* parameter and its `catch` callback to the second *fail* parameter
|
more `then()` and `catch()` calls, getting a new promise each time.
|
||||||
in this pattern: `.toPromise(this.extractData, this.handleError)`.
|
|
||||||
|
|
||||||
The `errorHandler` forwards an error message as a failed `Promise` instead of a failed `Observable`.
|
The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`.
|
||||||
|
It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again.
|
||||||
|
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
|
||||||
|
|
||||||
The diagnostic *log to console* is just one more `then()` in the Promise chain.
|
To understand the implications and consequences of subscriptions,
|
||||||
|
watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE)
|
||||||
You have to adjust the calling component to expect a `Promise` instead of an `Observable`:
|
or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
|
||||||
|
|
||||||
+makeTabs(
|
|
||||||
'server-communication/ts/src/app/toh/hero-list.component.promise.ts, server-communication/ts/src/app/toh/hero-list.component.ts',
|
|
||||||
'methods, methods',
|
|
||||||
'src/app/toh/hero-list.component.promise.ts (promise-based), src/app/toh/hero-list.component.ts (observable-based)')
|
|
||||||
:marked
|
|
||||||
The only obvious difference is that you call `then()` on the returned Promise instead of `subscribe`.
|
|
||||||
Both methods take the same functional arguments.
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
The less obvious but critical difference is that these two methods return very different results.
|
|
||||||
|
|
||||||
The Promise-based `then()` returns another Promise. You can keep chaining
|
|
||||||
more `then()` and `catch()` calls, getting a new promise each time.
|
|
||||||
|
|
||||||
The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`.
|
|
||||||
It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again.
|
|
||||||
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
|
|
||||||
|
|
||||||
To understand the implications and consequences of subscriptions,
|
|
||||||
watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE)
|
|
||||||
or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
|
|
||||||
|
|
||||||
h2#cors Cross-Origin Requests: Wikipedia example
|
h2#cors Cross-Origin Requests: Wikipedia example
|
||||||
:marked
|
:marked
|
||||||
You just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
|
You just learned how to make `XMLHttpRequests` using the Angular <code>Http</code> service.
|
||||||
This is the most common approach to server communication, but it doesn't work in all scenarios.
|
This is the most common approach to server communication, but it doesn't work in all scenarios.
|
||||||
|
|
||||||
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
|
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
|
||||||
|
@ -488,7 +479,7 @@ figure.image-display
|
||||||
block wikipedia-jsonp+
|
block wikipedia-jsonp+
|
||||||
:marked
|
:marked
|
||||||
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter.
|
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter.
|
||||||
The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts you to `GET` requests.
|
The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts you to `GET` requests.
|
||||||
All other HTTP methods throw an error because `JSONP` is a read-only facility.
|
All other HTTP methods throw an error because `JSONP` is a read-only facility.
|
||||||
|
|
||||||
As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
||||||
|
@ -536,8 +527,8 @@ block wikipedia-jsonp+
|
||||||
The template presents an `<input>` element *search box* to gather search terms from the user,
|
The template presents an `<input>` element *search box* to gather search terms from the user,
|
||||||
and calls a `search(term)` method after each `keyup` event.
|
and calls a `search(term)` method after each `keyup` event.
|
||||||
|
|
||||||
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
|
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
|
||||||
Observable !{_array} of string results (`Observable<string[]>`).
|
Observable array of string results (`Observable<string[]>`).
|
||||||
Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`,
|
Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`,
|
||||||
the app forwards the Observable result to the template (via `items`) where the `async` pipe
|
the app forwards the Observable result to the template (via `items`) where the `async` pipe
|
||||||
in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe)
|
in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe)
|
||||||
|
@ -706,8 +697,8 @@ a#in-mem-web-api
|
||||||
## Appendix: Tour of Heroes _in-memory web api_
|
## Appendix: Tour of Heroes _in-memory web api_
|
||||||
|
|
||||||
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
||||||
- var _heroesJsonPath = (_docsFor == 'dart' ? 'web' : 'src/app') + '/heroes.json';
|
|
||||||
+makeJson('server-communication/' + _docsFor + '/' + _heroesJsonPath, null, _heroesJsonPath)(format=".")
|
+makeJson('server-communication/ts/src/app/heroes.json', null, 'src/app/heroes.json')(format=".")
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
You wrap the heroes array in an object with a `data` property for the same reason that a data server does:
|
You wrap the heroes array in an object with a `data` property for the same reason that a data server does:
|
||||||
|
@ -717,7 +708,6 @@ a#in-mem-web-api
|
||||||
You'd set the endpoint to the JSON file like this:
|
You'd set the endpoint to the JSON file like this:
|
||||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json', 'src/app/toh/hero.service.ts')(format=".")
|
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json', 'src/app/toh/hero.service.ts')(format=".")
|
||||||
|
|
||||||
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
|
|
||||||
:marked
|
:marked
|
||||||
The *get heroes* scenario would work,
|
The *get heroes* scenario would work,
|
||||||
but since the app can't save changes to a JSON file, it needs a web API server.
|
but since the app can't save changes to a JSON file, it needs a web API server.
|
||||||
|
@ -736,9 +726,9 @@ a#in-mem-web-api
|
||||||
for configuration options, default behaviors, and limitations.
|
for configuration options, default behaviors, and limitations.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The in-memory web API gets its data from !{_a_ca_class_with} a `createDb()`
|
The in-memory web API gets its data from a custom application class with a `createDb()`
|
||||||
method that returns a map whose keys are collection names and whose values
|
method that returns a map whose keys are collection names and whose values
|
||||||
are !{_array}s of objects in those collections.
|
are arrays of objects in those collections.
|
||||||
|
|
||||||
Here's the class for this sample, based on the JSON data:
|
Here's the class for this sample, based on the JSON data:
|
||||||
+makeExample('server-communication/ts/src/app/hero-data.ts', null, 'src/app/hero-data.ts')(format=".")
|
+makeExample('server-communication/ts/src/app/hero-data.ts', null, 'src/app/hero-data.ts')(format=".")
|
||||||
|
@ -766,7 +756,7 @@ block redirect-to-web-api
|
||||||
The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
|
The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
|
||||||
while setting the metadata for the root `AppModule`. Don't call it again.
|
while setting the metadata for the root `AppModule`. Don't call it again.
|
||||||
:marked
|
:marked
|
||||||
Here is the final, revised version of <span ngio-ex>src/app/app.module.ts</span>, demonstrating these steps.
|
Here is the final, revised version of <code>src/app/app.module.ts</code>, demonstrating these steps.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.ts')
|
+makeExcerpt('src/app/app.module.ts')
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
|
|
|
@ -1,32 +1,24 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _prereq = 'node and npm';
|
|
||||||
- var _playground = 'playground';
|
|
||||||
- var _Install = 'Install';
|
|
||||||
//- npm commands
|
|
||||||
- var _install = 'install';
|
|
||||||
- var _start = 'start';
|
|
||||||
|
|
||||||
a#develop-locally
|
a#develop-locally
|
||||||
:marked
|
:marked
|
||||||
## Setup a local development environment
|
## Setup a local development environment
|
||||||
|
|
||||||
<span if-docs="ts">
|
|
||||||
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
|
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
|
||||||
It's not where you'd develop a real application.
|
It's not where you'd develop a real application.
|
||||||
You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
|
You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
|
||||||
</span>
|
|
||||||
|
|
||||||
Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
|
Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
|
||||||
maintained [on github](!{_qsRepo} "Install the github QuickStart repo").
|
maintained [on github](https://github.com/angular/quickstart "Install the github QuickStart repo").
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Make sure you have [!{_prereq} installed](#install-prerequisites "What if you don't have !{_prereq}?").
|
Make sure you have [node and npm installed](#install-prerequisites "What if you don't have node and npm?").
|
||||||
Then ...
|
Then ...
|
||||||
1. Create a project folder (you can call it `quickstart` and rename it later).
|
1. Create a project folder (you can call it `quickstart` and rename it later).
|
||||||
1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder.
|
1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder.
|
||||||
1. !{_Install} [!{_npm}](#install-prerequisites "What if you don't have !{_prereq}?") packages.
|
1. Install [npm](#install-prerequisites "What if you don't have node and npm?") packages.
|
||||||
1. Run `!{_npm} !{_start}` to launch the sample application.
|
1. Run `npm start` to launch the sample application.
|
||||||
|
|
||||||
a#clone
|
a#clone
|
||||||
:marked
|
:marked
|
||||||
|
@ -35,10 +27,10 @@ a#clone
|
||||||
Perform the _clone-to-launch_ steps with these terminal commands.
|
Perform the _clone-to-launch_ steps with these terminal commands.
|
||||||
|
|
||||||
code-example(language="sh" class="code-shell").
|
code-example(language="sh" class="code-shell").
|
||||||
git clone !{_qsRepo}.git quickstart
|
git clone https://github.com/angular/quickstart.git quickstart
|
||||||
cd quickstart
|
cd quickstart
|
||||||
!{_npm} !{_install}
|
npm install
|
||||||
!{_npm} !{_start}
|
npm start
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
|
@ -47,13 +39,13 @@ code-example(language="sh" class="code-shell").
|
||||||
a#download
|
a#download
|
||||||
:marked
|
:marked
|
||||||
### Download
|
### Download
|
||||||
<a href="!{_qsRepoZip}" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
|
<a href="https://github.com/angular/quickstart/archive/master.zip" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
|
||||||
and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
|
and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
|
||||||
|
|
||||||
code-example(language="sh" class="code-shell").
|
code-example(language="sh" class="code-shell").
|
||||||
cd quickstart
|
cd quickstart
|
||||||
!{_npm} !{_install}
|
npm install
|
||||||
!{_npm} !{_start}
|
npm start
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
|
@ -144,13 +136,13 @@ table(width="100%")
|
||||||
th File
|
th File
|
||||||
th Purpose
|
th Purpose
|
||||||
tr
|
tr
|
||||||
td <ngio-ex>app/app.component.ts</ngio-ex>
|
td <code>app/app.component.ts</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Defines the same `AppComponent` as the one in the QuickStart !{_playground}.
|
Defines the same `AppComponent` as the one in the QuickStart playground.
|
||||||
It is the **root** component of what will become a tree of nested components
|
It is the **root** component of what will become a tree of nested components
|
||||||
as the application evolves.
|
as the application evolves.
|
||||||
tr(if-docs="ts")
|
tr
|
||||||
td <code>app/app.module.ts</code>
|
td <code>app/app.module.ts</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
|
@ -158,7 +150,7 @@ table(width="100%")
|
||||||
Right now it declares only the `AppComponent`.
|
Right now it declares only the `AppComponent`.
|
||||||
Soon there will be more components to declare.
|
Soon there will be more components to declare.
|
||||||
tr
|
tr
|
||||||
td <ngio-ex>main.ts</ngio-ex>
|
td <code>main.ts</code>
|
||||||
td
|
td
|
||||||
:marked
|
:marked
|
||||||
Compiles the application with the [JIT compiler](../glossary.html#jit) and
|
Compiles the application with the [JIT compiler](../glossary.html#jit) and
|
||||||
|
@ -180,7 +172,7 @@ br
|
||||||
a#install-prerequisites
|
a#install-prerequisites
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Appendix: !{_prereq}
|
## Appendix: node and npm
|
||||||
block install-tooling
|
block install-tooling
|
||||||
:marked
|
:marked
|
||||||
Node.js and npm are essential to modern web development with Angular and other platforms.
|
Node.js and npm are essential to modern web development with Angular and other platforms.
|
||||||
|
@ -198,32 +190,31 @@ block install-tooling
|
||||||
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
|
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
|
||||||
use other versions of node and npm.
|
use other versions of node and npm.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
a#why-locally
|
||||||
a#why-locally
|
.l-main-section
|
||||||
.l-main-section
|
:marked
|
||||||
:marked
|
## Appendix: Why develop locally
|
||||||
## Appendix: Why develop locally
|
|
||||||
|
|
||||||
<live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular.
|
<live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular.
|
||||||
|
|
||||||
Links on almost every documentation page open completed samples in the browser.
|
Links on almost every documentation page open completed samples in the browser.
|
||||||
You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
|
You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
|
||||||
|
|
||||||
The [QuickStart](../quickstart.html "Angular QuickStart Playground") shows just the `AppComponent` file.
|
The [QuickStart](../quickstart.html "Angular QuickStart Playground") shows just the `AppComponent` file.
|
||||||
It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_.
|
It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_.
|
||||||
so the reader can discover Angular without distraction.
|
so the reader can discover Angular without distraction.
|
||||||
The other samples are based on the QuickStart seed.
|
The other samples are based on the QuickStart seed.
|
||||||
|
|
||||||
As much fun as this is ...
|
As much fun as this is ...
|
||||||
* you can't ship your app in plunker
|
* you can't ship your app in plunker
|
||||||
* you aren't always online when writing code
|
* you aren't always online when writing code
|
||||||
* transpiling TypeScript in the browser is slow
|
* transpiling TypeScript in the browser is slow
|
||||||
* the type support, refactoring, and code completion only work in your local IDE
|
* the type support, refactoring, and code completion only work in your local IDE
|
||||||
|
|
||||||
Use the <live-example title="QuickStart Seed in Plunker"><i>live coding</i></live-example> environment as a _playground_,
|
Use the <live-example title="QuickStart Seed in Plunker"><i>live coding</i></live-example> environment as a _playground_,
|
||||||
a place to try the documentation samples and experiment on your own.
|
a place to try the documentation samples and experiment on your own.
|
||||||
It's the perfect place to reproduce a bug when you want to
|
It's the perfect place to reproduce a bug when you want to
|
||||||
<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or
|
<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or
|
||||||
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>.
|
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>.
|
||||||
|
|
||||||
For real development, we strongly recommend [developing locally](#develop-locally).
|
For real development, we strongly recommend [developing locally](#develop-locally).
|
||||||
|
|
|
@ -566,7 +566,7 @@ a#unless
|
||||||
+makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs')
|
+makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
|
Add this directive to the `declarations` array of the AppModule.
|
||||||
|
|
||||||
Then create some HTML to try it.
|
Then create some HTML to try it.
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _JavaScript = 'JavaScript';
|
|
||||||
//- Double underscore means don't escape var, use !{__var}.
|
|
||||||
- var __chaining_op = '<code>;</code> or <code>,</code>';
|
|
||||||
- var __new_op = '<code>new</code>';
|
|
||||||
- var __objectAsMap = 'object';
|
|
||||||
|
|
||||||
//- The docs standard h4 style uppercases, making code terms unreadable. Override it.
|
//- The docs standard h4 style uppercases, making code terms unreadable. Override it.
|
||||||
style.
|
style.
|
||||||
|
@ -133,19 +128,19 @@ a#template-expressions
|
||||||
In the [property binding](#property-binding) section below,
|
In the [property binding](#property-binding) section below,
|
||||||
a template expression appears in quotes to the right of the `=` symbol as in `[property]="expression"`.
|
a template expression appears in quotes to the right of the `=` symbol as in `[property]="expression"`.
|
||||||
|
|
||||||
You write these template expressions in a language that looks like #{_JavaScript}.
|
You write these template expressions in a language that looks like JavaScript.
|
||||||
Many #{_JavaScript} expressions are legal template expressions, but not all.
|
Many JavaScript expressions are legal template expressions, but not all.
|
||||||
|
|
||||||
#{_JavaScript} expressions that have or promote side effects are prohibited,
|
JavaScript expressions that have or promote side effects are prohibited,
|
||||||
including:
|
including:
|
||||||
|
|
||||||
* assignments (`=`, `+=`, `-=`, ...)
|
* assignments (`=`, `+=`, `-=`, ...)
|
||||||
* !{__new_op}
|
* <code>new</code>
|
||||||
* chaining expressions with !{__chaining_op}
|
* chaining expressions with <code>;</code> or <code>,</code>
|
||||||
* increment and decrement operators (`++` and `--`)
|
* increment and decrement operators (`++` and `--`)
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Other notable differences from #{_JavaScript} syntax include:
|
Other notable differences from JavaScript syntax include:
|
||||||
|
|
||||||
* no support for the bitwise operators `|` and `&`
|
* no support for the bitwise operators `|` and `&`
|
||||||
* new [template expression operators](#expression-operators), such as `|` and `?.`
|
* new [template expression operators](#expression-operators), such as `|` and `?.`
|
||||||
|
@ -238,7 +233,7 @@ a#expression-guidelines
|
||||||
|
|
||||||
Dependent values should not change during a single turn of the event loop.
|
Dependent values should not change during a single turn of the event loop.
|
||||||
If an idempotent expression returns a string or a number, it returns the same string or number
|
If an idempotent expression returns a string or a number, it returns the same string or number
|
||||||
when called twice in a row. If the expression returns an object (including #{_an} `#{_Array}`),
|
when called twice in a row. If the expression returns an object (including an `array`),
|
||||||
it returns the same object *reference* when called twice in a row.
|
it returns the same object *reference* when called twice in a row.
|
||||||
|
|
||||||
a(href="#toc") back to top
|
a(href="#toc") back to top
|
||||||
|
@ -263,14 +258,14 @@ a#template-statements
|
||||||
Responding to events is the other side of Angular's "unidirectional data flow".
|
Responding to events is the other side of Angular's "unidirectional data flow".
|
||||||
You're free to change anything, anywhere, during this turn of the event loop.
|
You're free to change anything, anywhere, during this turn of the event loop.
|
||||||
|
|
||||||
Like template expressions, template *statements* use a language that looks like #{_JavaScript}.
|
Like template expressions, template *statements* use a language that looks like JavaScript.
|
||||||
The template statement parser differs from the template expression parser and
|
The template statement parser differs from the template expression parser and
|
||||||
specifically supports both basic assignment (`=`) and chaining expressions
|
specifically supports both basic assignment (`=`) and chaining expressions
|
||||||
(with !{__chaining_op}).
|
(with <code>;</code> or <code>,</code>).
|
||||||
|
|
||||||
However, certain #{_JavaScript} syntax is not allowed:
|
However, certain JavaScript syntax is not allowed:
|
||||||
|
|
||||||
* !{__new_op}
|
* <code>new</code>
|
||||||
* increment and decrement operators, `++` and `--`
|
* increment and decrement operators, `++` and `--`
|
||||||
* operator assignment, such as `+=` and `-=`
|
* operator assignment, such as `+=` and `-=`
|
||||||
* the bitwise operators `|` and `&`
|
* the bitwise operators `|` and `&`
|
||||||
|
@ -808,8 +803,8 @@ a(href="#toc") back to top
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Finally, you can bind to a specific class name.
|
Finally, you can bind to a specific class name.
|
||||||
Angular adds the class when the template expression evaluates to #{_truthy}.
|
Angular adds the class when the template expression evaluates to truthy.
|
||||||
It removes the class when the expression is #{_falsy}.
|
It removes the class when the expression is falsy.
|
||||||
|
|
||||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
|
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
|
||||||
|
|
||||||
|
@ -1127,7 +1122,7 @@ a#ngClass
|
||||||
:marked
|
:marked
|
||||||
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
|
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
|
||||||
|
|
||||||
Try binding `ngClass` to a key:value control !{__objectAsMap}.
|
Try binding `ngClass` to a key:value control object.
|
||||||
Each key of the object is a CSS class name; its value is `true` if the class should be added,
|
Each key of the object is a CSS class name; its value is `true` if the class should be added,
|
||||||
`false` if it should be removed.
|
`false` if it should be removed.
|
||||||
|
|
||||||
|
@ -1164,7 +1159,7 @@ a#ngStyle
|
||||||
:marked
|
:marked
|
||||||
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice.
|
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice.
|
||||||
|
|
||||||
Try binding `ngStyle` to a key:value control !{__objectAsMap}.
|
Try binding `ngStyle` to a key:value control object.
|
||||||
Each key of the object is a style name; its value is whatever is appropriate for that style.
|
Each key of the object is a style name; its value is whatever is appropriate for that style.
|
||||||
|
|
||||||
Consider a `setCurrentStyles` component method that sets a component property, `currentStyles`
|
Consider a `setCurrentStyles` component method that sets a component property, `currentStyles`
|
||||||
|
@ -1195,18 +1190,17 @@ a#ngModel
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.html', 'NgModel-1', '')
|
+makeExcerpt('src/app/app.component.html', 'NgModel-1', '')
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
:marked
|
||||||
:marked
|
#### _FormsModule_ is required to use _ngModel_
|
||||||
#### _FormsModule_ is required to use _ngModel_
|
|
||||||
|
|
||||||
Before using the `ngModel` directive in a two-way data binding,
|
Before using the `ngModel` directive in a two-way data binding,
|
||||||
you must import the `FormsModule` and add it to the Angular module's `imports` list.
|
you must import the `FormsModule` and add it to the Angular module's `imports` list.
|
||||||
Learn more about the `FormsModule` and `ngModel` in the
|
Learn more about the `FormsModule` and `ngModel` in the
|
||||||
[Forms](../guide/forms.html#ngModel) guide.
|
[Forms](../guide/forms.html#ngModel) guide.
|
||||||
|
|
||||||
Here's how to import the `FormsModule` to make `[(ngModel)]` available.
|
Here's how to import the `FormsModule` to make `[(ngModel)]` available.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '')
|
+makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
#### Inside <span class="syntax">[(ngModel)]</span>
|
#### Inside <span class="syntax">[(ngModel)]</span>
|
||||||
|
@ -1317,8 +1311,8 @@ a#ngIf
|
||||||
Don't forget the asterisk (`*`) in front of `ngIf`.
|
Don't forget the asterisk (`*`) in front of `ngIf`.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When the `isActive` expression returns a #{_truthy} value, `NgIf` adds the `HeroDetailComponent` to the DOM.
|
When the `isActive` expression returns a truthy value, `NgIf` adds the `HeroDetailComponent` to the DOM.
|
||||||
When the expression is #{_falsy}, `NgIf` removes the `HeroDetailComponent`
|
When the expression is falsy, `NgIf` removes the `HeroDetailComponent`
|
||||||
from the DOM, destroying that component and all of its sub-components.
|
from the DOM, destroying that component and all of its sub-components.
|
||||||
|
|
||||||
#### Show/hide is not the same thing
|
#### Show/hide is not the same thing
|
||||||
|
@ -1397,7 +1391,7 @@ a#microsyntax
|
||||||
It's a *microsyntax* — a little language of its own that Angular interprets.
|
It's a *microsyntax* — a little language of its own that Angular interprets.
|
||||||
The string `"let hero of heroes"` means:
|
The string `"let hero of heroes"` means:
|
||||||
|
|
||||||
> *Take each hero in the `heroes` #{_array}, store it in the local `hero` looping variable, and
|
> *Take each hero in the `heroes` array, store it in the local `hero` looping variable, and
|
||||||
make it available to the templated HTML for each iteration.*
|
make it available to the templated HTML for each iteration.*
|
||||||
|
|
||||||
Angular translates this instruction into a `<template>` around the host element,
|
Angular translates this instruction into a `<template>` around the host element,
|
||||||
|
@ -1412,8 +1406,8 @@ a#template-input-variables
|
||||||
### Template input variables
|
### Template input variables
|
||||||
|
|
||||||
The `let` keyword before `hero` creates a _template input variable_ called `hero`.
|
The `let` keyword before `hero` creates a _template input variable_ called `hero`.
|
||||||
The `ngFor` directive iterates over the `heroes` #{_array} returned by the parent component's `heroes` property
|
The `ngFor` directive iterates over the `heroes` array returned by the parent component's `heroes` property
|
||||||
and sets `hero` to the current item from the #{_array} during each iteration.
|
and sets `hero` to the current item from the array during each iteration.
|
||||||
|
|
||||||
You reference the `hero` input variable within the `ngFor` host element
|
You reference the `hero` input variable within the `ngFor` host element
|
||||||
(and within its descendents) to access the hero's properties.
|
(and within its descendents) to access the hero's properties.
|
||||||
|
@ -1655,13 +1649,13 @@ a#inputs-outputs
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Alternatively, you can identify members in the `inputs` and `outputs` #{_array}s
|
Alternatively, you can identify members in the `inputs` and `outputs` arrays
|
||||||
of the directive metadata, as in this example:
|
of the directive metadata, as in this example:
|
||||||
|
|
||||||
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
|
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You can specify an input/output property either with a decorator or in a metadata #{_array}.
|
You can specify an input/output property either with a decorator or in a metadata array.
|
||||||
Don't do both!
|
Don't do both!
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -1709,7 +1703,7 @@ h3#aliasing-io Aliasing input/output properties
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
You can also alias property names in the `inputs` and `outputs` #{_array}s.
|
You can also alias property names in the `inputs` and `outputs` arrays.
|
||||||
You write a colon-delimited (`:`) string with
|
You write a colon-delimited (`:`) string with
|
||||||
the directive property name on the *left* and the public alias on the *right*:
|
the directive property name on the *left* and the public alias on the *right*:
|
||||||
|
|
||||||
|
@ -1722,7 +1716,7 @@ a#expression-operators
|
||||||
:marked
|
:marked
|
||||||
## Template expression operators
|
## Template expression operators
|
||||||
|
|
||||||
The template expression language employs a subset of #{_JavaScript} syntax supplemented with a few special operators
|
The template expression language employs a subset of JavaScript syntax supplemented with a few special operators
|
||||||
for specific scenarios. The next sections cover two of these operators: _pipe_ and _safe navigation operator_.
|
for specific scenarios. The next sections cover two of these operators: _pipe_ and _safe navigation operator_.
|
||||||
|
|
||||||
a#pipe
|
a#pipe
|
||||||
|
@ -1818,12 +1812,11 @@ code-example(format="nocode").
|
||||||
|
|
||||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".")
|
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".")
|
||||||
|
|
||||||
block safe-op-alt
|
:marked
|
||||||
:marked
|
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
|
||||||
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
|
when it encounters the first null.
|
||||||
when it encounters the first null.
|
|
||||||
|
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
|
||||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
These approaches have merit but can be cumbersome, especially if the property path is long.
|
These approaches have merit but can be cumbersome, especially if the property path is long.
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _JavaScript = 'JavaScript';
|
|
||||||
//- Double underscore means don't escape var, use !{__var}.
|
|
||||||
- var __chaining_op = '<code>;</code> or <code>,</code>';
|
|
||||||
- var __new_op = '<code>new</code>';
|
|
||||||
- var __objectAsMap = 'object';
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This guide offers tips and techniques for testing Angular applications.
|
This guide offers tips and techniques for testing Angular applications.
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
- var vers = current.path[2]
|
- var vers = current.path[2]
|
||||||
|
|
||||||
.clearfix
|
.clearfix
|
||||||
a.card.c4(href="/docs/#{lang}/#{vers}/quickstart.html")
|
a.card.c4(href="/docs/ts/latest/quickstart.html")
|
||||||
h2.text-headline.text-uppercase Quickstart
|
h2.text-headline.text-uppercase Quickstart
|
||||||
p A short beginner guide explaining the basic concepts of Angular
|
p A short beginner guide explaining the basic concepts of Angular
|
||||||
|
|
||||||
footer View Quickstart
|
footer View Quickstart
|
||||||
|
|
||||||
a.card.c4(href="/docs/#{lang}/#{vers}/guide/")
|
a.card.c4(href="/docs/ts/latest/guide/")
|
||||||
h2.text-headline.text-uppercase Developer Guide
|
h2.text-headline.text-uppercase Developer Guide
|
||||||
p An intermediate development guide covering all major features of Angular
|
p An intermediate development guide covering all major features of Angular
|
||||||
|
|
||||||
footer View Guide
|
footer View Guide
|
||||||
|
|
||||||
a.card.c4(href="/docs/#{lang}/#{vers}/api/")
|
a.card.c4(href="/docs/ts/latest/api/")
|
||||||
h2.text-headline.text-uppercase API Reference
|
h2.text-headline.text-uppercase API Reference
|
||||||
p An advanced reference of all Angular Classes, Methods, etc.
|
p An advanced reference of all Angular Classes, Methods, etc.
|
||||||
|
|
||||||
|
@ -24,31 +24,31 @@
|
||||||
h4 Advanced Documentation
|
h4 Advanced Documentation
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/animations.html") Animations
|
a(href="/docs/ts/latest/guide/animations.html") Animations
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/attribute-directives.html") Attribute Directives
|
a(href="/docs/ts/latest/guide/attribute-directives.html") Attribute Directives
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/browser-support.html") Browser Support
|
a(href="/docs/ts/latest/guide/browser-support.html") Browser Support
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/component-styles.html") Component Styles
|
a(href="/docs/ts/latest/guide/component-styles.html") Component Styles
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/deployment.html") Deployment
|
a(href="/docs/ts/latest/guide/deployment.html") Deployment
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/guide/animations.html") View All...
|
a(href="/docs/ts/latest/guide/animations.html") View All...
|
||||||
|
|
||||||
.c4.secondary-content-list
|
.c4.secondary-content-list
|
||||||
h4 Cookbook
|
h4 Cookbook
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/cookbook/aot-compiler.html") Ahead-of-time Compilation
|
a(href="/docs/ts/latest/cookbook/aot-compiler.html") Ahead-of-time Compilation
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/cookbook/ajs-quick-reference.html") AngularJS to Angular
|
a(href="/docs/ts/latest/cookbook/ajs-quick-reference.html") AngularJS to Angular
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/cookbook/component-communication.html") Component Interaction
|
a(href="/docs/ts/latest/cookbook/component-communication.html") Component Interaction
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/cookbook/dependency-injection.html") Dependency Injection
|
a(href="/docs/ts/latest/cookbook/dependency-injection.html") Dependency Injection
|
||||||
li
|
li
|
||||||
a(href="/docs/#{lang}/#{vers}/cookbook/") View All...
|
a(href="/docs/ts/latest/cookbook/") View All...
|
||||||
|
|
||||||
.c4.secondary-content-list
|
.c4.secondary-content-list
|
||||||
h4 Tools & Libraries
|
h4 Tools & Libraries
|
||||||
|
|
|
@ -15,9 +15,8 @@ block qs-src-online-and-local
|
||||||
and prepare for development of a real Angular application.
|
and prepare for development of a real Angular application.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Every component begins with an `@Component` [!{_decorator}](glossary.html#!{_decorator} '"!{_decorator}" explained')
|
Every component begins with an `@Component` [decorator](glossary.html#decorator '"decorator" explained')
|
||||||
<span if-docs="ts">function</span> that
|
function that takes a _metadata_ object. The metadata object describes how the HTML template and component class work together.
|
||||||
<span if-docs="ts">takes a _metadata_ object. The metadata object</span> describes how the HTML template and component class work together.
|
|
||||||
|
|
||||||
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
|
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
|
||||||
+makeExample('src/index.html','my-app','index.html (inside <body>)')(format='.')
|
+makeExample('src/index.html','my-app','index.html (inside <body>)')(format='.')
|
||||||
|
@ -28,19 +27,18 @@ block qs-src-online-and-local
|
||||||
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
|
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
|
||||||
Interpolation binding is one of many Angular features you'll discover in this documentation.
|
Interpolation binding is one of many Angular features you'll discover in this documentation.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
|
||||||
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
|
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header A word about TypeScript
|
header A word about TypeScript
|
||||||
p.
|
p.
|
||||||
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
|
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
|
||||||
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how.
|
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
### Next step
|
### Next step
|
||||||
|
|
||||||
Start [**learning Angular**](guide/learning-angular.html "Learning Angular").
|
Start [**learning Angular**](guide/learning-angular.html "Learning Angular").
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ include ../_util-fns
|
||||||
:marked
|
:marked
|
||||||
## Setup to develop locally
|
## Setup to develop locally
|
||||||
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
||||||
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>.
|
named <code>angular-tour-of-heroes</code>.
|
||||||
|
|
||||||
The file structure should look like this:
|
The file structure should look like this:
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ include ../_util-fns
|
||||||
and makes it easy to unit-test components with a mock service.
|
and makes it easy to unit-test components with a mock service.
|
||||||
|
|
||||||
Because data services are invariably asynchronous,
|
Because data services are invariably asynchronous,
|
||||||
you'll finish the page with a *!{_Promise}*-based version of the data service.
|
you'll finish the page with a *Promise*-based version of the data service.
|
||||||
|
|
||||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ code-example(format="nocode").
|
||||||
|
|
||||||
<a id="async"></a>
|
<a id="async"></a>
|
||||||
:marked
|
:marked
|
||||||
## Async services and !{_Promise}s
|
## Async services and Promises
|
||||||
The `HeroService` returns a list of mock heroes immediately;
|
The `HeroService` returns a list of mock heroes immediately;
|
||||||
its `getHeroes()` signature is synchronous.
|
its `getHeroes()` signature is synchronous.
|
||||||
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
|
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
|
||||||
|
@ -240,12 +240,12 @@ code-example(format="nocode").
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
To coordinate the view with the response,
|
To coordinate the view with the response,
|
||||||
you can use *!{_Promise}s*, which is an asynchronous
|
you can use *Promises*, which is an asynchronous
|
||||||
technique that changes the signature of the `getHeroes()` method.
|
technique that changes the signature of the `getHeroes()` method.
|
||||||
|
|
||||||
### The hero service makes a !{_Promise}
|
### The hero service makes a Promise
|
||||||
|
|
||||||
A *!{_Promise}* essentially promises to call back when the results are ready.
|
A *Promise* essentially promises to call back when the results are ready.
|
||||||
You ask an asynchronous service to do some work and give it a callback function.
|
You ask an asynchronous service to do some work and give it a callback function.
|
||||||
The service does that work and eventually calls the function with the results or an error.
|
The service does that work and eventually calls the function with the results or an error.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -255,22 +255,22 @@ code-example(format="nocode").
|
||||||
[Exploring ES6](http://http://exploringjs.com/es6.html).
|
[Exploring ES6](http://http://exploringjs.com/es6.html).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Update the `HeroService` with this !{_Promise}-returning `getHeroes()` method:
|
Update the `HeroService` with this Promise-returning `getHeroes()` method:
|
||||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".")
|
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
|
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
|
||||||
by returning an *immediately resolved !{_Promise}* with the mock heroes as the result.
|
by returning an *immediately resolved Promise* with the mock heroes as the result.
|
||||||
|
|
||||||
### Act on the !{_Promise}
|
### Act on the Promise
|
||||||
|
|
||||||
As a result of the change to `HeroService`, `this.heroes` is now set to a !{_Promise} rather than an array of heroes.
|
As a result of the change to `HeroService`, `this.heroes` is now set to a `Promise` rather than an array of heroes.
|
||||||
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".")
|
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You have to change the implementation to *act on the !{_Promise} when it resolves*.
|
You have to change the implementation to *act on the `Promise` when it resolves*.
|
||||||
When the !{_Promise} resolves successfully, you'll have heroes to display.
|
When the `Promise` resolves successfully, you'll have heroes to display.
|
||||||
|
|
||||||
Pass the callback function as an argument to the !{_Promise}'s `then()` method:
|
Pass the callback function as an argument to the Promise's `then()` method:
|
||||||
+makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".")
|
+makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".")
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
|
@ -329,7 +329,7 @@ code-example(format="nocode").
|
||||||
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
|
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
|
||||||
* You defined the `HeroService` as a provider for the `AppComponent`.
|
* You defined the `HeroService` as a provider for the `AppComponent`.
|
||||||
* You created mock hero data and imported them into the service.
|
* You created mock hero data and imported them into the service.
|
||||||
* You designed the service to return a !{_Promise} and the component to get the data from the !{_Promise}.
|
* You designed the service to return a Promise and the component to get the data from the Promise.
|
||||||
|
|
||||||
Your app should look like this <live-example></live-example>.
|
Your app should look like this <live-example></live-example>.
|
||||||
|
|
||||||
|
@ -348,8 +348,8 @@ code-example(format="nocode").
|
||||||
import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`.
|
import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`.
|
||||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".")
|
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Like `getHeroes()`, it also returns a !{_Promise}.
|
Like `getHeroes()`, it also returns a `Promise`.
|
||||||
But this !{_Promise} waits two seconds before resolving the !{_Promise} with mock heroes.
|
But this Promise waits two seconds before resolving the Promise with mock heroes.
|
||||||
|
|
||||||
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
|
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
|
||||||
and see how the app behaves.
|
and see how the app behaves.
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
- var _example = 'toh-5';
|
|
||||||
|
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _appRoutingTsVsAppComp = 'app.module.ts'
|
|
||||||
- var _RoutesVsAtRouteConfig = 'Routes'
|
|
||||||
- var _RouterModuleVsRouterDirectives = 'RouterModule'
|
|
||||||
- var _redirect = 'redirect'
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
There are new requirements for the Tour of Heroes app:
|
There are new requirements for the Tour of Heroes app:
|
||||||
|
@ -30,40 +24,38 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
include ../../../_includes/_see-addr-bar
|
||||||
include ../../../_includes/_see-addr-bar
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Where you left off
|
## Where you left off
|
||||||
Before continuing with the Tour of Heroes, verify that you have the following structure.
|
Before continuing with the Tour of Heroes, verify that you have the following structure.
|
||||||
|
|
||||||
block intro-file-tree
|
|
||||||
.filetree
|
|
||||||
.file angular-tour-of-heroes
|
|
||||||
.children
|
|
||||||
.file src
|
|
||||||
.children
|
|
||||||
.file app
|
|
||||||
.children
|
|
||||||
.file app.component.ts
|
|
||||||
.file app.module.ts
|
|
||||||
.file hero.service.ts
|
|
||||||
.file hero.ts
|
|
||||||
.file hero-detail.component.ts
|
|
||||||
.file mock-heroes.ts
|
|
||||||
.file main.ts
|
|
||||||
.file index.html
|
|
||||||
.file styles.css
|
|
||||||
.file systemjs.config.js
|
|
||||||
.file tsconfig.json
|
|
||||||
.file node_modules ...
|
|
||||||
.file package.json
|
|
||||||
|
|
||||||
block keep-app-running
|
.filetree
|
||||||
:marked
|
.file angular-tour-of-heroes
|
||||||
## Keep the app transpiling and running
|
.children
|
||||||
Enter the following command in the terminal window:
|
.file src
|
||||||
|
.children
|
||||||
|
.file app
|
||||||
|
.children
|
||||||
|
.file app.component.ts
|
||||||
|
.file app.module.ts
|
||||||
|
.file hero.service.ts
|
||||||
|
.file hero.ts
|
||||||
|
.file hero-detail.component.ts
|
||||||
|
.file mock-heroes.ts
|
||||||
|
.file main.ts
|
||||||
|
.file index.html
|
||||||
|
.file styles.css
|
||||||
|
.file systemjs.config.js
|
||||||
|
.file tsconfig.json
|
||||||
|
.file node_modules ...
|
||||||
|
.file package.json
|
||||||
|
|
||||||
|
:marked
|
||||||
|
## Keep the app transpiling and running
|
||||||
|
Enter the following command in the terminal window:
|
||||||
|
|
||||||
code-example(language="sh" class="code-shell").
|
code-example(language="sh" class="code-shell").
|
||||||
npm start
|
npm start
|
||||||
|
@ -108,11 +100,11 @@ block keep-app-running
|
||||||
and create a separate `AppComponent` shell.
|
and create a separate `AppComponent` shell.
|
||||||
|
|
||||||
Do the following:
|
Do the following:
|
||||||
* Rename the <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>.
|
* Rename the <code>app.component.ts</code> file to <code>heroes.component.ts</code>.
|
||||||
* Rename the `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file).
|
* Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file).
|
||||||
* Rename the selector `my-app` to `my-heroes`.
|
* Rename the selector `my-app` as `my-heroes`.
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts (showing renamings only)', 'renaming')
|
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'renaming', 'src/app/heroes.component.ts (showing renamings only)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Create *AppComponent*
|
### Create *AppComponent*
|
||||||
|
@ -122,27 +114,26 @@ block keep-app-running
|
||||||
|
|
||||||
Perform these steps:
|
Perform these steps:
|
||||||
|
|
||||||
* Create the file <span ngio-ex>src/app/app.component.ts</span>.
|
* Create the file <code>src/app/app.component.ts</code>.
|
||||||
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
|
* Define an exported `AppComponent` class.
|
||||||
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
|
* Add an `@Component` decorator above the class with a `my-app` selector.
|
||||||
* Move the following from `HeroesComponent` to `AppComponent`:
|
* Move the following from `HeroesComponent` to `AppComponent`:
|
||||||
* `title` class property.
|
* `title` class property.
|
||||||
* `@Component` template `<h1>` element, which contains a binding to `title`.
|
* `@Component` template `<h1>` element, which contains a binding to `title`.
|
||||||
* Add a `<my-heroes>` element to the app template just below the heading so you still see the heroes.
|
* Add a `<my-heroes>` element to the app template just below the heading so you still see the heroes.
|
||||||
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
|
* Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `<my-heroes>` tags.
|
||||||
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because you'll need it in every other view.
|
* Add `HeroService` to the `providers` array of `AppModule` because you'll need it in every other view.
|
||||||
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it was promoted.
|
* Remove `HeroService` from the `HeroesComponent` `providers` array since it was promoted.
|
||||||
* Add the supporting `import` statements for `AppComponent`.
|
* Add the supporting `import` statements for `AppComponent`.
|
||||||
|
|
||||||
The first draft looks like this:
|
The first draft looks like this:
|
||||||
|
|
||||||
block app-comp-v1
|
+makeTabs(
|
||||||
+makeTabs(
|
`toh-5/ts/src/app/app.component.1.ts,
|
||||||
`toh-5/ts/src/app/app.component.1.ts,
|
toh-5/ts/src/app/app.module.1.ts`,
|
||||||
toh-5/ts/src/app/app.module.1.ts`,
|
',',
|
||||||
',',
|
`src/app/app.component.ts (v1),
|
||||||
`src/app/app.component.ts (v1),
|
src/app/app.module.ts (v1)`)
|
||||||
src/app/app.module.ts (v1)`)
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The app still runs and displays heroes.
|
The app still runs and displays heroes.
|
||||||
|
@ -155,12 +146,11 @@ block app-comp-v1
|
||||||
|
|
||||||
Use the Angular router to enable navigation.
|
Use the Angular router to enable navigation.
|
||||||
|
|
||||||
block angular-router
|
:marked
|
||||||
:marked
|
The Angular router is an external, optional Angular NgModule called `RouterModule`.
|
||||||
The Angular router is an external, optional Angular NgModule called `RouterModule`.
|
The router is a combination of multiple provided services (`RouterModule`),
|
||||||
The router is a combination of multiple provided services (`RouterModule`),
|
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
|
||||||
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
|
and a configuration (`Routes`). You'll configure the routes first.
|
||||||
and a configuration (`Routes`). You'll configure the routes first.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### *<base href>*
|
### *<base href>*
|
||||||
|
@ -169,7 +159,7 @@ block angular-router
|
||||||
(or a script that dynamically sets this element)
|
(or a script that dynamically sets this element)
|
||||||
at the top of the `<head>` section.
|
at the top of the `<head>` section.
|
||||||
|
|
||||||
+makeExcerpt('src/index.html', 'base-href')
|
+makeExample('toh-5/ts/src/index.html', 'base-href', 'src/index.html (base-href)')
|
||||||
|
|
||||||
.callout.is-important
|
.callout.is-important
|
||||||
header base href is essential
|
header base href is essential
|
||||||
|
@ -179,11 +169,10 @@ block angular-router
|
||||||
|
|
||||||
|
|
||||||
a#configure-routes
|
a#configure-routes
|
||||||
block router-config-intro
|
:marked
|
||||||
:marked
|
### Configure routes
|
||||||
### Configure routes
|
|
||||||
|
|
||||||
Create a configuration file for the app routes.
|
Create a configuration file for the app routes.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
*Routes* tell the router which views to display when a user clicks a link or
|
*Routes* tell the router which views to display when a user clicks a link or
|
||||||
|
@ -191,49 +180,42 @@ block router-config-intro
|
||||||
|
|
||||||
Define the first route as a route to the heroes component.
|
Define the first route as a route to the heroes component.
|
||||||
|
|
||||||
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts'
|
+makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)')
|
||||||
+makeExcerpt('src/app/' + _file + ' (heroes route)', 'heroes')
|
|
||||||
|
|
||||||
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
|
|
||||||
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
|
|
||||||
:marked
|
:marked
|
||||||
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
The `Routes` are an array of *route definitions*.
|
||||||
|
|
||||||
This route definition has the following parts:
|
This route definition has the following parts:
|
||||||
|
|
||||||
- *Path*: The router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}heroes`).
|
- *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`).
|
||||||
<li if-docs="dart"> *Name*: The official name of the route;
|
|
||||||
it must begin with a capital letter to avoid confusion with the path (`Heroes`).</li>
|
|
||||||
- *Component*: The component that the router should create when navigating to this route (`HeroesComponent`).
|
- *Component*: The component that the router should create when navigating to this route (`HeroesComponent`).
|
||||||
|
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Read more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing & Navigation](../guide/router.html) page.
|
Read more about defining routes with `Routes` in the [Routing & Navigation](../guide/router.html) page.
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
:marked
|
||||||
|
### Make the router available
|
||||||
|
|
||||||
|
Import the `RouterModule` and add it to the `AppModule` imports array.
|
||||||
|
|
||||||
|
+makeExample('toh-5/ts/src/app/app.module.2.ts', '', 'src/app/app.module.ts (app routing)')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
### Make the router available
|
The `forRoot()` method is called because a configured router is provided at the app's root.
|
||||||
|
The `forRoot()` method supplies the Router service providers and directives needed for routing, and
|
||||||
|
performs the initial navigation based on the current browser URL.
|
||||||
|
|
||||||
Import the `RouterModule` and add it to the `AppModule` imports !{_array}.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.2.ts (app routing)', '')
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
The `forRoot()` method is called because a configured router is provided at the app's root.
|
|
||||||
The `forRoot()` method supplies the Router service providers and directives needed for routing, and
|
|
||||||
performs the initial navigation based on the current browser URL.
|
|
||||||
|
|
||||||
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
|
|
||||||
:marked
|
:marked
|
||||||
### Router outlet
|
### Router outlet
|
||||||
|
|
||||||
If you paste the path, `/heroes`, into the browser address bar at the end of the URL,
|
If you paste the path, `/heroes`, into the browser address bar at the end of the URL,
|
||||||
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
the router should match it to the `heroes` route and display the `HeroesComponent`.
|
||||||
However, you have to tell the router where to display the component.
|
However, you have to tell the router where to display the component.
|
||||||
To do this, you can add a `<router-outlet>` element at the end of the template.
|
To do this, you can add a `<router-outlet>` element at the end of the template.
|
||||||
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
`RouterOutlet` is one of the directives provided by the `RouterModule`.
|
||||||
The router displays each component immediately below the `<router-outlet>` as users navigate through the app.
|
The router displays each component immediately below the `<router-outlet>` as users navigate through the app.
|
||||||
|
|
||||||
### Router links
|
### Router links
|
||||||
|
@ -243,21 +225,20 @@ block router-config-intro
|
||||||
|
|
||||||
The revised template looks like this:
|
The revised template looks like this:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.1.ts', 'template-v2')
|
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v2', 'src/app/app.component.ts (template-v2)')
|
||||||
|
|
||||||
block routerLink
|
:marked
|
||||||
|
Note the `routerLink` binding in the anchor tag.
|
||||||
|
The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string
|
||||||
|
that tells the router where to navigate when the user clicks the link.
|
||||||
|
|
||||||
|
Since the link is not dynamic, a routing instruction is defined with a one-time binding to the route path.
|
||||||
|
Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Note the `routerLink` binding in the anchor tag.
|
Read more about dynamic router links and the link parameters array
|
||||||
The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string
|
in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the
|
||||||
that tells the router where to navigate when the user clicks the link.
|
[Routing & Navigation](../guide/router.html#) page.
|
||||||
|
|
||||||
Since the link is not dynamic, a routing instruction is defined with a one-time binding to the route path.
|
|
||||||
Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
Read more about dynamic router links and the link parameters array
|
|
||||||
in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the
|
|
||||||
[Routing & Navigation](../guide/router.html#) page.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser. The browser displays the app title and heroes link, but not the heroes list.
|
Refresh the browser. The browser displays the app title and heroes link, but not the heroes list.
|
||||||
|
@ -274,7 +255,7 @@ block routerLink
|
||||||
|
|
||||||
`AppComponent` now looks like this:
|
`AppComponent` now looks like this:
|
||||||
|
|
||||||
+makeExample('src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)')
|
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The *AppComponent* is now attached to a router and displays routed views.
|
The *AppComponent* is now attached to a router and displays routed views.
|
||||||
|
@ -287,25 +268,23 @@ block routerLink
|
||||||
Routing only makes sense when multiple views exist.
|
Routing only makes sense when multiple views exist.
|
||||||
To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from.
|
To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from.
|
||||||
|
|
||||||
+makeExcerpt('src/app/dashboard.component.1.ts (v1)', '')
|
+makeExample('toh-5/ts/src/app/dashboard.component.1.ts', '', 'src/app/dashboard.component.ts (v1)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You'll make this component more useful later.
|
You'll make this component more useful later.
|
||||||
|
|
||||||
### Configure the dashboard route
|
### Configure the dashboard route
|
||||||
|
|
||||||
To teach `!{_appRoutingTsVsAppComp}` to navigate to the dashboard,
|
To teach `app.module.ts` to navigate to the dashboard,
|
||||||
import the dashboard component and
|
import the dashboard component and
|
||||||
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
add the following route definition to the `Routes` array of definitions.
|
||||||
|
|
||||||
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'src/app/app.module.3.ts'
|
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)')
|
||||||
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
|
|
||||||
|
|
||||||
+ifDocsFor('ts|js')
|
:marked
|
||||||
:marked
|
Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
|
||||||
Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.ts', 'dashboard')
|
+makeExample('toh-5/ts/src/app/app.module.ts', 'dashboard', 'src/app/app.module.ts (dashboard)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add a !{_redirect} route
|
### Add a !{_redirect} route
|
||||||
|
@ -314,25 +293,23 @@ block routerLink
|
||||||
When the app starts, it should show the dashboard and
|
When the app starts, it should show the dashboard and
|
||||||
display a `/dashboard` URL in the browser address bar.
|
display a `/dashboard` URL in the browser address bar.
|
||||||
|
|
||||||
block redirect-vs-use-as-default
|
:marked
|
||||||
|
To make this happen, use a redirect route. Add the following
|
||||||
|
to the array of route definitions:
|
||||||
|
|
||||||
|
+makeExample('toh-5/ts/src/app/app.module.3.ts','redirect', 'src/app/app.module.ts (redirect)')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
To make this happen, use a redirect route. Add the following
|
Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section
|
||||||
to the array of route definitions:
|
of the [Routing & Navigation](../guide/router.html#) page.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.module.3.ts','redirect')
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section
|
|
||||||
of the [Routing & Navigation](../guide/router.html#) page.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add navigation to the template
|
### Add navigation to the template
|
||||||
|
|
||||||
Add a dashboard navigation link to the template, just above the *Heroes* link.
|
Add a dashboard navigation link to the template, just above the *Heroes* link.
|
||||||
|
|
||||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)')
|
||||||
+makeExcerpt('src/app/app.component' + _vers + '.ts', 'template-v3')
|
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
|
@ -349,15 +326,12 @@ block redirect-vs-use-as-default
|
||||||
Replace the `template` metadata with a `templateUrl` property that points to a new
|
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||||||
template file.
|
template file.
|
||||||
|
|
||||||
+makeExcerpt('src/app/dashboard.component.ts', 'metadata')
|
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'metadata', 'src/app/dashboard.component.ts (metadata)')
|
||||||
|
|
||||||
block templateUrl-path-resolution
|
|
||||||
//- N/A for TS
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Create that file with this content:
|
Create that file with this content:
|
||||||
|
|
||||||
+makeExample('src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html')
|
+makeExample('toh-5/ts/src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
`*ngFor` is used again to iterate over a list of heroes and display their names.
|
`*ngFor` is used again to iterate over a list of heroes and display their names.
|
||||||
|
@ -365,32 +339,32 @@ block templateUrl-path-resolution
|
||||||
|
|
||||||
### Sharing the *HeroService*
|
### Sharing the *HeroService*
|
||||||
|
|
||||||
To populate the component's `heroes` !{_array}, you can re-use the `HeroService`.
|
To populate the component's `heroes` array, you can re-use the `HeroService`.
|
||||||
|
|
||||||
Earlier, you removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
Earlier, you removed the `HeroService` from the `providers` array of `HeroesComponent`
|
||||||
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
and added it to the `providers` array of `AppModule`.
|
||||||
That move created a singleton `HeroService` instance, available to all components of the app.
|
That move created a singleton `HeroService` instance, available to all components of the app.
|
||||||
Angular injects `HeroService` and you can use it in the `DashboardComponent`.
|
Angular injects `HeroService` and you can use it in the `DashboardComponent`.
|
||||||
|
|
||||||
### Get heroes
|
### Get heroes
|
||||||
|
|
||||||
In <span ngio-ex>dashboard.component.ts</span>, add the following `import` statements.
|
In <code>dashboard.component.ts</code>, add the following `import` statements.
|
||||||
|
|
||||||
+makeExcerpt('src/app/dashboard.component.ts','imports')
|
+makeExample('toh-5/ts/src/app/dashboard.component.ts','imports', 'src/app/dashboard.component.ts (imports)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Now create the `DashboardComponent` class like this:
|
Now create the `DashboardComponent` class like this:
|
||||||
|
|
||||||
+makeExcerpt('src/app/dashboard.component.ts (class)', 'class')
|
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'class', 'src/app/dashboard.component.ts (class)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This kind of logic is also used in the `HeroesComponent`:
|
This kind of logic is also used in the `HeroesComponent`:
|
||||||
|
|
||||||
* Define a `heroes` !{_array} property.
|
* Define a `heroes` array property.
|
||||||
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
* Inject the `HeroService` in the constructor and hold it in a private `heroService` field.
|
||||||
* Call the service to get heroes inside the Angular `ngOnInit()` lifecycle hook.
|
* Call the service to get heroes inside the Angular `ngOnInit()` lifecycle hook.
|
||||||
|
|
||||||
In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice()` method</span>.
|
In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th) with the `Array.slice` method.
|
||||||
|
|
||||||
Refresh the browser to see four hero names in the new dashboard.
|
Refresh the browser to see four hero names in the new dashboard.
|
||||||
|
|
||||||
|
@ -407,7 +381,7 @@ block templateUrl-path-resolution
|
||||||
|
|
||||||
### Routing to a hero detail
|
### Routing to a hero detail
|
||||||
|
|
||||||
You can add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}`, where the other routes are configured.
|
You can add a route to the `HeroDetailComponent` in `app.module.ts`, where the other routes are configured.
|
||||||
|
|
||||||
The new route is unusual in that you must tell the `HeroDetailComponent` which hero to show.
|
The new route is unusual in that you must tell the `HeroDetailComponent` which hero to show.
|
||||||
You didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
You didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||||||
|
@ -437,17 +411,15 @@ code-example(format="nocode").
|
||||||
|
|
||||||
Use the following *route definition*.
|
Use the following *route definition*.
|
||||||
|
|
||||||
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
|
+makeExample('toh-5/ts/src/app/app.module.3.ts','hero-detail', 'src/app/app.module.ts (hero detail)')
|
||||||
+makeExcerpt(_file + ' (hero detail)','hero-detail')
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id`
|
The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id`
|
||||||
when navigating to the `HeroDetailComponent`.
|
when navigating to the `HeroDetailComponent`.
|
||||||
|
|
||||||
+ifDocsFor('dart')
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
Be sure to import the hero detail component before creating this route.
|
||||||
Be sure to import the hero detail component before creating this route.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You're finished with the app routes.
|
You're finished with the app routes.
|
||||||
|
@ -471,68 +443,59 @@ code-example(format="nocode").
|
||||||
The template won't change. Hero names will display the same way.
|
The template won't change. Hero names will display the same way.
|
||||||
The major changes are driven by how you get hero names.
|
The major changes are driven by how you get hero names.
|
||||||
|
|
||||||
block route-params
|
:marked
|
||||||
:marked
|
You'll no longer receive the hero in a parent component property binding.
|
||||||
You'll no longer receive the hero in a parent component property binding.
|
The new `HeroDetailComponent` should take the `id` parameter from the `params` Observable
|
||||||
The new `HeroDetailComponent` should take the `id` parameter from the `params` Observable
|
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
|
||||||
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Add the following imports:
|
Add the following imports:
|
||||||
|
|
||||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
+makeExample('toh-5/ts/src/app/hero-detail.component.1.ts', 'added-imports', 'src/app/hero-detail.component.ts')
|
||||||
+makeExcerpt('src/app/hero-detail.component' + _vers + '.ts', 'added-imports', 'src/app/hero-detail.component')
|
|
||||||
|
|
||||||
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
|
|
||||||
:marked
|
:marked
|
||||||
Inject the `!{_ActivatedRoute}`, `HeroService`, and `Location` services
|
Inject the `ActivatedRoute`, `HeroService`, and `Location` services
|
||||||
into the constructor, saving their values in private fields:
|
into the constructor, saving their values in private fields:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts (constructor)', 'ctor')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ctor', 'src/app/hero-detail.component.ts (constructor)')
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
:marked
|
||||||
:marked
|
Import the `switchMap` operator to use later with the route parameters `Observable`.
|
||||||
Import the `switchMap` operator to use later with the route parameters `Observable`.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts (switchMap import)', 'rxjs-import')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'rxjs-import', 'src/app/hero-detail.component.ts (switchMap import)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Tell the class to implement the `OnInit` interface.
|
Tell the class to implement the `OnInit` interface.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts')
|
||||||
|
|
||||||
block ngOnInit
|
:marked
|
||||||
:marked
|
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
|
||||||
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
|
extract the `id` parameter value from the `ActivatedRoute` service
|
||||||
extract the `id` parameter value from the `ActivatedRoute` service
|
and use the `HeroService` to fetch the hero with that `id`.
|
||||||
and use the `HeroService` to fetch the hero with that `id`.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The `switchMap` operator maps the `id` in the Observable route parameters
|
||||||
|
to a new `Observable`, the result of the `HeroService.getHero()` method.
|
||||||
|
|
||||||
block extract-id
|
If a user re-navigates to this component while a `getHero` request is still processing,
|
||||||
:marked
|
`switchMap` cancels the old request and then calls `HeroService.getHero()` again.
|
||||||
The `switchMap` operator maps the `id` in the Observable route parameters
|
|
||||||
to a new `Observable`, the result of the `HeroService.getHero()` method.
|
|
||||||
|
|
||||||
If a user re-navigates to this component while a `getHero` request is still processing,
|
|
||||||
`switchMap` cancels the old request and then calls `HeroService.getHero()` again.
|
|
||||||
|
|
||||||
- var _str2int = _docsFor == 'dart' ? '<code>int.parse()</code> static method' : 'JavaScript (+) operator'
|
|
||||||
:marked
|
:marked
|
||||||
The hero `id` is a number. Route parameters are always strings.
|
The hero `id` is a number. Route parameters are always strings.
|
||||||
So the route parameter value is converted to a number with the !{_str2int}.
|
So the route parameter value is converted to a number with the JavaScript (+) operator.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-sub-section
|
||||||
.l-sub-section
|
:marked
|
||||||
:marked
|
### Do you need to unsubscribe?
|
||||||
### Do you need to unsubscribe?
|
|
||||||
|
|
||||||
As described in the [ActivatedRoute: the one-stop-shop for route information](../guide/router.html#activated-route)
|
As described in the [ActivatedRoute: the one-stop-shop for route information](../guide/router.html#activated-route)
|
||||||
section of the [Routing & Navigation](../guide/router.html) page,
|
section of the [Routing & Navigation](../guide/router.html) page,
|
||||||
the `Router` manages the observables it provides and localizes
|
the `Router` manages the observables it provides and localizes
|
||||||
the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against
|
the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against
|
||||||
memory leaks, so you don't need to unsubscribe from the route `params` `Observable`.
|
memory leaks, so you don't need to unsubscribe from the route `params` `Observable`.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add *HeroService.getHero()*
|
### Add *HeroService.getHero()*
|
||||||
|
@ -540,7 +503,7 @@ block extract-id
|
||||||
In the previous code snippet, `HeroService` doesn't have a `getHero()` method. To fix this issue,
|
In the previous code snippet, `HeroService` doesn't have a `getHero()` method. To fix this issue,
|
||||||
open `HeroService` and add a `getHero()` method that filters the heroes list from `getHeroes()` by `id`.
|
open `HeroService` and add a `getHero()` method that filters the heroes list from `getHeroes()` by `id`.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'getHero')
|
+makeExample('toh-5/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts (getHero)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Find the way back
|
### Find the way back
|
||||||
|
@ -551,31 +514,30 @@ block extract-id
|
||||||
Now add a third option, a `goBack()` method that navigates backward one step in the browser's history stack
|
Now add a third option, a `goBack()` method that navigates backward one step in the browser's history stack
|
||||||
using the `Location` service you injected previously.
|
using the `Location` service you injected previously.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts', 'goBack')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'goBack', 'src/app/hero-detail.component.ts (goBack)')
|
||||||
|
|
||||||
- var _CanDeactivateGuard = _docsFor == 'dart' ? '<em>routerCanDeactivate()</em> hook' : '<em>CanDeactivate</em> guard'
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Going back too far could take users out of the app.
|
Going back too far could take users out of the app.
|
||||||
In a real app, you can prevent this issue with the !{_CanDeactivateGuard}.
|
In a real app, you can prevent this issue with the <em>CanDeactivate</em> guard.
|
||||||
Read more on the [CanDeactivate](../api/router/index/CanDeactivate-interface.html) page.
|
Read more on the [CanDeactivate](../api/router/index/CanDeactivate-interface.html) page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
You'll wire this method with an event binding to a *Back* button that you'll add to the component template.
|
You'll wire this method with an event binding to a *Back* button that you'll add to the component template.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.html', 'back-button', '')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.html', 'back-button', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Migrate the template to its own file
|
Migrate the template to its own file
|
||||||
called <span ngio-ex>hero-detail.component.html</span>:
|
called <code>hero-detail.component.html</code>:
|
||||||
|
|
||||||
+makeExample('src/app/hero-detail.component.html')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.html', 'src/app/hero-detail.component.html')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Update the component metadata with a `templateUrl` pointing to the template file that you just created.
|
Update the component metadata with a `templateUrl` pointing to the template file that you just created.
|
||||||
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts', 'metadata')
|
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'metadata', 'src/app/hero-detail.component.ts (metadata)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser and see the results.
|
Refresh the browser and see the results.
|
||||||
|
@ -593,70 +555,67 @@ block extract-id
|
||||||
To achieve this effect, reopen `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
To achieve this effect, reopen `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
||||||
with `<a>` tags. Change the opening `<a>` tag to the following:
|
with `<a>` tags. Change the opening `<a>` tag to the following:
|
||||||
|
|
||||||
+makeExample('src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)')
|
+makeExample('toh-5/ts/src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)')
|
||||||
|
|
||||||
- var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
|
|
||||||
:marked
|
:marked
|
||||||
Notice the `[routerLink]` binding.
|
Notice the `[routerLink]` binding.
|
||||||
As described in the [Router links](#router-links) section of this page,
|
As described in the [Router links](#router-links) section of this page,
|
||||||
top-level navigation in the `AppComponent` template has router links set to fixed !{_pathVsName}s of the
|
top-level navigation in the `AppComponent` template has router links set to fixed paths of the
|
||||||
destination routes, "/dashboard" and "/heroes".
|
destination routes, "/dashboard" and "/heroes".
|
||||||
|
|
||||||
This time, you're binding to an expression containing a *link parameters !{_array}*.
|
This time, you're binding to an expression containing a *link parameters array*.
|
||||||
The !{_array} has two elements: the *!{_pathVsName}* of
|
The array has two elements: the *path* of
|
||||||
the destination route and a *route parameter* set to the value of the current hero's `id`.
|
the destination route and a *route parameter* set to the value of the current hero's `id`.
|
||||||
|
|
||||||
The two !{_array} items align with the *!{_pathVsName}* and *:id*
|
The two array items align with the *path* and *:id*
|
||||||
token in the parameterized hero detail route definition that you added to
|
token in the parameterized hero detail route definition that you added to
|
||||||
`!{_appRoutingTsVsAppComp}` earlier:
|
`app.module.ts` earlier:
|
||||||
|
|
||||||
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
|
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'hero-detail', 'src/app/app.module.ts (hero detail)')
|
||||||
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser and select a hero from the dashboard; the app navigates to that hero’s details.
|
Refresh the browser and select a hero from the dashboard; the app navigates to that hero’s details.
|
||||||
|
|
||||||
+ifDocsFor('ts')
|
.l-main-section
|
||||||
.l-main-section
|
:marked
|
||||||
:marked
|
## Refactor routes to a _Routing Module_
|
||||||
## Refactor routes to a _Routing Module_
|
|
||||||
|
|
||||||
Almost 20 lines of `AppModule` are devoted to configuring four routes.
|
Almost 20 lines of `AppModule` are devoted to configuring four routes.
|
||||||
Most applications have many more routes and they add guard services
|
Most applications have many more routes and they add guard services
|
||||||
to protect against unwanted or unauthorized navigations.
|
to protect against unwanted or unauthorized navigations.
|
||||||
(Read more about guard services in the [Route Guards](../guide/router.html#guards)
|
(Read more about guard services in the [Route Guards](../guide/router.html#guards)
|
||||||
section of the [Routing & Navigation](../guide/router.html) page.)
|
section of the [Routing & Navigation](../guide/router.html) page.)
|
||||||
Routing considerations could quickly dominate this module and obscure its primary purpose, which is to
|
Routing considerations could quickly dominate this module and obscure its primary purpose, which is to
|
||||||
establish key facts about the entire app for the Angular compiler.
|
establish key facts about the entire app for the Angular compiler.
|
||||||
|
|
||||||
It's a good idea to refactor the routing configuration into its own class.
|
It's a good idea to refactor the routing configuration into its own class.
|
||||||
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`,
|
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders`,
|
||||||
a class dedicated to routing should be a *routing module*.
|
a class dedicated to routing should be a *routing module*.
|
||||||
For more information, see the [Milestone #2: The Routing Module](../guide/router.html#routing-module)
|
For more information, see the [Milestone #2: The Routing Module](../guide/router.html#routing-module)
|
||||||
section of the [Routing & Navigation](../guide/router.html) page.
|
section of the [Routing & Navigation](../guide/router.html) page.
|
||||||
|
|
||||||
By convention, a routing module name contains the word "Routing" and
|
By convention, a routing module name contains the word "Routing" and
|
||||||
aligns with the name of the module that declares the components navigated to.
|
aligns with the name of the module that declares the components navigated to.
|
||||||
|
|
||||||
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`.
|
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`.
|
||||||
Give it the following contents, extracted from the `AppModule` class:
|
Give it the following contents, extracted from the `AppModule` class:
|
||||||
|
|
||||||
+makeExample('src/app/app-routing.module.ts')
|
+makeExample('toh-5/ts/src/app/app-routing.module.ts', null, 'src/app/app-routing.module.ts')
|
||||||
:marked
|
:marked
|
||||||
The following points are typical of routing modules:
|
The following points are typical of routing modules:
|
||||||
* The Routing Module pulls the routes into a variable. The variable clarifies the
|
* The Routing Module pulls the routes into a variable. The variable clarifies the
|
||||||
routing module pattern in case you export the module in the future.
|
routing module pattern in case you export the module in the future.
|
||||||
* The Routing Module adds `RouterModule.forRoot(routes)` to `imports`.
|
* The Routing Module adds `RouterModule.forRoot(routes)` to `imports`.
|
||||||
* The Routing Module adds `RouterModule` to `exports` so that the
|
* The Routing Module adds `RouterModule` to `exports` so that the
|
||||||
components in the companion module have access to Router declarables,
|
components in the companion module have access to Router declarables,
|
||||||
such as `RouterLink` and `RouterOutlet`.
|
such as `RouterLink` and `RouterOutlet`.
|
||||||
* There are no `declarations`. Declarations are the responsibility of the companion module.
|
* There are no `declarations`. Declarations are the responsibility of the companion module.
|
||||||
* If you have guard services, the Routing Module adds module `providers`. (There are none in this example.)
|
* If you have guard services, the Routing Module adds module `providers`. (There are none in this example.)
|
||||||
|
|
||||||
### Update *AppModule*
|
### Update *AppModule*
|
||||||
|
|
||||||
Delete the routing configuration from `AppModule` and import the `AppRoutingModule`.
|
Delete the routing configuration from `AppModule` and import the `AppRoutingModule`.
|
||||||
Use an ES `import` statement *and* add it to the `NgModule.imports` list.
|
Use an ES `import` statement *and* add it to the `NgModule.imports` list.
|
||||||
|
|
||||||
Here is the revised `AppModule`, compared to its pre-refactor state:
|
Here is the revised `AppModule`, compared to its pre-refactor state:
|
||||||
|
|
||||||
|
@ -692,7 +651,7 @@ block extract-id
|
||||||
|
|
||||||
Add the following HTML fragment at the bottom of the template where the `<hero-detail>` used to be:
|
Add the following HTML fragment at the bottom of the template where the `<hero-detail>` used to be:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts')
|
+makeExample('toh-5/ts/src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts')
|
||||||
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -707,7 +666,7 @@ figure.image-display
|
||||||
The hero's name is displayed in capital letters because of the `uppercase` pipe
|
The hero's name is displayed in capital letters because of the `uppercase` pipe
|
||||||
that's included in the interpolation binding, right after the pipe operator ( | ).
|
that's included in the interpolation binding, right after the pipe operator ( | ).
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.html', 'pipe', '')
|
+makeExample('toh-5/ts/src/app/heroes.component.html', 'pipe', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||||||
|
@ -729,10 +688,10 @@ figure.image-display
|
||||||
Before making any more changes, migrate the template and styles to their own files.
|
Before making any more changes, migrate the template and styles to their own files.
|
||||||
|
|
||||||
First, move the template contents from `heroes.component.ts`
|
First, move the template contents from `heroes.component.ts`
|
||||||
into a new <span ngio-ex>heroes.component.html</span> file.
|
into a new <code>heroes.component.html</code> file.
|
||||||
Don't copy the backticks. As for `heroes.component.ts`, you'll
|
Don't copy the backticks. As for `heroes.component.ts`, you'll
|
||||||
come back to it in a minute. Next, move the
|
come back to it in a minute. Next, move the
|
||||||
styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
styles contents into a new <code>heroes.component.css</code> file.
|
||||||
|
|
||||||
The two new files should look like this:
|
The two new files should look like this:
|
||||||
|
|
||||||
|
@ -747,14 +706,11 @@ figure.image-display
|
||||||
`templateUrl` and `styleUrls` respectively.
|
`templateUrl` and `styleUrls` respectively.
|
||||||
Set their properties to refer to the new files.
|
Set their properties to refer to the new files.
|
||||||
|
|
||||||
block heroes-component-cleanup
|
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'metadata', 'src/app/heroes.component.ts (revised metadata)')
|
||||||
//- Only relevant for Dart.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts (revised metadata)', 'metadata')
|
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
The `styleUrls` property is an array of style file names (with paths).
|
||||||
You could list multiple style files from different locations if you needed them.
|
You could list multiple style files from different locations if you needed them.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -770,16 +726,16 @@ block heroes-component-cleanup
|
||||||
1. Inject the `router` in the constructor, along with the `HeroService`.
|
1. Inject the `router` in the constructor, along with the `HeroService`.
|
||||||
1. Implement `gotoDetail()` by calling the router `navigate()` method.
|
1. Implement `gotoDetail()` by calling the router `navigate()` method.
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts', 'gotoDetail')
|
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'gotoDetail', 'src/app/heroes.component.ts (gotoDetail)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Note that you're passing a two-element *link parameters !{_array}*—a
|
Note that you're passing a two-element *link parameters array*—a
|
||||||
!{_pathVsName} and the route parameter—to
|
path and the route parameter—to
|
||||||
the router `navigate()` method, just as you did in the `[routerLink]` binding
|
the router `navigate()` method, just as you did in the `[routerLink]` binding
|
||||||
back in the `DashboardComponent`.
|
back in the `DashboardComponent`.
|
||||||
Here's the revised `HeroesComponent` class:
|
Here's the revised `HeroesComponent` class:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts', 'class')
|
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'class', 'src/app/heroes.component.ts (class)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser and start clicking.
|
Refresh the browser and start clicking.
|
||||||
|
@ -800,21 +756,21 @@ block heroes-component-cleanup
|
||||||
would obscure the component logic.
|
would obscure the component logic.
|
||||||
Instead, edit the CSS in a separate `*.css` file.
|
Instead, edit the CSS in a separate `*.css` file.
|
||||||
|
|
||||||
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference
|
Add a <code>dashboard.component.css</code> file to the `app` folder and reference
|
||||||
that file in the component metadata's `styleUrls` !{_array} property like this:
|
that file in the component metadata's `styleUrls` array property like this:
|
||||||
|
|
||||||
+makeExcerpt('src/app/dashboard.component.ts (styleUrls)', 'css')
|
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'css', 'src/app/dashboard.component.ts (styleUrls)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add stylish hero details
|
### Add stylish hero details
|
||||||
|
|
||||||
You've also been provided with CSS styles specifically for the `HeroDetailComponent`.
|
You've also been provided with CSS styles specifically for the `HeroDetailComponent`.
|
||||||
|
|
||||||
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
Add a <code>hero-detail.component.css</code> to the `app`
|
||||||
folder and refer to that file inside
|
folder and refer to that file inside
|
||||||
the `styleUrls` !{_array} as you did for `DashboardComponent`.
|
the `styleUrls` array as you did for `DashboardComponent`.
|
||||||
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` !{_decorator}
|
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` decorator
|
||||||
<span if-docs="ts">and its import</span>.
|
and its import.
|
||||||
|
|
||||||
Here's the content for the component CSS files.
|
Here's the content for the component CSS files.
|
||||||
|
|
||||||
|
@ -831,25 +787,24 @@ block heroes-component-cleanup
|
||||||
The provided CSS makes the navigation links in the `AppComponent` look more like selectable buttons.
|
The provided CSS makes the navigation links in the `AppComponent` look more like selectable buttons.
|
||||||
You'll surround those links in `<nav>` tags.
|
You'll surround those links in `<nav>` tags.
|
||||||
|
|
||||||
Add an <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
Add an <code>app.component.css</code> file to the `app` folder with the following content.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.css (navigation styles)', '')
|
+makeExample('toh-5/ts/src/app/app.component.css', '', 'src/app/app.component.css (navigation styles)')
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
block router-link-active
|
:marked
|
||||||
:marked
|
**The *routerLinkActive* directive**
|
||||||
**The *routerLinkActive* directive**
|
|
||||||
|
|
||||||
The Angular router provides a `routerLinkActive` directive you can use to
|
The Angular router provides a `routerLinkActive` directive you can use to
|
||||||
add a class to the HTML navigation element whose route matches the active route.
|
add a class to the HTML navigation element whose route matches the active route.
|
||||||
All you have to do is define the style for it.
|
All you have to do is define the style for it.
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.ts (active router links)', 'template')
|
+makeExample('toh-5/ts/src/app/app.component.ts', 'template', 'src/app/app.component.ts (active router links)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Add a `styleUrls` property that refers to this CSS file as follows:
|
Add a `styleUrls` property that refers to this CSS file as follows:
|
||||||
|
|
||||||
+makeExcerpt('src/app/app.component.ts','styleUrls')
|
+makeExample('toh-5/ts/src/app/app.component.ts','styleUrls')
|
||||||
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
|
@ -865,16 +820,14 @@ block heroes-component-cleanup
|
||||||
These correspond to the full set of master styles that you installed earlier during [setup](../guide/setup.html).
|
These correspond to the full set of master styles that you installed earlier during [setup](../guide/setup.html).
|
||||||
Here's an excerpt:
|
Here's an excerpt:
|
||||||
|
|
||||||
+makeExcerpt('src/styles.css (excerpt)', 'toh')
|
+makeExample('toh-5/ts/src/styles.css', 'toh', 'src/styles.css (excerpt)')
|
||||||
|
|
||||||
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css'
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Create the file <span ngio-ex>styles.css</span>.
|
Create the file <code>styles.css</code>.
|
||||||
Ensure that the file contains the [master styles provided here](!{styles_css}).
|
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css).
|
||||||
Also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
Also edit <code>index.html</code> to refer to this stylesheet.
|
||||||
|
|
||||||
+makeExcerpt('src/index.html (link ref)', 'css')
|
+makeExample('toh-5/ts/src/index.html', 'css', 'src/index.html (link ref)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Look at the app now. The dashboard, heroes, and navigation links are styled.
|
Look at the app now. The dashboard, heroes, and navigation links are styled.
|
||||||
|
@ -889,37 +842,36 @@ figure.image-display
|
||||||
Review the sample source code in the <live-example></live-example> for this page.
|
Review the sample source code in the <live-example></live-example> for this page.
|
||||||
Verify that you have the following structure:
|
Verify that you have the following structure:
|
||||||
|
|
||||||
block file-tree-end
|
.filetree
|
||||||
.filetree
|
.file angular-tour-of-heroes
|
||||||
.file angular-tour-of-heroes
|
.children
|
||||||
|
.file src
|
||||||
.children
|
.children
|
||||||
.file src
|
.file app
|
||||||
.children
|
.children
|
||||||
.file app
|
.file app.component.css
|
||||||
.children
|
.file app.component.ts
|
||||||
.file app.component.css
|
.file app.module.ts
|
||||||
.file app.component.ts
|
.file app-routing.module.ts
|
||||||
.file app.module.ts
|
.file dashboard.component.css
|
||||||
.file app-routing.module.ts
|
.file dashboard.component.html
|
||||||
.file dashboard.component.css
|
.file dashboard.component.ts
|
||||||
.file dashboard.component.html
|
.file hero.service.ts
|
||||||
.file dashboard.component.ts
|
.file hero.ts
|
||||||
.file hero.service.ts
|
.file hero-detail.component.css
|
||||||
.file hero.ts
|
.file hero-detail.component.html
|
||||||
.file hero-detail.component.css
|
.file hero-detail.component.ts
|
||||||
.file hero-detail.component.html
|
.file heroes.component.css
|
||||||
.file hero-detail.component.ts
|
.file heroes.component.html
|
||||||
.file heroes.component.css
|
.file heroes.component.ts
|
||||||
.file heroes.component.html
|
.file mock-heroes.ts
|
||||||
.file heroes.component.ts
|
.file main.ts
|
||||||
.file mock-heroes.ts
|
.file index.html
|
||||||
.file main.ts
|
.file styles.css
|
||||||
.file index.html
|
.file systemjs.config.js
|
||||||
.file styles.css
|
.file tsconfig.json
|
||||||
.file systemjs.config.js
|
.file node_modules ...
|
||||||
.file tsconfig.json
|
.file package.json
|
||||||
.file node_modules ...
|
|
||||||
.file package.json
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
|
|
|
@ -1,15 +1,5 @@
|
||||||
- var _example = 'toh-6';
|
|
||||||
|
|
||||||
block includes
|
block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
- var _Http = 'Http'; // Angular `Http` library name.
|
|
||||||
- var _Angular_Http = 'Angular <code>Http</code>'
|
|
||||||
- var _Angular_http_library = 'Angular HTTP library'
|
|
||||||
- var _HttpModule = 'HttpModule'
|
|
||||||
- var _JSON_stringify = 'JSON.stringify'
|
|
||||||
|
|
||||||
//- Shared var definitions
|
|
||||||
- var _promise = _Promise.toLowerCase()
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
In this page, you'll make the following improvements.
|
In this page, you'll make the following improvements.
|
||||||
|
@ -29,10 +19,10 @@ block includes
|
||||||
In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list,
|
In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list,
|
||||||
editing a selected hero along the way.
|
editing a selected hero along the way.
|
||||||
That's the starting point for this page.
|
That's the starting point for this page.
|
||||||
block start-server-and-watch
|
|
||||||
:marked
|
:marked
|
||||||
## Keep the app transpiling and running
|
## Keep the app transpiling and running
|
||||||
Enter the following command in the terminal window:
|
Enter the following command in the terminal window:
|
||||||
|
|
||||||
code-example(language="sh" class="code-shell").
|
code-example(language="sh" class="code-shell").
|
||||||
npm start
|
npm start
|
||||||
|
@ -46,77 +36,76 @@ block start-server-and-watch
|
||||||
|
|
||||||
.l-main-section#http-providers
|
.l-main-section#http-providers
|
||||||
h1 Providing HTTP Services
|
h1 Providing HTTP Services
|
||||||
block http-library
|
|
||||||
:marked
|
|
||||||
The `HttpModule` is not a core Angular module.
|
|
||||||
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
|
|
||||||
and is shipped in a separate script file as part of the Angular npm package.
|
|
||||||
|
|
||||||
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it.
|
:marked
|
||||||
|
The `HttpModule` is not a core Angular module.
|
||||||
|
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
|
||||||
|
and is shipped in a separate script file as part of the Angular npm package.
|
||||||
|
|
||||||
|
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Register for HTTP services
|
## Register for HTTP services
|
||||||
|
|
||||||
block http-providers
|
:marked
|
||||||
:marked
|
The app will depend on the Angular `http` service, which itself depends on other supporting services.
|
||||||
The app will depend on the Angular `http` service, which itself depends on other supporting services.
|
The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
|
||||||
The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
|
|
||||||
|
|
||||||
To allow access to these services from anywhere in the app,
|
To allow access to these services from anywhere in the app,
|
||||||
add `HttpModule` to the `imports` list of the `AppModule`.
|
add `HttpModule` to the `imports` list of the `AppModule`.
|
||||||
|
|
||||||
+makeExample('src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
|
+makeExample('toh-6/ts/src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Notice that you also supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
|
Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Simulate the web API
|
## Simulate the web API
|
||||||
|
We recommend registering app-wide services in the root
|
||||||
|
`AppModule` *providers*.
|
||||||
|
|
||||||
Until you have a web server that can handle requests for hero data,
|
Until you have a web server that can handle requests for hero data,
|
||||||
the HTTP client will fetch and save data from
|
the HTTP client will fetch and save data from
|
||||||
a mock service, the *in-memory web API*.
|
a mock service, the *in-memory web API*.
|
||||||
|
|
||||||
Update <span ngio-ex>!{_appModuleTsVsMainTs}</span> with this version, which uses the mock service:
|
Update <code>src/app/app.module.ts</code> with this version, which uses the mock service:
|
||||||
|
|
||||||
+makeExcerpt(_appModuleTsVsMainTs, 'v2')
|
+makeExample('toh-6/ts/src/app/app.module.ts', 'v2', 'src/app/app.module.ts (v2)')
|
||||||
|
|
||||||
block backend
|
:marked
|
||||||
:marked
|
Rather than require a real API server, this example simulates communication with the remote server by adding the
|
||||||
Rather than require a real API server, this example simulates communication with the remote server by adding the
|
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
|
||||||
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
|
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
|
||||||
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
|
|
||||||
|
|
||||||
+makeExcerpt(_appModuleTsVsMainTs, 'in-mem-web-api', '')
|
+makeExample('toh-6/ts/src/app/app.module.ts', 'in-mem-web-api', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
||||||
that primes the in-memory database.
|
that primes the in-memory database.
|
||||||
Add the file `in-memory-data.service.ts` in `!{_appDir}` with the following content:
|
Add the file `in-memory-data.service.ts` in `app` with the following content:
|
||||||
|
|
||||||
+makeExample('src/app/in-memory-data.service.ts', 'init')(format='.')
|
+makeExample('toh-6/ts/src/app/in-memory-data.service.ts', 'init', 'src/app/in-memory-data.service.ts')(format='.')
|
||||||
:marked
|
:marked
|
||||||
This file replaces `mock-heroes.ts`, which is now safe to delete.
|
This file replaces `mock-heroes.ts`, which is now safe to delete.
|
||||||
|
|
||||||
block dont-be-distracted-by-backend-subst
|
.alert.is-helpful
|
||||||
.alert.is-helpful
|
:marked
|
||||||
:marked
|
The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
|
||||||
The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
|
Don't worry about the details of this backend substitution; you can
|
||||||
Don't worry about the details of this backend substitution; you can
|
skip it when you have a real web API server.
|
||||||
skip it when you have a real web API server.
|
|
||||||
|
|
||||||
Read more about the in-memory web API in the
|
Read more about the in-memory web API in the
|
||||||
[Appendix: Tour of Heroes in-memory web api](../guide/server-communication.html#in-mem-web-api)
|
[Appendix: Tour of Heroes in-memory web api](../guide/server-communication.html#in-mem-web-api)
|
||||||
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
|
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Heroes and HTTP
|
## Heroes and HTTP
|
||||||
|
|
||||||
In the current `HeroService` implementation, a !{_Promise} resolved with mock heroes is returned.
|
In the current `HeroService` implementation, a Promise resolved with mock heroes is returned.
|
||||||
|
|
||||||
+makeExcerpt('toh-4/ts/src/app/hero.service.ts (old getHeroes)', 'get-heroes')
|
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (old getHeroes)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This was implemented in anticipation of ultimately
|
This was implemented in anticipation of ultimately
|
||||||
|
@ -124,56 +113,53 @@ block dont-be-distracted-by-backend-subst
|
||||||
|
|
||||||
Now convert `getHeroes()` to use HTTP.
|
Now convert `getHeroes()` to use HTTP.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'getHeroes', 'src/app/hero.service.ts (updated getHeroes and new class members)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Update the import statements as follows:
|
Update the import statements as follows:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts (updated imports)', 'imports')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'imports', 'src/app/hero.service.ts (updated imports)')
|
||||||
|
|
||||||
- var _h3id = `http-${_promise}`
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser. The hero data should successfully load from the
|
Refresh the browser. The hero data should successfully load from the
|
||||||
mock server.
|
mock server.
|
||||||
|
|
||||||
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
|
<h3 id="http-promise">HTTP Promise</h3>
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The Angular `http.get` returns an RxJS `Observable`.
|
||||||
|
*Observables* are a powerful way to manage asynchronous data flows.
|
||||||
|
You'll read about [Observables](#observables) later in this page.
|
||||||
|
|
||||||
block get-heroes-details
|
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-promise', '')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
|
||||||
|
|
||||||
|
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
|
||||||
|
To use those capabilities, you have to add the operators themselves.
|
||||||
|
That's as easy as importing them from the RxJS library like this:
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'rxjs', '')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The Angular `http.get` returns an RxJS `Observable`.
|
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
|
||||||
*Observables* are a powerful way to manage asynchronous data flows.
|
|
||||||
You'll read about [Observables](#observables) later in this page.
|
|
||||||
|
|
||||||
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
|
:marked
|
||||||
|
### Extracting the data in the *then* callback
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'to-promise', '')
|
In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
|
||||||
|
data within the response.
|
||||||
|
|
||||||
:marked
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-data', '')
|
||||||
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
|
|
||||||
|
|
||||||
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
|
|
||||||
To use those capabilities, you have to add the operators themselves.
|
|
||||||
That's as easy as importing them from the RxJS library like this:
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'rxjs', '')
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
|
|
||||||
|
|
||||||
:marked
|
|
||||||
### Extracting the data in the *then* callback
|
|
||||||
|
|
||||||
In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
|
|
||||||
data within the response.
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'to-data', '')
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The response JSON has a single `data` property, which
|
The response JSON has a single `data` property, which
|
||||||
holds the !{_array} of heroes that the caller wants.
|
holds the array of heroes that the caller wants.
|
||||||
So you grab that !{_array} and return it as the resolved !{_Promise} value.
|
So you grab that array and return it as the resolved Promise value.
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
|
@ -183,27 +169,26 @@ block get-heroes-details
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The caller is unaware that you fetched the heroes from the (mock) server.
|
The caller is unaware that you fetched the heroes from the (mock) server.
|
||||||
It receives a !{_Promise} of *heroes* just as it did before.
|
It receives a Promise of *heroes* just as it did before.
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
|
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'catch', '')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'catch', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This is a critical step.
|
This is a critical step.
|
||||||
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
|
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'handleError', '')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'handleError', '')
|
||||||
|
|
||||||
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
|
|
||||||
:marked
|
:marked
|
||||||
This demo service logs the error to the console; in real life,
|
This demo service logs the error to the console; in real life,
|
||||||
you would handle the error in code. For a demo, this works.
|
you would handle the error in code. For a demo, this works.
|
||||||
|
|
||||||
The code also includes an error to
|
The code also includes an error to
|
||||||
the caller in a !{rejected_promise}, so that the caller can display a proper error message to the user.
|
the caller in a rejected promise, so that the caller can display a proper error message to the user.
|
||||||
|
|
||||||
|
|
||||||
### Get hero by id
|
### Get hero by id
|
||||||
|
@ -215,19 +200,19 @@ block get-heroes-details
|
||||||
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
|
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
|
||||||
|
|
||||||
Update the `HeroService.getHero()` method to make a _get-by-id_ request:
|
Update the `HeroService.getHero()` method to make a _get-by-id_ request:
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This request is almost the same as `getHeroes()`.
|
This request is almost the same as `getHeroes()`.
|
||||||
The hero id in the URL identifies which hero the server should update.
|
The hero id in the URL identifies which hero the server should update.
|
||||||
|
|
||||||
Also, the `data` in the response is a single hero object rather than !{_an} !{_array}.
|
Also, the `data` in the response is a single hero object rather than an array.
|
||||||
|
|
||||||
### Unchanged _getHeroes_ API
|
### Unchanged _getHeroes_ API
|
||||||
|
|
||||||
Although you made significant internal changes to `getHeroes()` and `getHero()`,
|
Although you made significant internal changes to `getHeroes()` and `getHero()`,
|
||||||
the public signatures didn't change.
|
the public signatures didn't change.
|
||||||
You still return a !{_Promise} from both methods.
|
You still return a Promise from both methods.
|
||||||
You won't have to update any of the components that call them.
|
You won't have to update any of the components that call them.
|
||||||
|
|
||||||
Now it's time to add the ability to create and delete heroes.
|
Now it's time to add the ability to create and delete heroes.
|
||||||
|
@ -251,13 +236,13 @@ block get-heroes-details
|
||||||
At the end of the hero detail template, add a save button with a `click` event
|
At the end of the hero detail template, add a save button with a `click` event
|
||||||
binding that invokes a new component method named `save()`.
|
binding that invokes a new component method named `save()`.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.html', 'save')
|
+makeExample('toh-6/ts/src/app/hero-detail.component.html', 'save', 'src/app/hero-detail.component.html (save)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Add the following `save()` method, which persists hero name changes using the hero service
|
Add the following `save()` method, which persists hero name changes using the hero service
|
||||||
`update()` method and then navigates back to the previous view.
|
`update()` method and then navigates back to the previous view.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-detail.component.ts', 'save')
|
+makeExample('toh-6/ts/src/app/hero-detail.component.ts', 'save', 'src/app/hero-detail.component.ts (save)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add a hero service _update()_ method
|
### Add a hero service _update()_ method
|
||||||
|
@ -266,12 +251,12 @@ block get-heroes-details
|
||||||
`getHeroes()`, but it uses an HTTP `put()` to persist server-side changes.
|
`getHeroes()`, but it uses an HTTP `put()` to persist server-side changes.
|
||||||
|
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'update')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'update', 'src/app/hero.service.ts (update)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
To identify which hero the server should update, the hero `id` is encoded in
|
To identify which hero the server should update, the hero `id` is encoded in
|
||||||
the URL. The `put()` body is the JSON string encoding of the hero, obtained by
|
the URL. The `put()` body is the JSON string encoding of the hero, obtained by
|
||||||
calling `!{_JSON_stringify}`. The body content type
|
calling `JSON.stringify`. The body content type
|
||||||
(`application/json`) is identified in the request header.
|
(`application/json`) is identified in the request header.
|
||||||
|
|
||||||
Refresh the browser, change a hero name, save your change,
|
Refresh the browser, change a hero name, save your change,
|
||||||
|
@ -287,20 +272,20 @@ block get-heroes-details
|
||||||
Insert the following into the heroes component HTML, just after
|
Insert the following into the heroes component HTML, just after
|
||||||
the heading:
|
the heading:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.html', 'add')
|
+makeExample('toh-6/ts/src/app/heroes.component.html', 'add', 'src/app/heroes.component.html (add)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
In response to a click event, call the component's click handler and then
|
In response to a click event, call the component's click handler and then
|
||||||
clear the input field so that it's ready for another name.
|
clear the input field so that it's ready for another name.
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts', 'add')
|
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'add', 'src/app/heroes.component.ts (add)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When the given name is non-blank, the handler delegates creation of the
|
When the given name is non-blank, the handler delegates creation of the
|
||||||
named hero to the hero service, and then adds the new hero to the !{_array}.
|
named hero to the hero service, and then adds the new hero to the array.
|
||||||
|
|
||||||
Implement the `create()` method in the `HeroService` class.
|
Implement the `create()` method in the `HeroService` class.
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'create')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'create', 'src/app/hero.service.ts (create)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser and create some heroes.
|
Refresh the browser and create some heroes.
|
||||||
|
@ -314,12 +299,12 @@ block get-heroes-details
|
||||||
Add the following button element to the heroes component HTML, after the hero
|
Add the following button element to the heroes component HTML, after the hero
|
||||||
name in the repeated `<li>` element.
|
name in the repeated `<li>` element.
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.html', 'delete', '')
|
+makeExample('toh-6/ts/src/app/heroes.component.html', 'delete', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `<li>` element should now look like this:
|
The `<li>` element should now look like this:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.html', 'li-element')
|
+makeExample('toh-6/ts/src/app/heroes.component.html', 'li-element', 'src/app/heroes.component.html (li-element)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
In addition to calling the component's `delete()` method, the delete button's
|
In addition to calling the component's `delete()` method, the delete button's
|
||||||
|
@ -329,61 +314,60 @@ block get-heroes-details
|
||||||
|
|
||||||
The logic of the `delete()` handler is a bit trickier:
|
The logic of the `delete()` handler is a bit trickier:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.ts', 'delete')
|
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'delete', 'src/app/heroes.component.ts (delete)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Of course you delegate hero deletion to the hero service, but the component
|
Of course you delegate hero deletion to the hero service, but the component
|
||||||
is still responsible for updating the display: it removes the deleted hero
|
is still responsible for updating the display: it removes the deleted hero
|
||||||
from the !{_array} and resets the selected hero, if necessary.
|
from the array and resets the selected hero, if necessary.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
To place the delete button at the far right of the hero entry,
|
To place the delete button at the far right of the hero entry,
|
||||||
add this CSS:
|
add this CSS:
|
||||||
|
|
||||||
+makeExcerpt('src/app/heroes.component.css', 'additions')
|
+makeExample('toh-6/ts/src/app/heroes.component.css', 'additions', 'src/app/heroes.component.css (additions)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Hero service _delete()_ method
|
### Hero service _delete()_ method
|
||||||
|
|
||||||
Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server:
|
Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero.service.ts', 'delete')
|
+makeExample('toh-6/ts/src/app/hero.service.ts', 'delete', 'src/app/hero.service.ts (delete)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Refresh the browser and try the new delete functionality.
|
Refresh the browser and try the new delete functionality.
|
||||||
|
|
||||||
#observables
|
#observables
|
||||||
:marked
|
:marked
|
||||||
## !{_Observable}s
|
## Observables
|
||||||
|
|
||||||
block observables-section-intro
|
:marked
|
||||||
:marked
|
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
|
||||||
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
|
|
||||||
|
|
||||||
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
||||||
This section shows you how, when, and why to return the `Observable` directly.
|
This section shows you how, when, and why to return the `Observable` directly.
|
||||||
|
|
||||||
### Background
|
### Background
|
||||||
An *Observable* is a stream of events that you can process with array-like operators.
|
An *Observable* is a stream of events that you can process with array-like operators.
|
||||||
|
|
||||||
Angular core has basic support for observables.
|
Angular core has basic support for observables.
|
||||||
Developers augment that support with operators and extensions from the
|
Developers augment that support with operators and extensions from the
|
||||||
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
|
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
|
||||||
You'll see how shortly.
|
You'll see how shortly.
|
||||||
|
|
||||||
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`.
|
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`.
|
||||||
That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller.
|
That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller.
|
||||||
|
|
||||||
Converting to a Promise is often a good choice. You typically ask `http.get()` to fetch a single chunk of data.
|
Converting to a Promise is often a good choice. You typically ask `http.get()` to fetch a single chunk of data.
|
||||||
When you receive the data, you're done.
|
When you receive the data, you're done.
|
||||||
The calling component can easily consume a single result in the form of a Promise.
|
The calling component can easily consume a single result in the form of a Promise.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
But requests aren't always done only once.
|
But requests aren't always done only once.
|
||||||
You may start one request,
|
You may start one request,
|
||||||
cancel it, and make a different request before the server has responded to the first request.
|
cancel it, and make a different request before the server has responded to the first request.
|
||||||
A *request-cancel-new-request* sequence is difficult to implement with *!{_Promise}s*, but
|
A *request-cancel-new-request* sequence is difficult to implement with *Promises*, but
|
||||||
easy with *!{_Observable}s*.
|
easy with *Observables*.
|
||||||
|
|
||||||
### Add the ability to search by name
|
### Add the ability to search by name
|
||||||
You're going to add a *hero search* feature to the Tour of Heroes.
|
You're going to add a *hero search* feature to the Tour of Heroes.
|
||||||
|
@ -391,13 +375,13 @@ block observables-section-intro
|
||||||
|
|
||||||
Start by creating `HeroSearchService` that sends search queries to the server's web API.
|
Start by creating `HeroSearchService` that sends search queries to the server's web API.
|
||||||
|
|
||||||
+makeExample('src/app/hero-search.service.ts')
|
+makeExample('toh-6/ts/src/app/hero-search.service.ts', null, 'src/app/hero-search.service.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
|
The `http.get()` call in `HeroSearchService` is similar to the one
|
||||||
in the `HeroService`, although the URL now has a query string.
|
in the `HeroService`, although the URL now has a query string.
|
||||||
|
|
||||||
<span if-docs="ts">More importantly, you no longer call `toPromise()`.
|
More importantly, you no longer call `toPromise()`.
|
||||||
Instead you return the *Observable* from the the `htttp.get()`,
|
Instead you return the *Observable* from the the `htttp.get()`,
|
||||||
after chaining it to another RxJS operator, <code>map()</code>,
|
after chaining it to another RxJS operator, <code>map()</code>,
|
||||||
to extract heroes from the response data.
|
to extract heroes from the response data.
|
||||||
|
@ -411,121 +395,117 @@ block observables-section-intro
|
||||||
|
|
||||||
The component template is simple—just a text box and a list of matching search results.
|
The component template is simple—just a text box and a list of matching search results.
|
||||||
|
|
||||||
+makeExample('src/app/hero-search.component.html')
|
+makeExample('toh-6/ts/src/app/hero-search.component.html', null, 'src/app/hero-search.component.html')
|
||||||
:marked
|
:marked
|
||||||
Also, add styles for the new component.
|
Also, add styles for the new component.
|
||||||
+makeExample('src/app/hero-search.component.css')
|
+makeExample('toh-6/ts/src/app/hero-search.component.css', null, 'src/app/hero-search.component.css')
|
||||||
:marked
|
:marked
|
||||||
As the user types in the search box, a *keyup* event binding calls the component's `search()`
|
As the user types in the search box, a *keyup* event binding calls the component's `search()`
|
||||||
method with the new search box value.
|
method with the new search box value.
|
||||||
|
|
||||||
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
|
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
|
||||||
|
|
||||||
But as you'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
|
But as you'll soon see, the `heroes` property is now an *Observable* of hero arrays, rather than just a hero array.
|
||||||
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until you route it through the `async` pipe (`AsyncPipe`).
|
The `*ngFor` can't do anything with an `Observable` until you route it through the `async` pipe (`AsyncPipe`).
|
||||||
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
|
The `async` pipe subscribes to the `Observable` and produces the array of heroes to `*ngFor`.
|
||||||
|
|
||||||
Create the `HeroSearchComponent` class and metadata.
|
Create the `HeroSearchComponent` class and metadata.
|
||||||
|
|
||||||
+makeExample('src/app/hero-search.component.ts')
|
+makeExample('toh-6/ts/src/app/hero-search.component.ts', null, 'src/app/hero-search.component.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
#### Search terms
|
#### Search terms
|
||||||
|
|
||||||
Focus on `!{_priv}searchTerms`:
|
Focus on the `searchTerms`:
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-search.component.ts', 'searchTerms', '')
|
+makeExample('toh-6/ts/src/app/hero-search.component.ts', 'searchTerms', '')
|
||||||
|
|
||||||
block search-criteria-intro
|
:marked
|
||||||
:marked
|
A `Subject` is a producer of an _observable_ event stream;
|
||||||
A `Subject` is a producer of an _observable_ event stream;
|
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
|
||||||
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
|
|
||||||
|
|
||||||
Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`.
|
Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
<a id="ngoninit"></a>
|
<a id="ngoninit"></a>
|
||||||
#### Initialize the *heroes* property (*ngOnInit*)
|
#### Initialize the *heroes* property (*ngOnInit*)
|
||||||
|
|
||||||
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
|
A `Subject` is also an `Observable`.
|
||||||
You can turn the stream
|
You can turn the stream
|
||||||
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
|
of search terms into a stream of `Hero` arrays and assign the result to the `heroes` property.
|
||||||
|
|
||||||
+makeExcerpt('src/app/hero-search.component.ts', 'search', '')
|
+makeExample('toh-6/ts/src/app/hero-search.component.ts', 'search', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
|
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
|
||||||
taxing server resources and burning through the cellular network data plan.
|
taxing server resources and burning through the cellular network data plan.
|
||||||
|
|
||||||
block observable-transformers
|
:marked
|
||||||
|
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`.
|
||||||
|
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
|
||||||
|
|
||||||
|
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
||||||
|
before passing along the latest string. You'll never make requests more frequently than 300ms.
|
||||||
|
* `distinctUntilChanged` ensures that a request is sent only if the filter text changed.
|
||||||
|
* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`.
|
||||||
|
It cancels and discards previous search observables, returning only the latest search service observable.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`.
|
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
|
||||||
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
|
(formerly known as `flatMapLatest`),
|
||||||
|
every qualifying key event can trigger an `http()` method call.
|
||||||
|
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
|
||||||
|
and they may not return in the order sent.
|
||||||
|
|
||||||
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
`switchMap()` preserves the original request order while returning
|
||||||
before passing along the latest string. You'll never make requests more frequently than 300ms.
|
only the observable from the most recent `http` method call.
|
||||||
* `distinctUntilChanged()` ensures that a request is sent only if the filter text changed.
|
Results from prior calls are canceled and discarded.
|
||||||
* `switchMap()` calls the search service for each search term that makes it through `debounceTime()` and `distinctUntilChanged()`.
|
|
||||||
It cancels and discards previous search observables, returning only the latest search service observable.
|
|
||||||
|
|
||||||
.l-sub-section
|
If the search text is empty, the `http()` method call is also short circuited
|
||||||
:marked
|
and an observable containing an empty array is returned.
|
||||||
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
|
|
||||||
(formerly known as `flatMapLatest`),
|
|
||||||
every qualifying key event can trigger an `http()` method call.
|
|
||||||
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
|
|
||||||
and they may not return in the order sent.
|
|
||||||
|
|
||||||
`switchMap()` preserves the original request order while returning
|
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
|
||||||
only the observable from the most recent `http` method call.
|
doesn't actually abort a pending HTTP request.
|
||||||
Results from prior calls are canceled and discarded.
|
For now, unwanted results are discarded.
|
||||||
|
:marked
|
||||||
|
* `catch` intercepts a failed observable.
|
||||||
|
The simple example prints the error to the console; a real life app would do better.
|
||||||
|
Then to clear the search result, you return an observable containing an empty array.
|
||||||
|
|
||||||
If the search text is empty, the `http()` method call is also short circuited
|
a#rxjs-imports
|
||||||
and an observable containing an empty array is returned.
|
:marked
|
||||||
|
### Import RxJS operators
|
||||||
|
|
||||||
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
|
Most RxJS operators are not included in Angular's base `Observable` implementation.
|
||||||
doesn't actually abort a pending HTTP request.
|
The base implementation includes only what Angular itself requires.
|
||||||
For now, unwanted results are discarded.
|
|
||||||
:marked
|
|
||||||
* `catch` intercepts a failed observable.
|
|
||||||
The simple example prints the error to the console; a real life app would do better.
|
|
||||||
Then to clear the search result, you return an observable containing an empty array.
|
|
||||||
|
|
||||||
a#rxjs-imports
|
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
|
||||||
:marked
|
Here are all the RxJS imports that _this_ component needs:
|
||||||
### Import RxJS operators
|
|
||||||
|
|
||||||
Most RxJS operators are not included in Angular's base `Observable` implementation.
|
+makeExample('toh-6/ts/src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
|
||||||
The base implementation includes only what Angular itself requires.
|
|
||||||
|
|
||||||
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
|
:marked
|
||||||
Here are all the RxJS imports that _this_ component needs:
|
The `import 'rxjs/add/...'` syntax may be unfamiliar.
|
||||||
|
It's missing the usual list of symbols between the braces: `{...}`.
|
||||||
|
|
||||||
+makeExample('src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
|
You don't need the operator symbols themselves.
|
||||||
|
In each case, the mere act of importing the library
|
||||||
:marked
|
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
|
||||||
The `import 'rxjs/add/...'` syntax may be unfamiliar.
|
|
||||||
It's missing the usual list of symbols between the braces: `{...}`.
|
|
||||||
|
|
||||||
You don't need the operator symbols themselves.
|
|
||||||
In each case, the mere act of importing the library
|
|
||||||
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Add the search component to the dashboard
|
### Add the search component to the dashboard
|
||||||
|
|
||||||
Add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
Add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
||||||
|
|
||||||
+makeExample('src/app/dashboard.component.html')(format='.')
|
+makeExample('toh-6/ts/src/app/dashboard.component.html', null, 'src/app/dashboard.component.html')(format='.')
|
||||||
|
|
||||||
- var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
|
|
||||||
- var declFile = _docsFor == 'dart' ? 'src/app/dashboard.component.ts' : 'src/app/app.module.ts'
|
|
||||||
:marked
|
:marked
|
||||||
Finally, import `HeroSearchComponent` from
|
Finally, import `HeroSearchComponent` from
|
||||||
<span ngio-ex>hero-search.component.ts</span>
|
<code>hero-search.component.ts</code>
|
||||||
and add it to the `!{_declarations}` !{_array}.
|
and add it to the `declarations` array.
|
||||||
|
|
||||||
+makeExcerpt(declFile, 'search')
|
+makeExample('toh-6/ts/src/app/app.module.ts', 'search', 'src/app/app.module.ts (search)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Run the app again. In the Dashboard, enter some text in the search box.
|
Run the app again. In the Dashboard, enter some text in the search box.
|
||||||
|
@ -541,41 +521,40 @@ figure.image-display
|
||||||
Review the sample source code in the <live-example></live-example> for this page.
|
Review the sample source code in the <live-example></live-example> for this page.
|
||||||
Verify that you have the following structure:
|
Verify that you have the following structure:
|
||||||
|
|
||||||
block filetree
|
.filetree
|
||||||
.filetree
|
.file angular-tour-of-heroes
|
||||||
.file angular-tour-of-heroes
|
.children
|
||||||
|
.file src
|
||||||
.children
|
.children
|
||||||
.file src
|
.file app
|
||||||
.children
|
.children
|
||||||
.file app
|
.file app.component.ts
|
||||||
.children
|
.file app.component.css
|
||||||
.file app.component.ts
|
.file app.module.ts
|
||||||
.file app.component.css
|
.file app-routing.module.ts
|
||||||
.file app.module.ts
|
.file dashboard.component.css
|
||||||
.file app-routing.module.ts
|
.file dashboard.component.html
|
||||||
.file dashboard.component.css
|
.file dashboard.component.ts
|
||||||
.file dashboard.component.html
|
.file hero.ts
|
||||||
.file dashboard.component.ts
|
.file hero-detail.component.css
|
||||||
.file hero.ts
|
.file hero-detail.component.html
|
||||||
.file hero-detail.component.css
|
.file hero-detail.component.ts
|
||||||
.file hero-detail.component.html
|
.file hero-search.component.html (new)
|
||||||
.file hero-detail.component.ts
|
.file hero-search.component.css (new)
|
||||||
.file hero-search.component.html (new)
|
.file hero-search.component.ts (new)
|
||||||
.file hero-search.component.css (new)
|
.file hero-search.service.ts (new)
|
||||||
.file hero-search.component.ts (new)
|
.file hero.service.ts
|
||||||
.file hero-search.service.ts (new)
|
.file heroes.component.css
|
||||||
.file hero.service.ts
|
.file heroes.component.html
|
||||||
.file heroes.component.css
|
.file heroes.component.ts
|
||||||
.file heroes.component.html
|
.file in-memory-data.service.ts (new)
|
||||||
.file heroes.component.ts
|
.file main.ts
|
||||||
.file in-memory-data.service.ts (new)
|
.file index.html
|
||||||
.file main.ts
|
.file styles.css
|
||||||
.file index.html
|
.file systemjs.config.js
|
||||||
.file styles.css
|
.file tsconfig.json
|
||||||
.file systemjs.config.js
|
.file node_modules ...
|
||||||
.file tsconfig.json
|
.file package.json
|
||||||
.file node_modules ...
|
|
||||||
.file package.json
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
|
@ -587,44 +566,43 @@ block filetree
|
||||||
- You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
|
- You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
|
||||||
- You updated the components to allow adding, editing, and deleting of heroes.
|
- You updated the components to allow adding, editing, and deleting of heroes.
|
||||||
- You configured an in-memory web API.
|
- You configured an in-memory web API.
|
||||||
- You learned how to use !{_Observable}s.
|
- You learned how to use Observables.
|
||||||
|
|
||||||
Here are the files you added or changed in this page.
|
Here are the files you added or changed in this page.
|
||||||
|
|
||||||
block file-summary
|
+makeTabs(
|
||||||
+makeTabs(
|
`toh-6/ts/src/app/app.component.ts,
|
||||||
`toh-6/ts/src/app/app.component.ts,
|
toh-6/ts/src/app/app.module.ts,
|
||||||
toh-6/ts/src/app/app.module.ts,
|
toh-6/ts/src/app/heroes.component.ts,
|
||||||
toh-6/ts/src/app/heroes.component.ts,
|
toh-6/ts/src/app/heroes.component.html,
|
||||||
toh-6/ts/src/app/heroes.component.html,
|
toh-6/ts/src/app/heroes.component.css,
|
||||||
toh-6/ts/src/app/heroes.component.css,
|
toh-6/ts/src/app/hero-detail.component.ts,
|
||||||
toh-6/ts/src/app/hero-detail.component.ts,
|
toh-6/ts/src/app/hero-detail.component.html,
|
||||||
toh-6/ts/src/app/hero-detail.component.html,
|
toh-6/ts/src/app/hero.service.ts,
|
||||||
toh-6/ts/src/app/hero.service.ts,
|
toh-6/ts/src/app/in-memory-data.service.ts`,
|
||||||
toh-6/ts/src/app/in-memory-data.service.ts`,
|
',,,,,,,,',
|
||||||
',,,,,,,,',
|
`app.comp...ts,
|
||||||
`app.comp...ts,
|
app.mod...ts,
|
||||||
app.mod...ts,
|
heroes.comp...ts,
|
||||||
heroes.comp...ts,
|
heroes.comp...html,
|
||||||
heroes.comp...html,
|
heroes.comp...css,
|
||||||
heroes.comp...css,
|
hero-detail.comp...ts,
|
||||||
hero-detail.comp...ts,
|
hero-detail.comp...html,
|
||||||
hero-detail.comp...html,
|
hero.service.ts,
|
||||||
hero.service.ts,
|
in-memory-data.service.ts`
|
||||||
in-memory-data.service.ts`
|
)
|
||||||
)
|
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`toh-6/ts/src/app/hero-search.service.ts,
|
`toh-6/ts/src/app/hero-search.service.ts,
|
||||||
toh-6/ts/src/app/hero-search.component.ts,
|
toh-6/ts/src/app/hero-search.component.ts,
|
||||||
toh-6/ts/src/app/hero-search.component.html,
|
toh-6/ts/src/app/hero-search.component.html,
|
||||||
toh-6/ts/src/app/hero-search.component.css`,
|
toh-6/ts/src/app/hero-search.component.css`,
|
||||||
null,
|
null,
|
||||||
`hero-search.service.ts,
|
`hero-search.service.ts,
|
||||||
hero-search.component.ts,
|
hero-search.component.ts,
|
||||||
hero-search.component.html,
|
hero-search.component.html,
|
||||||
hero-search.component.css`
|
hero-search.component.css`
|
||||||
)
|
)
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
|
|
|
@ -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) { };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
Loading…
Reference in New Issue