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/if-docs.js")
|
||||
script(src="/resources/js/directives/live-example.js")
|
||||
script(src="/resources/js/directives/ngio-ex-path.js")
|
||||
script(src="/resources/js/directives/scroll-y-offset-element.js")
|
|
@ -285,7 +285,7 @@ a#rollup-plugins
|
|||
to use the ES `import` and `export` statements that Rollup requires.
|
||||
Rollup then preserves the parts of `RxJS` referenced by the application
|
||||
in the final bundle. Using it is straigthforward. Add the following to
|
||||
the `plugins` !{_array} in `rollup-config.js`:
|
||||
the `plugins` array in `rollup-config.js`:
|
||||
|
||||
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
||||
|
||||
|
@ -294,7 +294,7 @@ a#rollup-plugins
|
|||
|
||||
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
|
||||
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
|
||||
Add the following to the `plugins` !{_array}:
|
||||
Add the following to the `plugins` array:
|
||||
|
||||
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
|
||||
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
block includes
|
||||
include _util-fns
|
||||
|
||||
//- current.path = ['docs', lang, 'latest', ...]
|
||||
- var lang = current.path[1]
|
||||
- var docsPath='/' + current.path[0]
|
||||
- var docsLatest='/' + current.path.slice(0,3).join('/');
|
||||
- var _at = lang === 'js' ? '' : '@'
|
||||
- var _decoratorLink = '<a href="#' + _decorator + '">' + _decorator + '</a>'
|
||||
|
||||
:marked
|
||||
Angular has its own vocabulary.
|
||||
Most Angular terms are common English words
|
||||
|
@ -29,14 +22,13 @@ a#aot
|
|||
.l-sub-section
|
||||
:marked
|
||||
You can compile Angular applications at build time.
|
||||
By compiling your application<span if-docs="ts"> using the compiler-cli, `ngc`</span>, you can bootstrap directly
|
||||
to a<span if-docs="ts"> module</span> factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
||||
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
:marked
|
||||
## Angular module
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
Helps you organize an application into cohesive blocks of functionality.
|
||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||
|
@ -44,7 +36,7 @@ a#aot
|
|||
Every Angular application has an application root-module class. By convention, the class is
|
||||
called `AppModule` and resides in a file named `app.module.ts`.
|
||||
|
||||
For details and examples, see the [Angular Modules (NgModule)](!{docsLatest}/guide/ngmodule.html) page.
|
||||
For details and examples, see the [Angular Modules (NgModule)](./ngmodule.html) page.
|
||||
|
||||
:marked
|
||||
## Annotation
|
||||
|
@ -64,14 +56,13 @@ a#attribute-directives
|
|||
|
||||
For example, you can use the `ngClass` directive to add and remove CSS class names.
|
||||
|
||||
Learn about them in the [_Attribute Directives_](!{docsLatest}/guide/attribute-directives.html) guide.
|
||||
Learn about them in the [_Attribute Directives_](./attribute-directives.html) guide.
|
||||
|
||||
.l-main-section#B
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
:marked
|
||||
## Barrel
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
||||
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
||||
|
@ -126,7 +117,7 @@ a#attribute-directives
|
|||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
||||
Bootstrapping identifies an application's top level "root" [component](#component),
|
||||
which is the first component that is loaded for the application.
|
||||
For more information, see the [Setup](!{docsLatest}/guide/setup.html) page.
|
||||
For more information, see the [Setup](./setup.html) page.
|
||||
|
||||
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
||||
|
||||
|
@ -153,7 +144,7 @@ a#component
|
|||
The *component* is one of the most important building blocks in the Angular system.
|
||||
It is, in fact, an Angular [directive](#directive) with a companion [template](#template).
|
||||
|
||||
Apply the `!{_at}Component` !{_decoratorLink} to
|
||||
Apply the `@Component` [decorator](#decorator) to
|
||||
the component class, thereby attaching to the class the essential component metadata
|
||||
that Angular needs to create a component instance and render the component with its template
|
||||
as a view.
|
||||
|
@ -169,8 +160,8 @@ a#component
|
|||
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
||||
This form is also known as kebab-case.
|
||||
|
||||
[Directive](#directive) selectors (like `my-app`) <span if-docs="ts">and
|
||||
the root of filenames (such as `hero-list.component.ts`)</span> are often
|
||||
[Directive](#directive) selectors (like `my-app`) and
|
||||
the root of filenames (such as `hero-list.component.ts`) are often
|
||||
spelled in dash-case.
|
||||
|
||||
:marked
|
||||
|
@ -189,14 +180,14 @@ a#component
|
|||
Angular has a rich data-binding framework with a variety of data-binding
|
||||
operations and supporting declaration syntax.
|
||||
|
||||
Read about the following forms of binding in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page:
|
||||
* [Interpolation](!{docsLatest}/guide/template-syntax.html#interpolation).
|
||||
* [Property binding](!{docsLatest}/guide/template-syntax.html#property-binding).
|
||||
* [Event binding](!{docsLatest}/guide/template-syntax.html#event-binding).
|
||||
* [Attribute binding](!{docsLatest}/guide/template-syntax.html#attribute-binding).
|
||||
* [Class binding](!{docsLatest}/guide/template-syntax.html#class-binding).
|
||||
* [Style binding](!{docsLatest}/guide/template-syntax.html#style-binding).
|
||||
* [Two-way data binding with ngModel](!{docsLatest}/guide/template-syntax.html#ngModel).
|
||||
Read about the following forms of binding in the [Template Syntax](./template-syntax.html) page:
|
||||
* [Interpolation](./template-syntax.html#interpolation).
|
||||
* [Property binding](./template-syntax.html#property-binding).
|
||||
* [Event binding](./template-syntax.html#event-binding).
|
||||
* [Attribute binding](./template-syntax.html#attribute-binding).
|
||||
* [Class binding](./template-syntax.html#class-binding).
|
||||
* [Style binding](./template-syntax.html#style-binding).
|
||||
* [Two-way data binding with ngModel](./template-syntax.html#ngModel).
|
||||
|
||||
a#decorator
|
||||
a#decoration
|
||||
|
@ -280,7 +271,7 @@ a#decoration
|
|||
Angular registers some of its own providers with every injector.
|
||||
You can register your own providers.
|
||||
|
||||
Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page.
|
||||
Read more in the [Dependency Injection](./dependency-injection.html) page.
|
||||
|
||||
a#directive
|
||||
a#directives
|
||||
|
@ -370,11 +361,11 @@ a#H
|
|||
.l-sub-section
|
||||
:marked
|
||||
A directive property that can be the *target* of a
|
||||
[property binding](!{docsLatest}/guide/template-syntax.html#property-binding) (explained in detail in the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page).
|
||||
[property binding](./template-syntax.html#property-binding) (explained in detail in the [Template Syntax](./template-syntax.html) page).
|
||||
Data values flow *into* this property from the data source identified
|
||||
in the template expression to the right of the equal sign.
|
||||
|
||||
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
||||
See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
|
||||
|
||||
:marked
|
||||
## Interpolation
|
||||
|
@ -390,8 +381,8 @@ a#H
|
|||
<label>My current hero is {{hero.name}}</label>
|
||||
|
||||
:marked
|
||||
Read more about [interpolation](!{docsLatest}/guide/template-syntax.html#interpolation) in the
|
||||
[Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
||||
Read more about [interpolation](./template-syntax.html#interpolation) in the
|
||||
[Template Syntax](./template-syntax.html) page.
|
||||
|
||||
.l-main-section#J
|
||||
|
||||
|
@ -400,7 +391,7 @@ a#jit
|
|||
## Just-in-time (JIT) compilation
|
||||
.l-sub-section
|
||||
:marked
|
||||
A bootstrapping method of compiling components<span if-docs="ts"> and modules</span> in the browser
|
||||
A bootstrapping method of compiling components and modules in the browser
|
||||
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
||||
Consider using the [ahead-of-time](#aot) mode for production apps.
|
||||
|
||||
|
@ -435,7 +426,7 @@ a#jit
|
|||
* `ngAfterViewChecked`: after every check of a component's views.
|
||||
* `ngOnDestroy`: just before the directive is destroyed.
|
||||
|
||||
Read more in the [Lifecycle Hooks](!{docsLatest}/guide/lifecycle-hooks.html) page.
|
||||
Read more in the [Lifecycle Hooks](./lifecycle-hooks.html) page.
|
||||
|
||||
.l-main-section#M
|
||||
|
||||
|
@ -446,7 +437,7 @@ a#jit
|
|||
:marked
|
||||
Angular has the following types of modules:
|
||||
- [Angular modules](#angular-module).
|
||||
For details and examples, see the [Angular Modules](!{docsLatest}/guide/ngmodule.html) page.
|
||||
For details and examples, see the [Angular Modules](./ngmodule.html) page.
|
||||
- ES2015 modules, as described in this section.
|
||||
|
||||
:marked
|
||||
|
@ -477,10 +468,9 @@ a#jit
|
|||
a#N
|
||||
.l-main-section#O
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
:marked
|
||||
## Observable
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
An array whose items arrive asynchronously over time.
|
||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||
|
@ -494,12 +484,12 @@ a#N
|
|||
.l-sub-section
|
||||
:marked
|
||||
A directive property that can be the *target* of event binding
|
||||
(read more in the [event binding](!{docsLatest}/guide/template-syntax.html#event-binding)
|
||||
section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page).
|
||||
(read more in the [event binding](./template-syntax.html#event-binding)
|
||||
section of the [Template Syntax](./template-syntax.html) page).
|
||||
Events stream *out* of this property to the receiver identified
|
||||
in the template expression to the right of the equal sign.
|
||||
|
||||
See the [Input and output properties](!{docsLatest}/guide/template-syntax.html#inputs-outputs) section of the [Template Syntax](!{docsLatest}/guide/template-syntax.html) page.
|
||||
See the [Input and output properties](./template-syntax.html#inputs-outputs) section of the [Template Syntax](./template-syntax.html) page.
|
||||
|
||||
.l-main-section#P
|
||||
|
||||
|
@ -527,7 +517,7 @@ a#N
|
|||
|
||||
:marked
|
||||
You can also write your own custom pipes.
|
||||
Read more in the page on [pipes](!{docsLatest}/guide/pipes.html).
|
||||
Read more in the page on [pipes](./pipes.html).
|
||||
|
||||
:marked
|
||||
## Provider
|
||||
|
@ -566,8 +556,7 @@ a#Q
|
|||
|
||||
The Angular component router is a richly featured mechanism for configuring and managing the entire view navigation process, including the creation and destruction
|
||||
of views.
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
|
||||
In most cases, components become attached to a router by means
|
||||
of a `RouterConfig` that defines routes to views.
|
||||
|
||||
|
@ -577,7 +566,7 @@ a#Q
|
|||
Other views in the application likely have anchor tags or buttons with `RouterLink`
|
||||
directives that users can click to navigate.
|
||||
|
||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
||||
For more information, see the [Routing & Navigation](./router.html) page.
|
||||
|
||||
:marked
|
||||
## Router module
|
||||
|
@ -585,7 +574,7 @@ a#Q
|
|||
:marked
|
||||
A separate [Angular module](#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
||||
|
||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
||||
For more information, see the [Routing & Navigation](./router.html) page.
|
||||
|
||||
:marked
|
||||
## Routing component
|
||||
|
@ -593,7 +582,7 @@ a#Q
|
|||
:marked
|
||||
An Angular [component](#component) with a `RouterOutlet` that displays views based on router navigations.
|
||||
|
||||
For more information, see the [Routing & Navigation](!{docsLatest}/guide/router.html) page.
|
||||
For more information, see the [Routing & Navigation](./router.html) page.
|
||||
|
||||
.l-main-section#S
|
||||
|
||||
|
@ -629,7 +618,7 @@ a#Q
|
|||
|
||||
Applications often require services such as a data service or a logging service.
|
||||
|
||||
For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial.
|
||||
For more information, see the [Services](.ial/toh-pt4.html) page of the [Tour of Heroes](.ial/) tutorial.
|
||||
|
||||
a#snake-case
|
||||
:marked
|
||||
|
@ -650,7 +639,7 @@ a#structural-directives
|
|||
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
||||
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
||||
|
||||
Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page.
|
||||
Read more in the [Structural Directives](./structural-directives.html) page.
|
||||
|
||||
.l-main-section#T
|
||||
:marked
|
||||
|
@ -677,18 +666,18 @@ a#structural-directives
|
|||
Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios.
|
||||
|
||||
Read about how to build template-driven forms
|
||||
in the [Forms](!{docsLatest}/guide/forms.html) page.
|
||||
in the [Forms](./forms.html) page.
|
||||
|
||||
:marked
|
||||
## Template expression
|
||||
.l-sub-section
|
||||
:marked
|
||||
A !{_Lang}-like syntax that Angular evaluates within
|
||||
A TypeScript-like syntax that Angular evaluates within
|
||||
a [data binding](#data-binding).
|
||||
|
||||
Read about how to write template expressions
|
||||
in the [Template expressions](!{docsLatest}/guide/template-syntax.html#template-expressions) section
|
||||
of the [Template Syntax](!{docsLatest}/guide/template-syntax.html#) page.
|
||||
in the [Template expressions](./template-syntax.html#template-expressions) section
|
||||
of the [Template Syntax](./template-syntax.html#) page.
|
||||
|
||||
:marked
|
||||
## Transpile
|
||||
|
|
|
@ -172,7 +172,7 @@ figure
|
|||
* Enter: `void => *`
|
||||
* Leave: `* => void`
|
||||
|
||||
For example, in the `animations` !{_array} below there are two transitions that use
|
||||
For example, in the `animations` array below there are two transitions that use
|
||||
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
||||
+makeExample('animations/ts/src/app/hero-list-enter-leave.component.ts', 'animationdef', 'hero-list-enter-leave.component.ts (excerpt)')(format=".")
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
block includes
|
||||
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
|
||||
either JavaScript or a language like TypeScript that compiles to JavaScript.
|
||||
|
||||
|
@ -48,15 +45,14 @@ figure
|
|||
figure
|
||||
img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
block angular-modules
|
||||
:marked
|
||||
: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
|
||||
<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`.
|
||||
|
||||
|
@ -65,14 +61,14 @@ block angular-modules
|
|||
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
|
||||
.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
|
||||
: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.
|
||||
|
@ -89,18 +85,18 @@ block angular-modules
|
|||
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='.')
|
||||
+makeExample('src/app/mini-app.ts', 'module', 'src/app/app.module.ts')(format='.')
|
||||
|
||||
.l-sub-section
|
||||
.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
|
||||
: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='.')
|
||||
+makeExample('src/main.ts', '', 'src/main.ts')(format='.')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
### Angular modules vs. JavaScript modules
|
||||
|
||||
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
||||
|
@ -112,13 +108,13 @@ block angular-modules
|
|||
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='.')
|
||||
+makeExample('src/app/app.module.ts', 'imports', '')(format='.')
|
||||
+makeExample('src/app/app.module.ts', 'export', '')(format='.')
|
||||
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
|
||||
:marked
|
||||
:marked
|
||||
These are two different and _complementary_ module systems. Use them both to write your apps.
|
||||
|
||||
:marked
|
||||
|
@ -127,30 +123,29 @@ block angular-modules
|
|||
figure
|
||||
img(src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
block angular-libraries
|
||||
:marked
|
||||
:marked
|
||||
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
|
||||
|
||||
Each Angular library name begins with the `!{_at_angular}` prefix.
|
||||
Each Angular library name begins with the `@angular` prefix.
|
||||
|
||||
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
|
||||
<br class="l-clear-both"><br>
|
||||
|
||||
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
|
||||
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
|
||||
:marked
|
||||
+makeExample('src/app/app.component.ts', 'import', '')(format='.')
|
||||
:marked
|
||||
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
||||
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
|
||||
:marked
|
||||
+makeExample('src/app/mini-app.ts', 'import-browser-module', '')(format='.')
|
||||
:marked
|
||||
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
|
||||
:marked
|
||||
+makeExample('src/app/mini-app.ts', 'ngmodule-imports', '')(format='.')
|
||||
:marked
|
||||
In this way you're using both the Angular and JavaScript module systems _together_.
|
||||
|
||||
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
|
||||
Hang in there. The confusion yields to clarity with time and experience.
|
||||
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more from the [Angular modules](ngmodule.html) page.
|
||||
|
||||
|
@ -176,7 +171,7 @@ figure
|
|||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
<a id="component-code"></a>
|
||||
For example, this `HeroListComponent` has a `heroes` property that returns !{_an} !{_array} of heroes
|
||||
For example, this `HeroListComponent` has a `heroes` property that returns an array of heroes
|
||||
that it acquires from a service.
|
||||
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
|
||||
|
@ -238,17 +233,16 @@ figure
|
|||
|
||||
To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class.
|
||||
|
||||
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**.
|
||||
In TypeScript, you attach metadata by using a **decorator**.
|
||||
Here's some metadata for `HeroListComponent`:
|
||||
|
||||
+makeExcerpt('src/app/hero-list.component.ts', 'metadata')
|
||||
|
||||
:marked
|
||||
Here is the `@Component` !{_decorator}, which identifies the class
|
||||
Here is the `@Component` decorator, which identifies the class
|
||||
immediately below it as a component class.
|
||||
|
||||
block ts-decorator
|
||||
:marked
|
||||
:marked
|
||||
The `@Component` decorator takes a required configuration object with the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
|
@ -262,10 +256,8 @@ block ts-decorator
|
|||
|
||||
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
|
||||
|
||||
block dart-directives
|
||||
|
||||
:marked
|
||||
- `providers`: !{_array} of **dependency injection providers** for services that the component requires.
|
||||
- `providers`: array of **dependency injection providers** for services that the component requires.
|
||||
This is one way to tell Angular that the component's constructor requires a `HeroService`
|
||||
so it can get the list of heroes to display.
|
||||
|
||||
|
@ -277,8 +269,8 @@ figure
|
|||
|
||||
The template, metadata, and component together describe a view.
|
||||
|
||||
Apply other metadata !{_decorator}s in a similar fashion to guide Angular behavior.
|
||||
`@Injectable`, `@Input`, and `@Output` are a few of the more popular !{_decorator}s.
|
||||
Apply other metadata decorators in a similar fashion to guide Angular behavior.
|
||||
`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
The architectural takeaway is that you must add metadata to your code
|
||||
|
@ -352,9 +344,9 @@ figure
|
|||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
||||
according to the instructions given by **directives**.
|
||||
|
||||
A directive is a class with a `@Directive` !{_decorator}.
|
||||
A directive is a class with a `@Directive` decorator.
|
||||
A component is a *directive-with-a-template*;
|
||||
a `@Component` !{_decorator} is actually a `@Directive` !{_decorator} extended with template-oriented features.
|
||||
a `@Component` decorator is actually a `@Directive` decorator extended with template-oriented features.
|
||||
<br class="l-clear-both">
|
||||
|
||||
.l-sub-section
|
||||
|
@ -377,8 +369,6 @@ figure
|
|||
* [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
block dart-bool
|
||||
|
||||
:marked
|
||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
|
@ -430,7 +420,7 @@ figure
|
|||
+makeExcerpt('src/app/logger.service.ts', 'class')
|
||||
|
||||
:marked
|
||||
Here's a `HeroService` that uses a !{_PromiseLinked} to fetch heroes.
|
||||
Here's a `HeroService` that uses a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to fetch heroes.
|
||||
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'class')
|
||||
|
@ -491,14 +481,13 @@ figure
|
|||
In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
|
||||
A provider is something that can create or return a service, typically the service class itself.
|
||||
|
||||
block registering-providers
|
||||
:marked
|
||||
:marked
|
||||
You can register providers in modules or in components.
|
||||
|
||||
In general, add providers to the [root module](#module) so that
|
||||
the same instance of a service is available everywhere.
|
||||
|
||||
+makeExcerpt('src/app/app.module.ts (module providers)', 'providers')
|
||||
+makeExcerpt('src/app/app.module.ts (module providers)', 'providers')
|
||||
|
||||
:marked
|
||||
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
||||
|
@ -573,7 +562,6 @@ block registering-providers
|
|||
> [**Router**](router.html): Navigate from page to page within the client
|
||||
application and never leave the browser.
|
||||
|
||||
block angular-feature-testing
|
||||
:marked
|
||||
:marked
|
||||
> [**Testing**](testing.html): Run unit tests on your application parts as they interact with the Angular framework
|
||||
using the _Angular Testing Platform_.
|
||||
|
|
|
@ -56,7 +56,7 @@ block includes
|
|||
### Write the directive code
|
||||
|
||||
Follow the [setup](setup.html) instructions for creating a new local project
|
||||
named <span ngio-ex>attribute-directives</span>.
|
||||
named <code>attribute-directives</code>.
|
||||
|
||||
Create the following source file in the indicated folder:
|
||||
|
||||
|
@ -97,7 +97,7 @@ block includes
|
|||
:marked
|
||||
After the `@Directive` metadata comes the directive's controller class,
|
||||
called `HighlightDirective`, which contains the logic for the directive.
|
||||
<span if-docs="ts">Exporting `HighlightDirective` makes it accessible to other components.</span>
|
||||
Exporting `HighlightDirective` makes it accessible to other components.
|
||||
|
||||
Angular creates a new instance of the directive's controller class for
|
||||
each matching element, injecting an Angular `ElementRef`
|
||||
|
@ -113,7 +113,7 @@ block includes
|
|||
applies the directive as an attribute to a paragraph (`<p>`) element.
|
||||
In Angular terms, the `<p>` element is the attribute **host**.
|
||||
|
||||
Put the template in its own <span ngio-ex>app.component.html</span>
|
||||
Put the template in its own <code>app.component.html</code>
|
||||
file that looks like this:
|
||||
|
||||
+makeExample('src/app/app.component.1.html')
|
||||
|
@ -176,12 +176,12 @@ figure.image-display
|
|||
|
||||
:marked
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` !{_decorator}.
|
||||
each adorned by the `HostListener` decorator.
|
||||
|
||||
+makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '')
|
||||
|
||||
:marked
|
||||
The `@HostListener` !{_decorator} lets you subscribe to events of the DOM
|
||||
The `@HostListener` decorator lets you subscribe to events of the DOM
|
||||
element that hosts an attribute directive, the `<p>` in this case.
|
||||
|
||||
.l-sub-section
|
||||
|
@ -194,7 +194,7 @@ figure.image-display
|
|||
1. Talking to DOM API directly isn't a best practice.
|
||||
|
||||
:marked
|
||||
The handlers delegate to a helper method that sets the color on the DOM element, `#{_priv}el`,
|
||||
The handlers delegate to a helper method that sets the color on the DOM element, `el`,
|
||||
which you declare and initialize in the constructor.
|
||||
|
||||
+makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor')
|
||||
|
@ -226,7 +226,7 @@ a#input
|
|||
:marked
|
||||
### Binding to an _@Input_ property
|
||||
|
||||
Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
||||
Notice the `@Input` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
||||
|
||||
It's called an *input* property because data flows from the binding expression _into_ the directive.
|
||||
Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that.
|
||||
|
@ -299,7 +299,7 @@ a#input-alias
|
|||
In this section, you'll turn `AppComponent` into a harness that
|
||||
lets you pick the highlight color with a radio button and bind your color choice to the directive.
|
||||
|
||||
Update <span ngio-ex>app.component.html</span> as follows:
|
||||
Update <code>app.component.html</code> as follows:
|
||||
|
||||
+makeExcerpt('src/app/app.component.html', 'v2', '')
|
||||
|
||||
|
@ -345,7 +345,7 @@ figure.image-display
|
|||
|
||||
:marked
|
||||
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||
because you made it _public_ with the `@Input` !{_decorator}.
|
||||
because you made it _public_ with the `@Input` decorator.
|
||||
|
||||
Here's how the harness should work when you're done coding.
|
||||
|
||||
|
@ -400,7 +400,7 @@ figure.image-display
|
|||
+makeExcerpt('src/app/highlight.directive.ts', 'color', '')
|
||||
|
||||
:marked
|
||||
Either way, the `@Input` !{_decorator} tells Angular that this property is
|
||||
Either way, the `@Input` decorator tells Angular that this property is
|
||||
_public_ and available for binding by a parent component.
|
||||
Without `@Input`, Angular refuses to bind to the property.
|
||||
|
||||
|
@ -411,22 +411,22 @@ figure.image-display
|
|||
Angular treats a component's template as _belonging_ to the component.
|
||||
The component and its template trust each other implicitly.
|
||||
Therefore, the component's own template may bind to _any_ property of that component,
|
||||
with or without the `@Input` !{_decorator}.
|
||||
with or without the `@Input` decorator.
|
||||
|
||||
But a component or directive shouldn't blindly trust _other_ components and directives.
|
||||
The properties of a component or directive are hidden from binding by default.
|
||||
They are _private_ from an Angular binding perspective.
|
||||
When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective.
|
||||
When adorned with the `@Input` decorator, the property becomes _public_ from an Angular binding perspective.
|
||||
Only then can it be bound by some other component or directive.
|
||||
|
||||
You can tell if `@Input` is needed by the position of the property name in a binding.
|
||||
|
||||
* When it appears in the template expression to the ***right*** of the equals (=),
|
||||
it belongs to the template's component and does not require the `@Input` !{_decorator}.
|
||||
it belongs to the template's component and does not require the `@Input` decorator.
|
||||
|
||||
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
|
||||
the property belongs to some _other_ component or directive;
|
||||
that property must be adorned with the `@Input` !{_decorator}.
|
||||
that property must be adorned with the `@Input` decorator.
|
||||
|
||||
Now apply that reasoning to the following example:
|
||||
|
||||
|
@ -435,8 +435,8 @@ figure.image-display
|
|||
:marked
|
||||
* The `color` property in the expression on the right belongs to the template's component.
|
||||
The template and its component trust each other.
|
||||
The `color` property doesn't require the `@Input` !{_decorator}.
|
||||
The `color` property doesn't require the `@Input` decorator.
|
||||
|
||||
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
|
||||
not a property of the template's component. There are trust issues.
|
||||
Therefore, the directive property must carry the `@Input` !{_decorator}.
|
||||
Therefore, the directive property must carry the `@Input` decorator.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _at_angular = '@angular'
|
||||
|
||||
:marked
|
||||
Angular supports most recent browsers. This includes the following specific versions:
|
||||
|
|
|
@ -31,7 +31,7 @@ block includes
|
|||
specifying any selectors, rules, and media queries that you need.
|
||||
|
||||
One way to do this is to set the `styles` property in the component metadata.
|
||||
The `styles` property takes #{_an} #{_array} of strings that contain CSS code.
|
||||
The `styles` property takes an array of strings that contain CSS code.
|
||||
Usually you give it one string, as in the following example:
|
||||
|
||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
|
||||
|
@ -134,8 +134,8 @@ a(id='loading-styles')
|
|||
|
||||
### Styles in metadata
|
||||
|
||||
You can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
||||
Each string in the #{_array} (usually just one string) defines the CSS.
|
||||
You can add a `styles` array property to the `@Component` decorator.
|
||||
Each string in the array (usually just one string) defines the CSS.
|
||||
|
||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
|
||||
|
||||
|
@ -143,12 +143,11 @@ a(id='loading-styles')
|
|||
### Style URLs in metadata
|
||||
|
||||
You can load styles from external CSS files by adding a `styleUrls` attribute
|
||||
into a component's `@Component` #{_decorator}:
|
||||
into a component's `@Component` decorator:
|
||||
|
||||
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
|
||||
|
||||
block style-url
|
||||
.alert.is-important
|
||||
.alert.is-important
|
||||
:marked
|
||||
The URL is relative to the *application root*, which is usually the
|
||||
location of the `index.html` web page that hosts the application.
|
||||
|
@ -156,8 +155,7 @@ block style-url
|
|||
That's why the example URL begins `src/app/`.
|
||||
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
|
||||
|
||||
block module-bundlers
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
||||
to load styles from external files at build time. You could write:
|
||||
|
@ -195,8 +193,7 @@ block module-bundlers
|
|||
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
||||
on the [MDN](https://developer.mozilla.org) site.
|
||||
|
||||
block css-import-url
|
||||
:marked
|
||||
:marked
|
||||
In this case, the URL is relative to the CSS file into which you're importing.
|
||||
|
||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
|
||||
|
@ -300,9 +297,8 @@ code-example(format="nocode").
|
|||
Because these files are co-located with the component,
|
||||
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
||||
|
||||
block module-id
|
||||
:marked
|
||||
:marked
|
||||
You can use a relative URL by prefixing your filenames with `./`:
|
||||
|
||||
+makeExample('src/app/quest-summary.component.ts')
|
||||
+makeExample('src/app/quest-summary.component.ts')
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _thisDot = 'this.';
|
||||
|
||||
:marked
|
||||
**Dependency injection** is an important application design pattern.
|
||||
|
@ -62,7 +61,7 @@ block includes
|
|||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
That would break the `Car` class and it would stay broken until you rewrote it along the lines of
|
||||
`#{_thisDot}engine = new Engine(theNewParameter)`.
|
||||
`this.engine = new Engine(theNewParameter)`.
|
||||
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
|
||||
You may not anticipate them even now.
|
||||
But you'll *have* to start caring because
|
||||
|
@ -108,8 +107,7 @@ block includes
|
|||
The `Car` class no longer creates an `engine` or `tires`.
|
||||
It just consumes them.
|
||||
|
||||
block ctor-syntax
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
This example leverages TypeScript's constructor syntax for declaring
|
||||
parameters and properties simultaneously.
|
||||
|
@ -133,8 +131,7 @@ block ctor-syntax
|
|||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
something like this:
|
||||
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
|
||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', { otl: /(new Car.*$)/gm })(format=".")
|
||||
|
||||
:marked
|
||||
The critical point is this: the `Car` class did not have to change.
|
||||
|
@ -146,8 +143,7 @@ block ctor-syntax
|
|||
You can pass mocks to the constructor that do exactly what you want them to do
|
||||
during each test:
|
||||
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
|
||||
+makeExample('dependency-injection/ts/src/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', { otl: /(new Car.*$)/gm })(format=".")
|
||||
|
||||
:marked
|
||||
**You just learned what dependency injection is**.
|
||||
|
@ -236,8 +232,7 @@ block ctor-syntax
|
|||
Given that the service is a
|
||||
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
||||
consider writing the service code in its own file.
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
|
||||
See [this note](#one-class-per-file) for details.
|
||||
:marked
|
||||
The following `HeroService` exposes a `getHeroes` method that returns
|
||||
|
@ -248,15 +243,14 @@ block ctor-syntax
|
|||
:marked
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `@Injectable()` #{_decorator} above the service class is
|
||||
The `@Injectable()` decorator above the service class is
|
||||
covered [shortly](#injectable).
|
||||
|
||||
- var _perhaps = _docsFor == 'dart' ? '' : 'perhaps';
|
||||
.l-sub-section
|
||||
:marked
|
||||
Of course, this isn't a real service.
|
||||
If the app were actually getting data from a remote server, the API would have to be
|
||||
asynchronous, #{_perhaps} returning a !{_PromiseLinked}.
|
||||
asynchronous, perhaps returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
You'd also have to rewrite the way components consume the service.
|
||||
This is important in general, but not in this example.
|
||||
|
||||
|
@ -279,18 +273,16 @@ a#injector-config
|
|||
that create the services the application requires.
|
||||
This guide explains what [providers](#providers) are later.
|
||||
|
||||
block register-provider-ngmodule
|
||||
:marked
|
||||
:marked
|
||||
You can either register a provider within an [NgModule](ngmodule.html) or in application components.
|
||||
|
||||
a#register-providers-ngmodule
|
||||
:marked
|
||||
a#register-providers-ngmodule
|
||||
:marked
|
||||
### Registering providers in an _NgModule_
|
||||
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
|
||||
in its `providers` !{_array}.
|
||||
in its `providers` array.
|
||||
|
||||
- var app_module_ts = 'src/app/app.module.ts';
|
||||
+makeExcerpt(app_module_ts + ' (excerpt)', 'ngmodule', app_module_ts)
|
||||
+makeExcerpt('src/app/app.module.ts (excerpt)', 'ngmodule', app_module_ts)
|
||||
:marked
|
||||
Because the `HeroService` is used _only_ within the `HeroesComponent`
|
||||
and its subcomponents, the top-level `HeroesComponent` is the ideal
|
||||
|
@ -300,13 +292,12 @@ a#register-providers-component
|
|||
:marked
|
||||
### Registering providers in a component
|
||||
|
||||
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` !{_array}.
|
||||
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array.
|
||||
|
||||
+makeExample('src/app/heroes/heroes.component.1.ts', 'full', 'src/app/heroes/heroes.component.ts', stylePattern)(format='.')
|
||||
|
||||
block ngmodule-vs-component
|
||||
a#ngmodule-vs-comp
|
||||
:marked
|
||||
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
|
||||
|
@ -316,12 +307,12 @@ block ngmodule-vs-component
|
|||
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}.
|
||||
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
|
||||
.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).
|
||||
|
@ -352,12 +343,12 @@ a#prep-for-injection
|
|||
|
||||
:marked
|
||||
Note that the constructor parameter has the type `HeroService`, and that
|
||||
the `HeroListComponent` class has an `@Component` #{_decorator}
|
||||
the `HeroListComponent` class has an `@Component` decorator
|
||||
(scroll up to confirm that fact).
|
||||
Also recall that the parent component (`HeroesComponent`)
|
||||
has `providers` information for `HeroService`.
|
||||
|
||||
The constructor parameter type, the `@Component` #{_decorator},
|
||||
The constructor parameter type, the `@Component` decorator,
|
||||
and the parent's `providers` information combine to tell the
|
||||
Angular injector to inject an instance of
|
||||
`HeroService` whenever it creates a new `HeroListComponent`.
|
||||
|
@ -432,21 +423,19 @@ a#service-needs-service
|
|||
src/app/heroes/hero.service (v1)`)
|
||||
|
||||
:marked
|
||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `#{_priv}logger`.
|
||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
|
||||
You call that property within the `getHeroes()` method when anyone asks for heroes.
|
||||
|
||||
- var injUrl = '../api/core/index/Injectable-decorator.html';
|
||||
a#injectable
|
||||
:marked
|
||||
### Why _@Injectable()_?
|
||||
|
||||
**<a href="#{injUrl}">@Injectable()</a>** marks a class as available to an
|
||||
**<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>** marks a class as available to an
|
||||
injector for instantiation. Generally speaking, an injector reports an
|
||||
error when trying to instantiate a class that is not marked as
|
||||
`@Injectable()`.
|
||||
|
||||
block injectable-not-always-needed-in-ts
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
As it happens, you could have omitted `@Injectable()` from the first
|
||||
version of `HeroService` because it had no injected parameters.
|
||||
|
@ -454,7 +443,7 @@ block injectable-not-always-needed-in-ts
|
|||
You need it because Angular requires constructor parameter metadata
|
||||
in order to inject a `Logger`.
|
||||
|
||||
.callout.is-helpful
|
||||
.callout.is-helpful
|
||||
header Suggestion: add @Injectable() to every service class
|
||||
:marked
|
||||
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||
|
@ -462,7 +451,7 @@ block injectable-not-always-needed-in-ts
|
|||
|
||||
ul(style="font-size:inherit")
|
||||
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when you add a dependency later.
|
||||
li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why #{_a} #{_decorator} is missing.
|
||||
li <b>Consistency:</b> All services follow the same rules, and you don't have to wonder why a decorator is missing.
|
||||
|
||||
:marked
|
||||
Injectors are also responsible for instantiating components
|
||||
|
@ -471,13 +460,12 @@ block injectable-not-always-needed-in-ts
|
|||
|
||||
You *can* add it if you really want to. It isn't necessary because the
|
||||
`HeroesComponent` is already marked with `@Component`, and this
|
||||
!{_decorator} class (like `@Directive` and `@Pipe`, which you learn about later)
|
||||
is a subtype of <a href="#{injUrl}">@Injectable()</a>. It is in
|
||||
fact `@Injectable()` #{_decorator}s that
|
||||
decorator class (like `@Directive` and `@Pipe`, which you learn about later)
|
||||
is a subtype of <a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>. It is in
|
||||
fact `@Injectable()` decorators that
|
||||
identify a class as a target for instantiation by an injector.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
At runtime, injectors can read class metadata in the transpiled JavaScript code
|
||||
and use the constructor parameter type information
|
||||
|
@ -491,12 +479,11 @@ block injectable-not-always-needed-in-ts
|
|||
for _every class with at least one decorator_.
|
||||
|
||||
While any decorator will trigger this effect, mark the service class with the
|
||||
<a href="#{injUrl}">@Injectable()</a> #{_decorator}
|
||||
<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a> decorator
|
||||
to make the intent clear.
|
||||
|
||||
.callout.is-critical
|
||||
header Always include the parentheses
|
||||
block always-include-paren
|
||||
:marked
|
||||
Always write `@Injectable()`, not just `@Injectable`.
|
||||
The application will fail mysteriously if you forget the parentheses.
|
||||
|
@ -513,13 +500,10 @@ block injectable-not-always-needed-in-ts
|
|||
|
||||
+makeExample('src/app/logger.service.ts')
|
||||
|
||||
block real-logger
|
||||
//- N/A
|
||||
|
||||
:marked
|
||||
You're likely to need the same logger service everywhere in your application,
|
||||
so put it in the project's `#{_appDir}` folder and
|
||||
register it in the `providers` #{_array} of the application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`.
|
||||
so put it in the project's `app` folder and
|
||||
register it in the `providers` array of the application module, `AppModule`.
|
||||
|
||||
+makeExcerpt('src/app/providers.component.ts (excerpt)', 'providers-logger','src/app/app.module.ts')
|
||||
|
||||
|
@ -546,51 +530,44 @@ code-example(format="nocode").
|
|||
|
||||
You must register a service *provider* with the injector, or it won't know how to create the service.
|
||||
|
||||
Earlier you registered the `Logger` service in the `providers` #{_array} of the metadata for the `AppModule` like this:
|
||||
Earlier you registered the `Logger` service in the `providers` array of the metadata for the `AppModule` like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-logger')
|
||||
|
||||
- var implements = _docsFor == 'dart' ? 'implements' : 'looks and behaves like a '
|
||||
- var objectlike = _docsFor == 'dart' ? '' : 'an object that behaves like '
|
||||
- var loggerlike = _docsFor == 'dart' ? '' : 'You could provide a logger-like object. '
|
||||
:marked
|
||||
There are many ways to *provide* something that #{implements} `Logger`.
|
||||
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider.
|
||||
But it's not the only way.
|
||||
|
||||
You can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
||||
You could provide a substitute class. #{loggerlike}
|
||||
You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
||||
You could provide a substitute class. You could provide a logger-like object.
|
||||
You could give it a provider that calls a logger factory function.
|
||||
Any of these approaches might be a good choice under the right circumstances.
|
||||
|
||||
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||
|
||||
//- Dart limitation: the provide function isn't const so it cannot be used in an annotation.
|
||||
- var _andProvideFn = _docsFor == 'dart' ? '' : 'and <i>provide</i> object literal';
|
||||
|
||||
#provide
|
||||
:marked
|
||||
### The *Provider* class !{_andProvideFn}
|
||||
### The *Provider* class and _provide_ object literal
|
||||
|
||||
:marked
|
||||
You wrote the `providers` #{_array} like this:
|
||||
You wrote the `providers` array like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-1')
|
||||
|
||||
block provider-shorthand
|
||||
:marked
|
||||
:marked
|
||||
This is actually a shorthand expression for a provider registration
|
||||
using a _provider_ object literal with two properties:
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-3')
|
||||
|
||||
block provider-ctor-args
|
||||
- var _secondParam = 'provider definition object';
|
||||
|
||||
:marked
|
||||
The first is the [token](#token) that serves as the key for both locating a dependency value
|
||||
and registering the provider.
|
||||
|
||||
The second is a !{_secondParam},
|
||||
The second is a provider definition object,
|
||||
which you can think of as a *recipe* for creating the dependency value.
|
||||
There are many ways to create dependency values just as there are many ways to write a recipe.
|
||||
|
||||
|
@ -604,9 +581,6 @@ block provider-ctor-args
|
|||
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-4')
|
||||
|
||||
block dart-diff-const-metadata
|
||||
//- N/A
|
||||
|
||||
a#class-provider-dependencies
|
||||
:marked
|
||||
### Class provider with dependencies
|
||||
|
@ -653,9 +627,6 @@ a#value-provider
|
|||
:marked
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
|
||||
block dart-diff-const-metadata-ctor
|
||||
//- N/A
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','silent-logger')(format=".")
|
||||
|
||||
:marked
|
||||
|
@ -720,19 +691,17 @@ block dart-diff-const-metadata-ctor
|
|||
The `useFactory` field tells Angular that the provider is a factory function
|
||||
whose implementation is the `heroServiceFactory`.
|
||||
|
||||
The `deps` property is #{_an} #{_array} of [provider tokens](#token).
|
||||
The `deps` property is an array of [provider tokens](#token).
|
||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
|
||||
- var exportedvar = _docsFor == 'dart' ? 'constant' : 'exported variable'
|
||||
- var variable = _docsFor == 'dart' ? 'constant' : 'variable'
|
||||
:marked
|
||||
Notice that you captured the factory provider in #{_an} #{exportedvar}, `heroServiceProvider`.
|
||||
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
You can register the `HeroService` with this #{variable} wherever you need it.
|
||||
You can register the `HeroService` with this variable wherever you need it.
|
||||
|
||||
In this sample, you need it only in the `HeroesComponent`,
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` #{_array}.
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` array.
|
||||
Here you see the new and the old implementation side-by-side:
|
||||
|
||||
+makeTabs(
|
||||
|
@ -774,12 +743,10 @@ a#non-class-dependencies
|
|||
### Non-class dependencies
|
||||
p
|
||||
| What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
||||
block non-class-dep-eg
|
||||
| span string, function, or object.
|
||||
p
|
||||
| Applications often define configuration objects with lots of small facts
|
||||
| (like the title of the application or the address of a web API endpoint)
|
||||
block config-obj-maps
|
||||
| but these configuration objects aren't always instances of a class.
|
||||
| They can be object literals such as this one:
|
||||
|
||||
|
@ -789,13 +756,12 @@ p
|
|||
What if you'd like to make this configuration object available for injection?
|
||||
You know you can register an object with a [value provider](#value-provider).
|
||||
|
||||
block what-should-we-use-as-token
|
||||
:marked
|
||||
: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
|
||||
.l-sub-section#interface
|
||||
:marked
|
||||
### TypeScript interfaces aren't valid tokens
|
||||
|
||||
|
@ -811,14 +777,12 @@ block what-should-we-use-as-token
|
|||
The TypeScript interface disappears from the generated JavaScript.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
//- FIXME update once https://github.com/dart-lang/angular2/issues/16 is addressed.
|
||||
- var opaquetoken = _docsFor == 'dart' ? '<b>OpaqueToken</b>' : '<a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>'
|
||||
a#opaquetoken
|
||||
:marked
|
||||
### _OpaqueToken_
|
||||
|
||||
One solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an !{opaquetoken}.
|
||||
to define and use an <a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>.
|
||||
The definition looks like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
|
||||
|
@ -830,18 +794,16 @@ a#opaquetoken
|
|||
|
||||
:marked
|
||||
Now you can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject` #{_decorator}:
|
||||
the help of an `@Inject` decorator:
|
||||
|
||||
+makeExample('dependency-injection/ts/src/app/app.component.2.ts','ctor')(format=".")
|
||||
|
||||
- var configType = _docsFor == 'dart' ? '<code>Map</code>' : '<code>AppConfig</code>'
|
||||
.l-sub-section
|
||||
:marked
|
||||
Although the !{configType} interface plays no role in dependency injection,
|
||||
Although the `AppConfig` interface plays no role in dependency injection,
|
||||
it supports typing of the configuration object within the class.
|
||||
|
||||
block dart-map-alternative
|
||||
:marked
|
||||
:marked
|
||||
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
|
||||
|
||||
+makeExcerpt('src/app/app.module.ts','ngmodule-providers')
|
||||
|
@ -855,8 +817,7 @@ block dart-map-alternative
|
|||
You can tell Angular that the dependency is optional by annotating the
|
||||
constructor argument with `@Optional()`:
|
||||
|
||||
+ifDocsFor('ts')
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','import-optional', '')
|
||||
+makeExample('dependency-injection/ts/src/app/providers.component.ts','provider-10-ctor', '')(format='.')
|
||||
|
||||
:marked
|
||||
|
@ -915,9 +876,8 @@ block dart-map-alternative
|
|||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-main-section#one-class-per-file
|
||||
:marked
|
||||
.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.
|
||||
|
@ -929,7 +889,7 @@ block dart-map-alternative
|
|||
If you define the component before the service,
|
||||
you'll get a runtime null reference error.
|
||||
|
||||
.l-sub-section
|
||||
.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).
|
||||
|
|
|
@ -156,7 +156,7 @@ a#node-modules
|
|||
Practice with this sample before attempting these techniques on your application.
|
||||
|
||||
1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project
|
||||
named <ngio-ex path="simple-deployment"></ngio-ex>.
|
||||
named <code>simple-deployment</code>.
|
||||
|
||||
1. Add the "Simple deployment" sample files shown above.
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
|
||||
- var _boolean = 'truthy/falsy';
|
||||
|
||||
:marked
|
||||
You can display data by binding controls in an HTML template to properties of an Angular component.
|
||||
|
@ -19,7 +17,7 @@ figure.image-display
|
|||
# Contents
|
||||
|
||||
* [Showing component properties with interpolation](#interpolation).
|
||||
* [Showing !{_an} !{_array} property with NgFor](#ngFor).
|
||||
* [Showing an array property with NgFor](#ngFor).
|
||||
* [Conditional display with NgIf](#ngIf).
|
||||
|
||||
.l-sub-section
|
||||
|
@ -35,9 +33,9 @@ figure.image-display
|
|||
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||
|
||||
Follow the [setup](setup.html) instructions for creating a new project
|
||||
named <ngio-ex path="displaying-data"></ngio-ex>.
|
||||
named <code>displaying-data</code>.
|
||||
|
||||
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
||||
Then modify the <code>app.component.ts</code> file by
|
||||
changing the template and the body of the component.
|
||||
|
||||
When you're done, it should look like this:
|
||||
|
@ -52,8 +50,7 @@ figure.image-display
|
|||
|
||||
+makeExcerpt('src/app/app.component.1.ts', 'template', '')
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
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
|
||||
|
@ -74,13 +71,13 @@ figure.image-display
|
|||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for you. How?
|
||||
|
||||
The CSS `selector` in the `@Component` !{_decorator} specifies an element named `<my-app>`.
|
||||
The CSS `selector` in the `@Component` decorator specifies an element named `<my-app>`.
|
||||
That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
+makeExcerpt('src/index.html', 'body')
|
||||
|
||||
:marked
|
||||
When you bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), Angular looks for a `<my-app>`
|
||||
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<my-app>`
|
||||
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
||||
inside the `<my-app>` tag.
|
||||
|
||||
|
@ -88,8 +85,7 @@ figure.image-display
|
|||
figure.image-display
|
||||
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.
|
||||
|
||||
:marked
|
||||
|
@ -98,7 +94,7 @@ figure.image-display
|
|||
You can store your component's template in one of two places.
|
||||
You can define it *inline* using the `template` property, or you can define
|
||||
the template in a separate HTML file and link to it in
|
||||
the component metadata using the `@Component` !{_decorator}'s `templateUrl` property.
|
||||
the component metadata using the `@Component` decorator's `templateUrl` property.
|
||||
|
||||
The choice between inline and separate HTML is a matter of taste,
|
||||
circumstances, and organization policy.
|
||||
|
@ -107,22 +103,21 @@ figure.image-display
|
|||
|
||||
In either style, the template data bindings have the same access to the component's properties.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
:marked
|
||||
## Constructor or variable initialization?
|
||||
|
||||
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
|
||||
|
||||
+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.
|
||||
|
||||
.l-main-section#ngFor
|
||||
:marked
|
||||
## Showing !{_an} !{_array} property with ***ngFor**
|
||||
## Showing an array property with ***ngFor**
|
||||
|
||||
To display a list of heroes, begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}.
|
||||
To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
|
||||
|
||||
+makeExcerpt('src/app/app.component.2.ts', 'class')
|
||||
|
||||
|
@ -156,8 +151,8 @@ figure.image-display
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
|
||||
repeat items for any [iterable](!{_iterableUrl}) object.
|
||||
In this case, `ngFor` is displaying an array, but `ngFor` can
|
||||
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
|
||||
:marked
|
||||
Now the heroes appear in an unordered list.
|
||||
|
||||
|
@ -171,18 +166,17 @@ figure.image-display
|
|||
The app's code defines the data directly inside the component, which isn't best practice.
|
||||
In a simple demo, however, it's fine.
|
||||
|
||||
At the moment, the binding is to !{_an} !{_array} of strings.
|
||||
At the moment, the binding is to an array of strings.
|
||||
In real applications, most bindings are to more specialized objects.
|
||||
|
||||
To convert this binding to use specialized objects, turn the !{_array}
|
||||
of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `Hero` class.
|
||||
To convert this binding to use specialized objects, turn the array
|
||||
of hero names into an array of `Hero` objects. For that you'll need a `Hero` class.
|
||||
|
||||
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code:
|
||||
Create a new file in the `app` folder called `hero.ts` with the following code:
|
||||
|
||||
+makeExcerpt('src/app/hero.ts')
|
||||
|
||||
block hero-class
|
||||
:marked
|
||||
:marked
|
||||
You've defined a class with a constructor and two properties: `id` and `name`.
|
||||
|
||||
It might not look like the class has properties, but it does.
|
||||
|
@ -190,9 +184,9 @@ block hero-class
|
|||
|
||||
Consider the first parameter:
|
||||
|
||||
+makeExcerpt('src/app/hero.ts ()', 'id')
|
||||
+makeExcerpt('src/app/hero.ts ()', 'id')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
That brief syntax does a lot:
|
||||
* Declares a constructor parameter and its type.
|
||||
* Declares a public property of the same name.
|
||||
|
@ -202,7 +196,7 @@ block hero-class
|
|||
:marked
|
||||
## Using the Hero class
|
||||
|
||||
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ !{_array}
|
||||
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
|
||||
of `Hero` objects:
|
||||
|
||||
+makeExcerpt('src/app/app.component.3.ts', 'heroes')
|
||||
|
@ -225,7 +219,7 @@ block hero-class
|
|||
|
||||
Let's change the example to display a message if there are more than three heroes.
|
||||
|
||||
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition.
|
||||
The Angular `ngIf` directive inserts or removes an element based on a _truthy/falsy_ condition.
|
||||
To see it in action, add the following paragraph at the bottom of the template:
|
||||
|
||||
+makeExcerpt('src/app/app.component.ts', 'message')
|
||||
|
@ -237,7 +231,7 @@ block hero-class
|
|||
|
||||
:marked
|
||||
The template expression inside the double quotes,
|
||||
`*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}.
|
||||
`*ngIf="heros.length > 3"`, looks and behaves much like TypeScript.
|
||||
When the component's list of heroes has more than three items, Angular adds the paragraph
|
||||
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
||||
paragraph, so no message appears. For more information,
|
||||
|
@ -250,8 +244,8 @@ block hero-class
|
|||
big chunks of HTML with many data bindings.
|
||||
|
||||
:marked
|
||||
Try it out. Because the !{_array} has four items, the message should appear.
|
||||
Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}.
|
||||
Try it out. Because the array has four items, the message should appear.
|
||||
Go back into <code>app.component.ts"</code> and delete or comment out one of the elements from the hero array.
|
||||
The browser should refresh automatically and the message should disappear.
|
||||
|
||||
.l-main-section
|
||||
|
@ -259,14 +253,13 @@ block hero-class
|
|||
## Summary
|
||||
Now you know how to use:
|
||||
- **Interpolation** with double curly braces to display a component property.
|
||||
- **ngFor** to display !{_an} !{_array} of items.
|
||||
- A !{_Lang} class to shape the **model data** for your component and display properties of that model.
|
||||
- **ngFor** to display an array of items.
|
||||
- A TypeScript class to shape the **model data** for your component and display properties of that model.
|
||||
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||
|
||||
Here's the final code:
|
||||
|
||||
block final-code
|
||||
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
|
||||
+makeTabs(`displaying-data/ts/src/app/app.component.ts,
|
||||
displaying-data/ts/src/app/hero.ts,
|
||||
displaying-data/ts/src/app/app.module.ts,
|
||||
displaying-data/ts/src/main.ts`,
|
||||
|
|
|
@ -83,7 +83,7 @@ figure.image-display
|
|||
## Setup
|
||||
|
||||
Follow the [setup](setup.html) instructions for creating a new project
|
||||
named <span ngio-ex>angular-forms</span>.
|
||||
named angular-forms.
|
||||
|
||||
## Create the Hero model class
|
||||
|
||||
|
@ -94,7 +94,7 @@ figure.image-display
|
|||
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||
and one optional field (`alterEgo`).
|
||||
|
||||
In the `!{_appDir}` directory, create the following file with the given content:
|
||||
In the `app` directory, create the following file with the given content:
|
||||
|
||||
+makeExample('src/app/hero.ts')
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ figure.image-display
|
|||
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
|
||||
|
||||
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||
All requests bubble up to the root <span if-docs="ts"><code>NgModule</code></span> injector that you configured with the `!{_bootstrapModule}` method.
|
||||
All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
- var top="vertical-align:top"
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px")
|
||||
|
||||
|
@ -17,6 +15,7 @@ figure
|
|||
|
||||
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||
<br class="l-clear-both">
|
||||
<<<<<<< 93d3db1fd4f61c9c6078b2000dea164c87fa071c
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Contents
|
||||
|
@ -38,6 +37,28 @@ figure
|
|||
* [Content projection](#content-projection)
|
||||
* [AfterContent hooks](#aftercontent-hooks)
|
||||
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
|
||||
=======
|
||||
|
||||
:marked
|
||||
## Contents
|
||||
* [Component lifecycle hooks overview](#hooks-overview)
|
||||
* [Lifecycle sequence](#hooks-purpose-timing)
|
||||
* [Interfaces are optional (technically)](#interface-optional)
|
||||
* [Other Angular lifecycle hooks](#other-lifecycle-hooks)
|
||||
* [Lifecycle examples](#the-sample)
|
||||
* [Peek-a-boo: all hooks](#peek-a-boo)
|
||||
* [Spying OnInit and OnDestroy](#spy)
|
||||
* [OnInit](#oninit)
|
||||
* [OnDestroy](#ondestroy)
|
||||
* [OnChanges](#onchanges)
|
||||
* [DoCheck](#docheck)
|
||||
* [AfterView](#afterview)
|
||||
* [Abide by the unidirectional data flow rule](#wait-a-tick)
|
||||
* [AfterContent](#aftercontent)
|
||||
* [Content projection](#content-projection)
|
||||
* [AfterContent hooks](#aftercontent-hooks)
|
||||
* [No unidirectional flow worries with _AfterContent_](#no-unidirectional-flow-worries)
|
||||
>>>>>>> chore: remove dart remains
|
||||
|
||||
:marked
|
||||
Try the <live-example></live-example>.
|
||||
|
@ -72,7 +93,7 @@ table(width="100%")
|
|||
th Hook
|
||||
th Purpose and Timing
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngOnChanges()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -81,7 +102,7 @@ table(width="100%")
|
|||
|
||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngOnInit()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -90,7 +111,7 @@ table(width="100%")
|
|||
|
||||
Called _once_, after the _first_ `ngOnChanges()`.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngDoCheck()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -98,7 +119,7 @@ table(width="100%")
|
|||
|
||||
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngAfterContentInit()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -108,7 +129,7 @@ table(width="100%")
|
|||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngAfterContentChecked()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -118,7 +139,7 @@ table(width="100%")
|
|||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngAfterViewInit()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -128,7 +149,7 @@ table(width="100%")
|
|||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngAfterViewChecked()</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -138,7 +159,7 @@ table(width="100%")
|
|||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <code>ngOnDestroy</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -147,10 +168,9 @@ table(width="100%")
|
|||
|
||||
Called _just before_ Angular destroys the directive/component.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
a#interface-optional
|
||||
.l-main-section
|
||||
:marked
|
||||
a#interface-optional
|
||||
.l-main-section
|
||||
:marked
|
||||
## Interfaces are optional (technically)
|
||||
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
|
@ -173,9 +193,6 @@ a#other-lifecycle-hooks
|
|||
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
||||
|
||||
block other-angular-subsystems
|
||||
//- N/A for TS.
|
||||
|
||||
:marked
|
||||
3rd party libraries might implement their hooks as well in order to give developers more
|
||||
control over how these libraries are used.
|
||||
|
@ -199,13 +216,13 @@ table(width="100%")
|
|||
tr
|
||||
th Component
|
||||
th Description
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#peek-a-boo">Peek-a-boo</a>
|
||||
td
|
||||
:marked
|
||||
Demonstrates every lifecycle hook.
|
||||
Each hook method writes to the on-screen log.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#spy">Spy</a>
|
||||
td
|
||||
:marked
|
||||
|
@ -215,33 +232,33 @@ table(width="100%")
|
|||
|
||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
managed by the parent `SpyComponent`.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#onchanges">OnChanges</a>
|
||||
td
|
||||
:marked
|
||||
See how Angular calls the `ngOnChanges()` hook with a `changes` object
|
||||
every time one of the component input properties changes.
|
||||
Shows how to interpret the `changes` object.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#docheck">DoCheck</a>
|
||||
td
|
||||
:marked
|
||||
Implements an `ngDoCheck()` method with custom change detection.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#afterview">AfterView</a>
|
||||
td
|
||||
:marked
|
||||
Shows what Angular means by a *view*.
|
||||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td <a href="#aftercontent">AfterContent</a>
|
||||
td
|
||||
:marked
|
||||
Shows how to project external content into a component and
|
||||
how to distinguish projected content from a component's view children.
|
||||
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||||
tr(style=top)
|
||||
tr(style=vertical-align:top)
|
||||
td Counter
|
||||
td
|
||||
:marked
|
||||
|
@ -479,8 +496,6 @@ a#wait-a-tick
|
|||
Both of these hooks fire _after_ the component's view has been composed.
|
||||
|
||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||
block tick-methods
|
||||
:marked
|
||||
The `LoggerService.tick_then()` postpones the log update
|
||||
for one turn of the browser's JavaScript cycle and that's just long enough.
|
||||
|
||||
|
|
|
@ -142,8 +142,8 @@ figure.image-display
|
|||
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
||||
Your pipe has one such parameter: the `exponent`.
|
||||
* To tell Angular that this is a pipe, you apply the
|
||||
`@Pipe` #{_decorator}, which you import from the core Angular library.
|
||||
* The `@Pipe` #{_decorator} allows you to define the
|
||||
`@Pipe` decorator, which you import from the core Angular library.
|
||||
* The `@Pipe` decorator allows you to define the
|
||||
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
|
||||
Your pipe's name is `exponentialStrength`.
|
||||
|
||||
|
@ -161,16 +161,14 @@ figure.image-display
|
|||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||||
|
||||
- var _decls = _docsFor == 'dart' ? 'pipes' : 'declarations';
|
||||
- var _appMod = _docsFor == 'dart' ? '@Component' : 'AppModule';
|
||||
:marked
|
||||
Note the following:
|
||||
|
||||
* You use your custom pipe the same way you use built-in pipes.
|
||||
* You must include your pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
||||
* You must include your pipe in the `declarations` array of the `AppModule`.
|
||||
|
||||
.callout.is-helpful
|
||||
header Remember the !{_decls} #{_array}
|
||||
header Remember the declarations array
|
||||
:marked
|
||||
You must manually register custom pipes.
|
||||
If you don't, Angular reports an error.
|
||||
|
@ -206,17 +204,17 @@ a#change-detection
|
|||
### No pipe
|
||||
|
||||
In the next example, the component uses the default, aggressive change detection strategy to monitor and update
|
||||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
||||
its display of every hero in the `heroes` array. Here's the template:
|
||||
|
||||
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
|
||||
|
||||
:marked
|
||||
The companion component class provides heroes, adds heroes into the #{_array}, and can reset the #{_array}.
|
||||
The companion component class provides heroes, adds heroes into the array, and can reset the array.
|
||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
|
||||
|
||||
:marked
|
||||
You can add heroes and Angular updates the display when you do.
|
||||
If you click the `reset` button, Angular replaces `heroes` with a new #{_array} of the original heroes and updates the display.
|
||||
If you click the `reset` button, Angular replaces `heroes` with a new array of the original heroes and updates the display.
|
||||
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
|
||||
|
||||
### Flying-heroes pipe
|
||||
|
@ -237,15 +235,15 @@ a#change-detection
|
|||
Notice how a hero is added:
|
||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
|
||||
:marked
|
||||
You add the hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
||||
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
||||
You add the hero into the `heroes` array. The reference to the array hasn't changed.
|
||||
It's the same array. That's all Angular cares about. From its perspective, *same array, no change, no display update*.
|
||||
|
||||
To fix that, create an #{_array} with the new hero appended and assign that to `heroes`.
|
||||
This time Angular detects that the #{_array} reference has changed.
|
||||
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero.
|
||||
To fix that, create an array with the new hero appended and assign that to `heroes`.
|
||||
This time Angular detects that the array reference has changed.
|
||||
It executes the pipe and updates the display with the new array, which includes the new flying hero.
|
||||
|
||||
If you *mutate* the #{_array}, no pipe is invoked and the display isn't updated;
|
||||
if you *replace* the #{_array}, the pipe executes and the display is updated.
|
||||
If you *mutate* the array, no pipe is invoked and the display isn't updated;
|
||||
if you *replace* the array, the pipe executes and the display is updated.
|
||||
The Flying Heroes application extends the
|
||||
code with checkbox switches and additional displays to help you experience these effects.
|
||||
|
||||
|
@ -253,8 +251,8 @@ figure.image-display
|
|||
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
||||
|
||||
:marked
|
||||
Replacing the #{_array} is an efficient way to signal Angular to update the display.
|
||||
When do you replace the #{_array}? When the data change.
|
||||
Replacing the array is an efficient way to signal Angular to update the display.
|
||||
When do you replace the array? When the data change.
|
||||
That's an easy rule to follow in *this* example
|
||||
where the only way to change the data is by adding a hero.
|
||||
|
||||
|
@ -289,7 +287,7 @@ figure.image-display
|
|||
or a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||||
|
||||
Angular ignores changes within (composite) objects.
|
||||
It won't call a pure pipe if you change an input month, add to an input #{_array}, or update an input object property.
|
||||
It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.
|
||||
|
||||
This may seem restrictive but it's also fast.
|
||||
An object reference check is fast—much faster than a deep check for
|
||||
|
@ -335,28 +333,25 @@ figure.image-display
|
|||
:marked
|
||||
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
||||
|
||||
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html';
|
||||
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
|
||||
+makeExcerpt('src/app/flying-heroes' + _fnSuffix + ' (excerpt)', _region)
|
||||
+makeExcerpt('src/app/flying-heroes-impure.component.html (excerpt)', 'template-flying-heroes')
|
||||
|
||||
:marked
|
||||
The only substantive change is the pipe in the template.
|
||||
You can confirm in the <live-example></live-example> that the _flying heroes_
|
||||
display updates as you add heroes, even when you mutate the `heroes` #{_array}.
|
||||
display updates as you add heroes, even when you mutate the `heroes` array.
|
||||
|
||||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
||||
h3#async-pipe The impure #[i AsyncPipe]
|
||||
:marked
|
||||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||||
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
||||
The `AsyncPipe` accepts a `Promise` or `Observable` as input
|
||||
and subscribes to the input automatically, eventually returning the emitted values.
|
||||
|
||||
The `AsyncPipe` is also stateful.
|
||||
The pipe maintains a subscription to the input `#{_Observable}` and
|
||||
keeps delivering values from that `#{_Observable}` as they arrive.
|
||||
The pipe maintains a subscription to the input `Observable` and
|
||||
keeps delivering values from that `Observable` as they arrive.
|
||||
|
||||
This next example binds an `#{_Observable}` of message strings
|
||||
(`message#{_dollar}`) to a view with the `async` pipe.
|
||||
This next example binds an `Observable` of message strings
|
||||
(`message$`) to a view with the `async` pipe.
|
||||
|
||||
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
|
||||
|
||||
|
@ -375,7 +370,7 @@ h3#async-pipe The impure #[i AsyncPipe]
|
|||
If you're not careful, this pipe will punish the server with requests.
|
||||
|
||||
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
|
||||
The code<span if-docs="ts"> uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
||||
The code uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
||||
|
||||
+makeExample('src/app/fetch-json.pipe.ts')
|
||||
:marked
|
||||
|
|
|
@ -1543,8 +1543,8 @@ a#milestone-4
|
|||
Begin by imitating the heroes feature:
|
||||
|
||||
- Delete the placeholder crisis center file.
|
||||
- Create !{_an} `!{_appDir}/crisis-center` folder.
|
||||
- Copy the files from `!{_appDir}/heroes` into the new crisis center folder.
|
||||
- Create an `app/crisis-center` folder.
|
||||
- Copy the files from `app/heroes` into the new crisis center folder.
|
||||
- In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
|
||||
|
||||
You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes:
|
||||
|
@ -1560,7 +1560,7 @@ a#milestone-4
|
|||
:marked
|
||||
In keeping with the
|
||||
<a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" target="_blank" title="Separation of Concerns">*Separation of Concerns* principle</a>,
|
||||
changes to the *Crisis Center* won't affect the `!{_AppModuleVsAppComp}` or
|
||||
changes to the *Crisis Center* won't affect the `AppModule` or
|
||||
any other feature's component.
|
||||
a#crisis-child-routes
|
||||
:marked
|
||||
|
@ -2288,7 +2288,7 @@ a#CanDeactivate
|
|||
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
|
||||
|
||||
:marked
|
||||
Add the `Guard` to the main `AppRoutingModule` `providers` !{_array} so the
|
||||
Add the `Guard` to the main `AppRoutingModule` `providers` array so the
|
||||
`Router` can inject it during the navigation process.
|
||||
|
||||
+makeExample('src/app/app-routing.module.4.ts', '', '')
|
||||
|
@ -2350,7 +2350,7 @@ a#fetch-before-navigating
|
|||
Import this resolver in the `crisis-center-routing.module.ts`
|
||||
and add a `resolve` object to the `CrisisDetailComponent` route configuration.
|
||||
|
||||
Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` !{_array}.
|
||||
Remember to add the `CrisisDetailResolver` service to the `CrisisCenterRoutingModule`'s `providers` array.
|
||||
|
||||
+makeExcerpt('src/app/crisis-center/crisis-center-routing.module.4.ts (resolver)', 'crisis-detail-resolver')
|
||||
|
||||
|
@ -2549,7 +2549,7 @@ a#can-load-guard
|
|||
The `checkLogin()` method redirects to that URL once the user has logged in.
|
||||
|
||||
Now import the `AuthGuard` into the `AppRoutingModule` and add the `AuthGuard` to the `canLoad`
|
||||
!{_array} for the `admin` route.
|
||||
array for the `admin` route.
|
||||
The completed admin route looks like this:
|
||||
|
||||
+makeExample('router/ts/src/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)')
|
||||
|
|
|
@ -8,9 +8,8 @@ block includes
|
|||
|
||||
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-main-section
|
||||
:marked
|
||||
.l-main-section
|
||||
:marked
|
||||
# Contents
|
||||
|
||||
* [Reporting vulnerabilities](#report-issues).
|
||||
|
@ -108,12 +107,11 @@ h2#xss Preventing cross-site scripting (XSS)
|
|||
|
||||
+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>`
|
||||
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'
|
||||
alt='A screenshot showing interpolated and bound HTML values')
|
||||
|
||||
|
@ -152,10 +150,9 @@ block html-sanitization
|
|||
the server. Don't generate Angular templates on the server side using a templating language; doing this
|
||||
carries a high risk of introducing template-injection vulnerabilities.
|
||||
|
||||
block bypass-security-apis
|
||||
.l-main-section
|
||||
h2#bypass-security-apis Trusting safe values
|
||||
:marked
|
||||
.l-main-section
|
||||
h2#bypass-security-apis Trusting safe values
|
||||
:marked
|
||||
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
|
||||
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
|
||||
situations, you can tell Angular that you inspected a value, checked how it was generated, and made
|
||||
|
@ -176,20 +173,20 @@ block bypass-security-apis
|
|||
your intended use of the value. Imagine that the following template needs to bind a URL to a
|
||||
`javascript:alert(...)` call:
|
||||
|
||||
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
|
||||
+makeExcerpt('src/app/bypass-security.component.html', 'URL')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
|
||||
in development mode, logs this action to the console. To prevent
|
||||
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
||||
|
||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url')
|
||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-url')
|
||||
|
||||
figure.image-display
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/security/bypass-security-component.png'
|
||||
alt='A screenshot showing an alert box created from a trusted URL')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
If you need to convert user input into a trusted value, use a
|
||||
controller method. The following template allows users to enter a YouTube video ID and load the
|
||||
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
||||
|
@ -197,19 +194,18 @@ block bypass-security-apis
|
|||
could execute. So call a method on the controller to construct a trusted video URL, which causes
|
||||
Angular to allow binding into `<iframe src>`:
|
||||
|
||||
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
|
||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
|
||||
+makeExcerpt('src/app/bypass-security.component.html', 'iframe')
|
||||
+makeExcerpt('src/app/bypass-security.component.ts ()', 'trust-video-url')
|
||||
|
||||
block http
|
||||
.l-main-section
|
||||
h2#http HTTP-level vulnerabilities
|
||||
:marked
|
||||
.l-main-section
|
||||
h2#http HTTP-level vulnerabilities
|
||||
:marked
|
||||
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
|
||||
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
|
||||
on the server side, but Angular provides helpers to make integration on the client side easier.
|
||||
|
||||
h3#xsrf Cross-site request forgery
|
||||
:marked
|
||||
h3#xsrf Cross-site request forgery
|
||||
:marked
|
||||
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
||||
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
|
||||
to the application's web server (such as `example-bank.com`).
|
||||
|
@ -251,15 +247,15 @@ block http
|
|||
|
||||
Your server may use a different cookie or header name for this purpose.
|
||||
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
|
||||
code-example(language="typescript").
|
||||
code-example(language="typescript").
|
||||
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
|
||||
:marked
|
||||
:marked
|
||||
Or you can implement and provide an entirely custom `XSRFStrategy`:
|
||||
|
||||
code-example(language="typescript").
|
||||
code-example(language="typescript").
|
||||
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
|
||||
|
||||
:marked
|
||||
:marked
|
||||
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
||||
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and
|
||||
<a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
|
||||
|
@ -269,8 +265,8 @@ block http
|
|||
See also Dave Smith's easy-to-understand
|
||||
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
|
||||
|
||||
h3#xssi Cross-site script inclusion (XSSI)
|
||||
:marked
|
||||
h3#xssi Cross-site script inclusion (XSSI)
|
||||
:marked
|
||||
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
|
||||
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
|
||||
object constructors, and then including an API URL using a `<script>` tag.
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _Http = 'Http'; // Angular `Http` library name.
|
||||
- var _Angular_Http = 'Angular <code>Http</code>'
|
||||
- var _Angular_http_library = 'Angular HTTP library'
|
||||
|
||||
:marked
|
||||
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
|
||||
|
@ -16,7 +13,7 @@ block includes
|
|||
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
|
||||
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
||||
|
||||
The !{_Angular_http_library} simplifies application programming with the **XHR** and **JSONP** APIs.
|
||||
The Angular HTTP library simplifies application programming with the **XHR** and **JSONP** APIs.
|
||||
# Contents
|
||||
|
||||
* [Demos](#demos)
|
||||
|
@ -24,7 +21,7 @@ block includes
|
|||
* [The Tour of Heroes *HTTP* client demo](#http-client)
|
||||
- [The `HeroListComponent` class](#HeroListComponent)
|
||||
* [Fetch data with `http.get()`](#fetch-data)
|
||||
<li if-docs="ts"> [RxJS library](#rxjs-library)
|
||||
<li>[RxJS library](#rxjs-library)
|
||||
<ul>
|
||||
<li> [Enable RxJS operators](#enable-rxjs-operators)</li>
|
||||
</ul>
|
||||
|
@ -38,16 +35,16 @@ block includes
|
|||
- [Headers](#headers)
|
||||
- [JSON results](#json-results)
|
||||
|
||||
<ul><li if-docs="ts"> [Fall back to promises](#promises)</ul>
|
||||
<ul><li> [Fall back to promises](#promises)</ul>
|
||||
|
||||
* [Cross-Origin Requests: Wikipedia example](#cors)
|
||||
<ul if-docs="ts">
|
||||
<ul>
|
||||
<li> [Search Wikipedia](#search-wikipedia)</li>
|
||||
<li> [Search parameters](#search-parameters)</li>
|
||||
<li> [The WikiComponent](#wikicomponent)</li>
|
||||
</ul>
|
||||
* [A wasteful app](#wasteful-app)
|
||||
<li if-docs="ts"> [More fun with Observables](#more-observables)
|
||||
<li> [More fun with Observables](#more-observables)
|
||||
<ul>
|
||||
<li> [Create a stream of search terms](#create-stream)</li>
|
||||
<li> [Listen for search terms](#listen-for-search-terms)</li>
|
||||
|
@ -69,7 +66,7 @@ a#demos
|
|||
block demos-list
|
||||
:marked
|
||||
- [The Tour of Heroes *HTTP* client demo](#http-client).
|
||||
- [Fall back to !{_Promise}s](#promises).
|
||||
- [Fall back to Promises](#promises).
|
||||
- [Cross-Origin Requests: Wikipedia example](#cors).
|
||||
- [More fun with Observables](#more-observables).
|
||||
|
||||
|
@ -83,18 +80,17 @@ block demos-list
|
|||
|
||||
First, configure the application to use server communication facilities.
|
||||
|
||||
The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol.
|
||||
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||
The Angular <code>Http</code> client communicates with the server using a familiar HTTP request/response protocol.
|
||||
The `Http` client is one of a family of services in the Angular HTTP library.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
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.
|
||||
|
||||
:marked
|
||||
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system.
|
||||
Before you can use the `Http` client, you need to register it as a service provider with the dependency injection system.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -108,7 +104,7 @@ block demos-list
|
|||
block http-providers
|
||||
:marked
|
||||
Begin by importing the necessary members.
|
||||
The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
|
||||
The newcomers are the `HttpModule` and the `JsonpModule` from the Angular HTTP library. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
|
||||
|
||||
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
|
||||
.l-sub-section
|
||||
|
@ -123,7 +119,7 @@ block http-providers
|
|||
|
||||
The first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
|
||||
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
|
||||
The app uses the !{_Angular_Http} client to communicate via **XMLHttpRequest (XHR)**.
|
||||
The app uses the Angular <code>Http</code> client to communicate via **XMLHttpRequest (XHR)**.
|
||||
|
||||
It works like this:
|
||||
figure.image-display
|
||||
|
@ -152,7 +148,7 @@ a#HeroListComponent
|
|||
Angular [injects](dependency-injection.html) a `HeroService` into the constructor
|
||||
and the component calls that service to fetch and save data.
|
||||
|
||||
The component **does not talk directly to the !{_Angular_Http} client**.
|
||||
The component **does not talk directly to the Angular <code>Http</code> client**.
|
||||
The component doesn't know or care how it gets the data.
|
||||
It delegates to the `HeroService`.
|
||||
|
||||
|
@ -169,7 +165,7 @@ a#HeroListComponent
|
|||
(especially calling a remote server) is handled in a separate method.
|
||||
block getheroes-and-create
|
||||
:marked
|
||||
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
|
||||
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the Angular <code>Http</code> client fetched from the server.
|
||||
|
||||
Think of an `Observable` as a stream of events published by some source.
|
||||
To listen for events in this stream, ***subscribe*** to the `Observable`.
|
||||
|
@ -188,15 +184,15 @@ a#HeroService
|
|||
returning mock heroes in a service like this one:
|
||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'just-get-heroes')(format=".")
|
||||
:marked
|
||||
You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
|
||||
You can revise that `HeroService` to get the heroes from the server using the Angular <code>Http</code> client service:
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'v1', 'src/app/toh/hero.service.ts (revised)')
|
||||
|
||||
:marked
|
||||
Notice that the !{_Angular_Http} client service is
|
||||
Notice that the Angular <code>Http</code> client service is
|
||||
[injected](dependency-injection.html) into the `HeroService` constructor.
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'ctor')
|
||||
:marked
|
||||
Look closely at how to call `!{_priv}http.get`:
|
||||
Look closely at how to call `http.get`:
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'http-get', 'src/app/toh/hero.service.ts (getHeroes)')(format=".")
|
||||
:marked
|
||||
You pass the resource URL to `get` and it calls the server which returns heroes.
|
||||
|
@ -208,8 +204,7 @@ a#HeroService
|
|||
Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
:marked
|
||||
<a id="rxjs"></a>
|
||||
If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" title="Promise">promise</a>.
|
||||
|
@ -220,9 +215,9 @@ a#HeroService
|
|||
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
|
||||
and `map()` is one of the RxJS *operators*.
|
||||
|
||||
a#rxjs-library
|
||||
.l-main-section
|
||||
:marked
|
||||
a#rxjs-library
|
||||
.l-main-section
|
||||
:marked
|
||||
## RxJS library
|
||||
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS Reactive Extensions">RxJS</a>
|
||||
is a third party library, endorsed by Angular, that implements the
|
||||
|
@ -241,13 +236,13 @@ a#HeroService
|
|||
|
||||
Each code file should add the operators it needs by importing from an RxJS library.
|
||||
The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this.
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'rxjs-imports', 'src/app/app.component.ts (import rxjs)')(format=".")
|
||||
|
||||
.l-main-section
|
||||
a#extract-data
|
||||
:marked
|
||||
## Process the response object
|
||||
Remember that the `getHeroes()` method used an `!{_priv}extractData()` helper method to map the `!{_priv}http.get` response object to heroes:
|
||||
Remember that the `getHeroes()` method used an `extractData()` helper method to map the `http.get` response object to heroes:
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'extract-data', 'src/app/toh/hero.service.ts (excerpt)')(format=".")
|
||||
:marked
|
||||
The `response` object doesn't hold the data in a form the app can use directly.
|
||||
|
@ -271,7 +266,7 @@ block parse-json
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Don't expect the decoded JSON to be the heroes !{_array} directly.
|
||||
Don't expect the decoded JSON to be the heroes array directly.
|
||||
This server always wraps JSON results in an object with a `data`
|
||||
property. You have to unwrap it to get the heroes.
|
||||
This is conventional web API behavior, driven by
|
||||
|
@ -291,11 +286,10 @@ a#no-return-response-object
|
|||
The component that calls the `HeroService` only wants heroes and is kept separate
|
||||
from getting them, the code dealing with where they come from, and the response object.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.callout.is-important
|
||||
.callout.is-important
|
||||
header HTTP GET is delayed
|
||||
:marked
|
||||
The `!{_priv}http.get` does **not send the request just yet.** This Observable is
|
||||
The `http.get` does **not send the request just yet.** This Observable is
|
||||
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables),
|
||||
which means that the request won't go out until something *subscribes* to the Observable.
|
||||
That *something* is the [HeroListComponent](#subscribe).
|
||||
|
@ -324,7 +318,7 @@ a#hero-list-component
|
|||
h3 #[b HeroListComponent] error handling
|
||||
block hlc-error-handling
|
||||
:marked
|
||||
Back in the `HeroListComponent`, in `!{_priv}heroService.getHeroes()`,
|
||||
Back in the `HeroListComponent`, in `heroService.getHeroes()`,
|
||||
the `subscribe` function has a second function parameter to handle the error message.
|
||||
It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template.
|
||||
|
||||
|
@ -369,8 +363,7 @@ code-example(format="." language="javascript").
|
|||
|
||||
Now that you know how the API works, implement `create()` as follows:
|
||||
|
||||
+ifDocsFor('ts')
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'import-request-options', 'src/app/toh/hero.service.ts (additional imports)')(format=".")
|
||||
+makeExcerpt('src/app/toh/hero.service.ts', 'create')
|
||||
|
||||
a#headers
|
||||
|
@ -379,8 +372,7 @@ a#headers
|
|||
|
||||
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`
|
||||
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).
|
||||
|
@ -391,7 +383,7 @@ a#json-results
|
|||
:marked
|
||||
### JSON results
|
||||
|
||||
As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](#extract-data)
|
||||
As with `getHeroes()`, use the `extractData()` helper to [extract the data](#extract-data)
|
||||
from the response.
|
||||
|
||||
block hero-list-comp-add-hero
|
||||
|
@ -400,25 +392,24 @@ block hero-list-comp-add-hero
|
|||
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
||||
+makeExample('server-communication/ts/src/app/toh/hero-list.component.ts', 'addHero', 'src/app/toh/hero-list.component.ts (addHero)')(format=".")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
h2#promises Fall back to promises
|
||||
:marked
|
||||
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
|
||||
.l-sub-section
|
||||
:marked
|
||||
While Promises may be more familiar, Observables have many advantages.
|
||||
|
||||
:marked
|
||||
:marked
|
||||
Here is a comparison of the `HeroService` using Promises versus Observables,
|
||||
highlighting just the parts that are different.
|
||||
+makeTabs(
|
||||
+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
|
||||
:marked
|
||||
You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in
|
||||
this example.
|
||||
|
||||
|
@ -432,14 +423,14 @@ block hero-list-comp-add-hero
|
|||
|
||||
You have to adjust the calling component to expect a `Promise` instead of an `Observable`:
|
||||
|
||||
+makeTabs(
|
||||
+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
|
||||
: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
|
||||
.l-sub-section
|
||||
:marked
|
||||
The less obvious but critical difference is that these two methods return very different results.
|
||||
|
||||
|
@ -456,7 +447,7 @@ block hero-list-comp-add-hero
|
|||
|
||||
h2#cors Cross-Origin Requests: Wikipedia example
|
||||
:marked
|
||||
You just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
|
||||
You just learned how to make `XMLHttpRequests` using the Angular <code>Http</code> service.
|
||||
This is the most common approach to server communication, but it doesn't work in all scenarios.
|
||||
|
||||
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
|
||||
|
@ -488,7 +479,7 @@ figure.image-display
|
|||
block wikipedia-jsonp+
|
||||
:marked
|
||||
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter.
|
||||
The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts you to `GET` requests.
|
||||
The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts you to `GET` requests.
|
||||
All other HTTP methods throw an error because `JSONP` is a read-only facility.
|
||||
|
||||
As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
||||
|
@ -537,7 +528,7 @@ block wikipedia-jsonp+
|
|||
and calls a `search(term)` method after each `keyup` event.
|
||||
|
||||
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
|
||||
Observable !{_array} of string results (`Observable<string[]>`).
|
||||
Observable array of string results (`Observable<string[]>`).
|
||||
Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`,
|
||||
the app forwards the Observable result to the template (via `items`) where the `async` pipe
|
||||
in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe)
|
||||
|
@ -706,8 +697,8 @@ a#in-mem-web-api
|
|||
## Appendix: Tour of Heroes _in-memory web api_
|
||||
|
||||
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
||||
- var _heroesJsonPath = (_docsFor == 'dart' ? 'web' : 'src/app') + '/heroes.json';
|
||||
+makeJson('server-communication/' + _docsFor + '/' + _heroesJsonPath, null, _heroesJsonPath)(format=".")
|
||||
|
||||
+makeJson('server-communication/ts/src/app/heroes.json', null, 'src/app/heroes.json')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
You wrap the heroes array in an object with a `data` property for the same reason that a data server does:
|
||||
|
@ -717,7 +708,6 @@ a#in-mem-web-api
|
|||
You'd set the endpoint to the JSON file like this:
|
||||
+makeExample('server-communication/ts/src/app/toh/hero.service.ts', 'endpoint-json', 'src/app/toh/hero.service.ts')(format=".")
|
||||
|
||||
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
|
||||
:marked
|
||||
The *get heroes* scenario would work,
|
||||
but since the app can't save changes to a JSON file, it needs a web API server.
|
||||
|
@ -736,9 +726,9 @@ a#in-mem-web-api
|
|||
for configuration options, default behaviors, and limitations.
|
||||
|
||||
:marked
|
||||
The in-memory web API gets its data from !{_a_ca_class_with} a `createDb()`
|
||||
The in-memory web API gets its data from a custom application class with a `createDb()`
|
||||
method that returns a map whose keys are collection names and whose values
|
||||
are !{_array}s of objects in those collections.
|
||||
are arrays of objects in those collections.
|
||||
|
||||
Here's the class for this sample, based on the JSON data:
|
||||
+makeExample('server-communication/ts/src/app/hero-data.ts', null, 'src/app/hero-data.ts')(format=".")
|
||||
|
@ -766,7 +756,7 @@ block redirect-to-web-api
|
|||
The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
|
||||
while setting the metadata for the root `AppModule`. Don't call it again.
|
||||
:marked
|
||||
Here is the final, revised version of <span ngio-ex>src/app/app.module.ts</span>, demonstrating these steps.
|
||||
Here is the final, revised version of <code>src/app/app.module.ts</code>, demonstrating these steps.
|
||||
|
||||
+makeExcerpt('src/app/app.module.ts')
|
||||
.alert.is-important
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _prereq = 'node and npm';
|
||||
- var _playground = 'playground';
|
||||
- var _Install = 'Install';
|
||||
//- npm commands
|
||||
- var _install = 'install';
|
||||
- var _start = 'start';
|
||||
|
||||
a#develop-locally
|
||||
:marked
|
||||
## Setup a local development environment
|
||||
|
||||
<span if-docs="ts">
|
||||
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
|
||||
It's not where you'd develop a real application.
|
||||
You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
|
||||
</span>
|
||||
|
||||
Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
|
||||
maintained [on github](!{_qsRepo} "Install the github QuickStart repo").
|
||||
maintained [on github](https://github.com/angular/quickstart "Install the github QuickStart repo").
|
||||
|
||||
:marked
|
||||
Make sure you have [!{_prereq} installed](#install-prerequisites "What if you don't have !{_prereq}?").
|
||||
Make sure you have [node and npm installed](#install-prerequisites "What if you don't have node and npm?").
|
||||
Then ...
|
||||
1. Create a project folder (you can call it `quickstart` and rename it later).
|
||||
1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder.
|
||||
1. !{_Install} [!{_npm}](#install-prerequisites "What if you don't have !{_prereq}?") packages.
|
||||
1. Run `!{_npm} !{_start}` to launch the sample application.
|
||||
1. Install [npm](#install-prerequisites "What if you don't have node and npm?") packages.
|
||||
1. Run `npm start` to launch the sample application.
|
||||
|
||||
a#clone
|
||||
:marked
|
||||
|
@ -35,10 +27,10 @@ a#clone
|
|||
Perform the _clone-to-launch_ steps with these terminal commands.
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
git clone !{_qsRepo}.git quickstart
|
||||
git clone https://github.com/angular/quickstart.git quickstart
|
||||
cd quickstart
|
||||
!{_npm} !{_install}
|
||||
!{_npm} !{_start}
|
||||
npm install
|
||||
npm start
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
|
@ -47,13 +39,13 @@ code-example(language="sh" class="code-shell").
|
|||
a#download
|
||||
:marked
|
||||
### Download
|
||||
<a href="!{_qsRepoZip}" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
|
||||
<a href="https://github.com/angular/quickstart/archive/master.zip" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
|
||||
and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
cd quickstart
|
||||
!{_npm} !{_install}
|
||||
!{_npm} !{_start}
|
||||
npm install
|
||||
npm start
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
|
@ -144,13 +136,13 @@ table(width="100%")
|
|||
th File
|
||||
th Purpose
|
||||
tr
|
||||
td <ngio-ex>app/app.component.ts</ngio-ex>
|
||||
td <code>app/app.component.ts</code>
|
||||
td
|
||||
:marked
|
||||
Defines the same `AppComponent` as the one in the QuickStart !{_playground}.
|
||||
Defines the same `AppComponent` as the one in the QuickStart playground.
|
||||
It is the **root** component of what will become a tree of nested components
|
||||
as the application evolves.
|
||||
tr(if-docs="ts")
|
||||
tr
|
||||
td <code>app/app.module.ts</code>
|
||||
td
|
||||
:marked
|
||||
|
@ -158,7 +150,7 @@ table(width="100%")
|
|||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
tr
|
||||
td <ngio-ex>main.ts</ngio-ex>
|
||||
td <code>main.ts</code>
|
||||
td
|
||||
:marked
|
||||
Compiles the application with the [JIT compiler](../glossary.html#jit) and
|
||||
|
@ -180,7 +172,7 @@ br
|
|||
a#install-prerequisites
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: !{_prereq}
|
||||
## Appendix: node and npm
|
||||
block install-tooling
|
||||
:marked
|
||||
Node.js and npm are essential to modern web development with Angular and other platforms.
|
||||
|
@ -198,10 +190,9 @@ block install-tooling
|
|||
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
|
||||
use other versions of node and npm.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
a#why-locally
|
||||
.l-main-section
|
||||
:marked
|
||||
a#why-locally
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: Why develop locally
|
||||
|
||||
<live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular.
|
||||
|
|
|
@ -566,7 +566,7 @@ a#unless
|
|||
+makeExcerpt('src/app/unless.directive.ts (excerpt)', 'no-docs')
|
||||
|
||||
:marked
|
||||
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
|
||||
Add this directive to the `declarations` array of the AppModule.
|
||||
|
||||
Then create some HTML to try it.
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _JavaScript = 'JavaScript';
|
||||
//- Double underscore means don't escape var, use !{__var}.
|
||||
- var __chaining_op = '<code>;</code> or <code>,</code>';
|
||||
- var __new_op = '<code>new</code>';
|
||||
- var __objectAsMap = 'object';
|
||||
|
||||
//- The docs standard h4 style uppercases, making code terms unreadable. Override it.
|
||||
style.
|
||||
|
@ -133,19 +128,19 @@ a#template-expressions
|
|||
In the [property binding](#property-binding) section below,
|
||||
a template expression appears in quotes to the right of the `=` symbol as in `[property]="expression"`.
|
||||
|
||||
You write these template expressions in a language that looks like #{_JavaScript}.
|
||||
Many #{_JavaScript} expressions are legal template expressions, but not all.
|
||||
You write these template expressions in a language that looks like JavaScript.
|
||||
Many JavaScript expressions are legal template expressions, but not all.
|
||||
|
||||
#{_JavaScript} expressions that have or promote side effects are prohibited,
|
||||
JavaScript expressions that have or promote side effects are prohibited,
|
||||
including:
|
||||
|
||||
* assignments (`=`, `+=`, `-=`, ...)
|
||||
* !{__new_op}
|
||||
* chaining expressions with !{__chaining_op}
|
||||
* <code>new</code>
|
||||
* chaining expressions with <code>;</code> or <code>,</code>
|
||||
* increment and decrement operators (`++` and `--`)
|
||||
|
||||
:marked
|
||||
Other notable differences from #{_JavaScript} syntax include:
|
||||
Other notable differences from JavaScript syntax include:
|
||||
|
||||
* no support for the bitwise operators `|` and `&`
|
||||
* new [template expression operators](#expression-operators), such as `|` and `?.`
|
||||
|
@ -238,7 +233,7 @@ a#expression-guidelines
|
|||
|
||||
Dependent values should not change during a single turn of the event loop.
|
||||
If an idempotent expression returns a string or a number, it returns the same string or number
|
||||
when called twice in a row. If the expression returns an object (including #{_an} `#{_Array}`),
|
||||
when called twice in a row. If the expression returns an object (including an `array`),
|
||||
it returns the same object *reference* when called twice in a row.
|
||||
|
||||
a(href="#toc") back to top
|
||||
|
@ -263,14 +258,14 @@ a#template-statements
|
|||
Responding to events is the other side of Angular's "unidirectional data flow".
|
||||
You're free to change anything, anywhere, during this turn of the event loop.
|
||||
|
||||
Like template expressions, template *statements* use a language that looks like #{_JavaScript}.
|
||||
Like template expressions, template *statements* use a language that looks like JavaScript.
|
||||
The template statement parser differs from the template expression parser and
|
||||
specifically supports both basic assignment (`=`) and chaining expressions
|
||||
(with !{__chaining_op}).
|
||||
(with <code>;</code> or <code>,</code>).
|
||||
|
||||
However, certain #{_JavaScript} syntax is not allowed:
|
||||
However, certain JavaScript syntax is not allowed:
|
||||
|
||||
* !{__new_op}
|
||||
* <code>new</code>
|
||||
* increment and decrement operators, `++` and `--`
|
||||
* operator assignment, such as `+=` and `-=`
|
||||
* the bitwise operators `|` and `&`
|
||||
|
@ -808,8 +803,8 @@ a(href="#toc") back to top
|
|||
|
||||
:marked
|
||||
Finally, you can bind to a specific class name.
|
||||
Angular adds the class when the template expression evaluates to #{_truthy}.
|
||||
It removes the class when the expression is #{_falsy}.
|
||||
Angular adds the class when the template expression evaluates to truthy.
|
||||
It removes the class when the expression is falsy.
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
|
||||
|
||||
|
@ -1127,7 +1122,7 @@ a#ngClass
|
|||
:marked
|
||||
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
|
||||
|
||||
Try binding `ngClass` to a key:value control !{__objectAsMap}.
|
||||
Try binding `ngClass` to a key:value control object.
|
||||
Each key of the object is a CSS class name; its value is `true` if the class should be added,
|
||||
`false` if it should be removed.
|
||||
|
||||
|
@ -1164,7 +1159,7 @@ a#ngStyle
|
|||
:marked
|
||||
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice.
|
||||
|
||||
Try binding `ngStyle` to a key:value control !{__objectAsMap}.
|
||||
Try binding `ngStyle` to a key:value control object.
|
||||
Each key of the object is a style name; its value is whatever is appropriate for that style.
|
||||
|
||||
Consider a `setCurrentStyles` component method that sets a component property, `currentStyles`
|
||||
|
@ -1195,8 +1190,7 @@ a#ngModel
|
|||
|
||||
+makeExcerpt('src/app/app.component.html', 'NgModel-1', '')
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
:marked
|
||||
#### _FormsModule_ is required to use _ngModel_
|
||||
|
||||
Before using the `ngModel` directive in a two-way data binding,
|
||||
|
@ -1206,7 +1200,7 @@ a#ngModel
|
|||
|
||||
Here's how to import the `FormsModule` to make `[(ngModel)]` available.
|
||||
|
||||
+makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '')
|
||||
+makeExcerpt('src/app/app.module.1.ts (FormsModule import)', '')
|
||||
|
||||
:marked
|
||||
#### Inside <span class="syntax">[(ngModel)]</span>
|
||||
|
@ -1317,8 +1311,8 @@ a#ngIf
|
|||
Don't forget the asterisk (`*`) in front of `ngIf`.
|
||||
|
||||
:marked
|
||||
When the `isActive` expression returns a #{_truthy} value, `NgIf` adds the `HeroDetailComponent` to the DOM.
|
||||
When the expression is #{_falsy}, `NgIf` removes the `HeroDetailComponent`
|
||||
When the `isActive` expression returns a truthy value, `NgIf` adds the `HeroDetailComponent` to the DOM.
|
||||
When the expression is falsy, `NgIf` removes the `HeroDetailComponent`
|
||||
from the DOM, destroying that component and all of its sub-components.
|
||||
|
||||
#### Show/hide is not the same thing
|
||||
|
@ -1397,7 +1391,7 @@ a#microsyntax
|
|||
It's a *microsyntax* — a little language of its own that Angular interprets.
|
||||
The string `"let hero of heroes"` means:
|
||||
|
||||
> *Take each hero in the `heroes` #{_array}, store it in the local `hero` looping variable, and
|
||||
> *Take each hero in the `heroes` array, store it in the local `hero` looping variable, and
|
||||
make it available to the templated HTML for each iteration.*
|
||||
|
||||
Angular translates this instruction into a `<template>` around the host element,
|
||||
|
@ -1412,8 +1406,8 @@ a#template-input-variables
|
|||
### Template input variables
|
||||
|
||||
The `let` keyword before `hero` creates a _template input variable_ called `hero`.
|
||||
The `ngFor` directive iterates over the `heroes` #{_array} returned by the parent component's `heroes` property
|
||||
and sets `hero` to the current item from the #{_array} during each iteration.
|
||||
The `ngFor` directive iterates over the `heroes` array returned by the parent component's `heroes` property
|
||||
and sets `hero` to the current item from the array during each iteration.
|
||||
|
||||
You reference the `hero` input variable within the `ngFor` host element
|
||||
(and within its descendents) to access the hero's properties.
|
||||
|
@ -1655,13 +1649,13 @@ a#inputs-outputs
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Alternatively, you can identify members in the `inputs` and `outputs` #{_array}s
|
||||
Alternatively, you can identify members in the `inputs` and `outputs` arrays
|
||||
of the directive metadata, as in this example:
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
|
||||
|
||||
:marked
|
||||
You can specify an input/output property either with a decorator or in a metadata #{_array}.
|
||||
You can specify an input/output property either with a decorator or in a metadata array.
|
||||
Don't do both!
|
||||
|
||||
:marked
|
||||
|
@ -1709,7 +1703,7 @@ h3#aliasing-io Aliasing input/output properties
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
You can also alias property names in the `inputs` and `outputs` #{_array}s.
|
||||
You can also alias property names in the `inputs` and `outputs` arrays.
|
||||
You write a colon-delimited (`:`) string with
|
||||
the directive property name on the *left* and the public alias on the *right*:
|
||||
|
||||
|
@ -1722,7 +1716,7 @@ a#expression-operators
|
|||
:marked
|
||||
## Template expression operators
|
||||
|
||||
The template expression language employs a subset of #{_JavaScript} syntax supplemented with a few special operators
|
||||
The template expression language employs a subset of JavaScript syntax supplemented with a few special operators
|
||||
for specific scenarios. The next sections cover two of these operators: _pipe_ and _safe navigation operator_.
|
||||
|
||||
a#pipe
|
||||
|
@ -1818,12 +1812,11 @@ code-example(format="nocode").
|
|||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-4')(format=".")
|
||||
|
||||
block safe-op-alt
|
||||
:marked
|
||||
:marked
|
||||
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
|
||||
when it encounters the first null.
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'safe-5')(format=".")
|
||||
|
||||
:marked
|
||||
These approaches have merit but can be cumbersome, especially if the property path is long.
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _JavaScript = 'JavaScript';
|
||||
//- Double underscore means don't escape var, use !{__var}.
|
||||
- var __chaining_op = '<code>;</code> or <code>,</code>';
|
||||
- var __new_op = '<code>new</code>';
|
||||
- var __objectAsMap = 'object';
|
||||
|
||||
:marked
|
||||
This guide offers tips and techniques for testing Angular applications.
|
||||
|
|
|
@ -2,19 +2,19 @@
|
|||
- var vers = current.path[2]
|
||||
|
||||
.clearfix
|
||||
a.card.c4(href="/docs/#{lang}/#{vers}/quickstart.html")
|
||||
a.card.c4(href="/docs/ts/latest/quickstart.html")
|
||||
h2.text-headline.text-uppercase Quickstart
|
||||
p A short beginner guide explaining the basic concepts of Angular
|
||||
|
||||
footer View Quickstart
|
||||
|
||||
a.card.c4(href="/docs/#{lang}/#{vers}/guide/")
|
||||
a.card.c4(href="/docs/ts/latest/guide/")
|
||||
h2.text-headline.text-uppercase Developer Guide
|
||||
p An intermediate development guide covering all major features of Angular
|
||||
|
||||
footer View Guide
|
||||
|
||||
a.card.c4(href="/docs/#{lang}/#{vers}/api/")
|
||||
a.card.c4(href="/docs/ts/latest/api/")
|
||||
h2.text-headline.text-uppercase API Reference
|
||||
p An advanced reference of all Angular Classes, Methods, etc.
|
||||
|
||||
|
@ -24,31 +24,31 @@
|
|||
h4 Advanced Documentation
|
||||
ul
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/animations.html") Animations
|
||||
a(href="/docs/ts/latest/guide/animations.html") Animations
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/attribute-directives.html") Attribute Directives
|
||||
a(href="/docs/ts/latest/guide/attribute-directives.html") Attribute Directives
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/browser-support.html") Browser Support
|
||||
a(href="/docs/ts/latest/guide/browser-support.html") Browser Support
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/component-styles.html") Component Styles
|
||||
a(href="/docs/ts/latest/guide/component-styles.html") Component Styles
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/deployment.html") Deployment
|
||||
a(href="/docs/ts/latest/guide/deployment.html") Deployment
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/guide/animations.html") View All...
|
||||
a(href="/docs/ts/latest/guide/animations.html") View All...
|
||||
|
||||
.c4.secondary-content-list
|
||||
h4 Cookbook
|
||||
ul
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/cookbook/aot-compiler.html") Ahead-of-time Compilation
|
||||
a(href="/docs/ts/latest/cookbook/aot-compiler.html") Ahead-of-time Compilation
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/cookbook/ajs-quick-reference.html") AngularJS to Angular
|
||||
a(href="/docs/ts/latest/cookbook/ajs-quick-reference.html") AngularJS to Angular
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/cookbook/component-communication.html") Component Interaction
|
||||
a(href="/docs/ts/latest/cookbook/component-communication.html") Component Interaction
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/cookbook/dependency-injection.html") Dependency Injection
|
||||
a(href="/docs/ts/latest/cookbook/dependency-injection.html") Dependency Injection
|
||||
li
|
||||
a(href="/docs/#{lang}/#{vers}/cookbook/") View All...
|
||||
a(href="/docs/ts/latest/cookbook/") View All...
|
||||
|
||||
.c4.secondary-content-list
|
||||
h4 Tools & Libraries
|
||||
|
|
|
@ -15,9 +15,8 @@ block qs-src-online-and-local
|
|||
and prepare for development of a real Angular application.
|
||||
|
||||
:marked
|
||||
Every component begins with an `@Component` [!{_decorator}](glossary.html#!{_decorator} '"!{_decorator}" explained')
|
||||
<span if-docs="ts">function</span> that
|
||||
<span if-docs="ts">takes a _metadata_ object. The metadata object</span> describes how the HTML template and component class work together.
|
||||
Every component begins with an `@Component` [decorator](glossary.html#decorator '"decorator" explained')
|
||||
function that takes a _metadata_ object. The metadata object describes how the HTML template and component class work together.
|
||||
|
||||
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
|
||||
+makeExample('src/index.html','my-app','index.html (inside <body>)')(format='.')
|
||||
|
@ -28,11 +27,10 @@ block qs-src-online-and-local
|
|||
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
|
||||
Interpolation binding is one of many Angular features you'll discover in this documentation.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
:marked
|
||||
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
|
||||
p.
|
||||
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
|
||||
|
|
|
@ -3,7 +3,7 @@ include ../_util-fns
|
|||
:marked
|
||||
## Setup to develop locally
|
||||
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
||||
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>.
|
||||
named <code>angular-tour-of-heroes</code>.
|
||||
|
||||
The file structure should look like this:
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ include ../_util-fns
|
|||
and makes it easy to unit-test components with a mock service.
|
||||
|
||||
Because data services are invariably asynchronous,
|
||||
you'll finish the page with a *!{_Promise}*-based version of the data service.
|
||||
you'll finish the page with a *Promise*-based version of the data service.
|
||||
|
||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||
|
||||
|
@ -229,7 +229,7 @@ code-example(format="nocode").
|
|||
|
||||
<a id="async"></a>
|
||||
:marked
|
||||
## Async services and !{_Promise}s
|
||||
## Async services and Promises
|
||||
The `HeroService` returns a list of mock heroes immediately;
|
||||
its `getHeroes()` signature is synchronous.
|
||||
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
|
||||
|
@ -240,12 +240,12 @@ code-example(format="nocode").
|
|||
|
||||
:marked
|
||||
To coordinate the view with the response,
|
||||
you can use *!{_Promise}s*, which is an asynchronous
|
||||
you can use *Promises*, which is an asynchronous
|
||||
technique that changes the signature of the `getHeroes()` method.
|
||||
|
||||
### The hero service makes a !{_Promise}
|
||||
### The hero service makes a Promise
|
||||
|
||||
A *!{_Promise}* essentially promises to call back when the results are ready.
|
||||
A *Promise* essentially promises to call back when the results are ready.
|
||||
You ask an asynchronous service to do some work and give it a callback function.
|
||||
The service does that work and eventually calls the function with the results or an error.
|
||||
.l-sub-section
|
||||
|
@ -255,22 +255,22 @@ code-example(format="nocode").
|
|||
[Exploring ES6](http://http://exploringjs.com/es6.html).
|
||||
|
||||
:marked
|
||||
Update the `HeroService` with this !{_Promise}-returning `getHeroes()` method:
|
||||
Update the `HeroService` with this Promise-returning `getHeroes()` method:
|
||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".")
|
||||
:marked
|
||||
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
|
||||
by returning an *immediately resolved !{_Promise}* with the mock heroes as the result.
|
||||
by returning an *immediately resolved Promise* with the mock heroes as the result.
|
||||
|
||||
### Act on the !{_Promise}
|
||||
### Act on the Promise
|
||||
|
||||
As a result of the change to `HeroService`, `this.heroes` is now set to a !{_Promise} rather than an array of heroes.
|
||||
As a result of the change to `HeroService`, `this.heroes` is now set to a `Promise` rather than an array of heroes.
|
||||
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".")
|
||||
|
||||
:marked
|
||||
You have to change the implementation to *act on the !{_Promise} when it resolves*.
|
||||
When the !{_Promise} resolves successfully, you'll have heroes to display.
|
||||
You have to change the implementation to *act on the `Promise` when it resolves*.
|
||||
When the `Promise` resolves successfully, you'll have heroes to display.
|
||||
|
||||
Pass the callback function as an argument to the !{_Promise}'s `then()` method:
|
||||
Pass the callback function as an argument to the Promise's `then()` method:
|
||||
+makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -329,7 +329,7 @@ code-example(format="nocode").
|
|||
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
|
||||
* You defined the `HeroService` as a provider for the `AppComponent`.
|
||||
* You created mock hero data and imported them into the service.
|
||||
* You designed the service to return a !{_Promise} and the component to get the data from the !{_Promise}.
|
||||
* You designed the service to return a Promise and the component to get the data from the Promise.
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
|
@ -348,8 +348,8 @@ code-example(format="nocode").
|
|||
import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`.
|
||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".")
|
||||
:marked
|
||||
Like `getHeroes()`, it also returns a !{_Promise}.
|
||||
But this !{_Promise} waits two seconds before resolving the !{_Promise} with mock heroes.
|
||||
Like `getHeroes()`, it also returns a `Promise`.
|
||||
But this Promise waits two seconds before resolving the Promise with mock heroes.
|
||||
|
||||
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
|
||||
and see how the app behaves.
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
- var _example = 'toh-5';
|
||||
|
||||
block includes
|
||||
include ../_util-fns
|
||||
- var _appRoutingTsVsAppComp = 'app.module.ts'
|
||||
- var _RoutesVsAtRouteConfig = 'Routes'
|
||||
- var _RouterModuleVsRouterDirectives = 'RouterModule'
|
||||
- var _redirect = 'redirect'
|
||||
|
||||
:marked
|
||||
There are new requirements for the Tour of Heroes app:
|
||||
|
@ -30,16 +24,15 @@ figure.image-display
|
|||
:marked
|
||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
include ../../../_includes/_see-addr-bar
|
||||
include ../../../_includes/_see-addr-bar
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Where you left off
|
||||
Before continuing with the Tour of Heroes, verify that you have the following structure.
|
||||
|
||||
block intro-file-tree
|
||||
.filetree
|
||||
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file src
|
||||
|
@ -60,8 +53,7 @@ block intro-file-tree
|
|||
.file node_modules ...
|
||||
.file package.json
|
||||
|
||||
block keep-app-running
|
||||
:marked
|
||||
:marked
|
||||
## Keep the app transpiling and running
|
||||
Enter the following command in the terminal window:
|
||||
|
||||
|
@ -108,11 +100,11 @@ block keep-app-running
|
|||
and create a separate `AppComponent` shell.
|
||||
|
||||
Do the following:
|
||||
* Rename the <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>.
|
||||
* Rename the `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file).
|
||||
* Rename the selector `my-app` to `my-heroes`.
|
||||
* Rename the <code>app.component.ts</code> file to <code>heroes.component.ts</code>.
|
||||
* Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file).
|
||||
* Rename the selector `my-app` as `my-heroes`.
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts (showing renamings only)', 'renaming')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'renaming', 'src/app/heroes.component.ts (showing renamings only)')
|
||||
|
||||
:marked
|
||||
### Create *AppComponent*
|
||||
|
@ -122,22 +114,21 @@ block keep-app-running
|
|||
|
||||
Perform these steps:
|
||||
|
||||
* Create the file <span ngio-ex>src/app/app.component.ts</span>.
|
||||
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
|
||||
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
|
||||
* Create the file <code>src/app/app.component.ts</code>.
|
||||
* Define an exported `AppComponent` class.
|
||||
* Add an `@Component` decorator above the class with a `my-app` selector.
|
||||
* Move the following from `HeroesComponent` to `AppComponent`:
|
||||
* `title` class property.
|
||||
* `@Component` template `<h1>` element, which contains a binding to `title`.
|
||||
* Add a `<my-heroes>` element to the app template just below the heading so you still see the heroes.
|
||||
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
|
||||
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because you'll need it in every other view.
|
||||
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it was promoted.
|
||||
* Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `<my-heroes>` tags.
|
||||
* Add `HeroService` to the `providers` array of `AppModule` because you'll need it in every other view.
|
||||
* Remove `HeroService` from the `HeroesComponent` `providers` array since it was promoted.
|
||||
* Add the supporting `import` statements for `AppComponent`.
|
||||
|
||||
The first draft looks like this:
|
||||
|
||||
block app-comp-v1
|
||||
+makeTabs(
|
||||
+makeTabs(
|
||||
`toh-5/ts/src/app/app.component.1.ts,
|
||||
toh-5/ts/src/app/app.module.1.ts`,
|
||||
',',
|
||||
|
@ -155,8 +146,7 @@ block app-comp-v1
|
|||
|
||||
Use the Angular router to enable navigation.
|
||||
|
||||
block angular-router
|
||||
:marked
|
||||
:marked
|
||||
The Angular router is an external, optional Angular NgModule called `RouterModule`.
|
||||
The router is a combination of multiple provided services (`RouterModule`),
|
||||
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
|
||||
|
@ -169,7 +159,7 @@ block angular-router
|
|||
(or a script that dynamically sets this element)
|
||||
at the top of the `<head>` section.
|
||||
|
||||
+makeExcerpt('src/index.html', 'base-href')
|
||||
+makeExample('toh-5/ts/src/index.html', 'base-href', 'src/index.html (base-href)')
|
||||
|
||||
.callout.is-important
|
||||
header base href is essential
|
||||
|
@ -179,8 +169,7 @@ block angular-router
|
|||
|
||||
|
||||
a#configure-routes
|
||||
block router-config-intro
|
||||
:marked
|
||||
:marked
|
||||
### Configure routes
|
||||
|
||||
Create a configuration file for the app routes.
|
||||
|
@ -191,49 +180,42 @@ block router-config-intro
|
|||
|
||||
Define the first route as a route to the heroes component.
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts'
|
||||
+makeExcerpt('src/app/' + _file + ' (heroes route)', 'heroes')
|
||||
+makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)')
|
||||
|
||||
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
|
||||
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
|
||||
:marked
|
||||
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
||||
The `Routes` are an array of *route definitions*.
|
||||
|
||||
This route definition has the following parts:
|
||||
|
||||
- *Path*: The router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}heroes`).
|
||||
<li if-docs="dart"> *Name*: The official name of the route;
|
||||
it must begin with a capital letter to avoid confusion with the path (`Heroes`).</li>
|
||||
- *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`).
|
||||
- *Component*: The component that the router should create when navigating to this route (`HeroesComponent`).
|
||||
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Read more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing & Navigation](../guide/router.html) page.
|
||||
Read more about defining routes with `Routes` in the [Routing & Navigation](../guide/router.html) page.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
:marked
|
||||
### Make the router available
|
||||
|
||||
Import the `RouterModule` and add it to the `AppModule` imports !{_array}.
|
||||
Import the `RouterModule` and add it to the `AppModule` imports array.
|
||||
|
||||
+makeExcerpt('src/app/app.module.2.ts (app routing)', '')
|
||||
+makeExample('toh-5/ts/src/app/app.module.2.ts', '', 'src/app/app.module.ts (app routing)')
|
||||
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `forRoot()` method is called because a configured router is provided at the app's root.
|
||||
The `forRoot()` method supplies the Router service providers and directives needed for routing, and
|
||||
performs the initial navigation based on the current browser URL.
|
||||
|
||||
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
|
||||
:marked
|
||||
### Router outlet
|
||||
|
||||
If you paste the path, `/heroes`, into the browser address bar at the end of the URL,
|
||||
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
||||
the router should match it to the `heroes` route and display the `HeroesComponent`.
|
||||
However, you have to tell the router where to display the component.
|
||||
To do this, you can add a `<router-outlet>` element at the end of the template.
|
||||
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
||||
`RouterOutlet` is one of the directives provided by the `RouterModule`.
|
||||
The router displays each component immediately below the `<router-outlet>` as users navigate through the app.
|
||||
|
||||
### Router links
|
||||
|
@ -243,17 +225,16 @@ block router-config-intro
|
|||
|
||||
The revised template looks like this:
|
||||
|
||||
+makeExcerpt('src/app/app.component.1.ts', 'template-v2')
|
||||
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v2', 'src/app/app.component.ts (template-v2)')
|
||||
|
||||
block routerLink
|
||||
:marked
|
||||
: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
|
||||
.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
|
||||
|
@ -274,7 +255,7 @@ block routerLink
|
|||
|
||||
`AppComponent` now looks like this:
|
||||
|
||||
+makeExample('src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)')
|
||||
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)')
|
||||
|
||||
:marked
|
||||
The *AppComponent* is now attached to a router and displays routed views.
|
||||
|
@ -287,25 +268,23 @@ block routerLink
|
|||
Routing only makes sense when multiple views exist.
|
||||
To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from.
|
||||
|
||||
+makeExcerpt('src/app/dashboard.component.1.ts (v1)', '')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.1.ts', '', 'src/app/dashboard.component.ts (v1)')
|
||||
|
||||
:marked
|
||||
You'll make this component more useful later.
|
||||
|
||||
### Configure the dashboard route
|
||||
|
||||
To teach `!{_appRoutingTsVsAppComp}` to navigate to the dashboard,
|
||||
To teach `app.module.ts` to navigate to the dashboard,
|
||||
import the dashboard component and
|
||||
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
||||
add the following route definition to the `Routes` array of definitions.
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'src/app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
|
||||
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)')
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
:marked
|
||||
Also import and add `DashboardComponent` to the `AppModule`'s `declarations`.
|
||||
|
||||
+makeExcerpt('src/app/app.module.ts', 'dashboard')
|
||||
+makeExample('toh-5/ts/src/app/app.module.ts', 'dashboard', 'src/app/app.module.ts (dashboard)')
|
||||
|
||||
:marked
|
||||
### Add a !{_redirect} route
|
||||
|
@ -314,14 +293,13 @@ block routerLink
|
|||
When the app starts, it should show the dashboard and
|
||||
display a `/dashboard` URL in the browser address bar.
|
||||
|
||||
block redirect-vs-use-as-default
|
||||
:marked
|
||||
:marked
|
||||
To make this happen, use a redirect route. Add the following
|
||||
to the array of route definitions:
|
||||
|
||||
+makeExcerpt('src/app/app.module.3.ts','redirect')
|
||||
+makeExample('toh-5/ts/src/app/app.module.3.ts','redirect', 'src/app/app.module.ts (redirect)')
|
||||
|
||||
.l-sub-section
|
||||
.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.
|
||||
|
@ -331,8 +309,7 @@ block redirect-vs-use-as-default
|
|||
|
||||
Add a dashboard navigation link to the template, just above the *Heroes* link.
|
||||
|
||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||||
+makeExcerpt('src/app/app.component' + _vers + '.ts', 'template-v3')
|
||||
+makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -349,15 +326,12 @@ block redirect-vs-use-as-default
|
|||
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||||
template file.
|
||||
|
||||
+makeExcerpt('src/app/dashboard.component.ts', 'metadata')
|
||||
|
||||
block templateUrl-path-resolution
|
||||
//- N/A for TS
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'metadata', 'src/app/dashboard.component.ts (metadata)')
|
||||
|
||||
:marked
|
||||
Create that file with this content:
|
||||
|
||||
+makeExample('src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.1.html', '', 'src/app/dashboard.component.html')
|
||||
|
||||
:marked
|
||||
`*ngFor` is used again to iterate over a list of heroes and display their names.
|
||||
|
@ -365,32 +339,32 @@ block templateUrl-path-resolution
|
|||
|
||||
### Sharing the *HeroService*
|
||||
|
||||
To populate the component's `heroes` !{_array}, you can re-use the `HeroService`.
|
||||
To populate the component's `heroes` array, you can re-use the `HeroService`.
|
||||
|
||||
Earlier, you removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
||||
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
||||
Earlier, you removed the `HeroService` from the `providers` array of `HeroesComponent`
|
||||
and added it to the `providers` array of `AppModule`.
|
||||
That move created a singleton `HeroService` instance, available to all components of the app.
|
||||
Angular injects `HeroService` and you can use it in the `DashboardComponent`.
|
||||
|
||||
### Get heroes
|
||||
|
||||
In <span ngio-ex>dashboard.component.ts</span>, add the following `import` statements.
|
||||
In <code>dashboard.component.ts</code>, add the following `import` statements.
|
||||
|
||||
+makeExcerpt('src/app/dashboard.component.ts','imports')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.ts','imports', 'src/app/dashboard.component.ts (imports)')
|
||||
|
||||
:marked
|
||||
Now create the `DashboardComponent` class like this:
|
||||
|
||||
+makeExcerpt('src/app/dashboard.component.ts (class)', 'class')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'class', 'src/app/dashboard.component.ts (class)')
|
||||
|
||||
:marked
|
||||
This kind of logic is also used in the `HeroesComponent`:
|
||||
|
||||
* Define a `heroes` !{_array} property.
|
||||
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
||||
* Define a `heroes` array property.
|
||||
* Inject the `HeroService` in the constructor and hold it in a private `heroService` field.
|
||||
* Call the service to get heroes inside the Angular `ngOnInit()` lifecycle hook.
|
||||
|
||||
In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice()` method</span>.
|
||||
In this dashboard you specify four heroes (2nd, 3rd, 4th, and 5th) with the `Array.slice` method.
|
||||
|
||||
Refresh the browser to see four hero names in the new dashboard.
|
||||
|
||||
|
@ -407,7 +381,7 @@ block templateUrl-path-resolution
|
|||
|
||||
### Routing to a hero detail
|
||||
|
||||
You can add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}`, where the other routes are configured.
|
||||
You can add a route to the `HeroDetailComponent` in `app.module.ts`, where the other routes are configured.
|
||||
|
||||
The new route is unusual in that you must tell the `HeroDetailComponent` which hero to show.
|
||||
You didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||||
|
@ -437,15 +411,13 @@ code-example(format="nocode").
|
|||
|
||||
Use the following *route definition*.
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (hero detail)','hero-detail')
|
||||
+makeExample('toh-5/ts/src/app/app.module.3.ts','hero-detail', 'src/app/app.module.ts (hero detail)')
|
||||
|
||||
:marked
|
||||
The colon (:) in the path indicates that `:id` is a placeholder for a specific hero `id`
|
||||
when navigating to the `HeroDetailComponent`.
|
||||
|
||||
+ifDocsFor('dart')
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
Be sure to import the hero detail component before creating this route.
|
||||
|
||||
|
@ -471,8 +443,7 @@ code-example(format="nocode").
|
|||
The template won't change. Hero names will display the same way.
|
||||
The major changes are driven by how you get hero names.
|
||||
|
||||
block route-params
|
||||
:marked
|
||||
:marked
|
||||
You'll no longer receive the hero in a parent component property binding.
|
||||
The new `HeroDetailComponent` should take the `id` parameter from the `params` Observable
|
||||
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
|
||||
|
@ -480,51 +451,43 @@ block route-params
|
|||
:marked
|
||||
Add the following imports:
|
||||
|
||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||||
+makeExcerpt('src/app/hero-detail.component' + _vers + '.ts', 'added-imports', 'src/app/hero-detail.component')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.1.ts', 'added-imports', 'src/app/hero-detail.component.ts')
|
||||
|
||||
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
|
||||
:marked
|
||||
Inject the `!{_ActivatedRoute}`, `HeroService`, and `Location` services
|
||||
Inject the `ActivatedRoute`, `HeroService`, and `Location` services
|
||||
into the constructor, saving their values in private fields:
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts (constructor)', 'ctor')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ctor', 'src/app/hero-detail.component.ts (constructor)')
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
:marked
|
||||
Import the `switchMap` operator to use later with the route parameters `Observable`.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts (switchMap import)', 'rxjs-import')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'rxjs-import', 'src/app/hero-detail.component.ts (switchMap import)')
|
||||
|
||||
:marked
|
||||
Tell the class to implement the `OnInit` interface.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'implement', 'src/app/hero-detail.component.ts')
|
||||
|
||||
block ngOnInit
|
||||
:marked
|
||||
:marked
|
||||
Inside the `ngOnInit()` lifecycle hook, use the `params` Observable to
|
||||
extract the `id` parameter value from the `ActivatedRoute` service
|
||||
and use the `HeroService` to fetch the hero with that `id`.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'ngOnInit', 'src/app/hero-detail.component.ts')
|
||||
|
||||
|
||||
block extract-id
|
||||
:marked
|
||||
:marked
|
||||
The `switchMap` operator maps the `id` in the Observable route parameters
|
||||
to a new `Observable`, the result of the `HeroService.getHero()` method.
|
||||
|
||||
If a user re-navigates to this component while a `getHero` request is still processing,
|
||||
`switchMap` cancels the old request and then calls `HeroService.getHero()` again.
|
||||
|
||||
- var _str2int = _docsFor == 'dart' ? '<code>int.parse()</code> static method' : 'JavaScript (+) operator'
|
||||
:marked
|
||||
The hero `id` is a number. Route parameters are always strings.
|
||||
So the route parameter value is converted to a number with the !{_str2int}.
|
||||
So the route parameter value is converted to a number with the JavaScript (+) operator.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Do you need to unsubscribe?
|
||||
|
||||
|
@ -540,7 +503,7 @@ block extract-id
|
|||
In the previous code snippet, `HeroService` doesn't have a `getHero()` method. To fix this issue,
|
||||
open `HeroService` and add a `getHero()` method that filters the heroes list from `getHeroes()` by `id`.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'getHero')
|
||||
+makeExample('toh-5/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts (getHero)')
|
||||
|
||||
:marked
|
||||
### Find the way back
|
||||
|
@ -551,31 +514,30 @@ block extract-id
|
|||
Now add a third option, a `goBack()` method that navigates backward one step in the browser's history stack
|
||||
using the `Location` service you injected previously.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts', 'goBack')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'goBack', 'src/app/hero-detail.component.ts (goBack)')
|
||||
|
||||
- var _CanDeactivateGuard = _docsFor == 'dart' ? '<em>routerCanDeactivate()</em> hook' : '<em>CanDeactivate</em> guard'
|
||||
.l-sub-section
|
||||
:marked
|
||||
Going back too far could take users out of the app.
|
||||
In a real app, you can prevent this issue with the !{_CanDeactivateGuard}.
|
||||
In a real app, you can prevent this issue with the <em>CanDeactivate</em> guard.
|
||||
Read more on the [CanDeactivate](../api/router/index/CanDeactivate-interface.html) page.
|
||||
|
||||
:marked
|
||||
You'll wire this method with an event binding to a *Back* button that you'll add to the component template.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.html', 'back-button', '')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.html', 'back-button', '')
|
||||
|
||||
:marked
|
||||
Migrate the template to its own file
|
||||
called <span ngio-ex>hero-detail.component.html</span>:
|
||||
called <code>hero-detail.component.html</code>:
|
||||
|
||||
+makeExample('src/app/hero-detail.component.html')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.html', 'src/app/hero-detail.component.html')
|
||||
|
||||
:marked
|
||||
Update the component metadata with a `templateUrl` pointing to the template file that you just created.
|
||||
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts', 'metadata')
|
||||
+makeExample('toh-5/ts/src/app/hero-detail.component.ts', 'metadata', 'src/app/hero-detail.component.ts (metadata)')
|
||||
|
||||
:marked
|
||||
Refresh the browser and see the results.
|
||||
|
@ -593,32 +555,29 @@ block extract-id
|
|||
To achieve this effect, reopen `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
||||
with `<a>` tags. Change the opening `<a>` tag to the following:
|
||||
|
||||
+makeExample('src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.html', 'click', 'src/app/dashboard.component.html (repeated <a> tag)')
|
||||
|
||||
- var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
|
||||
:marked
|
||||
Notice the `[routerLink]` binding.
|
||||
As described in the [Router links](#router-links) section of this page,
|
||||
top-level navigation in the `AppComponent` template has router links set to fixed !{_pathVsName}s of the
|
||||
top-level navigation in the `AppComponent` template has router links set to fixed paths of the
|
||||
destination routes, "/dashboard" and "/heroes".
|
||||
|
||||
This time, you're binding to an expression containing a *link parameters !{_array}*.
|
||||
The !{_array} has two elements: the *!{_pathVsName}* of
|
||||
This time, you're binding to an expression containing a *link parameters array*.
|
||||
The array has two elements: the *path* of
|
||||
the destination route and a *route parameter* set to the value of the current hero's `id`.
|
||||
|
||||
The two !{_array} items align with the *!{_pathVsName}* and *:id*
|
||||
The two array items align with the *path* and *:id*
|
||||
token in the parameterized hero detail route definition that you added to
|
||||
`!{_appRoutingTsVsAppComp}` earlier:
|
||||
`app.module.ts` earlier:
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'src/app/app.component.ts' : 'src/app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
|
||||
+makeExample('toh-5/ts/src/app/app.module.3.ts', 'hero-detail', 'src/app/app.module.ts (hero detail)')
|
||||
|
||||
:marked
|
||||
Refresh the browser and select a hero from the dashboard; the app navigates to that hero’s details.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-main-section
|
||||
:marked
|
||||
.l-main-section
|
||||
:marked
|
||||
## Refactor routes to a _Routing Module_
|
||||
|
||||
Almost 20 lines of `AppModule` are devoted to configuring four routes.
|
||||
|
@ -641,8 +600,8 @@ block extract-id
|
|||
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`.
|
||||
Give it the following contents, extracted from the `AppModule` class:
|
||||
|
||||
+makeExample('src/app/app-routing.module.ts')
|
||||
:marked
|
||||
+makeExample('toh-5/ts/src/app/app-routing.module.ts', null, 'src/app/app-routing.module.ts')
|
||||
:marked
|
||||
The following points are typical of routing modules:
|
||||
* The Routing Module pulls the routes into a variable. The variable clarifies the
|
||||
routing module pattern in case you export the module in the future.
|
||||
|
@ -692,7 +651,7 @@ block extract-id
|
|||
|
||||
Add the following HTML fragment at the bottom of the template where the `<hero-detail>` used to be:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.html', 'mini-detail', 'src/app/heroes.component.ts')
|
||||
|
||||
|
||||
:marked
|
||||
|
@ -707,7 +666,7 @@ figure.image-display
|
|||
The hero's name is displayed in capital letters because of the `uppercase` pipe
|
||||
that's included in the interpolation binding, right after the pipe operator ( | ).
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.html', 'pipe', '')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.html', 'pipe', '')
|
||||
|
||||
:marked
|
||||
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||||
|
@ -729,10 +688,10 @@ figure.image-display
|
|||
Before making any more changes, migrate the template and styles to their own files.
|
||||
|
||||
First, move the template contents from `heroes.component.ts`
|
||||
into a new <span ngio-ex>heroes.component.html</span> file.
|
||||
into a new <code>heroes.component.html</code> file.
|
||||
Don't copy the backticks. As for `heroes.component.ts`, you'll
|
||||
come back to it in a minute. Next, move the
|
||||
styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
||||
styles contents into a new <code>heroes.component.css</code> file.
|
||||
|
||||
The two new files should look like this:
|
||||
|
||||
|
@ -747,14 +706,11 @@ figure.image-display
|
|||
`templateUrl` and `styleUrls` respectively.
|
||||
Set their properties to refer to the new files.
|
||||
|
||||
block heroes-component-cleanup
|
||||
//- Only relevant for Dart.
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts (revised metadata)', 'metadata')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'metadata', 'src/app/heroes.component.ts (revised metadata)')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
||||
The `styleUrls` property is an array of style file names (with paths).
|
||||
You could list multiple style files from different locations if you needed them.
|
||||
|
||||
:marked
|
||||
|
@ -770,16 +726,16 @@ block heroes-component-cleanup
|
|||
1. Inject the `router` in the constructor, along with the `HeroService`.
|
||||
1. Implement `gotoDetail()` by calling the router `navigate()` method.
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts', 'gotoDetail')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'gotoDetail', 'src/app/heroes.component.ts (gotoDetail)')
|
||||
|
||||
:marked
|
||||
Note that you're passing a two-element *link parameters !{_array}*—a
|
||||
!{_pathVsName} and the route parameter—to
|
||||
Note that you're passing a two-element *link parameters array*—a
|
||||
path and the route parameter—to
|
||||
the router `navigate()` method, just as you did in the `[routerLink]` binding
|
||||
back in the `DashboardComponent`.
|
||||
Here's the revised `HeroesComponent` class:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts', 'class')
|
||||
+makeExample('toh-5/ts/src/app/heroes.component.ts', 'class', 'src/app/heroes.component.ts (class)')
|
||||
|
||||
:marked
|
||||
Refresh the browser and start clicking.
|
||||
|
@ -800,21 +756,21 @@ block heroes-component-cleanup
|
|||
would obscure the component logic.
|
||||
Instead, edit the CSS in a separate `*.css` file.
|
||||
|
||||
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference
|
||||
that file in the component metadata's `styleUrls` !{_array} property like this:
|
||||
Add a <code>dashboard.component.css</code> file to the `app` folder and reference
|
||||
that file in the component metadata's `styleUrls` array property like this:
|
||||
|
||||
+makeExcerpt('src/app/dashboard.component.ts (styleUrls)', 'css')
|
||||
+makeExample('toh-5/ts/src/app/dashboard.component.ts', 'css', 'src/app/dashboard.component.ts (styleUrls)')
|
||||
|
||||
:marked
|
||||
### Add stylish hero details
|
||||
|
||||
You've also been provided with CSS styles specifically for the `HeroDetailComponent`.
|
||||
|
||||
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
||||
Add a <code>hero-detail.component.css</code> to the `app`
|
||||
folder and refer to that file inside
|
||||
the `styleUrls` !{_array} as you did for `DashboardComponent`.
|
||||
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` !{_decorator}
|
||||
<span if-docs="ts">and its import</span>.
|
||||
the `styleUrls` array as you did for `DashboardComponent`.
|
||||
Also, in `hero-detail.component.ts`, remove the `hero` property `@Input` decorator
|
||||
and its import.
|
||||
|
||||
Here's the content for the component CSS files.
|
||||
|
||||
|
@ -831,12 +787,11 @@ block heroes-component-cleanup
|
|||
The provided CSS makes the navigation links in the `AppComponent` look more like selectable buttons.
|
||||
You'll surround those links in `<nav>` tags.
|
||||
|
||||
Add an <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
||||
Add an <code>app.component.css</code> file to the `app` folder with the following content.
|
||||
|
||||
+makeExcerpt('src/app/app.component.css (navigation styles)', '')
|
||||
+makeExample('toh-5/ts/src/app/app.component.css', '', 'src/app/app.component.css (navigation styles)')
|
||||
|
||||
.l-sub-section
|
||||
block router-link-active
|
||||
:marked
|
||||
**The *routerLinkActive* directive**
|
||||
|
||||
|
@ -844,12 +799,12 @@ block heroes-component-cleanup
|
|||
add a class to the HTML navigation element whose route matches the active route.
|
||||
All you have to do is define the style for it.
|
||||
|
||||
+makeExcerpt('src/app/app.component.ts (active router links)', 'template')
|
||||
+makeExample('toh-5/ts/src/app/app.component.ts', 'template', 'src/app/app.component.ts (active router links)')
|
||||
|
||||
:marked
|
||||
Add a `styleUrls` property that refers to this CSS file as follows:
|
||||
|
||||
+makeExcerpt('src/app/app.component.ts','styleUrls')
|
||||
+makeExample('toh-5/ts/src/app/app.component.ts','styleUrls')
|
||||
|
||||
|
||||
:marked
|
||||
|
@ -865,16 +820,14 @@ block heroes-component-cleanup
|
|||
These correspond to the full set of master styles that you installed earlier during [setup](../guide/setup.html).
|
||||
Here's an excerpt:
|
||||
|
||||
+makeExcerpt('src/styles.css (excerpt)', 'toh')
|
||||
|
||||
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css'
|
||||
+makeExample('toh-5/ts/src/styles.css', 'toh', 'src/styles.css (excerpt)')
|
||||
|
||||
:marked
|
||||
Create the file <span ngio-ex>styles.css</span>.
|
||||
Ensure that the file contains the [master styles provided here](!{styles_css}).
|
||||
Also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
||||
Create the file <code>styles.css</code>.
|
||||
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/src/styles.css).
|
||||
Also edit <code>index.html</code> to refer to this stylesheet.
|
||||
|
||||
+makeExcerpt('src/index.html (link ref)', 'css')
|
||||
+makeExample('toh-5/ts/src/index.html', 'css', 'src/index.html (link ref)')
|
||||
|
||||
:marked
|
||||
Look at the app now. The dashboard, heroes, and navigation links are styled.
|
||||
|
@ -889,8 +842,7 @@ figure.image-display
|
|||
Review the sample source code in the <live-example></live-example> for this page.
|
||||
Verify that you have the following structure:
|
||||
|
||||
block file-tree-end
|
||||
.filetree
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file src
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
- var _example = 'toh-6';
|
||||
|
||||
block includes
|
||||
include ../_util-fns
|
||||
- var _Http = 'Http'; // Angular `Http` library name.
|
||||
- var _Angular_Http = 'Angular <code>Http</code>'
|
||||
- var _Angular_http_library = 'Angular HTTP library'
|
||||
- var _HttpModule = 'HttpModule'
|
||||
- var _JSON_stringify = 'JSON.stringify'
|
||||
|
||||
//- Shared var definitions
|
||||
- var _promise = _Promise.toLowerCase()
|
||||
|
||||
:marked
|
||||
In this page, you'll make the following improvements.
|
||||
|
@ -29,8 +19,8 @@ block includes
|
|||
In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list,
|
||||
editing a selected hero along the way.
|
||||
That's the starting point for this page.
|
||||
block start-server-and-watch
|
||||
:marked
|
||||
|
||||
:marked
|
||||
## Keep the app transpiling and running
|
||||
Enter the following command in the terminal window:
|
||||
|
||||
|
@ -46,8 +36,8 @@ block start-server-and-watch
|
|||
|
||||
.l-main-section#http-providers
|
||||
h1 Providing HTTP Services
|
||||
block http-library
|
||||
:marked
|
||||
|
||||
: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.
|
||||
|
@ -57,50 +47,49 @@ block http-library
|
|||
:marked
|
||||
## 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 `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
|
||||
|
||||
To allow access to these services from anywhere in the app,
|
||||
add `HttpModule` to the `imports` list of the `AppModule`.
|
||||
|
||||
+makeExample('src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
|
||||
+makeExample('toh-6/ts/src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
|
||||
|
||||
:marked
|
||||
Notice that you also supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
|
||||
:marked
|
||||
Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Simulate the web API
|
||||
We recommend registering app-wide services in the root
|
||||
`AppModule` *providers*.
|
||||
|
||||
Until you have a web server that can handle requests for hero data,
|
||||
the HTTP client will fetch and save data from
|
||||
a mock service, the *in-memory web API*.
|
||||
|
||||
Update <span ngio-ex>!{_appModuleTsVsMainTs}</span> with this version, which uses the mock service:
|
||||
Update <code>src/app/app.module.ts</code> with this version, which uses the mock service:
|
||||
|
||||
+makeExcerpt(_appModuleTsVsMainTs, 'v2')
|
||||
+makeExample('toh-6/ts/src/app/app.module.ts', 'v2', 'src/app/app.module.ts (v2)')
|
||||
|
||||
block backend
|
||||
:marked
|
||||
:marked
|
||||
Rather than require a real API server, this example simulates communication with the remote server by adding the
|
||||
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
|
||||
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
|
||||
|
||||
+makeExcerpt(_appModuleTsVsMainTs, 'in-mem-web-api', '')
|
||||
+makeExample('toh-6/ts/src/app/app.module.ts', 'in-mem-web-api', '')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
||||
that primes the in-memory database.
|
||||
Add the file `in-memory-data.service.ts` in `!{_appDir}` with the following content:
|
||||
Add the file `in-memory-data.service.ts` in `app` with the following content:
|
||||
|
||||
+makeExample('src/app/in-memory-data.service.ts', 'init')(format='.')
|
||||
+makeExample('toh-6/ts/src/app/in-memory-data.service.ts', 'init', 'src/app/in-memory-data.service.ts')(format='.')
|
||||
:marked
|
||||
This file replaces `mock-heroes.ts`, which is now safe to delete.
|
||||
|
||||
block dont-be-distracted-by-backend-subst
|
||||
.alert.is-helpful
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
|
||||
Don't worry about the details of this backend substitution; you can
|
||||
|
@ -114,9 +103,9 @@ block dont-be-distracted-by-backend-subst
|
|||
:marked
|
||||
## Heroes and HTTP
|
||||
|
||||
In the current `HeroService` implementation, a !{_Promise} resolved with mock heroes is returned.
|
||||
In the current `HeroService` implementation, a Promise resolved with mock heroes is returned.
|
||||
|
||||
+makeExcerpt('toh-4/ts/src/app/hero.service.ts (old getHeroes)', 'get-heroes')
|
||||
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (old getHeroes)')
|
||||
|
||||
:marked
|
||||
This was implemented in anticipation of ultimately
|
||||
|
@ -124,56 +113,53 @@ block dont-be-distracted-by-backend-subst
|
|||
|
||||
Now convert `getHeroes()` to use HTTP.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'getHeroes', 'src/app/hero.service.ts (updated getHeroes and new class members)')
|
||||
|
||||
:marked
|
||||
Update the import statements as follows:
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts (updated imports)', 'imports')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'imports', 'src/app/hero.service.ts (updated imports)')
|
||||
|
||||
- var _h3id = `http-${_promise}`
|
||||
:marked
|
||||
Refresh the browser. The hero data should successfully load from the
|
||||
mock server.
|
||||
|
||||
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
|
||||
<h3 id="http-promise">HTTP Promise</h3>
|
||||
|
||||
|
||||
block get-heroes-details
|
||||
:marked
|
||||
: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.
|
||||
|
||||
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'to-promise', '')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-promise', '')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
|
||||
|
||||
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
|
||||
To use those capabilities, you have to add the operators themselves.
|
||||
That's as easy as importing them from the RxJS library like this:
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'rxjs', '')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'rxjs', '')
|
||||
|
||||
.l-sub-section
|
||||
.l-sub-section
|
||||
:marked
|
||||
You'll add more operators, and learn why you must do so, [later in this tutorial](#rxjs-imports).
|
||||
|
||||
:marked
|
||||
:marked
|
||||
### Extracting the data in the *then* callback
|
||||
|
||||
In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
|
||||
data within the response.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'to-data', '')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'to-data', '')
|
||||
|
||||
:marked
|
||||
The response JSON has a single `data` property, which
|
||||
holds the !{_array} of heroes that the caller wants.
|
||||
So you grab that !{_array} and return it as the resolved !{_Promise} value.
|
||||
holds the array of heroes that the caller wants.
|
||||
So you grab that array and return it as the resolved Promise value.
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
|
@ -183,27 +169,26 @@ block get-heroes-details
|
|||
|
||||
:marked
|
||||
The caller is unaware that you fetched the heroes from the (mock) server.
|
||||
It receives a !{_Promise} of *heroes* just as it did before.
|
||||
It receives a Promise of *heroes* just as it did before.
|
||||
|
||||
### Error Handling
|
||||
|
||||
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'catch', '')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'catch', '')
|
||||
|
||||
:marked
|
||||
This is a critical step.
|
||||
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'handleError', '')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'handleError', '')
|
||||
|
||||
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
|
||||
:marked
|
||||
This demo service logs the error to the console; in real life,
|
||||
you would handle the error in code. For a demo, this works.
|
||||
|
||||
The code also includes an error to
|
||||
the caller in a !{rejected_promise}, so that the caller can display a proper error message to the user.
|
||||
the caller in a rejected promise, so that the caller can display a proper error message to the user.
|
||||
|
||||
|
||||
### Get hero by id
|
||||
|
@ -215,19 +200,19 @@ block get-heroes-details
|
|||
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
|
||||
|
||||
Update the `HeroService.getHero()` method to make a _get-by-id_ request:
|
||||
+makeExcerpt('src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
|
||||
|
||||
:marked
|
||||
This request is almost the same as `getHeroes()`.
|
||||
The hero id in the URL identifies which hero the server should update.
|
||||
|
||||
Also, the `data` in the response is a single hero object rather than !{_an} !{_array}.
|
||||
Also, the `data` in the response is a single hero object rather than an array.
|
||||
|
||||
### Unchanged _getHeroes_ API
|
||||
|
||||
Although you made significant internal changes to `getHeroes()` and `getHero()`,
|
||||
the public signatures didn't change.
|
||||
You still return a !{_Promise} from both methods.
|
||||
You still return a Promise from both methods.
|
||||
You won't have to update any of the components that call them.
|
||||
|
||||
Now it's time to add the ability to create and delete heroes.
|
||||
|
@ -251,13 +236,13 @@ block get-heroes-details
|
|||
At the end of the hero detail template, add a save button with a `click` event
|
||||
binding that invokes a new component method named `save()`.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.html', 'save')
|
||||
+makeExample('toh-6/ts/src/app/hero-detail.component.html', 'save', 'src/app/hero-detail.component.html (save)')
|
||||
|
||||
:marked
|
||||
Add the following `save()` method, which persists hero name changes using the hero service
|
||||
`update()` method and then navigates back to the previous view.
|
||||
|
||||
+makeExcerpt('src/app/hero-detail.component.ts', 'save')
|
||||
+makeExample('toh-6/ts/src/app/hero-detail.component.ts', 'save', 'src/app/hero-detail.component.ts (save)')
|
||||
|
||||
:marked
|
||||
### Add a hero service _update()_ method
|
||||
|
@ -266,12 +251,12 @@ block get-heroes-details
|
|||
`getHeroes()`, but it uses an HTTP `put()` to persist server-side changes.
|
||||
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'update')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'update', 'src/app/hero.service.ts (update)')
|
||||
|
||||
:marked
|
||||
To identify which hero the server should update, the hero `id` is encoded in
|
||||
the URL. The `put()` body is the JSON string encoding of the hero, obtained by
|
||||
calling `!{_JSON_stringify}`. The body content type
|
||||
calling `JSON.stringify`. The body content type
|
||||
(`application/json`) is identified in the request header.
|
||||
|
||||
Refresh the browser, change a hero name, save your change,
|
||||
|
@ -287,20 +272,20 @@ block get-heroes-details
|
|||
Insert the following into the heroes component HTML, just after
|
||||
the heading:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.html', 'add')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.html', 'add', 'src/app/heroes.component.html (add)')
|
||||
|
||||
:marked
|
||||
In response to a click event, call the component's click handler and then
|
||||
clear the input field so that it's ready for another name.
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts', 'add')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'add', 'src/app/heroes.component.ts (add)')
|
||||
|
||||
:marked
|
||||
When the given name is non-blank, the handler delegates creation of the
|
||||
named hero to the hero service, and then adds the new hero to the !{_array}.
|
||||
named hero to the hero service, and then adds the new hero to the array.
|
||||
|
||||
Implement the `create()` method in the `HeroService` class.
|
||||
+makeExcerpt('src/app/hero.service.ts', 'create')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'create', 'src/app/hero.service.ts (create)')
|
||||
|
||||
:marked
|
||||
Refresh the browser and create some heroes.
|
||||
|
@ -314,12 +299,12 @@ block get-heroes-details
|
|||
Add the following button element to the heroes component HTML, after the hero
|
||||
name in the repeated `<li>` element.
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.html', 'delete', '')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.html', 'delete', '')
|
||||
|
||||
:marked
|
||||
The `<li>` element should now look like this:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.html', 'li-element')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.html', 'li-element', 'src/app/heroes.component.html (li-element)')
|
||||
|
||||
:marked
|
||||
In addition to calling the component's `delete()` method, the delete button's
|
||||
|
@ -329,35 +314,34 @@ block get-heroes-details
|
|||
|
||||
The logic of the `delete()` handler is a bit trickier:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.ts', 'delete')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.ts', 'delete', 'src/app/heroes.component.ts (delete)')
|
||||
|
||||
:marked
|
||||
Of course you delegate hero deletion to the hero service, but the component
|
||||
is still responsible for updating the display: it removes the deleted hero
|
||||
from the !{_array} and resets the selected hero, if necessary.
|
||||
from the array and resets the selected hero, if necessary.
|
||||
|
||||
:marked
|
||||
To place the delete button at the far right of the hero entry,
|
||||
add this CSS:
|
||||
|
||||
+makeExcerpt('src/app/heroes.component.css', 'additions')
|
||||
+makeExample('toh-6/ts/src/app/heroes.component.css', 'additions', 'src/app/heroes.component.css (additions)')
|
||||
|
||||
:marked
|
||||
### Hero service _delete()_ method
|
||||
|
||||
Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server:
|
||||
|
||||
+makeExcerpt('src/app/hero.service.ts', 'delete')
|
||||
+makeExample('toh-6/ts/src/app/hero.service.ts', 'delete', 'src/app/hero.service.ts (delete)')
|
||||
|
||||
:marked
|
||||
Refresh the browser and try the new delete functionality.
|
||||
|
||||
#observables
|
||||
:marked
|
||||
## !{_Observable}s
|
||||
## Observables
|
||||
|
||||
block observables-section-intro
|
||||
:marked
|
||||
:marked
|
||||
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
|
||||
|
||||
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
||||
|
@ -382,8 +366,8 @@ block observables-section-intro
|
|||
But requests aren't always done only once.
|
||||
You may start one request,
|
||||
cancel it, and make a different request before the server has responded to the first request.
|
||||
A *request-cancel-new-request* sequence is difficult to implement with *!{_Promise}s*, but
|
||||
easy with *!{_Observable}s*.
|
||||
A *request-cancel-new-request* sequence is difficult to implement with *Promises*, but
|
||||
easy with *Observables*.
|
||||
|
||||
### Add the ability to search by name
|
||||
You're going to add a *hero search* feature to the Tour of Heroes.
|
||||
|
@ -391,13 +375,13 @@ block observables-section-intro
|
|||
|
||||
Start by creating `HeroSearchService` that sends search queries to the server's web API.
|
||||
|
||||
+makeExample('src/app/hero-search.service.ts')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.service.ts', null, 'src/app/hero-search.service.ts')
|
||||
|
||||
:marked
|
||||
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
|
||||
The `http.get()` call in `HeroSearchService` is similar to the one
|
||||
in the `HeroService`, although the URL now has a query string.
|
||||
|
||||
<span if-docs="ts">More importantly, you no longer call `toPromise()`.
|
||||
More importantly, you no longer call `toPromise()`.
|
||||
Instead you return the *Observable* from the the `htttp.get()`,
|
||||
after chaining it to another RxJS operator, <code>map()</code>,
|
||||
to extract heroes from the response data.
|
||||
|
@ -411,33 +395,32 @@ block observables-section-intro
|
|||
|
||||
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
|
||||
Also, add styles for the new component.
|
||||
+makeExample('src/app/hero-search.component.css')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.component.css', null, 'src/app/hero-search.component.css')
|
||||
:marked
|
||||
As the user types in the search box, a *keyup* event binding calls the component's `search()`
|
||||
method with the new search box value.
|
||||
|
||||
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
|
||||
|
||||
But as you'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
|
||||
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until you route it through the `async` pipe (`AsyncPipe`).
|
||||
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
|
||||
But as you'll soon see, the `heroes` property is now an *Observable* of hero arrays, rather than just a hero array.
|
||||
The `*ngFor` can't do anything with an `Observable` until you route it through the `async` pipe (`AsyncPipe`).
|
||||
The `async` pipe subscribes to the `Observable` and produces the array of heroes to `*ngFor`.
|
||||
|
||||
Create the `HeroSearchComponent` class and metadata.
|
||||
|
||||
+makeExample('src/app/hero-search.component.ts')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.component.ts', null, 'src/app/hero-search.component.ts')
|
||||
|
||||
:marked
|
||||
#### Search terms
|
||||
|
||||
Focus on `!{_priv}searchTerms`:
|
||||
Focus on the `searchTerms`:
|
||||
|
||||
+makeExcerpt('src/app/hero-search.component.ts', 'searchTerms', '')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.component.ts', 'searchTerms', '')
|
||||
|
||||
block search-criteria-intro
|
||||
:marked
|
||||
:marked
|
||||
A `Subject` is a producer of an _observable_ event stream;
|
||||
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
|
||||
|
||||
|
@ -447,28 +430,27 @@ block search-criteria-intro
|
|||
<a id="ngoninit"></a>
|
||||
#### Initialize the *heroes* property (*ngOnInit*)
|
||||
|
||||
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
|
||||
A `Subject` is also an `Observable`.
|
||||
You can turn the stream
|
||||
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
|
||||
of search terms into a stream of `Hero` arrays and assign the result to the `heroes` property.
|
||||
|
||||
+makeExcerpt('src/app/hero-search.component.ts', 'search', '')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.component.ts', 'search', '')
|
||||
|
||||
:marked
|
||||
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
|
||||
taxing server resources and burning through the cellular network data plan.
|
||||
|
||||
block observable-transformers
|
||||
:marked
|
||||
: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 `debounceTime()` and `distinctUntilChanged()`.
|
||||
* `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
|
||||
.l-sub-section
|
||||
:marked
|
||||
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
|
||||
(formerly known as `flatMapLatest`),
|
||||
|
@ -486,13 +468,13 @@ block observable-transformers
|
|||
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
|
||||
doesn't actually abort a pending HTTP request.
|
||||
For now, unwanted results are discarded.
|
||||
:marked
|
||||
: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
|
||||
:marked
|
||||
a#rxjs-imports
|
||||
:marked
|
||||
### Import RxJS operators
|
||||
|
||||
Most RxJS operators are not included in Angular's base `Observable` implementation.
|
||||
|
@ -501,9 +483,9 @@ block observable-transformers
|
|||
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
|
||||
Here are all the RxJS imports that _this_ component needs:
|
||||
|
||||
+makeExample('src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
|
||||
+makeExample('toh-6/ts/src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
|
||||
|
||||
:marked
|
||||
:marked
|
||||
The `import 'rxjs/add/...'` syntax may be unfamiliar.
|
||||
It's missing the usual list of symbols between the braces: `{...}`.
|
||||
|
||||
|
@ -516,16 +498,14 @@ block observable-transformers
|
|||
|
||||
Add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
||||
|
||||
+makeExample('src/app/dashboard.component.html')(format='.')
|
||||
+makeExample('toh-6/ts/src/app/dashboard.component.html', null, 'src/app/dashboard.component.html')(format='.')
|
||||
|
||||
- var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
|
||||
- var declFile = _docsFor == 'dart' ? 'src/app/dashboard.component.ts' : 'src/app/app.module.ts'
|
||||
:marked
|
||||
Finally, import `HeroSearchComponent` from
|
||||
<span ngio-ex>hero-search.component.ts</span>
|
||||
and add it to the `!{_declarations}` !{_array}.
|
||||
<code>hero-search.component.ts</code>
|
||||
and add it to the `declarations` array.
|
||||
|
||||
+makeExcerpt(declFile, 'search')
|
||||
+makeExample('toh-6/ts/src/app/app.module.ts', 'search', 'src/app/app.module.ts (search)')
|
||||
|
||||
:marked
|
||||
Run the app again. In the Dashboard, enter some text in the search box.
|
||||
|
@ -541,8 +521,7 @@ figure.image-display
|
|||
Review the sample source code in the <live-example></live-example> for this page.
|
||||
Verify that you have the following structure:
|
||||
|
||||
block filetree
|
||||
.filetree
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file src
|
||||
|
@ -587,12 +566,11 @@ block filetree
|
|||
- You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
|
||||
- You updated the components to allow adding, editing, and deleting of heroes.
|
||||
- You configured an in-memory web API.
|
||||
- You learned how to use !{_Observable}s.
|
||||
- You learned how to use Observables.
|
||||
|
||||
Here are the files you added or changed in this page.
|
||||
|
||||
block file-summary
|
||||
+makeTabs(
|
||||
+makeTabs(
|
||||
`toh-6/ts/src/app/app.component.ts,
|
||||
toh-6/ts/src/app/app.module.ts,
|
||||
toh-6/ts/src/app/heroes.component.ts,
|
||||
|
@ -612,9 +590,9 @@ block file-summary
|
|||
hero-detail.comp...html,
|
||||
hero.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.component.ts,
|
||||
toh-6/ts/src/app/hero-search.component.html,
|
||||
|
@ -624,7 +602,7 @@ block file-summary
|
|||
hero-search.component.ts,
|
||||
hero-search.component.html,
|
||||
hero-search.component.css`
|
||||
)
|
||||
)
|
||||
|
||||
.l-sub-section
|
||||
: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