chore: drop ts/_cache (#2974)
* chore: drop dart cache and link check scripts * chore: delete ts/_cache * chore(README): remove mention of ts/_cache * chore: ignore ts/_cache files
This commit is contained in:
parent
f0206cc957
commit
6fd0a141ef
|
@ -34,3 +34,4 @@ link-checker-results.txt
|
|||
*a2docs.css
|
||||
/dist
|
||||
/public/docs/dart
|
||||
/public/docs/ts/_cache
|
||||
|
|
|
@ -21,8 +21,6 @@ Filing issues is helpful but **pull requests** that improve the docs are even be
|
|||
|
||||
Learn how to [contribute to Angular.io](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
|
||||
|
||||
> **IMPORTANT**: Do _NOT_ make changes to cached files under `public/docs/ts/_cache`. Cached files are updated through a separate workflow.
|
||||
|
||||
## Development Setup
|
||||
This site relies heavily on node and npm.
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.l-sub-section
|
||||
:marked
|
||||
Alternatively, begin with a [download of the QuickStart source](!{_qsRepo}).
|
|
@ -1 +0,0 @@
|
|||
include ../latest/_util-fns
|
|
@ -1,758 +0,0 @@
|
|||
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 a vocabulary of its own.
|
||||
Most Angular terms are everyday English words
|
||||
with a specific meaning within the Angular system.
|
||||
|
||||
This glossary lists the most prominent terms
|
||||
and a few less familiar ones that have unusual or
|
||||
unexpected definitions.
|
||||
|
||||
[A](#A) [B](#B) [C](#C) [D](#D) [E](#E) [F](#F) [G](#G) [H](#H) [I](#I)
|
||||
[J](#J) [K](#K) [L](#L) [M](#M) [N](#N) [O](#O) [P](#P) [Q](#Q) [R](#R)
|
||||
[S](#S) [T](#T) [U](#U) [V](#V) [W](#W) [X](#X) [Y](#Y) [Z](#Z)
|
||||
|
||||
.l-main-section#A
|
||||
|
||||
a#aot
|
||||
:marked
|
||||
## Ahead-of-time (AOT) compilation
|
||||
.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.
|
||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
## Angular module
|
||||
.l-sub-section
|
||||
:marked
|
||||
Helps you organize an application into cohesive blocks of functionality.
|
||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||
|
||||
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 Module](!{docsLatest}/guide/ngmodule.html) page.
|
||||
|
||||
+ifDocsFor('ts|dart')
|
||||
:marked
|
||||
## Annotation
|
||||
.l-sub-section
|
||||
block annotation-defn
|
||||
:marked
|
||||
In practice, a synonym for [Decoration](#decorator).
|
||||
|
||||
:marked
|
||||
## Attribute directive
|
||||
.l-sub-section
|
||||
:marked
|
||||
A category of [directive](#directive) that can listen to and modify the behavior of
|
||||
other HTML elements, attributes, properties, and components. They are usually represented
|
||||
as HTML attributes, hence the name.
|
||||
|
||||
A good example of an attribute directive is the `ngClass` directive for adding and removing CSS class names.
|
||||
|
||||
.l-main-section#B
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Barrel
|
||||
.l-sub-section
|
||||
:marked
|
||||
A barrel is a way to *rollup 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.
|
||||
|
||||
Imagine three ES2015 modules in a `heroes` folder:
|
||||
code-example.
|
||||
// heroes/hero.component.ts
|
||||
export class HeroComponent {}
|
||||
|
||||
// heroes/hero.model.ts
|
||||
export class Hero {}
|
||||
|
||||
// heroes/hero.service.ts
|
||||
export class HeroService {}
|
||||
:marked
|
||||
Without a barrel, a consumer would need three import statements:
|
||||
code-example.
|
||||
import { HeroComponent } from '../heroes/hero.component.ts';
|
||||
import { Hero } from '../heroes/hero.model.ts';
|
||||
import { HeroService } from '../heroes/hero.service.ts';
|
||||
:marked
|
||||
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
|
||||
code-example.
|
||||
export * from './hero.model.ts'; // re-export all of its exports
|
||||
export * from './hero.service.ts'; // re-export all of its exports
|
||||
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
||||
:marked
|
||||
Now a consumer can import what it needs from the barrel.
|
||||
code-example.
|
||||
import { Hero, HeroService } from '../heroes'; // index is implied
|
||||
:marked
|
||||
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Note that you can often achieve this using [Angular modules](#angular-module) instead.
|
||||
|
||||
:marked
|
||||
## Binding
|
||||
.l-sub-section
|
||||
:marked
|
||||
Almost always refers to [Data Binding](#data-binding) and the act of
|
||||
binding an HTML object property to a data object property.
|
||||
|
||||
May refer to a [dependency injection](#dependency-injection) binding
|
||||
between a "token", also referred to as a "key", and a dependency [provider](#provider).
|
||||
This more rare usage should be clear in context.
|
||||
|
||||
:marked
|
||||
## Bootstrap
|
||||
.l-sub-section
|
||||
block bootstrap-defn-top
|
||||
:marked
|
||||
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.
|
||||
:marked
|
||||
You can bootstrap multiple apps in the same `index.html`, each with its own top level root.
|
||||
|
||||
.l-main-section#C
|
||||
:marked
|
||||
## camelCase
|
||||
.l-sub-section
|
||||
:marked
|
||||
The practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter
|
||||
_except the first letter, which is lowercase_.
|
||||
|
||||
Function, property, and method names are typically spelled in camelCase. Examples include: `square`, `firstName` and `getHeroes`. Notice that `square` is an example of how you write a single word in camelCase.
|
||||
|
||||
This form is also known as **lower camel case**, to distinguish it from **upper camel case**, which is [PascalCase](#pascalcase).
|
||||
When you see "camelCase" in this documentation it always means *lower camel case*.
|
||||
|
||||
:marked
|
||||
## Component
|
||||
.l-sub-section
|
||||
:marked
|
||||
An Angular class responsible for exposing data to a [view](#view) and handling most of the view’s display and user-interaction logic.
|
||||
|
||||
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).
|
||||
|
||||
You apply the `!{_at}Component` !{_decoratorLink} to
|
||||
the component class, thereby attaching to the class the essential component metadata
|
||||
that Angular needs to create a component instance and render it with its template
|
||||
as a view.
|
||||
|
||||
Those familiar with "MVC" and "MVVM" patterns will recognize
|
||||
the component in the role of "controller" or "view model".
|
||||
|
||||
.l-main-section#D
|
||||
:marked
|
||||
## dash-case
|
||||
.l-sub-section
|
||||
:marked
|
||||
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](#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
|
||||
spelled in dash-case.
|
||||
|
||||
:marked
|
||||
## Data binding
|
||||
.l-sub-section
|
||||
:marked
|
||||
Applications display data values to a user and respond to user
|
||||
actions (clicks, touches, keystrokes).
|
||||
|
||||
Instead of manually pushing application data values into HTML, attaching
|
||||
event listeners, pulling changed values from the screen, and
|
||||
updating application data values, you can use data binding by declaring the relationship between an HTML widget and data source and let the
|
||||
framework handle the details.
|
||||
|
||||
Angular has a rich data binding framework with a variety of data binding
|
||||
operations and supporting declaration syntax.
|
||||
|
||||
Read about the 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).
|
||||
|
||||
|
||||
+ifDocsFor('ts|dart')
|
||||
a#decorator
|
||||
a#decoration
|
||||
:marked
|
||||
## Decorator | decoration
|
||||
.l-sub-section
|
||||
block decorator-defn
|
||||
:marked
|
||||
A decorator is a **function** that adds metadata to a class, its members (properties, methods) and function arguments.
|
||||
|
||||
Decorators are a JavaScript language [feature](https://github.com/wycats/javascript-decorators), implemented in TypeScript and proposed for ES2016 (AKA ES7).
|
||||
|
||||
To apply a decorator, position it immediately above or to the left of the thing it decorates.
|
||||
|
||||
Angular has its own set of decorators to help it interoperate with your application parts.
|
||||
Here is an example of a `@Component` decorator that identifies a
|
||||
class as an Angular [component](#component) and an `@Input` decorator applied to the `name` property
|
||||
of that component. The elided object argument to the `@Component` decorator would contain the pertinent component metadata.
|
||||
```
|
||||
@Component({...})
|
||||
export class AppComponent {
|
||||
constructor(@Inject('SpecialFoo') public foo:Foo) {}
|
||||
@Input() name:string;
|
||||
}
|
||||
```
|
||||
The scope of a decorator is limited to the language feature
|
||||
that it decorates. None of the decorations shown here will "leak" to other
|
||||
classes appearing below it in the file.
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Always include parentheses `()` when applying a decorator.
|
||||
|
||||
:marked
|
||||
## Dependency injection
|
||||
.l-sub-section
|
||||
:marked
|
||||
Dependency injection is both a design pattern and a mechanism
|
||||
for creating and delivering parts of an application to other
|
||||
parts of an application that request them.
|
||||
|
||||
Angular developers prefer to build applications by defining many simple parts
|
||||
that each do one thing well and then wiring them together at runtime.
|
||||
|
||||
These parts often rely on other parts. An Angular [component](#component)
|
||||
part might rely on a service part to get data or perform a calculation. When
|
||||
part "A" relies on another part "B", you say that "A" depends on "B" and
|
||||
that "B" is a dependency of "A".
|
||||
|
||||
You can ask a "dependency injection system" to create "A"
|
||||
for us and handle all the dependencies.
|
||||
If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies
|
||||
and returns a fully prepared instance of "A".
|
||||
|
||||
Angular provides and relies upon its own sophisticated
|
||||
[dependency injection](!{docsLatest}/guide/dependency-injection.html) system
|
||||
to assemble and run applications by "injecting" application parts
|
||||
into other application parts where and when needed.
|
||||
|
||||
At the core there is an [`injector`](#injector) that returns dependency values on request.
|
||||
The expression `injector.get(token)` returns the value associated with the given token.
|
||||
|
||||
A token is an Angular type (`OpaqueToken`). You rarely deal with tokens directly; most
|
||||
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
|
||||
to a token. When you write `injector.get(Foo)`, the injector returns
|
||||
the value associated with the token for the `Foo` class, typically an instance of `Foo` itself.
|
||||
|
||||
During many of its operations, Angular makes similar requests internally, such as when it creates a [`component`](#component) for display.
|
||||
|
||||
The `Injector` maintains an internal map of tokens to dependency values.
|
||||
If the `Injector` can't find a value for a given token, it creates
|
||||
a new value using a `Provider` for that token.
|
||||
|
||||
A [provider](#provider) is a recipe for
|
||||
creating new instances of a dependency value associated with a particular token.
|
||||
|
||||
An injector can only create a value for a given token if it has
|
||||
a `provider` for that token in its internal provider registry.
|
||||
Registering providers is a critical preparatory step.
|
||||
|
||||
Angular registers some of its own providers with every injector.
|
||||
We can register our own providers.
|
||||
|
||||
Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page.
|
||||
:marked
|
||||
## Directive
|
||||
.l-sub-section
|
||||
:marked
|
||||
An Angular class responsible for creating, reshaping, and interacting with HTML elements
|
||||
in the browser DOM. Directives are Angular's most fundamental feature.
|
||||
|
||||
A Directive is almost always associated with an HTML element or attribute.
|
||||
We often refer to such an element or attribute as the directive itself.
|
||||
When Angular finds a directive in an HTML template,
|
||||
it creates the matching directive class instance
|
||||
and gives the instance control over that portion of the browser DOM.
|
||||
|
||||
You can invent custom HTML markup (for example, `<my-directive>`) to
|
||||
associate with your custom directives. You add this custom markup to HTML templates
|
||||
as if you were writing native HTML. In this way, directives become extensions of
|
||||
HTML itself.
|
||||
|
||||
Directives fall into one of three categories:
|
||||
|
||||
1. [Components](#component) that combine application logic with an HTML template to
|
||||
render application [views]. Components are usually represented as HTML elements.
|
||||
They are the building blocks of an Angular application and the
|
||||
developer can expect to write a lot of them.
|
||||
|
||||
1. [Attribute directives](#attribute-directive) that can listen to and modify the behavior of
|
||||
other HTML elements, attributes, properties, and components. They are usually represented
|
||||
as HTML attributes, hence the name.
|
||||
|
||||
1. [Structural directives](#structural-directive), a directive responsible for
|
||||
shaping or reshaping HTML layout, typically by adding, removing, or manipulating
|
||||
elements and their children.
|
||||
|
||||
.l-main-section#E
|
||||
|
||||
:marked
|
||||
## ECMAScript
|
||||
.l-sub-section
|
||||
:marked
|
||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||
|
||||
The latest approved version of JavaScript is
|
||||
[ECMAScript 2016](http://www.ecma-international.org/ecma-262/7.0/)
|
||||
(AKA "ES2016" or "ES7") and many Angular developers write their applications
|
||||
either in this version of the language or a dialect that strives to be
|
||||
compatible with it, such as [TypeScript](#typescript).
|
||||
|
||||
Most modern browsers today only support the much older "ECMAScript 5" (AKA ES5) standard.
|
||||
Applications written in ES2016, ES2015 or one of their dialects must be "[transpiled](#transpile)"
|
||||
to ES5 JavaScript.
|
||||
|
||||
Angular developers may choose to write in ES5 directly.
|
||||
|
||||
:marked
|
||||
## ES2015
|
||||
.l-sub-section
|
||||
:marked
|
||||
Short hand for [ECMAScript](#ecmascript) 2015.
|
||||
:marked
|
||||
## ES6
|
||||
.l-sub-section
|
||||
:marked
|
||||
Short hand for [ECMAScript](#ecmascript) 2015.
|
||||
:marked
|
||||
## ES5
|
||||
.l-sub-section
|
||||
:marked
|
||||
Short hand for [ECMAScript](#ecmascript) 5, the version of JavaScript run by most modern browsers.
|
||||
See [ECMAScript](#ecmascript).
|
||||
|
||||
a#F
|
||||
a#G
|
||||
a#H
|
||||
.l-main-section#I
|
||||
:marked
|
||||
## Injector
|
||||
.l-sub-section
|
||||
:marked
|
||||
An object in the Angular [dependency injection system](#dependency-injection)
|
||||
that can find a named "dependency" in its cache or create such a thing
|
||||
with a registered [provider](#provider).
|
||||
|
||||
:marked
|
||||
## Input
|
||||
.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).
|
||||
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.
|
||||
|
||||
:marked
|
||||
## Interpolation
|
||||
.l-sub-section
|
||||
:marked
|
||||
A form of [property data binding](#data-binding) in which a
|
||||
[template expression](#template-expression) between double-curly braces
|
||||
renders as text. That text may be concatenated with neighboring text
|
||||
before it is assigned to an element property
|
||||
or displayed between element tags, as in this example.
|
||||
|
||||
code-example(language="html" escape="html").
|
||||
<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.
|
||||
|
||||
.l-main-section#J
|
||||
|
||||
a#jit
|
||||
:marked
|
||||
## Just-in-time (JIT) compilation
|
||||
.l-sub-section
|
||||
:marked
|
||||
With Angular _just-in-time_ bootstrapping you compile your components<span if-docs="ts"> and modules</span> in the browser
|
||||
and launch the application dynamically. This is a good choice during development.
|
||||
Consider using the [ahead-of-time](#aot) mode for production apps.
|
||||
|
||||
.l-main-section#K
|
||||
:marked
|
||||
## kebab-case
|
||||
.l-sub-section
|
||||
:marked
|
||||
See [dash-case](#dash-case).
|
||||
|
||||
.l-main-section#L
|
||||
:marked
|
||||
## Lifecycle hooks
|
||||
.l-sub-section
|
||||
:marked
|
||||
[Directives](#directive) and [components](#component) have a lifecycle
|
||||
managed by Angular as it creates, updates, and destroys them.
|
||||
|
||||
You can tap into key moments in that lifecycle by implementing
|
||||
one or more of the lifecycle hook interfaces.
|
||||
|
||||
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`.
|
||||
|
||||
Angular calls these hook methods in the following order:
|
||||
* `ngOnChanges` - when an [input](#input)/[output](#output) binding value changes.
|
||||
* `ngOnInit` - after the first `ngOnChanges`.
|
||||
* `ngDoCheck` - developer's custom change detection.
|
||||
* `ngAfterContentInit` - after component content initialized.
|
||||
* `ngAfterContentChecked` - after every check of component content.
|
||||
* `ngAfterViewInit` - after component's view(s) are initialized.
|
||||
* `ngAfterViewChecked` - after every check of a component's view(s).
|
||||
* `ngOnDestroy` - just before the directive is destroyed.
|
||||
|
||||
Read more in the [Lifecycle Hooks](!{docsLatest}/guide/lifecycle-hooks.html) page.
|
||||
|
||||
.l-main-section#M
|
||||
|
||||
:marked
|
||||
## Module
|
||||
.l-sub-section
|
||||
block module-defn
|
||||
.alert.is-important
|
||||
:marked
|
||||
In Angular, there are two types of modules:
|
||||
- [Angular modules](#angular-module).
|
||||
For details and examples, see the [Angular Modules](!{docsLatest}/guide/ngmodule.html) page.
|
||||
- ES2015 modules, as described in this section.
|
||||
|
||||
:marked
|
||||
Angular apps are modular.
|
||||
|
||||
In general, you assemble your application from many modules, both the ones you write and the ones you acquire from others.
|
||||
|
||||
A typical module is a cohesive block of code dedicated to a single purpose.
|
||||
|
||||
A module **exports** something of value in that code, typically one thing such as a class.
|
||||
A module that needs that thing, **imports** it.
|
||||
|
||||
The structure of Angular modules and the import/export syntax
|
||||
is based on the [ES2015 module standard](http://www.2ality.com/2014/09/es6-modules-final.html).
|
||||
|
||||
An application that adheres to this standard requires a module loader to
|
||||
load modules on request, and resolve inter-module dependencies.
|
||||
Angular does not ship with a module loader and does not have a preference
|
||||
for any particular 3rd party library (although most examples use SystemJS).
|
||||
You may pick any module library that conforms to the standard.
|
||||
|
||||
Modules are typically named after the file in which the exported thing is defined.
|
||||
The Angular [DatePipe](https://github.com/angular/angular/blob/master/modules/@angular/common/src/pipes/date_pipe.ts)
|
||||
class belongs to a feature module named `date_pipe` in the file `date_pipe.ts`.
|
||||
|
||||
You rarely access Angular feature modules directly. You usually import them from one of the Angular [scoped packages](#scoped-package) such as `@angular/core`.
|
||||
|
||||
a#N
|
||||
.l-main-section#O
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Observable
|
||||
.l-sub-section
|
||||
:marked
|
||||
You can think of an observable as an array whose items arrive asynchronously over time.
|
||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||
Observables are used within Angular itself, including Angular's event system and its http client service.
|
||||
|
||||
To use observables, Angular uses a third-party library called Reactive Extensions (RxJS).
|
||||
Observables are a proposed feature for ES 2016, the next version of JavaScript.
|
||||
|
||||
:marked
|
||||
## Output
|
||||
.l-sub-section
|
||||
:marked
|
||||
A directive property that can be the ***target*** of
|
||||
[event binding](!{docsLatest}/guide/template-syntax.html#event-binding).
|
||||
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.
|
||||
|
||||
.l-main-section#P
|
||||
|
||||
:marked
|
||||
## PascalCase
|
||||
.l-sub-section
|
||||
:marked
|
||||
The practice of writing individual words, compound words, or phrases such that each word or abbreviation begins with a capital letter. Class names are typically spelled in PascalCase. Examples include: `Person` and `HeroDetailComponent`.
|
||||
|
||||
This form is also known as **upper camel case** to distinguish it from **lower camel case**, which is simply called [camelCase](#camelcase). In this documentation, "PascalCase" means *upper camel case* and "camelCase" means *lower camel case*.
|
||||
|
||||
:marked
|
||||
## Pipe
|
||||
.l-sub-section
|
||||
:marked
|
||||
An Angular pipe is a function that transforms input values to output values for
|
||||
display in a [view](#view). Use the `!{_at}Pipe` !{_decoratorLink}
|
||||
to associate the pipe function with a name. You then use that
|
||||
name in your HTML to declaratively transform values on screen.
|
||||
|
||||
Here's an example that uses the built-in `currency` pipe to display
|
||||
a numeric value in the local currency.
|
||||
|
||||
code-example(language="html" escape="html").
|
||||
<label>Price: </label>{{product.price | currency}}
|
||||
:marked
|
||||
Read more in the page on [pipes](!{docsLatest}/guide/pipes.html).
|
||||
|
||||
:marked
|
||||
## Provider
|
||||
.l-sub-section
|
||||
:marked
|
||||
A _provider_ creates a new instance of a dependency for the
|
||||
[dependency injection](#dependency-injection) system.
|
||||
It relates a lookup token to code—sometimes called a "recipe"—that can create a dependency value.
|
||||
|
||||
a#Q
|
||||
.l-main-section#R
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Reactive forms
|
||||
.l-sub-section
|
||||
:marked
|
||||
A technique for building Angular forms through code in a component.
|
||||
The alternate technique is [Template-Driven Forms](#template-driven-forms).
|
||||
|
||||
When building reactive forms:
|
||||
- The "source of truth" is the component. The validation is defined using code in the component.
|
||||
- Each control is explicitly created in the component class with `new FormControl()` or with `FormBuilder`.
|
||||
- The template input elements do *not* use `ngModel`.
|
||||
- The associated Angular directives are all prefixed with `Form` such as `FormGroup`, `FormControl`, and `FormControlName`.
|
||||
|
||||
Reactive forms are powerful, flexible, and great for more complex data entry form scenarios such as dynamic generation of form controls.
|
||||
|
||||
:marked
|
||||
## Router
|
||||
.l-sub-section
|
||||
:marked
|
||||
Most applications consist of many screens or [views](#view).
|
||||
The user navigates among them by clicking links and buttons,
|
||||
and performing other similar actions that cause the application to
|
||||
replace one view with another.
|
||||
|
||||
The Angular [component router](!{docsLatest}/guide/router.html) 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](#router) by means
|
||||
of a `RouterConfig` that defines routes to views.
|
||||
|
||||
A [routing component's](#routing-component) template has a `RouterOutlet` element
|
||||
where it can display views produced by the router.
|
||||
|
||||
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.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Router module
|
||||
.l-sub-section
|
||||
: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.
|
||||
|
||||
:marked
|
||||
## Routing component
|
||||
.l-sub-section
|
||||
: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.
|
||||
|
||||
.l-main-section#S
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Scoped package
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular modules are delivered within *scoped packages* such as `@angular/core`, `@angular/common`, `@angular/platform-browser-dynamic`,
|
||||
`@angular/http`, and `@angular/router`.
|
||||
|
||||
A [*scoped package*](https://docs.npmjs.com/misc/scope) is a way to group related *npm* packages.
|
||||
|
||||
You import a scoped package the same way that you'd import a *normal* package.
|
||||
The only difference, from a consumer perspective,
|
||||
is that the *scoped package* name begins with the Angular *scope name*, `@angular`.
|
||||
|
||||
+makeExcerpt('architecture/ts/app/app.component.ts', 'import', '')
|
||||
|
||||
a#snake-case
|
||||
:marked
|
||||
## snake_case
|
||||
|
||||
.l-sub-section
|
||||
block snake-case-defn
|
||||
:marked
|
||||
The practice of writing compound words or phrases such that an
|
||||
underscore (`_`) separates one word from the next. This form is also known as **underscore case**.
|
||||
|
||||
:marked
|
||||
## Service
|
||||
.l-sub-section
|
||||
:marked
|
||||
For data or logic that is not associated
|
||||
with a specific view or that you want to share across components, build services.
|
||||
|
||||
Applications often require services such as a hero data service or a logging service.
|
||||
|
||||
A service is a class with a focused purpose.
|
||||
We often create a service to implement features that are
|
||||
independent from any specific view,
|
||||
provide shared data or logic across components, or encapsulate external interactions.
|
||||
|
||||
For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial.
|
||||
|
||||
:marked
|
||||
## Structural directive
|
||||
.l-sub-section
|
||||
:marked
|
||||
A category of [directive](#directive) that can
|
||||
shape or reshape HTML layout, typically by adding, removing, or manipulating
|
||||
elements and their children; for example, the `ngIf` "conditional element" directive and the `ngFor` "repeater" directive.
|
||||
|
||||
Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page.
|
||||
|
||||
.l-main-section#T
|
||||
:marked
|
||||
## Template
|
||||
.l-sub-section
|
||||
:marked
|
||||
A template is a chunk of HTML that Angular uses to render a [view](#view) with
|
||||
the support and continuing guidance of an Angular [directive](#directive),
|
||||
most notably a [component](#component).
|
||||
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Template-driven forms
|
||||
.l-sub-section
|
||||
:marked
|
||||
A technique for building Angular forms using HTML forms and input elements in the view.
|
||||
The alternate technique is [Reactive Forms](#reactive-forms).
|
||||
|
||||
When building template-driven forms:
|
||||
- The "source of truth" is the template. The validation is defined using attributes on the individual input elements.
|
||||
- [Two-way binding](#data-binding) with `ngModel` keeps the component model in synchronization with the user's entry into the input elements.
|
||||
- Behind the scenes, Angular creates a new control for each input element, provided you have set up a `name` attribute and two-way binding for each input.
|
||||
- The associated Angular directives are all prefixed with `ng` such as `ngForm`, `ngModel`, and `ngModelGroup`.
|
||||
|
||||
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.
|
||||
|
||||
:marked
|
||||
## Template expression
|
||||
.l-sub-section
|
||||
:marked
|
||||
An expression is a !{_Lang}-like syntax that Angular evaluates within
|
||||
a [data binding](#data-binding).
|
||||
|
||||
Read about how to write template expressions
|
||||
in the [Template Syntax](!{docsLatest}/guide/template-syntax.html#template-expressions) page.
|
||||
|
||||
:marked
|
||||
## Transpile
|
||||
.l-sub-section
|
||||
:marked
|
||||
The process of transforming code written in one form of JavaScript
|
||||
(for example, TypeScript) into another form of JavaScript (for example, [ES5](#es5)).
|
||||
|
||||
:marked
|
||||
## TypeScript
|
||||
.l-sub-section
|
||||
:marked
|
||||
A version of JavaScript that supports most [ECMAScript 2015](#es2015)
|
||||
language features such as [decorators](#decorator).
|
||||
|
||||
TypeScript is also noteable for its optional typing system, which gives
|
||||
us compile-time type checking and strong tooling support (for example, "intellisense",
|
||||
code completion, refactoring, and intelligent search). Many code editors
|
||||
and IDEs support TypeScript either natively or with plugins.
|
||||
|
||||
TypeScript is the preferred language for Angular development although
|
||||
you can use other JavaScript dialects such as [ES5](#es5).
|
||||
|
||||
Read more about TypeScript at [typescript.org](http://www.typescriptlang.org/).
|
||||
|
||||
a#U
|
||||
.l-main-section#V
|
||||
|
||||
:marked
|
||||
## View
|
||||
.l-sub-section
|
||||
:marked
|
||||
A view is a portion of the screen that displays information and responds
|
||||
to user actions such as clicks, mouse moves, and keystrokes.
|
||||
|
||||
Angular renders a view under the control of one or more [directives](#directive),
|
||||
especially [component](#component) directives and their companion [templates](#template).
|
||||
The component plays such a prominent role that it's often
|
||||
convenient to refer to a component as a view.
|
||||
|
||||
Views often contain other views and any view might be loaded and unloaded
|
||||
dynamically as the user navigates through the application, typically
|
||||
under the control of a [router](#router).
|
||||
|
||||
a#W
|
||||
a#X
|
||||
a#Y
|
||||
.l-main-section#Z
|
||||
|
||||
:marked
|
||||
## Zone
|
||||
.l-sub-section
|
||||
block zone-defn
|
||||
:marked
|
||||
Zones are a mechanism for encapsulating and intercepting
|
||||
a JavaScript application's asynchronous activity.
|
||||
|
||||
The browser DOM and JavaScript have a limited number
|
||||
of asynchronous activities, activities such as DOM events (for example, clicks),
|
||||
[promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), and
|
||||
[XHR](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
||||
calls to remote servers.
|
||||
|
||||
Zones intercept all of these activities and give a "zone client" the opportunity
|
||||
to take action before and after the async activity finishes.
|
||||
|
||||
Angular runs your application in a zone where it can respond to
|
||||
asynchronous events by checking for data changes, and updating
|
||||
the information it displays via [data bindings](#data-binding).
|
||||
|
||||
Learn more about zones in this
|
||||
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
|
@ -1,597 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _library_module = 'library module'
|
||||
- var _at_angular = '@angular'
|
||||
|
||||
:marked
|
||||
Angular is a framework to help us build client applications in HTML and
|
||||
either JavaScript or a language (like Dart or TypeScript) that compiles to JavaScript.
|
||||
|
||||
block angular-parts
|
||||
:marked
|
||||
The framework consists of several cooperating libraries, some of them core and some optional.
|
||||
|
||||
:marked
|
||||
With Angular, we write applications by composing HTML *templates* with Angularized-markup,
|
||||
writing *component* classes to manage those templates, adding application logic in *services*,
|
||||
and handing the top root component to Angular's *bootstrapper*.
|
||||
|
||||
Angular takes over, presenting our application content in a browser and
|
||||
responding to user interactions according to the instructions we provided.
|
||||
|
||||
Of course there is more to it than this.
|
||||
We'll learn the details when we dive into the guide chapters.
|
||||
Let's get the big picture first.
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700")
|
||||
|
||||
:marked
|
||||
The architecture diagram identifies the eight main building blocks of an Angular application:
|
||||
|
||||
1. [Modules](#modules)
|
||||
1. [Components](#components)
|
||||
1. [Templates](#templates)
|
||||
1. [Metadata](#metadata)
|
||||
1. [Data binding](#data-binding)
|
||||
1. [Directives](#directives)
|
||||
1. [Services](#services)
|
||||
1. [Dependency injection](#dependency-injection)
|
||||
|
||||
Learn these, and we're on our way.
|
||||
|
||||
.l-sub-section
|
||||
p The code referenced in this chapter is available as a <live-example></live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Modules
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Angular apps are modular.
|
||||
|
||||
In general we assemble our application from many **modules**.
|
||||
|
||||
A typical module is a cohesive block of code dedicated to a single purpose.
|
||||
A module **exports** something of value in that code, typically one thing such as a class.
|
||||
<br class="l-clear-both">
|
||||
|
||||
block modules-in-dart
|
||||
//- N/A
|
||||
|
||||
block modules-are-optional
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Modules are optional
|
||||
We highly recommend modular design. TypeScript has great support for ES2015 module syntax and our chapters assume we're taking a modular
|
||||
approach using that syntax. That's why we list *Module* among the basic building blocks.
|
||||
|
||||
Angular itself doesn't require a modular approach nor this particular syntax. Don't use it if you don't want it.
|
||||
Each chapter has plenty to offer after you steer clear of the `import` and `export` statements.
|
||||
|
||||
Find setup and organization clues in the JavaScript track (select it from the combo-box at the top of this page)
|
||||
which demonstrates Angular development with plain old JavaScript and no module system.
|
||||
|
||||
- var _app_comp_filename = _docsFor == 'dart' ? 'app_component.dart' : 'app.component.ts';
|
||||
:marked
|
||||
Perhaps the first module we meet is a module that exports a *component* class.
|
||||
The component is one of the basic Angular blocks, we write a lot of them,
|
||||
and we'll talk about components in the next segment. For the moment it is enough to know that a
|
||||
component class is the kind of thing we'd export from a module.
|
||||
|
||||
Most applications have an `AppComponent`. By convention, we'll find it in a file named `!{_app_comp_filename}`.
|
||||
Look inside such a file and we'll see a declaration such as this one.
|
||||
|
||||
+makeExcerpt('app/app.component.ts ()', 'export')
|
||||
|
||||
block export-qualifier
|
||||
:marked
|
||||
The `export` statement tells TypeScript that this is a module whose
|
||||
`AppComponent` class is public and accessible to other modules of the application.
|
||||
|
||||
:marked
|
||||
When we need a reference to the `AppComponent`, we **import** it like this:
|
||||
|
||||
+makeExcerpt('app/main.ts', 'import')
|
||||
|
||||
block ts-import
|
||||
:marked
|
||||
The `import` statement tells the system it can get an `AppComponent` from a module named `app.component`
|
||||
located in a neighboring file.
|
||||
The **module name** (AKA module id) is often the same as the filename without its extension.
|
||||
|
||||
:marked
|
||||
### Libraries
|
||||
|
||||
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-library-modules
|
||||
:marked
|
||||
Some modules are _libraries_ of other modules.
|
||||
Angular itself ships as a collection of library modules within several npm packages.
|
||||
Their names begin with the `!{_at_angular}` prefix.
|
||||
|
||||
Each Angular library contains a [barrel](../glossary.html#barrel) module
|
||||
that is actually a public façade over several logically-related private modules.
|
||||
|
||||
:marked
|
||||
`!{_at_angular}/core` is the primary Angular library from which we get most of what we need.
|
||||
<br class="l-clear-both">
|
||||
|
||||
There are other important Angular libraries too, such as `!{_at_angular}/common`<span if-docs="ts">,
|
||||
`!{_at_angular}/http`</span> and `!{_at_angular}/router`.
|
||||
We import what we need from an Angular !{_library_module}.
|
||||
|
||||
block angular-imports
|
||||
:marked
|
||||
For example, we import the Angular **`Component` *function*** from `@angular/core` like this:
|
||||
+makeExcerpt('app/app.component.ts', 'import')
|
||||
:marked
|
||||
Compare that syntax to our previous import of `AppComponent`.
|
||||
+makeExcerpt('app/main.ts', 'import')
|
||||
|
||||
:marked
|
||||
Notice the difference?
|
||||
In the first case, when importing from an Angular library module,
|
||||
the import statement refers to the bare module name, `@angular/core`, *without a path prefix*.
|
||||
|
||||
When we import from one of *our* own files, we prefix the module name with the file path.
|
||||
In this example we specify a relative file path (`./`). That means the
|
||||
source module is in the same folder (`./`) as the module importing it.
|
||||
We could path up and around the application folder structure if the source module were somewhere else.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We import and export in the ECMAScript 2015 (ES2015) module syntax.
|
||||
Learn more about that syntax [here](http://www.2ality.com/2014/09/es6-modules-final.html)
|
||||
and many other places on the web.
|
||||
|
||||
The infrastructure *behind* module loading and importing is an important subject.
|
||||
But it's a subject outside the scope of this introduction to Angular.
|
||||
While we're focused on our application, *import* and *export*
|
||||
is about all we need to know.
|
||||
|
||||
- var _export = _docsFor == 'dart' ? 'publicly declare' : 'export';
|
||||
- var _declare = _docsFor == 'dart' ? 'declare' : 'export';
|
||||
:marked
|
||||
The key take-aways are:
|
||||
* Angular apps are composed of modules.
|
||||
* Modules !{_export} things — classes, function, values — that other modules import.
|
||||
* We prefer to write our application as a collection of modules, each module exporting one thing.
|
||||
|
||||
The first module we write will most likely !{_declare} a component.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Components
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/hero-component.png" alt="Component" align="left" style="width:200px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
:marked
|
||||
A **component** controls a patch of screen real estate that we could call a *view*.
|
||||
The shell at the application root with navigation links, a list of heroes, a hero editor ...
|
||||
they're all views controlled by components.
|
||||
|
||||
We define a component's application logic — what it does to support the view — inside a class.
|
||||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
<a id="component-code"></a>
|
||||
A `HeroListComponent`, for example, might have a `heroes` property that returns !{_an} !{_array} of heroes
|
||||
that it acquired from a service.
|
||||
It might have a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
The component might be a class like this:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.ts', 'class')
|
||||
:marked
|
||||
Angular creates, updates, and destroys components as the user moves through the application.
|
||||
The developer can take action at each moment in this lifecycle through optional [lifecycle hooks](lifecycle-hooks.html), like `ngOnInit()` declared above.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We may wonder who is calling the component's constructor? Who provides the service parameter?
|
||||
For the moment, have faith that Angular will call the constructor and deliver an
|
||||
appropriate `HeroService` when we need it.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Templates
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/template.png" alt="Template" align="left" style="width:200px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
We define a component's view with its companion **template**. A template is a form of HTML
|
||||
that tells Angular how to render the component.
|
||||
|
||||
A template looks like regular HTML much of the time ... and then it gets a bit strange. Here is a
|
||||
template for our `HeroListComponent`:
|
||||
|
||||
+makeExample('app/hero-list.component.html')
|
||||
|
||||
:marked
|
||||
This template features typical HTML elements like `<h2>` and `<p>`.
|
||||
But what are `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<hero-detail>`?
|
||||
|
||||
These are examples of Angular's [template syntax](template-syntax.html).
|
||||
We will grow accustomed to that syntax and may even learn to love it.
|
||||
We'll begin to explain it in a moment.
|
||||
|
||||
Before we do, focus attention on the last line.
|
||||
The `<hero-detail>` tag is a custom element representing the `HeroDetailComponent`.
|
||||
|
||||
The `HeroDetailComponent` is a *different* component than the `HeroListComponent` we've been reviewing.
|
||||
The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the
|
||||
hero that the user selects from the list presented by the `HeroListComponent`.
|
||||
The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/component-tree.png" alt="Metadata" align="left" style="width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Notice how `<hero-detail>` rests comfortably among native HTML elements.
|
||||
We can and _will_ mix our custom components with native HTML in the same layouts.
|
||||
|
||||
In this manner we'll compose complex component trees to build out our richly featured application.
|
||||
<br class="l-clear-both">
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Metadata
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/metadata.png" alt="Metadata" align="left" style="width:150px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
:marked
|
||||
<p style="padding-top:10px">Metadata tells Angular how to process a class.</p>
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
[Looking back at the code](#component-code) for `HeroListComponent`, we see that it's just a class.
|
||||
There is no evidence of a framework, no "Angular" in it at all.
|
||||
|
||||
In fact, it really is *just a class*. It's not a component until we *tell Angular about it*.
|
||||
|
||||
We tell Angular that `HeroListComponent` is a component by attaching **metadata** to the class.
|
||||
|
||||
In !{_Lang}, we attach metadata by using !{_a} **!{_decorator}**.
|
||||
Here's some metadata for `HeroListComponent`:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.ts', 'metadata')
|
||||
|
||||
:marked
|
||||
Here we see the `@Component` !{_decorator} which (no surprise) identifies the class
|
||||
immediately below it as a component class.
|
||||
|
||||
block ts-decorator
|
||||
:marked
|
||||
A decorator is a function. Decorators often have a configuration parameter.
|
||||
The `@Component` decorator takes a required configuration object with the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
Here are a few of the possible `@Component` configuration options:
|
||||
|
||||
:marked
|
||||
- `selector`: CSS selector that tells Angular to create and insert an instance of this component
|
||||
where it finds a `<hero-list>` tag in *parent* HTML.
|
||||
For example, if an app's HTML contains `<hero-list></hero-list>`, then
|
||||
Angular inserts an instance of the `HeroListComponent` view between those tags.
|
||||
|
||||
- `templateUrl`: address of this component's template, which we showed [above](#templates).
|
||||
|
||||
- `directives`: !{_array} of the components or directives that *this* template requires.
|
||||
We saw in the last line of our template that we expect Angular to insert a `HeroDetailComponent`
|
||||
in the space indicated by `<hero-detail>` tags.
|
||||
Angular will do so only if we mention the `HeroDetailComponent` in this `directives` !{_array}.
|
||||
|
||||
- `providers`: !{_array} of **dependency injection providers** for services that the component requires.
|
||||
This is one way to tell Angular that our component's constructor requires a `HeroService`
|
||||
so it can get the list of heroes to display. We'll get to dependency injection later.
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/template-metadata-component.png" alt="Metadata" align="left" style="height:200px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
:marked
|
||||
Angular reads the metadata specified by the `@Component`
|
||||
annotation. That's how Angular learns to do "the right thing".
|
||||
|
||||
The template, metadata, and component together describe a view.
|
||||
|
||||
We 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
|
||||
we'll master as our Angular knowledge grows.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
The architectural takeaway is that we must add metadata to our code
|
||||
so that Angular knows what to do.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Data binding
|
||||
Without a framework, we would be responsible for pushing data values into the HTML controls and turning user responses
|
||||
into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to
|
||||
read as any experienced jQuery programmer can attest.
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/databinding.png" alt="Data Binding" style="width:220px; float:left; margin-left:-40px;margin-right:20px" )
|
||||
:marked
|
||||
Angular supports **data binding**,
|
||||
a mechanism for coordinating parts of a template with parts of a component.
|
||||
We add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
|
||||
There are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions —
|
||||
as indicated by the arrows in the diagram.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
We saw three forms of data binding in our [example](#templates) template:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.1.html', 'binding')
|
||||
|
||||
:marked
|
||||
* The `{{hero.name}}` [*interpolation*](displaying-data.html#interpolation)
|
||||
displays the component's `hero.name` property value within the `<li>` tags.
|
||||
|
||||
* The `[hero]` [*property binding*](template-syntax.html#property-binding) passes the value of `selectedHero` from
|
||||
the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||
|
||||
* The `(click)` [*event binding*](user-input.html#click) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||
|
||||
**Two-way data binding** is an important fourth form
|
||||
that combines property and event binding in a single notation, using the `ngModel` directive.
|
||||
We didn't have a two-way binding in the `HeroListComponent` template;
|
||||
here's an example from the `HeroDetailComponent` template:
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.html', 'ngModel')
|
||||
|
||||
:marked
|
||||
In two-way binding, a data property value flows to the input box from the component as with property binding.
|
||||
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||
as with event binding.
|
||||
|
||||
Angular processes *all* data bindings once per JavaScript event cycle,
|
||||
from the root of the application component tree down to the leaves.
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/component-databinding.png" alt="Data Binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
We don't know all the details yet,
|
||||
but it's clear from these examples that data binding plays an important role in communication
|
||||
between a template and its component.
|
||||
<br class="l-clear-both">
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/parent-child-binding.png" alt="Parent/Child binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Data binding is also important for communication between parent and child components.
|
||||
<br class="l-clear-both">
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Directives
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/directive.png" alt="Parent child" style="float:left; width:150px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
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 directive metadata. In !{_Lang} we apply the `@Directive` !{_decorator}
|
||||
to attach metadata to the class.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
We already met one form of directive: the component. A component is a *directive-with-a-template*;
|
||||
a `@Component` !{_decorator} is actually a `@Directive` !{_decorator} extended with template-oriented features.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
While **a component is technically a directive**,
|
||||
components are so distinctive and central to Angular applications that we chose
|
||||
to separate components from directives in this architectural overview.
|
||||
:marked
|
||||
Two *other* kinds of directives exist: _structural_ and _attribute_ directives.
|
||||
|
||||
They tend to appear within an element tag as attributes do,
|
||||
sometimes by name but more often as the target of an assignment or a binding.
|
||||
|
||||
**Structural** directives alter layout by adding, removing, and replacing elements in DOM.
|
||||
|
||||
Our [example](#templates) template uses two built-in structural directives:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.1.html', 'structural')
|
||||
|
||||
:marked
|
||||
* [`*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
|
||||
//- N/A
|
||||
|
||||
:marked
|
||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
|
||||
The `ngModel` directive, which implements two-way data binding, is
|
||||
an example of an attribute directive. `ngModel` modifies the behavior of
|
||||
an existing element (typically an `<input>`)
|
||||
by setting its display value property and responding to change events.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.html', 'ngModel')
|
||||
:marked
|
||||
Angular ships with a small number of other directives that either alter the layout structure
|
||||
(for example, [ngSwitch](template-syntax.html#ngSwitch))
|
||||
or modify aspects of DOM elements and components
|
||||
(for example, [ngStyle](template-syntax.html#ngStyle) and [ngClass](template-syntax.html#ngClass)).
|
||||
|
||||
Of course, we can also write our own directives. Components such as
|
||||
`HeroListComponent` are one kind of custom directive.
|
||||
<!-- PENDING: link to where to learn more about other kinds! -->
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Services
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/service.png" alt="Service" style="float:left; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
_Service_ is a broad category encompassing any value, function, or feature that our application needs.
|
||||
|
||||
Almost anything can be a service.
|
||||
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
Examples include:
|
||||
* logging service
|
||||
* data service
|
||||
* message bus
|
||||
* tax calculator
|
||||
* application configuration
|
||||
|
||||
There is nothing specifically _Angular_ about services. Angular itself has no definition of a service.
|
||||
There is no service base class, and no place to register a service.
|
||||
|
||||
Yet services are fundamental to any Angular application. Our components are big consumers of services.
|
||||
|
||||
Here's an example of a service class that logs to the browser console
|
||||
|
||||
+makeExcerpt('app/logger.service.ts', 'class')
|
||||
|
||||
:marked
|
||||
Here's a `HeroService` that fetches heroes and returns them in a resolved !{_PromiseLinked}.
|
||||
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'class')
|
||||
|
||||
:marked
|
||||
Services are everywhere.
|
||||
|
||||
We prefer our component classes lean. Our components don't fetch data from the server,
|
||||
they don't validate user input, and they don't log directly to the console.
|
||||
They delegate such tasks to services.
|
||||
|
||||
A component's job is to enable the user experience and nothing more. It mediates between the view (rendered by the template)
|
||||
and the application logic (which often includes some notion of a _model_).
|
||||
A good component presents properties and methods for data binding.
|
||||
It delegates everything nontrivial to services.
|
||||
|
||||
Angular doesn't *enforce* these principles.
|
||||
It won't complain if we write a "kitchen sink" component with 3000 lines.
|
||||
|
||||
Angular does help us *follow* these principles by making it easy to factor our
|
||||
application logic into services and make those services available to components through *dependency injection*.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Dependency injection
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/dependency-injection.png" alt="Service" style="float:left; width:200px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
_Dependency injection_ is a way to supply a new instance of a class
|
||||
with the fully-formed dependencies it requires. Most dependencies are services.
|
||||
Angular uses dependency injection to provide new components with the services they need.
|
||||
<br class="l-clear-both">
|
||||
:marked
|
||||
Angular can tell which services a component needs by looking at the types of its constructor parameters.
|
||||
For example, the constructor of our `HeroListComponent` needs a `HeroService`:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.ts (constructor)', 'ctor')
|
||||
|
||||
:marked
|
||||
When Angular creates a component, it first asks an **injector** for
|
||||
the services that the component requires.
|
||||
|
||||
An injector maintains a container of service instances that it has previously created.
|
||||
If a requested service instance is not in the container, the injector makes one and adds it to the container
|
||||
before returning the service to Angular.
|
||||
When all requested services have been resolved and returned,
|
||||
Angular can call the component's constructor with those services as arguments.
|
||||
This is what we mean by *dependency injection*.
|
||||
|
||||
The process of `HeroService` injection looks a bit like this:
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/injector-injects.png" alt="Service" )
|
||||
:marked
|
||||
If the injector doesn't have a `HeroService`, how does it know how to make one?
|
||||
|
||||
In brief, we 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.
|
||||
|
||||
We can register providers at any level of the application component tree.
|
||||
We often do so at the root when we bootstrap the application so that
|
||||
the same instance of a service is available everywhere.
|
||||
|
||||
+makeExcerpt('app/main.ts', 'bootstrap')
|
||||
|
||||
:marked
|
||||
Alternatively, we might register at a component level, in the providers property of the `@Component` metadata:
|
||||
|
||||
+makeExcerpt('app/hero-list.component.ts', 'providers')
|
||||
|
||||
:marked
|
||||
Registering at a component level means we get a new instance of the
|
||||
service with each new instance of that component.
|
||||
|
||||
<!-- We've vastly oversimplified dependency injection for this overview.
|
||||
The full story is in the [Dependency Injection](dependency-injection.html) chapter. -->
|
||||
|
||||
Points to remember about dependency injection:
|
||||
|
||||
* Dependency injection is wired into the Angular framework and used everywhere.
|
||||
|
||||
* The *injector* is the main mechanism.
|
||||
* An injector maintains a *container* of service instances that it created.
|
||||
* An injector can create a new service instance from a *provider*.
|
||||
|
||||
* A *provider* is a recipe for creating a service.
|
||||
|
||||
* We register *providers* with injectors.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Wrap up
|
||||
|
||||
We've learned just a bit about the eight main building blocks of an Angular application:
|
||||
|
||||
1. [Modules](#modules)
|
||||
1. [Components](#components)
|
||||
1. [Templates](#templates)
|
||||
1. [Metadata](#metadata)
|
||||
1. [Data binding](#data-binding)
|
||||
1. [Directives](#directives)
|
||||
1. [Services](#services)
|
||||
1. [Dependency injection](#dependency-injection)
|
||||
|
||||
That's a foundation for everything else in an Angular application,
|
||||
and it's more than enough to get going.
|
||||
But it doesn't include everything we'll need or want to know.
|
||||
|
||||
Here is a brief, alphabetical list of other important Angular features and services.
|
||||
Most of them are covered in this Developers Guide (or soon will be).
|
||||
|
||||
> [**Animations**](animations.html): The animation library makes it easy for developers to animate component behavior
|
||||
without deep knowledge of animation techniques or CSS.
|
||||
|
||||
> **Bootstrap**: A method to configure and launch the root application component.
|
||||
|
||||
> **Change detection**: Learn how Angular decides that a component property value has changed and
|
||||
when to update the screen.
|
||||
Learn how it uses **zones** to intercept asynchronous activity and run its change detection strategies.
|
||||
|
||||
> **Component router**: With the component Router service, users can navigate a multi-screen application
|
||||
in a familiar web browsing style using URLs.
|
||||
|
||||
> **Events**: The DOM raises events. So can components and services. Angular offers mechanisms for
|
||||
publishing and subscribing to events.
|
||||
|
||||
> [**Forms**](forms.html): Support complex data entry scenarios with HTML-based validation and dirty checking.
|
||||
|
||||
> [**HTTP**](server-communication.html): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client.
|
||||
|
||||
> [**Lifecycle hooks**](lifecycle-hooks.html): We can tap into key moments in the lifetime of a component, from its creation to its destruction,
|
||||
by implementing the lifecycle hook interfaces.
|
||||
|
||||
> [**Pipes**](pipes.html): Services that transform values for display.
|
||||
We can put pipes in our templates to improve the user experience. Consider
|
||||
this `currency` pipe expression:
|
||||
<div style="margin-left:40px">
|
||||
code-example().
|
||||
price | currency:'USD':true
|
||||
</div>
|
||||
:marked
|
||||
> It displays a price of "42.33" as `$42.33`.
|
||||
|
||||
> [**Router**](router.html): Navigate from page to page within the client
|
||||
application and never leave the browser.
|
||||
|
||||
> [**Testing**](testing.html): Angular provides a
|
||||
[testing library](https://pub.dartlang.org/packages/angular2_testing)
|
||||
to run unit tests on our application parts as they interact with the Angular framework.
|
|
@ -1,372 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
An **Attribute** directive changes the appearance or behavior of a DOM element.
|
||||
|
||||
:marked
|
||||
In this chapter we will
|
||||
* [write an attribute directive to change the background color](#write-directive)
|
||||
* [apply the attribute directive to an element in a template](#apply-directive)
|
||||
* [respond to user-initiated events](#respond-to-user)
|
||||
* [pass values into the directive using data binding](#bindings)
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
## Directives overview
|
||||
|
||||
There are three kinds of directives in Angular:
|
||||
1. Components
|
||||
1. Structural directives
|
||||
1. Attribute directives
|
||||
|
||||
A *Component* is really a directive with a template.
|
||||
It's the most common of the three directives and we tend to write lots of them as we build applications.
|
||||
|
||||
[*Structural* directives](structural-directives.html) can change the DOM layout by adding and removing DOM elements.
|
||||
[NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf) are two familiar examples.
|
||||
|
||||
An *Attribute* directive can change the appearance or behavior of an element.
|
||||
The built-in [NgStyle](template-syntax.html#ngStyle) directive, for example,
|
||||
can change several element styles at the same time.
|
||||
|
||||
We are going to write our own attribute directive to set an element's background color
|
||||
when the user hovers over that element.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We don't need *any* directive to simply set the background color.
|
||||
We can set it with the special [Style Binding](template-syntax.html#style-binding) like this:
|
||||
|
||||
+makeExample('attribute-directives/ts/app/app.component.1.html','p-style-background')
|
||||
|
||||
:marked
|
||||
That wouldn't be nearly as much fun as creating our own directive.
|
||||
|
||||
Besides, we're not just *setting* the color; we'll be *changing* the color
|
||||
in response to a user action, a mouse hover.
|
||||
|
||||
.l-main-section
|
||||
a#write-directive
|
||||
:marked
|
||||
## Build a simple attribute directive
|
||||
An attribute directive minimally requires building a controller class annotated with
|
||||
`@Directive`, which specifies the selector identifying
|
||||
the attribute associated with the directive.
|
||||
The controller class implements the desired directive behavior.
|
||||
|
||||
Let's build a small illustrative example together.
|
||||
|
||||
:marked
|
||||
### Our first draft
|
||||
Create a new project folder (`attribute-directives`) and follow the steps in the [QuickStart](../quickstart.html).
|
||||
|
||||
include ../_quickstart_repo
|
||||
:marked
|
||||
Create the following source file in the indicated folder with the given code:
|
||||
+makeExample('app/highlight.directive.1.ts')
|
||||
|
||||
block highlight-directive-1
|
||||
:marked
|
||||
We begin by importing some symbols from the Angular `core`.
|
||||
We need the `Directive` symbol for the `@Directive` decorator.
|
||||
We need the `ElementRef` to [inject](dependency-injection.html) into the directive's constructor
|
||||
so we can access the DOM element.
|
||||
We don't need `Input` immediately but we will need it later in the chapter.
|
||||
|
||||
Then we define the directive metadata in a configuration object passed
|
||||
as an argument to the `@Directive` decorator function.
|
||||
:marked
|
||||
`@Directive` requires a CSS selector to identify
|
||||
the HTML in the template that is associated with our directive.
|
||||
The [CSS selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
|
||||
is the attribute name in square brackets.
|
||||
Our directive's selector is `[myHighlight]`.
|
||||
Angular will locate all elements in the template that have an attribute named `myHighlight`.
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Why not call it "highlight"?
|
||||
*highlight* is a nicer name than *myHighlight* and, technically, it would work if we called it that.
|
||||
|
||||
However, we recommend picking a selector name with a prefix to ensure
|
||||
that it cannot conflict with any standard HTML attribute, now or in the future.
|
||||
There is also less risk of colliding with a third-party directive name when we give ours a prefix.
|
||||
|
||||
We do **not** prefix our `highlight` directive name with **`ng`**.
|
||||
That prefix belongs to Angular.
|
||||
|
||||
We need a prefix of our own, preferably short, and `my` will do for now.
|
||||
p
|
||||
| After the #[code @Directive] metadata comes the directive's controller class, which contains the logic for the directive.
|
||||
+ifDocsFor('ts')
|
||||
| We export `HighlightDirective` to make it accessible to other components.
|
||||
:marked
|
||||
Angular creates a new instance of the directive's controller class for
|
||||
each matching element, injecting an Angular `ElementRef`
|
||||
into the constructor.
|
||||
`ElementRef` is a service that grants us direct access to the DOM element
|
||||
through its `nativeElement` property.
|
||||
That's all we need to set the element's background color using the browser DOM API.
|
||||
|
||||
.l-main-section
|
||||
a#apply-directive
|
||||
:marked
|
||||
## Apply the attribute directive
|
||||
The `AppComponent` in this sample is a test harness for our `HighlightDirective`.
|
||||
Let's give it a new template that
|
||||
applies the directive as an attribute to a paragraph (`p`) element.
|
||||
In Angular terms, the `<p>` element will be the attribute **host**.
|
||||
p
|
||||
| We'll put the template in its own
|
||||
code #[+adjExPath('app.component.html')]
|
||||
| file that looks like this:
|
||||
+makeExample('attribute-directives/ts/app/app.component.1.html',null,'app/app.component.html')(format=".")
|
||||
:marked
|
||||
A separate template file is clearly overkill for a 2-line template.
|
||||
Hang in there; we're going to expand it later.
|
||||
Meanwhile, we'll revise the `AppComponent` to reference this template.
|
||||
+makeExample('attribute-directives/ts/app/app.component.ts',null,'app/app.component.ts')
|
||||
:marked
|
||||
We've added an `import` statement to fetch the 'Highlight' directive and,
|
||||
added that class to a `directives` component metadata so that Angular
|
||||
will recognize our directive when it encounters `myHighlight` in the template.
|
||||
|
||||
We run the app and see that our directive highlights the paragraph text.
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight")
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Your directive isn't working?
|
||||
|
||||
Did you remember to set the `directives` attribute of `@Component`? It is easy to forget!
|
||||
|
||||
Open the console in the browser tools and look for an error like this:
|
||||
code-example(format="nocode").
|
||||
EXCEPTION: Template parse errors:
|
||||
Can't bind to 'myHighlight' since it isn't a known native property
|
||||
:marked
|
||||
Angular detects that we're trying to bind to *something* but it doesn't know what.
|
||||
We have to tell it by listing `HighlightDirective` in the `directives` metadata array.
|
||||
:marked
|
||||
Let's recap what happened.
|
||||
|
||||
Angular found the `myHighlight` attribute on the `<p>` element. It created
|
||||
an instance of the `HighlightDirective` class,
|
||||
injecting a reference to the element into the constructor
|
||||
where we set the `<p>` element's background style to yellow.
|
||||
|
||||
.l-main-section
|
||||
a#respond-to-user
|
||||
:marked
|
||||
## Respond to user action
|
||||
|
||||
We are not satisfied to simply set an element color.
|
||||
Our directive should set the color in response to a user action.
|
||||
Specifically, we want to set the color when the user hovers over an element.
|
||||
|
||||
We'll need to
|
||||
1. detect when the user hovers into and out of the element,
|
||||
2. respond to those actions by setting and clearing the highlight color, respectively.
|
||||
|
||||
We apply the `@HostListener` !{_decorator} to methods which are called when an event is raised.
|
||||
|
||||
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','host')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `@HostListener` !{_decorator} refers to the DOM element that hosts our attribute directive, the `<p>` in our case.
|
||||
|
||||
We could have attached event listeners by manipulating the host DOM element directly, but
|
||||
there are at least three problems with such an approach:
|
||||
|
||||
1. We have to write the listeners correctly.
|
||||
1. We must *detach* our listener when the directive is destroyed to avoid memory leaks.
|
||||
1. We'd be talking to DOM API directly which, we learned, is something to avoid.
|
||||
|
||||
Let's roll with the `@HostListener` !{_decorator}.
|
||||
:marked
|
||||
Now we implement the two mouse event handlers:
|
||||
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')(format=".")
|
||||
:marked
|
||||
Notice that they delegate to a helper method that sets the color via a private local variable, `#{_priv}el`.
|
||||
We revise the constructor to capture the `ElementRef.nativeElement` in this variable.
|
||||
|
||||
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')(format=".")
|
||||
:marked
|
||||
Here's the updated directive:
|
||||
+makeExample('app/highlight.directive.2.ts')
|
||||
:marked
|
||||
We run the app and confirm that the background color appears as we move the mouse over the `p` and
|
||||
disappears as we move out.
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight")
|
||||
.l-main-section
|
||||
a#bindings
|
||||
:marked
|
||||
## Configure the directive with binding
|
||||
|
||||
Currently the highlight color is hard-coded within the directive. That's inflexible.
|
||||
We should set the color externally with a binding like this:
|
||||
+makeExample('attribute-directives/ts/app/app.component.html','pHost')
|
||||
:marked
|
||||
We'll extend our directive class with a bindable **input** `highlightColor` property and use it when we highlight text.
|
||||
|
||||
Here is the final version of the class:
|
||||
+makeExcerpt('app/highlight.directive.ts', 'class')
|
||||
a#input
|
||||
:marked
|
||||
The new `highlightColor` property is called an *input* property because data flows from the binding expression into our directive.
|
||||
Notice the `@Input()` #{_decorator} applied to the property.
|
||||
+makeExcerpt('app/highlight.directive.ts', 'color')
|
||||
:marked
|
||||
`@Input` adds metadata to the class that makes the `highlightColor` property available for
|
||||
property binding under the `myHighlight` alias.
|
||||
We must add this input metadata or Angular will reject the binding.
|
||||
See the [appendix](#why-input) below to learn why.
|
||||
.l-sub-section
|
||||
:marked
|
||||
### @Input(_alias_)
|
||||
The developer who uses this directive expects to bind to the attribute name, `myHighlight`.
|
||||
The directive property name is `highlightColor`. That's a disconnect.
|
||||
|
||||
We could resolve the discrepancy by renaming the property to `myHighlight` and define it as follows:
|
||||
|
||||
+makeExcerpt('app/highlight.directive.ts', 'highlight', '')
|
||||
:marked
|
||||
Maybe we don't want that property name inside the directive perhaps because it
|
||||
doesn't express our intention well.
|
||||
We can **alias** the `highlightColor` property with the attribute name by
|
||||
passing `myHighlight` into the `@Input` #{_decorator}:
|
||||
+makeExcerpt('app/highlight.directive.ts', 'color', '')
|
||||
:marked
|
||||
Now that we're getting the highlight color as an input, we modify the `onMouseEnter()` method to use
|
||||
it instead of the hard-coded color name.
|
||||
We also define red as the default color to fallback on in case
|
||||
the user neglects to bind with a color.
|
||||
+makeExcerpt('attribute-directives/ts/app/highlight.directive.ts', 'mouse-enter', '')
|
||||
:marked
|
||||
Now we'll update our `AppComponent` template to let
|
||||
users pick the highlight color and bind their choice to our directive.
|
||||
|
||||
Here is the updated template:
|
||||
+makeExcerpt('attribute-directives/ts/app/app.component.html', 'v2', '')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Where is the templated *color* property?
|
||||
|
||||
The eagle-eyed may notice that the radio button click handlers in the template set a `color` property
|
||||
and we are binding that `color` to the directive.
|
||||
We should expect to find a `color` on the host `AppComponent`.
|
||||
|
||||
**We never defined a color property for the host *AppComponent***!
|
||||
And yet this code works. Where is the template `color` value going?
|
||||
|
||||
Browser debugging reveals that Angular dynamically added a `color` property
|
||||
to the runtime instance of the `AppComponent`.
|
||||
|
||||
This is *convenient* behavior but it is also *implicit* behavior that could be confusing.
|
||||
While it's cool that this technique works, we recommend adding the `color` property to the `AppComponent`.
|
||||
|
||||
:marked
|
||||
Here is our second version of the directive in action.
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Bind to a second property
|
||||
Our directive only has a single, customizable property. What if we had ***two properties***?
|
||||
|
||||
Let's allow the template developer to set the default color, the color that prevails until the user picks a highlight color.
|
||||
We'll add a second **input** property to `HighlightDirective` called `defaultColor`:
|
||||
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor')(format=".")
|
||||
:marked
|
||||
The `defaultColor` property has a setter that overrides the hard-coded default color, "red".
|
||||
We don't need a getter.
|
||||
|
||||
How do we bind to it? We already "burned" the `myHighlight` attribute name as a binding target.
|
||||
|
||||
Remember that a *component is a directive too*.
|
||||
We can add as many component property bindings as we need by stringing them along in the template
|
||||
as in this example that sets the `a`, `b`, `c` properties to the string literals 'a', 'b', and 'c'.
|
||||
code-example(format="." ).
|
||||
<my-component [a]="'a'" [b]="'b'" [c]="'c'"><my-component>
|
||||
:marked
|
||||
We do the same thing with an attribute directive.
|
||||
+makeExample('attribute-directives/ts/app/app.component.html', 'defaultColor')(format=".")
|
||||
:marked
|
||||
Here we're binding the user's color choice to the `myHighlight` attribute as we did before.
|
||||
We're *also* binding the literal string, 'violet', to the `defaultColor`.
|
||||
|
||||
Here is the final version of the directive in action.
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Summary
|
||||
We now know how to
|
||||
- [build a simple **attribute directive** to attach behavior to an HTML element](#write-directive),
|
||||
- [use that directive in a template](#apply-directive),
|
||||
- [respond to **events** to change behavior based on an event](#respond-to-user),
|
||||
- and [use **binding** to pass values to the attribute directive](#bindings).
|
||||
|
||||
The final source:
|
||||
|
||||
+makeTabs(
|
||||
`attribute-directives/ts/app/app.component.ts,
|
||||
attribute-directives/ts/app/app.component.html,
|
||||
attribute-directives/ts/app/highlight.directive.ts,
|
||||
attribute-directives/ts/app/main.ts,
|
||||
attribute-directives/ts/index.html
|
||||
`,
|
||||
',,full',
|
||||
`app.component.ts,
|
||||
app.component.html,
|
||||
highlight.directive.ts,
|
||||
main.ts,
|
||||
index.html
|
||||
`)
|
||||
|
||||
|
||||
a#why-input
|
||||
.l-main-section
|
||||
:marked
|
||||
### Appendix: Input properties
|
||||
|
||||
Earlier we declared the `highlightColor` property to be an ***input*** property of our
|
||||
`HighlightDirective`
|
||||
|
||||
We've seen properties in bindings before.
|
||||
We never had to declare them as anything. Why now?
|
||||
|
||||
Angular makes a subtle but important distinction between binding **sources** and **targets**.
|
||||
|
||||
In all previous bindings, the directive or component property was a binding ***source***.
|
||||
A property is a *source* if it appears in the template expression to the ***right*** of the equals (=).
|
||||
|
||||
A property is a *target* when it appears in **square brackets** ([ ]) to the **left** of the equals (=) ...
|
||||
as it is does when we bind to the `myHighlight` property of the `HighlightDirective`,
|
||||
+makeExample('attribute-directives/ts/app/app.component.html','pHost')(format=".")
|
||||
:marked
|
||||
The 'color' in `[myHighlight]="color"` is a binding ***source***.
|
||||
A source property doesn't require a declaration.
|
||||
|
||||
The 'myHighlight' in `[myHighlight]="color"` *is* a binding ***target***.
|
||||
We must declare it as an *input* property.
|
||||
Angular rejects the binding with a clear error if we don't.
|
||||
|
||||
Angular treats a *target* property differently for a good reason.
|
||||
A component or directive in target position needs protection.
|
||||
|
||||
Imagine that our `HighlightDirective` did truly wonderous things.
|
||||
We graciously made a gift of it to the world.
|
||||
|
||||
To our surprise, some people — perhaps naively —
|
||||
started binding to *every* property of our directive.
|
||||
Not just the one or two properties we expected them to target. *Every* property.
|
||||
That could really mess up our directive in ways we didn't anticipate and have no desire to support.
|
||||
|
||||
The *input* declaration ensures that consumers of our directive can only bind to
|
||||
the properties of our public API ... nothing else.
|
|
@ -1,319 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
Angular applications are styled with regular CSS. That means we can apply
|
||||
everything we know about CSS stylesheets, selectors, rules, and media queries
|
||||
to our Angular applications directly.
|
||||
|
||||
On top of this, Angular has the ability to bundle *component styles*
|
||||
with our components enabling a more modular design than regular stylesheets.
|
||||
|
||||
In this chapter we learn how to load and apply these *component styles*.
|
||||
|
||||
## Table Of Contents
|
||||
|
||||
* [Using Component Styles](#using-component-styles)
|
||||
* [Special selectors](#special-selectors)
|
||||
* [Loading Styles into Components](#loading-styles)
|
||||
* [Controlling View Encapsulation: Emulated, Native, and None](#view-encapsulation)
|
||||
* [Appendix 1: Inspecting the generated runtime component styles](#inspect-generated-css)
|
||||
* [Appendix 2: Loading Styles with Relative URLs](#relative-urls)
|
||||
|
||||
Run the <live-example></live-example> of the code shown in this chapter.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Using Component Styles
|
||||
|
||||
For every Angular component we write, we may define not only an HTML template,
|
||||
but also the CSS styles that go with that template,
|
||||
specifying any selectors, rules, and media queries that we 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.
|
||||
Usually we give it one string as in this example:
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-app.component.ts')(format='.')
|
||||
|
||||
:marked
|
||||
Component styles differ from traditional, global styles in a couple of ways.
|
||||
|
||||
Firstly, the selectors we put into a component's styles *only apply within the template
|
||||
of that component*. The `h1` selector in the example above only applies to the `<h1>` tag
|
||||
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
|
||||
the application are unaffected.
|
||||
|
||||
This is a big improvement in modularity compared to how CSS traditionally works:
|
||||
|
||||
1. We can use the CSS class names and selectors that make the most sense in the context of each component.
|
||||
|
||||
1. Class names and selectors are local to the component and won't collide with
|
||||
classes and selectors used elsewhere in the application.
|
||||
|
||||
1. Our component's styles *cannot* be changed by changes to styles elsewhere in the application.
|
||||
|
||||
1. We can co-locate the CSS code of each component with the TypeScript and HTML code of the component,
|
||||
which leads to a neat and tidy project structure.
|
||||
|
||||
1. We can change or remove component CSS code in the future without trawling through the
|
||||
whole application to see where else it may have been used. We just look at the component we're in.
|
||||
|
||||
a(id="special-selectors")
|
||||
.l-main-section
|
||||
:marked
|
||||
## Special selectors
|
||||
|
||||
Component styles have a few special *selectors* from the world of
|
||||
[shadow DOM style scoping](https://www.w3.org/TR/css-scoping-1):
|
||||
|
||||
### :host
|
||||
|
||||
Use the `:host` pseudo-class selector to target styles in the element that *hosts* the component (as opposed to
|
||||
targeting elements *inside* the component's template):
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-details.component.css', 'host')(format='.')
|
||||
|
||||
:marked
|
||||
This is the *only* way we can target the host element. We cannot reach
|
||||
it from inside the component with other selectors, because it is not part of the
|
||||
component's own template. It is in a parent component's template.
|
||||
|
||||
Use the *function form* to apply host styles conditionally by
|
||||
including another selector inside parentheses after `:host`.
|
||||
|
||||
In the next example we target the host element again, but only when it also has the `active` CSS class.
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-details.component.css', 'hostfunction')(format=".")
|
||||
|
||||
:marked
|
||||
### :host-context
|
||||
|
||||
Sometimes it is useful to apply styles based on some condition *outside* a component's view.
|
||||
For example, there may be a CSS theme class applied to the document `<body>` element, and
|
||||
we want to change how our component looks based on that.
|
||||
|
||||
Use the `:host-context()` pseudo-class selector. It works just like the function
|
||||
form of `:host()`. It looks for a CSS class in *any ancestor* of the component host element, all the way
|
||||
up to the document root. It's useful when combined with another selector.
|
||||
|
||||
In the following example, we apply a `background-color` style to all `<h2>` elements *inside* the component, only
|
||||
if some ancestor element has the CSS class `theme-light`.
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-details.component.css', 'hostcontext')(format='.')
|
||||
|
||||
:marked
|
||||
### /deep/
|
||||
|
||||
Component styles normally apply only to the HTML in the component's own template.
|
||||
|
||||
We can use the `/deep/` selector to force a style down through the child component tree into all the child component views.
|
||||
The `/deep/` selector works to any depth of nested components, and it applies *both to the view
|
||||
children and the content children* of the component.
|
||||
|
||||
In this example, we target all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM:
|
||||
+makeExample('component-styles/ts/app/hero-details.component.css', 'deep')(format=".")
|
||||
|
||||
:marked
|
||||
The `/deep/` selector also has the alias `>>>`. We can use either of the two interchangeably.
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
The `/deep/` and `>>>` selectors should only be used with **emulated** view encapsulation.
|
||||
This is the default and it is what we use most of the time. See the
|
||||
[Controlling View Encapsulation](#view-encapsulation)
|
||||
section for more details.
|
||||
|
||||
a(id='loading-styles')
|
||||
.l-main-section
|
||||
:marked
|
||||
## Loading Styles into Components
|
||||
|
||||
We have several ways to add styles to a component:
|
||||
* inline in the template HTML
|
||||
* by setting `styles` or `styleUrls` metadata
|
||||
* with CSS imports
|
||||
|
||||
The scoping rules outlined above apply to each of these loading patterns.
|
||||
|
||||
### Styles in Metadata
|
||||
|
||||
We 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/app/hero-app.component.ts')
|
||||
|
||||
:marked
|
||||
### Template Inline Styles
|
||||
|
||||
We can embed styles directly into the HTML template by putting them
|
||||
inside `<style>` tags.
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-controls.component.ts', 'inlinestyles')
|
||||
|
||||
:marked
|
||||
### Style URLs in Metadata
|
||||
|
||||
We can load styles from external CSS files by adding a `styleUrls` attribute
|
||||
into a component's `@Component` #{_decorator}:
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-details.component.ts', 'styleurls')
|
||||
|
||||
block style-url
|
||||
.alert.is-important
|
||||
:marked
|
||||
The URL is ***relative to the application root*** which is usually the
|
||||
location of the `index.html` web page that hosts the application.
|
||||
The style file URL is *not* relative to the component file.
|
||||
That's why the example URL begins `app/`.
|
||||
See [Appendix 2](#relative-urls) to specify a URL relative to the
|
||||
component file.
|
||||
|
||||
block module-bundlers
|
||||
.l-sub-section
|
||||
:marked
|
||||
Users of module bundlers like Webpack may also use the `styles` attribute
|
||||
to load styles from external files at build time. They could write:
|
||||
|
||||
`styles: [require('my.component.css')]`
|
||||
|
||||
We set the `styles` property, **not** `styleUrls` property! The module
|
||||
bundler is loading the CSS strings, not Angular.
|
||||
Angular only sees the CSS strings *after* the bundler loads them.
|
||||
To Angular it is as if we wrote the `styles` array by hand.
|
||||
Refer to the module bundler's documentation for information on
|
||||
loading CSS in this manner.
|
||||
|
||||
:marked
|
||||
### Template Link Tags
|
||||
|
||||
We can also embed `<link>` tags into the component's HTML template.
|
||||
|
||||
As with `styleUrls`, the link tag's `href` URL is relative to the
|
||||
application root, not relative to the component file.
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-team.component.ts', 'stylelink')
|
||||
|
||||
:marked
|
||||
### CSS @imports
|
||||
|
||||
We can also import CSS files into our CSS files by using the standard CSS
|
||||
[`@import` rule](https://developer.mozilla.org/en/docs/Web/CSS/@import).
|
||||
|
||||
block css-import-url
|
||||
:marked
|
||||
In *this* case the URL is relative to the CSS file into which we are importing.
|
||||
|
||||
+makeExample('component-styles/ts/app/hero-details.component.css', 'import', 'app/hero-details.component.css (excerpt)')
|
||||
|
||||
a#view-encapsulation
|
||||
.l-main-section
|
||||
:marked
|
||||
## Controlling View Encapsulation: Native, Emulated, and None
|
||||
|
||||
As discussed above, component CSS styles are *encapsulated* into the component's own view and do
|
||||
not affect the rest of the application.
|
||||
|
||||
We can control how this encapsulation happens on a *per
|
||||
component* basis by setting the *view encapsulation mode* in the component metadata. There
|
||||
are three modes to choose from:
|
||||
|
||||
* `Native` view encapsulation uses the browser's native [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||
implementation to attach a Shadow DOM to the component's host element, and then puts the component
|
||||
view inside that Shadow DOM. The component's styles are included within the Shadow DOM.
|
||||
|
||||
* `Emulated` view encapsulation (**the default**) emulates the behavior of Shadow DOM by preprocessing
|
||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||
See [Appendix 1](#inspect-generated-css) for details.
|
||||
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier do not apply.
|
||||
This is essentially the same as pasting the component's styles into the HTML.
|
||||
|
||||
Set the components encapsulation mode using the `encapsulation` property in the component metadata:
|
||||
|
||||
+makeExample('component-styles/ts/app/quest-summary.component.ts', 'encapsulation.native')(format='.')
|
||||
|
||||
:marked
|
||||
`Native` view encapsulation only works on [browsers that have native support
|
||||
for Shadow DOM](http://caniuse.com/#feat=shadowdom). The support is still limited,
|
||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||
in most cases.
|
||||
|
||||
a#inspect-generated-css
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix 1: Inspecting The CSS Generated in Emulated View Encapsulation
|
||||
|
||||
When using the default emulated view encapsulation, Angular preprocesses
|
||||
all component styles so that they approximate the standard Shadow CSS scoping rules.
|
||||
|
||||
When we inspect the DOM of a running Angular application with emulated view
|
||||
encapsulation enabled, we see that each DOM element has some extra attributes
|
||||
attached to it:
|
||||
|
||||
code-example(format="").
|
||||
<hero-details _nghost-pmm-5>
|
||||
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||
<h3 _ngcontent-pmm-6>Team</h3>
|
||||
</hero-team>
|
||||
</hero-detail>
|
||||
|
||||
:marked
|
||||
We see two kinds of generated attributes:
|
||||
* An element that would be a Shadow DOM host in native encapsulation has a
|
||||
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
that identifies to which host's emulated Shadow DOM this element belongs.
|
||||
|
||||
The exact values of these attributes are not important. They are automatically
|
||||
generated and we never refer to them in application code. But they are targeted
|
||||
by the generated component styles, which we'll find in the `<head>` section of the DOM:
|
||||
|
||||
code-example(format="").
|
||||
[_nghost-pmm-5] {
|
||||
display: block;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
h3[_ngcontent-pmm-6] {
|
||||
background-color: white;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
|
||||
:marked
|
||||
These are the styles we wrote, post-processed so that each selector is augmented
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
These extra selectors enable the scoping rules described in this guide.
|
||||
|
||||
We'll likely live with *emulated* mode until shadow DOM gains traction.
|
||||
|
||||
a#relative-urls
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix 2: Loading Styles with Relative URLs
|
||||
|
||||
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
|
||||
|
||||
code-example(format='').
|
||||
quest-summary.component.ts
|
||||
quest-summary.component.html
|
||||
quest-summary.component.css
|
||||
|
||||
:marked
|
||||
We include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
||||
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
|
||||
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property to `module.id`.
|
||||
|
||||
+makeExample('component-styles/ts/app/quest-summary.component.ts','', 'app/quest-summary.component.ts')
|
||||
:marked
|
||||
Learn more about `moduleId` in the [Component-Relative Paths](../cookbook/component-relative-paths.html) chapter.
|
||||
|
|
@ -1,897 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _thisDot = 'this.';
|
||||
|
||||
:marked
|
||||
**Dependency injection** is an important application design pattern.
|
||||
Angular has its own dependency injection framework, and
|
||||
we really can't build an Angular application without it.
|
||||
It's used so widely that almost everyone just calls it _DI_.
|
||||
|
||||
In this chapter we'll learn what DI is and why we want it.
|
||||
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
||||
|
||||
- [Why dependency injection?](#why-dependency-injection)
|
||||
- [Angular dependency injection](#angular-dependency-injection)
|
||||
- [Injector providers](#injector-providers)
|
||||
- [Dependency injection tokens](#dependency-injection-tokens)
|
||||
- [Summary](#summary)
|
||||
|
||||
Run the <live-example></live-example>.
|
||||
|
||||
.l-main-section#why-di
|
||||
:marked
|
||||
## Why dependency injection?
|
||||
|
||||
Let's start with the following code.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/car/car-no-di.ts', 'car', 'app/car/car.ts (without DI)')
|
||||
|
||||
:marked
|
||||
Our `Car` creates everything it needs inside its constructor.
|
||||
What's the problem?
|
||||
The problem is that our `Car` class is brittle, inflexible, and hard to test.
|
||||
|
||||
Our `Car` needs an engine and tires. Instead of asking for them,
|
||||
the `Car` constructor instantiates its own copies from
|
||||
the very specific classes `Engine` and `Tires`.
|
||||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
||||
`#{_thisDot}engine = new Engine(theNewParameter)`.
|
||||
We didn't care about `Engine` constructor parameters when we first wrote `Car`.
|
||||
We don't really care about them now.
|
||||
But we'll *have* to start caring because
|
||||
when the definition of `Engine` changes, our `Car` class must change.
|
||||
That makes `Car` brittle.
|
||||
|
||||
What if we want to put a different brand of tires on our `Car`? Too bad.
|
||||
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
||||
|
||||
Right now each new car gets its own engine. It can't share an engine with other cars.
|
||||
While that makes sense for an automobile engine,
|
||||
we can think of other dependencies that should be shared, such as the onboard
|
||||
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
||||
to share services that have been created previously for other consumers.
|
||||
|
||||
When we write tests for our `Car` we're at the mercy of its hidden dependencies.
|
||||
Is it even possible to create a new `Engine` in a test environment?
|
||||
What does `Engine`itself depend upon? What does that dependency depend on?
|
||||
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||
We certainly don't want that going on during our tests.
|
||||
|
||||
What if our `Car` should flash a warning signal when tire pressure is low?
|
||||
How do we confirm that it actually does flash a warning
|
||||
if we can't swap in low-pressure tires during the test?
|
||||
|
||||
We have no control over the car's hidden dependencies.
|
||||
When we can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
How can we make `Car` more robust, flexible, and testable?
|
||||
|
||||
<a id="ctor-injection"></a>
|
||||
That's super easy. We change our `Car` constructor to a version with DI:
|
||||
|
||||
+makeTabs(
|
||||
'dependency-injection/ts/app/car/car.ts, dependency-injection/ts/app/car/car-no-di.ts',
|
||||
'car-ctor, car-ctor',
|
||||
'app/car/car.ts (excerpt with DI), app/car/car.ts (excerpt without DI)')(format=".")
|
||||
|
||||
:marked
|
||||
See what happened? We moved the definition of the dependencies to the constructor.
|
||||
Our `Car` class no longer creates an engine or tires.
|
||||
It just consumes them.
|
||||
|
||||
block ctor-syntax
|
||||
.l-sub-section
|
||||
:marked
|
||||
We also leveraged TypeScript's constructor syntax for declaring
|
||||
parameters and properties simultaneously.
|
||||
|
||||
:marked
|
||||
Now we create a car by passing the engine and tires to the constructor.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation', '')(format=".")
|
||||
|
||||
:marked
|
||||
How cool is that?
|
||||
The definition of the engine and tire dependencies are
|
||||
decoupled from the `Car` class itself.
|
||||
We can pass in any kind of engine or tires we like, as long as they
|
||||
conform to the general API requirements of an engine or tires.
|
||||
|
||||
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
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/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
|
||||
|
||||
:marked
|
||||
The critical point is this: `Car` itself did not have to change.
|
||||
We'll take care of the consumer's problem soon enough.
|
||||
|
||||
:marked
|
||||
The `Car` class is much easier to test because we are in complete control
|
||||
of its dependencies.
|
||||
We can pass mocks to the constructor that do exactly what we want them to do
|
||||
during each test:
|
||||
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
|
||||
|
||||
:marked
|
||||
**We just learned what dependency injection is**.
|
||||
|
||||
It's a coding pattern in which a class receives its dependencies from external
|
||||
sources rather than creating them itself.
|
||||
|
||||
Cool! But what about that poor consumer?
|
||||
Anyone who wants a `Car` must now
|
||||
create all three parts: the `Car`, `Engine`, and `Tires`.
|
||||
The `Car` class shed its problems at the consumer's expense.
|
||||
We need something that takes care of assembling these parts for us.
|
||||
|
||||
We could write a giant class to do that:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/car/car-factory.ts', null, 'app/car/car-factory.ts')
|
||||
|
||||
:marked
|
||||
It's not so bad now with only three creation methods.
|
||||
But maintaining it will be hairy as the application grows.
|
||||
This factory is going to become a huge spiderweb of
|
||||
interdependent factory methods!
|
||||
|
||||
Wouldn't it be nice if we could simply list the things we want to build without
|
||||
having to define which dependency gets injected into what?
|
||||
|
||||
This is where the dependency injection framework comes into play.
|
||||
Imagine the framework had something called an _injector_.
|
||||
We register some classes with this injector, and it figures out how to create them.
|
||||
|
||||
When we need a `Car`, we simply ask the injector to get it for us and we're good to go.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-call')(format=".")
|
||||
|
||||
:marked
|
||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||
The consumer knows nothing about creating a `Car`.
|
||||
We don't have a gigantic factory class to maintain.
|
||||
Both `Car` and consumer simply ask for what they need and the injector delivers.
|
||||
|
||||
This is what a **dependency injection framework** is all about.
|
||||
|
||||
Now that we know what dependency injection is and appreciate its benefits,
|
||||
let's see how it is implemented in Angular.
|
||||
|
||||
.l-main-section#angular-di
|
||||
:marked
|
||||
## Angular dependency injection
|
||||
|
||||
Angular ships with its own dependency injection framework. This framework can also be used
|
||||
as a standalone module by other applications and frameworks.
|
||||
|
||||
That sounds nice. What does it do for us when building components in Angular?
|
||||
Let's see, one step at a time.
|
||||
|
||||
We'll begin with a simplified version of the `HeroesComponent`
|
||||
that we built in the [The Tour of Heroes](../tutorial/).
|
||||
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.1.ts,
|
||||
dependency-injection/ts/app/heroes/hero-list.component.1.ts,
|
||||
dependency-injection/ts/app/heroes/hero.ts,
|
||||
dependency-injection/ts/app/heroes/mock-heroes.ts`,
|
||||
'v1,,,',
|
||||
`app/heroes/heroes.component.ts,
|
||||
app/heroes/hero-list.component.ts,
|
||||
app/heroes/hero.ts,
|
||||
app/heroes/mock-heroes.ts`)
|
||||
|
||||
:marked
|
||||
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
||||
It governs all the child components of this area.
|
||||
Our stripped down version has only one child, `HeroListComponent`,
|
||||
which displays a list of heroes.
|
||||
|
||||
:marked
|
||||
Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection
|
||||
defined in another file.
|
||||
That may suffice in the early stages of development, but it's far from ideal.
|
||||
As soon as we try to test this component or want to get our heroes data from a remote server,
|
||||
we'll have to change the implementation of `heroes` and
|
||||
fix every other use of the `HEROES` mock data.
|
||||
|
||||
Let's make a service that hides how we get hero data.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Given that the service is a
|
||||
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
||||
we suggest that you
|
||||
write the service code in its own file.
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
See [this note](#one-class-per-file) for details.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.1.ts',null, 'app/heroes/hero.service.ts' )
|
||||
|
||||
:marked
|
||||
Our `HeroService` exposes a `getHeroes` method that returns
|
||||
the same mock data as before, but none of its consumers need to know that.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Notice the `@Injectable()` #{_decorator} above the service class.
|
||||
We'll discuss its purpose [shortly](#injectable).
|
||||
|
||||
- var _perhaps = _docsFor == 'dart' ? '' : 'perhaps';
|
||||
.l-sub-section
|
||||
:marked
|
||||
We aren't even pretending this is a real service.
|
||||
If we were actually getting data from a remote server, the API would have to be
|
||||
asynchronous, #{_perhaps} returning a !{_PromiseLinked}.
|
||||
We'd also have to rewrite the way components consume our service.
|
||||
This is important in general, but not to our current story.
|
||||
|
||||
:marked
|
||||
A service is nothing more than a class in Angular.
|
||||
It remains nothing more than a class until we register it with an Angular injector.
|
||||
|
||||
#bootstrap
|
||||
:marked
|
||||
### Configuring the injector
|
||||
|
||||
We don't have to create an Angular injector.
|
||||
Angular creates an application-wide injector for us during the bootstrap process.
|
||||
|
||||
+makeExcerpt('app/main.ts', 'bootstrap')
|
||||
|
||||
:marked
|
||||
We do have to configure the injector by registering the **providers**
|
||||
that create the services our application requires.
|
||||
We'll explain what [providers](#providers) are later in this chapter.
|
||||
|
||||
block register-provider-ngmodule
|
||||
:marked
|
||||
We can either register a provider within an [NgModule](ngmodule.html) or in application components
|
||||
|
||||
### Registering providers in an NgModule
|
||||
Here's our AppModule where we register a `Logger`, a `UserService`, and an `APP_CONFIG` provider.
|
||||
|
||||
- var app_module_ts = 'app/app.module.ts';
|
||||
+makeExcerpt(app_module_ts + ' (excerpt)', 'ngmodule', app_module_ts, { otl: /(providers:)/ })
|
||||
//- The preferred approach is to register application providers in application components.
|
||||
//- Because the `HeroService` is used within the *Heroes* feature area —
|
||||
//- and nowhere else — the ideal place to register it is in the top-level `HeroesComponent`.
|
||||
|
||||
:marked
|
||||
### Registering providers in a component
|
||||
|
||||
Here's a revised `HeroesComponent` that registers the `HeroService`.
|
||||
|
||||
- var stylePattern = { otl: /(providers:[^,]+),/ };
|
||||
+makeExample('app/heroes/heroes.component.1.ts', 'full', 'app/heroes/heroes.component.ts', stylePattern)(format='.')
|
||||
|
||||
block ngmodule-vs-component
|
||||
:marked
|
||||
### When to use the NgModule and when an application component?
|
||||
|
||||
On the one hand, a provider in an NgModule is registered in the root injector. That means that every provider
|
||||
registered within an NgModule will be accessible in the _entire application_.
|
||||
|
||||
On the other hand, a provider registered in an application component is available only on that component and all its children.
|
||||
|
||||
We want the `APP_CONFIG` service to be available all across the application, but a `HeroService` is only used within the *Heroes*
|
||||
feature area and nowhere else.
|
||||
|
||||
.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#root-component-or-module).
|
||||
|
||||
:marked
|
||||
### Preparing the HeroListComponent for injection
|
||||
|
||||
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
||||
Per the dependency injection pattern, the component must ask for the service in its
|
||||
constructor, [as we explained earlier](#ctor-injection).
|
||||
It's a small change:
|
||||
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/hero-list.component.2.ts,
|
||||
dependency-injection/ts/app/heroes/hero-list.component.1.ts`,
|
||||
null,
|
||||
`app/heroes/hero-list.component (with DI),
|
||||
app/heroes/hero-list.component (without DI)`)
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
#### Focus on the constructor
|
||||
|
||||
Adding a parameter to the constructor isn't all that's happening here.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero-list.component.2.ts', 'ctor')(format=".")
|
||||
|
||||
:marked
|
||||
Note that the constructor parameter has the type `HeroService`, and that
|
||||
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},
|
||||
and the parent's `providers` information combine to tell the
|
||||
Angular injector to inject an instance of
|
||||
`HeroService` whenever it creates a new `HeroListComponent`.
|
||||
|
||||
#di-metadata
|
||||
:marked
|
||||
### Implicit injector creation
|
||||
|
||||
When we introduced the idea of an injector above, we showed how to
|
||||
use it to create a new `Car`. Here we also show how such an injector
|
||||
would be explicitly created:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-create-and-call')(format=".")
|
||||
|
||||
:marked
|
||||
We won't find code like that in the Tour of Heroes or any of our other samples.
|
||||
We *could* write code that [explicitly creates an injector](#explicit-injector) if we *had* to, but we rarely do.
|
||||
Angular takes care of creating and calling injectors
|
||||
when it creates components for us — whether through HTML markup, as in `<hero-list></hero-list>`,
|
||||
or after navigating to a component with the [router](./router.html).
|
||||
If we let Angular do its job, we'll enjoy the benefits of automated dependency injection.
|
||||
|
||||
:marked
|
||||
### Singleton services
|
||||
|
||||
Dependencies are singletons within the scope of an injector.
|
||||
In our example, a single `HeroService` instance is shared among the
|
||||
`HeroesComponent` and its `HeroListComponent` children.
|
||||
|
||||
However, Angular DI is an hierarchical injection
|
||||
system, which means that nested injectors can create their own service instances.
|
||||
Learn more about that in the [Hierarchical Injectors](./hierarchical-dependency-injection.html) chapter.
|
||||
|
||||
:marked
|
||||
### Testing the component
|
||||
|
||||
We emphasized earlier that designing a class for dependency injection makes the class easier to test.
|
||||
Listing dependencies as constructor parameters may be all we need to test application parts effectively.
|
||||
|
||||
For example, we can create a new `HeroListComponent` with a mock service that we can manipulate
|
||||
under test:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/test.component.ts', 'spec')(format='.')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more in [Testing](./testing.html).
|
||||
|
||||
:marked
|
||||
### When the service needs a service
|
||||
|
||||
Our `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||
|
||||
|
||||
What if it had a dependency? What if it reported its activities through a logging service?
|
||||
We'd apply the same *constructor injection* pattern,
|
||||
adding a constructor that takes a `Logger` parameter.
|
||||
|
||||
Here is the revision compared to the original.
|
||||
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/hero.service.2.ts,
|
||||
dependency-injection/ts/app/heroes/hero.service.1.ts`,
|
||||
null,
|
||||
`app/heroes/hero.service (v2),
|
||||
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`.
|
||||
We call that property within our `getHeroes` method when anyone asks for heroes.
|
||||
|
||||
- var injUrl = '../api/core/index/Injectable-decorator.html';
|
||||
h3#injectable Why @Injectable()?
|
||||
:marked
|
||||
**<a href="#{injUrl}">@Injectable()</a>** marks a class as available to an
|
||||
injector for instantiation. Generally speaking, an injector will report an
|
||||
error when trying to instantiate a class that is not marked as
|
||||
`@Injectable()`.
|
||||
|
||||
block injectable-not-always-needed-in-ts
|
||||
.l-sub-section
|
||||
:marked
|
||||
As it happens, we could have omitted `@Injectable()` from our first
|
||||
version of `HeroService` because it had no injected parameters.
|
||||
But we must have it now that our service has an injected dependency.
|
||||
We need it because Angular requires constructor parameter metadata
|
||||
in order to inject a `Logger`.
|
||||
|
||||
.callout.is-helpful
|
||||
header Suggestion: add @Injectable() to every service class
|
||||
:marked
|
||||
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||
and, therefore, do not technically require it. Here's why:
|
||||
|
||||
ul(style="font-size:inherit")
|
||||
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when we add a dependency later.
|
||||
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why #{_a} #{_decorator} is missing.
|
||||
|
||||
:marked
|
||||
Injectors are also responsible for instantiating components
|
||||
like `HeroesComponent`. Why haven't we marked `HeroesComponent` as
|
||||
`@Injectable()`?
|
||||
|
||||
We *can* add it if we really want to. It isn't necessary because the
|
||||
`HeroesComponent` is already marked with `@Component`, and this
|
||||
!{_decorator} class (like `@Directive` and `@Pipe`, which we'll learn about later)
|
||||
is a subtype of <a href="#{injUrl}">Injectable</a>. It is in
|
||||
fact `Injectable` #{_decorator}s that
|
||||
identify a class as a target for instantiation by an injector.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
:marked
|
||||
At runtime, injectors can read class metadata in the transpiled JavaScript code
|
||||
and use the constructor parameter type information
|
||||
to determine what things to inject.
|
||||
|
||||
Not every JavaScript class has metadata.
|
||||
The TypeScript compiler discards metadata by default.
|
||||
If the `emitDecoratorMetadata` compiler option is true
|
||||
(as it should be in the `tsconfig.json`),
|
||||
the compiler adds the metadata to the generated JavaScript
|
||||
for _every class with at least one decorator_.
|
||||
|
||||
While any decorator will trigger this effect, mark the service class with the
|
||||
<a href="#{injUrl}">Injectable</a> #{_decorator}
|
||||
to make the intent clear.
|
||||
|
||||
.callout.is-critical
|
||||
header Always include the parentheses
|
||||
block always-include-paren
|
||||
:marked
|
||||
Always write `@Injectable()`, not just `@Injectable`.
|
||||
Our application will fail mysteriously if we forget the parentheses.
|
||||
|
||||
.l-main-section#logger-service
|
||||
:marked
|
||||
## Creating and registering a logger service
|
||||
|
||||
We're injecting a logger into our `HeroService` in two steps:
|
||||
1. Create the logger service.
|
||||
1. Register it with the application.
|
||||
|
||||
Our logger service is quite simple:
|
||||
|
||||
+makeExample('app/logger.service.ts')
|
||||
|
||||
block real-logger
|
||||
//- N/A
|
||||
|
||||
:marked
|
||||
We're likely to need the same logger service everywhere in our application,
|
||||
so we put it in the project's `#{_appDir}` folder, and
|
||||
we register it in the `providers` #{_array} of our application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`.
|
||||
|
||||
+makeExcerpt('app/providers.component.ts (excerpt)', 'providers-logger','app/app.module.ts')
|
||||
|
||||
:marked
|
||||
If we forget to register the logger, Angular throws an exception when it first looks for the logger:
|
||||
code-example(format="nocode").
|
||||
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||
|
||||
:marked
|
||||
That's Angular telling us that the dependency injector couldn't find the *provider* for the logger.
|
||||
It needed that provider to create a `Logger` to inject into a new
|
||||
`HeroService`, which it needed to
|
||||
create and inject into a new `HeroListComponent`.
|
||||
|
||||
The chain of creations started with the `Logger` provider. *Providers* are the subject of our next section.
|
||||
|
||||
.l-main-section#providers
|
||||
:marked
|
||||
## Injector providers
|
||||
|
||||
A provider *provides* the concrete, runtime version of a dependency value.
|
||||
The injector relies on **providers** to create instances of the services
|
||||
that the injector injects into components and other services.
|
||||
|
||||
We must register a service *provider* with the injector, or it won't know how to create the service.
|
||||
|
||||
Earlier we registered the `Logger` service in the `providers` #{_array} of the metadata for the `AppModule` like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/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' ? '' : 'We could provide a logger-like object. '
|
||||
:marked
|
||||
There are many ways to *provide* something that #{implements} `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider.
|
||||
But it's not the only way.
|
||||
|
||||
We can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
||||
We could provide a substitute class. #{loggerlike}
|
||||
We 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}
|
||||
|
||||
:marked
|
||||
We wrote the `providers` #{_array} like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-1')
|
||||
|
||||
block provider-shorthand
|
||||
:marked
|
||||
This is actually a shorthand expression for a provider registration
|
||||
using a _provider_ object literal with two properties:
|
||||
|
||||
+makeExample('dependency-injection/ts/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},
|
||||
which we can think of as a *recipe* for creating the dependency value.
|
||||
There are many ways to create dependency values ... and many ways to write a recipe.
|
||||
|
||||
#class-provider
|
||||
:marked
|
||||
### Alternative class providers
|
||||
|
||||
Occasionally we'll ask a different class to provide the service.
|
||||
The following code tells the injector
|
||||
to return a `BetterLogger` when something asks for the `Logger`.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-4')
|
||||
|
||||
block dart-diff-const-metadata
|
||||
//- N/A
|
||||
|
||||
:marked
|
||||
### Class provider with dependencies
|
||||
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||
This logger gets the user from the injected `UserService`,
|
||||
which happens also to be injected at the application level.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','EvenBetterLogger')(format='.')
|
||||
|
||||
:marked
|
||||
Configure it like we did `BetterLogger`.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-5')(format=".")
|
||||
|
||||
:marked
|
||||
### Aliased class providers
|
||||
|
||||
Suppose an old component depends upon an `OldLogger` class.
|
||||
`OldLogger` has the same interface as the `NewLogger`, but for some reason
|
||||
we can't update the old component to use it.
|
||||
|
||||
When the *old* component logs a message with `OldLogger`,
|
||||
we want the singleton instance of `NewLogger` to handle it instead.
|
||||
|
||||
The dependency injector should inject that singleton instance
|
||||
when a component asks for either the new or the old logger.
|
||||
The `OldLogger` should be an alias for `NewLogger`.
|
||||
|
||||
We certainly do not want two different `NewLogger` instances in our app.
|
||||
Unfortunately, that's what we get if we try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6a')(format=".")
|
||||
|
||||
:marked
|
||||
The solution: alias with the `useExisting` option.
|
||||
|
||||
- var stylePattern = { otl: /(useExisting: \w*)/gm };
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6b', '', stylePattern)(format=".")
|
||||
|
||||
#value-provider
|
||||
:marked
|
||||
### Value providers
|
||||
|
||||
: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/app/providers.component.ts','silent-logger')(format=".")
|
||||
|
||||
:marked
|
||||
Then we register a provider with the `useValue` option,
|
||||
which makes this object play the logger role.
|
||||
|
||||
- var stylePattern = { otl: /(useValue: \w*)/gm };
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-7', '', stylePattern)(format=".")
|
||||
|
||||
:marked
|
||||
See more `useValue` examples in the
|
||||
[Non-class dependencies](#non-class-dependencies) and
|
||||
[OpaqueToken](#opaquetoken) sections.
|
||||
|
||||
#factory-provider
|
||||
:marked
|
||||
### Factory providers
|
||||
|
||||
Sometimes we need to create the dependent value dynamically,
|
||||
based on information we won't have until the last possible moment.
|
||||
Maybe the information changes repeatedly in the course of the browser session.
|
||||
|
||||
Suppose also that the injectable service has no independent access to the source of this information.
|
||||
|
||||
This situation calls for a **factory provider**.
|
||||
|
||||
Let's illustrate by adding a new business requirement:
|
||||
the HeroService must hide *secret* heroes from normal users.
|
||||
Only authorized users should see secret heroes.
|
||||
|
||||
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||
It needs to know if the user is authorized to see secret heroes.
|
||||
That authorization can change during the course of a single application session,
|
||||
as when we log in a different user.
|
||||
|
||||
Unlike `EvenBetterLogger`, we can't inject the `UserService` into the `HeroService`.
|
||||
The `HeroService` won't have direct access to the user information to decide
|
||||
who is authorized and who is not.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Why? We don't know either. Stuff like this happens.
|
||||
|
||||
:marked
|
||||
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.ts','internals', 'app/heroes/hero.service.ts (excerpt)')(format='.')
|
||||
|
||||
:marked
|
||||
We can inject the `Logger`, but we can't inject the boolean `isAuthorized`.
|
||||
We'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||
|
||||
A factory provider needs a factory function:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','factory', 'app/heroes/hero.service.provider.ts (excerpt)')(format='.')
|
||||
|
||||
:marked
|
||||
Although the `HeroService` has no access to the `UserService`, our factory function does.
|
||||
|
||||
We inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','provider', 'app/heroes/hero.service.provider.ts (excerpt)')(format='.')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
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 `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 we captured the factory provider in #{_an} #{exportedvar}, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
We can register our `HeroService` with this #{variable} wherever we need it.
|
||||
|
||||
In our sample, we need it only in the `HeroesComponent`,
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` #{_array}.
|
||||
Here we see the new and the old implementation side-by-side:
|
||||
|
||||
- var stylePattern = { otl: /(providers.*),$/gm };
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.ts,
|
||||
dependency-injection/ts/app/heroes/heroes.component.1.ts`,
|
||||
',full',
|
||||
`app/heroes/heroes.component (v3),
|
||||
app/heroes/heroes.component (v2)`,
|
||||
stylePattern)
|
||||
|
||||
.l-main-section#token
|
||||
:marked
|
||||
## Dependency injection tokens
|
||||
|
||||
When we register a provider with an injector, we associate that provider with a dependency injection token.
|
||||
The injector maintains an internal *token-provider* map that it references when
|
||||
asked for a dependency. The token is the key to the map.
|
||||
|
||||
In all previous examples, the dependency value has been a class *instance*, and
|
||||
the class *type* served as its own lookup key.
|
||||
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.')
|
||||
|
||||
:marked
|
||||
We have similar good fortune when we write a constructor that requires an injected class-based dependency.
|
||||
We define a constructor parameter with the `HeroService` class type,
|
||||
and Angular knows to inject the
|
||||
service associated with that `HeroService` class token:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero-list.component.ts', 'ctor-signature')
|
||||
|
||||
:marked
|
||||
This is especially convenient when we consider that most dependency values are provided by classes.
|
||||
|
||||
//- TODO: if function injection is useful explain or illustrate why.
|
||||
:marked
|
||||
### Non-class dependencies
|
||||
p
|
||||
| What if the dependency value isn't a class? Sometimes the thing we 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:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','config','app/app-config.ts (excerpt)')(format='.')
|
||||
|
||||
:marked
|
||||
We'd like to make this configuration object available for injection.
|
||||
We know we can register an object with a [value provider](#value-provider).
|
||||
|
||||
block what-should-we-use-as-token
|
||||
:marked
|
||||
But what should we use as the token?
|
||||
We don't have a class to serve as a token.
|
||||
There is no `AppConfig` class.
|
||||
|
||||
.l-sub-section#interface
|
||||
:marked
|
||||
### TypeScript interfaces aren't valid tokens
|
||||
|
||||
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, we
|
||||
cannot use a TypeScript interface as a token:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9-interface')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9-ctor-interface')(format=".")
|
||||
:marked
|
||||
That seems strange if we're used to dependency injection in strongly typed languages, where
|
||||
an interface is the preferred dependency lookup key.
|
||||
|
||||
It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
|
||||
The TypeScript interface disappears from the generated JavaScript.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
//- FIXME update once https://github.com/dart-lang/angular2/issues/16 is addressed.
|
||||
- var opaquetoken = _docsFor == 'dart' ? '<b>OpaqueToken</b>' : '<a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>'
|
||||
:marked
|
||||
### OpaqueToken
|
||||
|
||||
One solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an !{opaquetoken}.
|
||||
The definition looks like this:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
||||
|
||||
:marked
|
||||
We register the dependency provider using the `OpaqueToken` object:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9')(format=".")
|
||||
|
||||
:marked
|
||||
Now we can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject` #{_decorator}:
|
||||
|
||||
+makeExample('dependency-injection/ts/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,
|
||||
it supports typing of the configuration object within the class.
|
||||
|
||||
block dart-map-alternative
|
||||
:marked
|
||||
Or we can provide and inject the configuration object in an ngModule like `AppModule`.
|
||||
|
||||
+makeExcerpt('app/app.module.ts','ngmodule-providers')
|
||||
|
||||
#optional
|
||||
:marked
|
||||
## Optional dependencies
|
||||
|
||||
Our `HeroService` *requires* a `Logger`, but what if it could get by without
|
||||
a logger?
|
||||
We can tell Angular that the dependency is optional by annotating the
|
||||
constructor argument with `@Optional()`:
|
||||
|
||||
+ifDocsFor('ts')
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','import-optional', '')
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-ctor', '')(format='.')
|
||||
|
||||
:marked
|
||||
When using `@Optional()`, our code must be prepared for a null value. If we
|
||||
don't register a logger somewhere up the line, the injector will set the
|
||||
value of `logger` to null.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Summary
|
||||
|
||||
We learned the basics of Angular dependency injection in this chapter.
|
||||
We can register various kinds of providers,
|
||||
and we know how to ask for an injected object (such as a service) by
|
||||
adding a parameter to a constructor.
|
||||
|
||||
Angular dependency injection is more capable than we've described.
|
||||
We can learn more about its advanced features, beginning with its support for
|
||||
nested injectors, in the
|
||||
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
||||
|
||||
.l-main-section#explicit-injector
|
||||
:marked
|
||||
## Appendix: Working with injectors directly
|
||||
|
||||
We rarely work directly with an injector, but
|
||||
here's an `InjectorComponent` that does.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts', 'injector', 'app/injector.component.ts')
|
||||
|
||||
:marked
|
||||
An `Injector` is itself an injectable service.
|
||||
|
||||
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
||||
The component then asks the injected injector for the services it wants.
|
||||
|
||||
Note that the services themselves are not injected into the component.
|
||||
They are retrieved by calling `injector.get`.
|
||||
|
||||
The `get` method throws an error if it can't resolve the requested service.
|
||||
We can call `get` with a second parameter (the value to return if the service is not found)
|
||||
instead, which we do in one case
|
||||
to retrieve a service (`ROUS`) that isn't registered with this or any ancestor injector.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The technique we just described is an example of the
|
||||
[service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
|
||||
We **avoid** this technique unless we genuinely need it.
|
||||
It encourages a careless grab-bag approach such as we see here.
|
||||
It's difficult to explain, understand, and test.
|
||||
We can't know by inspecting the constructor what this class requires or what it will do.
|
||||
It could acquire services from any ancestor component, not just its own.
|
||||
We're forced to spelunk the implementation to discover what it does.
|
||||
|
||||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-main-section#one-class-per-file
|
||||
:marked
|
||||
## Appendix: Why we recommend one class per file
|
||||
|
||||
Having multiple classes in the same file is confusing and best avoided.
|
||||
Developers expect one class per file. Keep them happy.
|
||||
|
||||
If we scorn this advice and, say,
|
||||
combine our `HeroService` class with the `HeroesComponent` in the same file,
|
||||
**define the component last!**
|
||||
If we define the component before the service,
|
||||
we'll get a runtime null reference error.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We actually can define the component first with the help of the `forwardRef()` method as explained
|
||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||
But why flirt with trouble?
|
||||
Avoid the problem altogether by defining components and services in separate files.
|
|
@ -1,280 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
|
||||
- var _boolean = 'truthy/falsey';
|
||||
|
||||
:marked
|
||||
We typically display data in Angular by binding controls in an HTML template
|
||||
to properties of an Angular component.
|
||||
|
||||
In this chapter, we'll create a component with a list of heroes. Each hero has a name.
|
||||
We'll display the list of hero names and
|
||||
conditionally show a message below the list.
|
||||
|
||||
The final UI looks like this:
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI")
|
||||
|
||||
:marked
|
||||
# Table Of Contents
|
||||
|
||||
* [Showing component properties with interpolation](#interpolation)
|
||||
* [Showing !{_an} !{_array} property with NgFor](#ngFor)
|
||||
* [Conditional display with NgIf](#ngIf)
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The <live-example></live-example> demonstrates all of the syntax and code
|
||||
snippets described in this chapter.
|
||||
|
||||
.l-main-section#interpolation
|
||||
:marked
|
||||
## Showing component properties with interpolation
|
||||
The easiest way to display a component property
|
||||
is to bind the property name through interpolation.
|
||||
With interpolation, we put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||
|
||||
Let's build a small illustrative example together.
|
||||
|
||||
Create a new project folder (<ngio-ex path="displaying-data"></ngio-ex>) and follow the steps in the [QuickStart](../quickstart.html).
|
||||
|
||||
:marked
|
||||
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
||||
changing the template and the body of the component.
|
||||
When we're done, it should look like this:
|
||||
|
||||
+makeExample('app/app.component.1.ts')
|
||||
|
||||
:marked
|
||||
We added two properties to the formerly empty component: `title` and `myHero`.
|
||||
|
||||
Our revised template displays the two component properties using double curly brace
|
||||
interpolation:
|
||||
|
||||
+makeExcerpt('app/app.component.1.ts', 'template', '')
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
:marked
|
||||
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
|
||||
The backtick (<code>\`</code>) — which is *not* the same character as a single
|
||||
quote (`'`) — has many nice features. The feature we're exploiting here
|
||||
is the ability to compose the string over several lines, which makes for
|
||||
much more readable HTML.
|
||||
|
||||
:marked
|
||||
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
||||
inserts those values into the browser. Angular updates the display
|
||||
when these properties change.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
More precisely, the redisplay occurs after some kind of asynchronous event related to
|
||||
the view such as a keystroke, a timer completion, or an async `XHR` response.
|
||||
We don't have those in this sample.
|
||||
But then the properties aren't changing on their own either. For the moment we must operate on faith.
|
||||
|
||||
:marked
|
||||
Notice that we haven't called **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for us. How?
|
||||
|
||||
Notice the CSS `selector` in the `@Component` !{_decorator} that specifies an element named `my-app`.
|
||||
Remember back in [QuickStart](../quickstart.html) that we added the `<my-app>` element to the body of our `index.html` file:
|
||||
|
||||
+makeExcerpt('index.html', 'body')
|
||||
|
||||
:marked
|
||||
When we bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), 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.
|
||||
|
||||
Try running the app. It should display the title and hero name:
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
Let's review some of the choices we made and consider alternatives.
|
||||
|
||||
:marked
|
||||
## Template inline or template file?
|
||||
|
||||
We can store our component's template in one of two places.
|
||||
We can define it *inline* using the `template` property, as we do here.
|
||||
Or we 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 choice between inline and separate HTML is a matter of taste,
|
||||
circumstances, and organization policy.
|
||||
Here we're using inline HTML because the template is small, and the demo
|
||||
is simpler without the additional HTML file.
|
||||
|
||||
In either style, the template data bindings have the same access to the component's properties.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
## Constructor or variable initialization?
|
||||
|
||||
We initialized our component properties using variable assignment.
|
||||
This is a wonderfully concise and compact technique.
|
||||
|
||||
Some folks prefer to declare the properties and initialize them within a constructor like this:
|
||||
+makeExcerpt('app/app-ctor.component.ts', 'class')
|
||||
|
||||
:marked
|
||||
That's fine too. The choice is a matter of taste and organization policy.
|
||||
We'll adopt the more terse "variable assignment" style in this chapter simply because
|
||||
there will be less code to read.
|
||||
|
||||
.l-main-section#ngFor
|
||||
:marked
|
||||
## Showing !{_an} !{_array} property with ***ngFor**
|
||||
|
||||
We want to display a list of heroes. We begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}.
|
||||
|
||||
+makeExcerpt('app/app.component.2.ts', 'class')
|
||||
|
||||
:marked
|
||||
Now we use the Angular `ngFor` directive in the template to display
|
||||
each item in the `heroes` list.
|
||||
|
||||
+makeExcerpt('app/app.component.2.ts', 'template')
|
||||
|
||||
:marked
|
||||
Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag.
|
||||
|
||||
+makeExcerpt('app/app.component.2.ts ()', 'li', '')
|
||||
|
||||
:marked
|
||||
We added a somewhat mysterious `*ngFor` to the `<li>` element.
|
||||
That's the Angular "repeater" directive.
|
||||
Its presence on the `<li>` tag marks that `<li>` element (and its children) as the "repeater template".
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
|
||||
Learn more about this and `ngFor` in the [Template Syntax](./template-syntax.html#ngFor) chapter.
|
||||
|
||||
:marked
|
||||
Notice the `hero` in the `ngFor` double-quoted instruction;
|
||||
it is an example of a [template input variable](./template-syntax.html#ngForMicrosyntax).
|
||||
|
||||
Angular duplicates the `<li>` for each item in the list, setting the `hero` variable
|
||||
to the item (the hero) in the current iteration. Angular uses that variable as the
|
||||
context for the interpolation in the double curly braces.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We happened to give `ngFor` !{_an} !{_array} to display.
|
||||
In fact, `ngFor` can repeat items for any [iterable](!{_iterableUrl})
|
||||
object.
|
||||
:marked
|
||||
Now the heroes appear in an unordered list.
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Creating a class for the data
|
||||
|
||||
We are defining our data directly inside our component.
|
||||
That's fine for a demo but certainly isn't a best practice. It's not even a good practice.
|
||||
Although we won't do anything about that in this chapter, we'll make a mental note to fix this down the road.
|
||||
|
||||
At the moment, we're binding to !{_an} !{_array} of strings. We do that occasionally in real applications, but
|
||||
most of the time we're binding to more specialized objects.
|
||||
|
||||
Let's turn our !{_array} of hero names into !{_an} !{_array} of `Hero` objects. For that we'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:
|
||||
|
||||
+makeExcerpt('app/hero.ts')
|
||||
|
||||
block hero-class
|
||||
:marked
|
||||
We've defined a class with a constructor and two properties: `id` and `name`.
|
||||
|
||||
It might not look like we have properties, but we do. We're taking
|
||||
advantage of a TypeScript shortcut in our declaration of the constructor parameters.
|
||||
|
||||
Consider the first parameter:
|
||||
|
||||
+makeExcerpt('app/hero.ts ()', 'id')
|
||||
|
||||
:marked
|
||||
That brief syntax does a lot:
|
||||
* Declares a constructor parameter and its type
|
||||
* Declares a public property of the same name
|
||||
* Initializes that property with the corresponding argument when we "new" an instance of the class
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Using the Hero class
|
||||
Let's make the `heroes` property in our component return !{_an} !{_array} of these `Hero` objects.
|
||||
|
||||
+makeExcerpt('app/app.component.3.ts', 'heroes')
|
||||
|
||||
:marked
|
||||
We'll have to update the template.
|
||||
At the moment it displays the hero's `id` and `name`.
|
||||
Let's fix that so we display only the hero's `name` property.
|
||||
|
||||
+makeExcerpt('app/app.component.3.ts', 'template')
|
||||
|
||||
:marked
|
||||
Our display looks the same, but now we know much better what a hero really is.
|
||||
|
||||
.l-main-section#ngIf
|
||||
:marked
|
||||
## Conditional display with NgIf
|
||||
|
||||
Sometimes an app needs to display a view or a portion of a view only under specific circumstances.
|
||||
|
||||
In our example, we'd like to display a message if we have a large number of heroes, say, more than 3.
|
||||
|
||||
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition.
|
||||
We can see it in action by adding the following paragraph at the bottom of the template:
|
||||
|
||||
+makeExcerpt('app/app.component.ts', 'message')
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
|
||||
Learn more about this and `ngIf` in the [Template Syntax](./template-syntax.html#ngIf) chapter.
|
||||
|
||||
:marked
|
||||
The [template expression](./template-syntax.html#template-expressions) inside the double quotes
|
||||
looks much like !{_Lang}, and it _is_ much like !{_Lang}.
|
||||
When the component's list of heroes has more than 3 items, Angular adds the paragraph to the DOM and the message appears.
|
||||
If there are 3 or fewer items, Angular omits the paragraph, so no message appears.
|
||||
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM.
|
||||
That hardly matters here. But it would matter a great deal, from a performance perspective, if
|
||||
we were conditionally including or excluding a big chunk 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}.
|
||||
The browser should refresh automatically and the message should disappear.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Summary
|
||||
Now we 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 our component and display properties of that model
|
||||
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression
|
||||
|
||||
Here's our final code:
|
||||
|
||||
block final-code
|
||||
+makeTabs(`displaying-data/ts/app/app.component.ts,
|
||||
displaying-data/ts/app/hero.ts,
|
||||
displaying-data/ts/app/main.ts`,
|
||||
'final,,',
|
||||
'app/app.component.ts, app/hero.ts, main.ts')
|
|
@ -1,175 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
We learned the basics of Angular Dependency injection in the
|
||||
[Dependency Injection](./dependency-injection.html) chapter.
|
||||
|
||||
Angular has a Hierarchical Dependency Injection system.
|
||||
There is actually a tree of injectors
|
||||
that parallel an application's component tree.
|
||||
We can reconfigure the injectors at any level of that component tree with
|
||||
interesting and useful results.
|
||||
|
||||
In this chapter we explore these points and write some code.
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Injector Tree
|
||||
|
||||
In the [Dependency Injection](./dependency-injection.html) chapter
|
||||
we learned how to configure a dependency injector and how to retrieve dependencies where we need them.
|
||||
|
||||
We oversimplified. In fact, there is no such thing as ***the*** injector!
|
||||
An application may have multiple injectors!
|
||||
|
||||
An Angular application is a tree of components. Each component instance has its own injector!
|
||||
The tree of components parallels the tree of injectors.
|
||||
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular doesn't *literally* create a separate injector for each component.
|
||||
Every component doesn't need its own injector and it would be horribly inefficient to create
|
||||
masses of injectors for no good purpose.
|
||||
|
||||
But it is true that every component ***has an injector*** (even if it shares that injector with another component)
|
||||
and there may be many different injector instances operating at different levels of the component tree.
|
||||
|
||||
It is useful to pretend that every component has its own injector.
|
||||
:marked
|
||||
Consider a simple variation on the Tour of Heroes application consisting of three different components:
|
||||
`HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`.
|
||||
The `HeroesApp` holds a single instance of `HeroesListComponent`.
|
||||
The new twist is that the `HeroesListComponent` may hold and manage multiple instances of the `HeroesCardComponent`.
|
||||
|
||||
The following diagram represents the state of the component tree when there are three instances of `HeroesCardComponent`
|
||||
open simultaneously.
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="500")
|
||||
|
||||
:marked
|
||||
Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.
|
||||
|
||||
When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
||||
If that injector can't satisfy the request, it passes it along to *its* parent component's injector.
|
||||
The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors.
|
||||
If we run out of ancestors, Angular throws an error.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
There's a third possibility. An intermediate component can declare that it is the "host" component.
|
||||
The hunt for providers will climb no higher than the injector for this host component.
|
||||
We'll reserve discussion of this option for another day.
|
||||
:marked
|
||||
Such a proliferation of injectors makes little sense until we consider the possibility that injectors at different levels can be
|
||||
configured with different providers. We don't *have* to reconfigure providers at every level. But we *can*.
|
||||
|
||||
If we don't reconfigure, the tree of injectors appears to be flat. All requests bubble up to the root
|
||||
<span if-docs="ts">NgModule</span> injector that we configured with the `!{_bootstrapModule}` method.
|
||||
|
||||
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||
|
||||
Let’s return to our Car example.
|
||||
Suppose we configured the root injector (marked as A) with providers for `Car`, `Engine` and `Tires`.
|
||||
We create a child component (B) that defines its own providers for `Car` and `Engine`
|
||||
This child is the parent of another component (C) that defines its own provider for `Car`.
|
||||
|
||||
Behind the scenes each component sets up its own injector with one or more providers defined for that component itself.
|
||||
|
||||
When we resolve an instance of `Car` at the deepest component (C),
|
||||
its injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
|
||||
`Tires` resolved by the root injector (A).
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree" width="600")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Component Injectors
|
||||
|
||||
In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they find the requested thing to inject. But when do we actually want to provide providers on the root injector and when do we want to provide them on a child injector?
|
||||
|
||||
Consider you are building a component to show a list of super heroes that displays each super hero in a card with its name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero.
|
||||
|
||||
One important aspect of the editing functionality is that we want to allow multiple heroes to be in edit mode at the same time and that one can always either commit or cancel the proposed changes.
|
||||
|
||||
Let’s take a look at the `HeroesListComponent` which is the root component for this example.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/heroes-list.component.ts', null, 'app/heroes-list.component.ts')
|
||||
|
||||
:marked
|
||||
Notice that it imports the `HeroService` that we’ve used before so we can skip its declaration. The only difference is that we’ve used a more formal approach for our `Hero`model and defined it upfront as such.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/hero.ts', null, 'app/hero.ts')(format=".")
|
||||
|
||||
:marked
|
||||
Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponent`s and `HeroEditorComponent`s, each bound to an instance of hero that is returned from the `HeroService`. Ok, that’s not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/edit-item.ts', null, 'app/edit-item.ts')(format=".")
|
||||
|
||||
:marked
|
||||
But how is `HeroCardComponent` implemented? Let’s take a look.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/hero-card.component.ts', null, 'app/hero-card.component.ts')
|
||||
|
||||
:marked
|
||||
The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more.
|
||||
|
||||
Let’s get to the interesting part and take a look at the `HeroEditorComponent`
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/hero-editor.component.ts', null, 'app/hero-editor.component.ts')
|
||||
|
||||
:marked
|
||||
Now here it’s getting interesting. The `HeroEditorComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead, this is a perfect use case to abstract it into its own generic service since we have probably more cases like this in our app.
|
||||
|
||||
And this is where the `RestoreService` enters the stage.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/restore.service.ts', null, 'app/restore.service.ts')
|
||||
|
||||
:marked
|
||||
All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to its initial value. That’s exactly what we need to implement the desired functionality.
|
||||
|
||||
Our `HeroEditComponent` uses this services under the hood for its `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we won’t work on the original item but on a copy instead.
|
||||
|
||||
At this point we may be scratching our heads asking what this has to do with component injectors?
|
||||
Look closely at the metadata for our `HeroEditComponent`. Notice the `providers` property.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/ts/app/hero-editor.component.ts', 'providers')
|
||||
|
||||
- var _root_NgModule = _docsFor == 'dart' ? '<code>bootstrap</code> arguments' : 'root <code>NgModule</code>'
|
||||
:marked
|
||||
This adds a `RestoreService` provider to the injector of the `HeroEditComponent`.
|
||||
Couldn’t we simply alter our !{_root_NgModule} to include this provider?
|
||||
|
||||
+makeExcerpt(_appModuleTsVsMainTs, 'bad-alternative')
|
||||
|
||||
:marked
|
||||
Technically we could, but our component wouldn’t quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically, each instance of `HeroEditComponent` needs to be bound to its own instance of the `RestoreService`.
|
||||
|
||||
By configuring a provider for the `RestoreService` on the `HeroEditComponent`, we get exactly one new instance of the `RestoreService`per `HeroEditComponent`.
|
||||
|
||||
Does that mean that services aren’t singletons anymore in Angular? Yes and no.
|
||||
There can be only one instance of a service type in a particular injector.
|
||||
But we've learned that we can have multiple injectors operating at different levels of the application's component tree.
|
||||
Any of those injectors could have its own instance of the service.
|
||||
|
||||
If we defined a `RestoreService` provider only on the root component,
|
||||
we would have exactly one instance of that service and it would be shared across the entire application.
|
||||
|
||||
That’s clearly not what we want in this scenario. We want each component to have its own instance of the `RestoreService`.
|
||||
Defining (or redefining) a provider at the component level creates a new instance of the service for each new instance
|
||||
of that component. We've made the `RestoreService` a kind of "private" singleton for each `HeroEditComponent`,
|
||||
scoped to that component instance and its child components.
|
||||
|
||||
//- ## Advanced Dependency Injection in Angular
|
||||
//- Restrict Dependency Lookups
|
||||
//- [TODO] (@Host) This has been postponed for now until we come up with a decent use case
|
||||
//- .l-main-section
|
||||
//- :marked
|
||||
//- ## Dependency Visibility
|
||||
//- [TODO] (providers vs viewProviders) This has been postponed for now until come up with a decent use case
|
|
@ -1,92 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
- var _angular_io = 'angular.io';
|
||||
|
||||
- var __lang = _docsFor || current.path[1] || 'ts';
|
||||
- var guideData = public.docs[__lang].latest.guide._data;
|
||||
- var advancedLandingPage = '';
|
||||
- for(var page in guideData) {
|
||||
- if (!guideData[page].basics && !guideData[page].hide) { advancedLandingPage = page; break; }
|
||||
- }
|
||||
- var advancedUrl = './' + advancedLandingPage + '.html'
|
||||
|
||||
|
||||
:marked
|
||||
This page describes the Angular documentation at a high level.
|
||||
If you're new to Angular, you may want to visit "[Learning Angular](learning-angular.html)" first.
|
||||
|
||||
## Themes
|
||||
|
||||
The documentation is divided into major thematic sections, each
|
||||
a collection of pages devoted to that theme.
|
||||
|
||||
block js-alert
|
||||
|
||||
- var top="vertical-align:top"
|
||||
table(width="100%")
|
||||
col(width="15%")
|
||||
col
|
||||
tr(style=top)
|
||||
td <b><a href="../quickstart.html">QuickStart</a></b>
|
||||
td
|
||||
:marked
|
||||
A first taste of Angular<span if-docs="ts"> with zero installation.
|
||||
Run "Hello World" in an online code editor and start playing with live code</span>.
|
||||
tr(style=top)
|
||||
td <b>Guide</b>
|
||||
td
|
||||
:marked
|
||||
Learn the Angular basics (you're already here!) like the setup for local development,
|
||||
displaying data and accepting user input, injecting application services into components,
|
||||
and building simple forms.
|
||||
tr(style=top)
|
||||
td <b><a href="../api/">API Reference</a></b>
|
||||
td
|
||||
:marked
|
||||
Authoritative details about each of the Angular libraries.
|
||||
tr(style=top)
|
||||
td <b><a href="../tutorial/">Tutorial</a></b>
|
||||
td
|
||||
:marked
|
||||
A step-by-step, immersive approach to learning Angular that
|
||||
introduces the major features of Angular in an application context.
|
||||
tr(style=top)
|
||||
td <b><a href="!{advancedUrl}">Advanced</a></b>
|
||||
td
|
||||
:marked
|
||||
In-depth analysis of Angular features and development practices.
|
||||
tr(style=top if-docs="ts")
|
||||
td <b><a href="../cookbook/">Cookbook</a></b>
|
||||
td
|
||||
:marked
|
||||
Recipes for specific application challenges, mostly code snippets with a minimum of exposition.
|
||||
|
||||
:marked
|
||||
A few early pages are written as tutorials and are clearly marked as such.
|
||||
The rest of the pages highlight key points in code rather than explain each step necessary to build the sample.
|
||||
You can always get the full source through the #{_liveLink}s.
|
||||
|
||||
## Code samples
|
||||
|
||||
Each page includes code snippets from a sample application that accompanies the page.
|
||||
You can reuse these snippets in your applications.
|
||||
|
||||
Look for a link to a running version of that sample, often near the top of the page,
|
||||
such as this <live-example name="architecture"></live-example> from the [Architecture](architecture.html) page.
|
||||
<span if-docs="ts">
|
||||
The link launches a browser-based, code editor where you can inspect, modify, save, and download the code.
|
||||
</span>
|
||||
|
||||
## Reference pages
|
||||
|
||||
* The [Cheat Sheet](cheatsheet.html) lists Angular syntax for common scenarios.
|
||||
* The [Glossary](glossary.html) defines terms that Angular developers should know.
|
||||
<li if-docs="ts">The [Change Log](change-log.html) announces what's new and changed in the documentation.</li>
|
||||
* The [API Reference](../api/) is the authority on every public-facing member of the Angular libraries.
|
||||
|
||||
## Feedback
|
||||
|
||||
We welcome feedback!
|
||||
|
||||
* Use the <a href="!{_ngDocRepoURL}" target="_blank" title="angular docs on github">!{_angular_io} Github repository</a> for **documentation** issues and pull requests.
|
||||
* Use the <a href="!{_ngRepoURL}" target="_blank" title="angular source on github">Angular Github repository</a> to report issues with **Angular** itself.
|
|
@ -1,43 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/intro/people.png" width="200px" height="152px" alt="Us" align="left" style="margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Everyone learns differently.
|
||||
You don't have to read the documentation straight through. Most pages stand on their own.
|
||||
Those new to Angular may wish to follow this popular learning path.
|
||||
<br class="l-clear-left">
|
||||
|
||||
1. [Setup](setup.html "Setup locally withe Quickstart seed") for local Angular development, if you haven't already done so.
|
||||
|
||||
1. Take the [*Tour of Heroes* tutorial](../tutorial "Tour of Heroes").
|
||||
|
||||
The *Tour of Heroes* takes you step-by-step from [setup](setup.html)
|
||||
to a full-featured example that demonstrates the essential characteristics of a professional application:
|
||||
a sensible project structure, data binding, master/detail, services, dependency injection, navigation, and remote data access.
|
||||
|
||||
1. <a id="architecture"></a>Read the [Architecture](architecture.html) overview for the big picture.
|
||||
<li if-docs="ts"><p>
|
||||
[The Root Module](appmodule.html) introduces the `NgModule` class that tells Angular how to compile and run your application.
|
||||
</p></li>
|
||||
|
||||
1. [Displaying Data](displaying-data.html) shows how data binding puts component property values on screen.
|
||||
|
||||
1. [User Input](user-input.html) explains how to respond to user-initiated DOM events.
|
||||
|
||||
1. [Forms](forms.html) covers data entry and validation within the UI.
|
||||
|
||||
1. [Dependency Injection](dependency-injection.html) is the way to build large, maintainable applications
|
||||
from small, single-purpose parts.
|
||||
|
||||
1. [Template Syntax](template-syntax.html) is a comprehensive study of Angular template HTML.
|
||||
|
||||
After reading the above sections, feel free to skip around among the other pages on this site.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Next Step
|
||||
|
||||
Try the [tutorial](../tutorial "Tour of Heroes") if you're ready to start coding or
|
||||
visit the [Architecture](architecture.html "Basic Concepts") page if you prefer to learn the basic concepts first.
|
|
@ -1,544 +0,0 @@
|
|||
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")
|
||||
|
||||
:marked
|
||||
A component has a lifecycle managed by Angular itself.
|
||||
|
||||
Angular creates it, renders it, creates and renders its children,
|
||||
checks it when its data-bound properties change, and destroys it before removing it from the DOM.
|
||||
|
||||
Angular offers **lifecycle hooks**
|
||||
that provide visibility into these key life moments and the ability to act when they occur.
|
||||
|
||||
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">
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
## Table of Contents
|
||||
* [Overview](#hooks-overview)
|
||||
* [Each hook's purpose and timing](#hooks-purpose-timing)
|
||||
* [Interfaces are optional (technically)](#interface-optional)
|
||||
* [Other Angular lifecycle hooks](#other-lifecycle-hooks)
|
||||
* [The lifecycle sample](#the-sample)
|
||||
* [All](#peek-a-boo)
|
||||
* [Spying OnInit and OnDestroy](#spy)
|
||||
* [OnChanges](#onchanges)
|
||||
* [DoCheck](#docheck)
|
||||
* [AfterViewInit and AfterViewChecked](#afterview)
|
||||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||||
:marked
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
a#hooks-overview
|
||||
.l-main-section
|
||||
:marked
|
||||
## Component lifecycle hooks
|
||||
Directive and component instances have a lifecycle
|
||||
as Angular creates, updates, and destroys them.
|
||||
Developers can tap into key moments in that lifecycle by implementing
|
||||
one or more of the *Lifecycle Hook* interfaces in the Angular `core` library.
|
||||
|
||||
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`
|
||||
that Angular calls shortly after creating the component:
|
||||
+makeExample('lifecycle-hooks/ts/app/peek-a-boo.component.ts', 'ngOnInit', 'peek-a-boo.component.ts (excerpt)')(format='.')
|
||||
:marked
|
||||
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
|
||||
Angular only calls a directive/component hook method *if it is defined*.
|
||||
|
||||
a#hooks-purpose-timing
|
||||
.l-main-section
|
||||
:marked
|
||||
## Lifecycle sequence
|
||||
*After* creating a component/directive by calling its constructor, Angular
|
||||
calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
table(width="100%")
|
||||
col(width="20%")
|
||||
col(width="80%")
|
||||
tr
|
||||
th Hook
|
||||
th Purpose and Timing
|
||||
|
||||
tr(style=top)
|
||||
td ngOnChanges
|
||||
td
|
||||
:marked
|
||||
Respond when Angular (re)sets data-bound input properties.
|
||||
The method receives a `SimpleChanges` object of current and previous property values.
|
||||
|
||||
Called before `ngOnInit` and whenever one or more data-bound input properties change.
|
||||
|
||||
tr(style=top)
|
||||
td ngOnInit
|
||||
td
|
||||
:marked
|
||||
Initialize the directive/component after Angular first displays the data-bound properties
|
||||
and sets the directive/component's input properties.
|
||||
|
||||
Called _once_, after the _first_ `ngOnChanges`.
|
||||
|
||||
tr(style=top)
|
||||
td ngDoCheck
|
||||
td
|
||||
:marked
|
||||
Detect and act upon changes that Angular can't or won't detect on its own.
|
||||
|
||||
Called during every change detection run, immediately after `ngOnChanges` and `ngOnInit`.
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterContentInit
|
||||
td
|
||||
:marked
|
||||
Respond after Angular projects external content into the component's view.
|
||||
|
||||
Called _once_ after the first `NgDoCheck`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterContentChecked
|
||||
td
|
||||
:marked
|
||||
Respond after Angular checks the content projected into the component.
|
||||
|
||||
Called after the `ngAfterContentInit` and every subsequent `NgDoCheck`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterViewInit
|
||||
td
|
||||
:marked
|
||||
Respond after Angular initializes the component's views and child views.
|
||||
|
||||
Called _once_ after the first `ngAfterContentChecked`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterViewChecked
|
||||
td
|
||||
:marked
|
||||
Respond after Angular checks the component's views and child views.
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
tr(style=top)
|
||||
td ngOnDestroy
|
||||
td
|
||||
:marked
|
||||
Cleanup just before Angular destroys the directive/component.
|
||||
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||||
|
||||
Called _just before_ Angular destroys the directive/component.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
a#interface-optional
|
||||
.l-main-section
|
||||
:marked
|
||||
## Interface are optional (technically)
|
||||
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||
|
||||
Fortunately, they aren't necessary.
|
||||
You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves.
|
||||
|
||||
Angular instead inspects directive and component classes and calls the hook methods *if they are defined*.
|
||||
Angular finds and calls methods like `ngOnInit()`, with or without the interfaces.
|
||||
|
||||
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
|
||||
in order to benefit from strong typing and editor tooling.
|
||||
|
||||
a#other-lifecycle-hooks
|
||||
.l-main-section
|
||||
:marked
|
||||
## 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.
|
||||
|
||||
.l-main-section#the-sample
|
||||
:marked
|
||||
## Lifecycle exercises
|
||||
|
||||
The <live-example></live-example>
|
||||
demonstrates the lifecycle hooks in action through a series of exercises
|
||||
presented as components under the control of the root `AppComponent`.
|
||||
|
||||
They follow a common pattern: a *parent* component serves as a test rig for
|
||||
a *child* component that illustrates one or more of the lifecycle hook methods.
|
||||
|
||||
Here's a brief description of each exercise:
|
||||
|
||||
table(width="100%")
|
||||
col(width="20%")
|
||||
col(width="80%")
|
||||
tr
|
||||
th Component
|
||||
th Description
|
||||
tr(style=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)
|
||||
td <a href="#spy">Spy</a>
|
||||
td
|
||||
:marked
|
||||
Directives have lifecycle hooks too.
|
||||
A `SpyDirective` can log when the element it spies upon is
|
||||
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||||
|
||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
managed by the parent `SpyComponent`.
|
||||
tr(style=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)
|
||||
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)
|
||||
td <a href="#afterview">AfterView</a>
|
||||
td
|
||||
:marked
|
||||
Shows what Angular means by a *view*.
|
||||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||
tr(style=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)
|
||||
td Counter
|
||||
td
|
||||
:marked
|
||||
Demonstrates a combination of a component and a directive
|
||||
each with its own hooks.
|
||||
|
||||
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||||
every time the parent component increments its input counter property.
|
||||
Meanwhile, the `SpyDirective` from the previous example is applied
|
||||
to the `CounterComponent` log where it watches log entries being created and destroyed.
|
||||
|
||||
:marked
|
||||
The remainder of this chapter discusses selected exercises in further detail.
|
||||
|
||||
a#peek-a-boo
|
||||
.l-main-section
|
||||
:marked
|
||||
## Peek-a-boo: all hooks
|
||||
The `PeekABooComponent` demonstrates all of the hooks in one component.
|
||||
|
||||
You would rarely, if ever, implement all of the interfaces like this.
|
||||
The peek-a-boo exists to show how Angular calls the hooks in the expected order.
|
||||
|
||||
This snapshot reflects the state of the log after the user clicked the *Create...* button and then the *Destroy...* button.
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo")
|
||||
:marked
|
||||
The sequence of log messages follows the prescribed hook calling order:
|
||||
`OnChanges`, `OnInit`, `DoCheck` (3x), `AfterContentInit`, `AfterContentChecked` (3x),
|
||||
`AfterViewInit`, `AfterViewChecked` (3x), and `OnDestroy`.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The constructor isn't an Angular hook *per se*.
|
||||
The log confirms that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
:marked
|
||||
Had the user clicked the *Update Hero* button, the log would show another `OnChanges` and two more triplets of
|
||||
`DoCheck`, `AfterContentChecked` and `AfterViewChecked`.
|
||||
Clearly these three hooks fire a *often*. Keep the logic in these hooks as lean as possible!
|
||||
|
||||
The next examples focus on hook details.
|
||||
|
||||
a#spy
|
||||
.l-main-section
|
||||
:marked
|
||||
## Spying *OnInit* and *OnDestroy*
|
||||
|
||||
Go undercover with these two spy hooks to discover when an element is initialized or destroyed.
|
||||
|
||||
This is the perfect infiltration job for a directive.
|
||||
The heroes will never know they're being watched.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Kidding aside, pay attention to two key points:
|
||||
|
||||
1. Angular calls hook methods for *directives* as well as components.<br><br>
|
||||
|
||||
2. A spy directive can provide insight into a DOM object that you cannot change directly.
|
||||
Obviously you can't touch the implementation of a native `div`.
|
||||
You can't modify a third party component either.
|
||||
But you can watch both with a directive.
|
||||
|
||||
|
||||
:marked
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit` and `ngOnDestroy` hooks
|
||||
that log messages to the parent via an injected `LoggerService`.
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/spy.directive.ts', 'spy-directive')(format=".")
|
||||
|
||||
:marked
|
||||
You can apply the spy to any native or component element and it'll be initialized and destroyed
|
||||
at the same time as that element.
|
||||
Here it is attached to the repeated hero `<div>`
|
||||
+makeExample('lifecycle-hooks/ts/app/spy.component.html', 'template')(format=".")
|
||||
|
||||
:marked
|
||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||
with an entry in the *Hook Log* as seen here:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/spy-directive.gif' alt="Spy Directive")
|
||||
|
||||
:marked
|
||||
Adding a hero results in a new hero `<div>`. The spy's `ngOnInit` logs that event.
|
||||
|
||||
The *Reset* button clears the `heroes` list.
|
||||
Angular removes all hero `<div>` elements from the DOM and destroys their spy directives at the same time.
|
||||
The spy's `ngOnDestroy` method reports its last moments.
|
||||
|
||||
The `ngOnInit` and `ngOnDestroy` methods have more vital roles to play in real applications.
|
||||
|
||||
### OnInit
|
||||
|
||||
Use `ngOnInit` for two main reasons:
|
||||
1. to perform complex initializations shortly after construction
|
||||
1. to set up the component after Angular sets the input properties
|
||||
|
||||
Experienced developers agree that components should be cheap and safe to construct.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Misko Hevery, Angular team lead,
|
||||
[explains why](http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)
|
||||
you should avoid complex constructor logic.
|
||||
|
||||
:marked
|
||||
Don't fetch data in a component constructor.
|
||||
You shouldn't worry that a new component will try to contact a remote server when
|
||||
created under test or before you decide to display it.
|
||||
Constructors should do no more than set the initial local variables to simple values.
|
||||
|
||||
An `ngOnInit` is a good place for a component to fetch its initial data. The
|
||||
[Tutorial](../tutorial/toh-pt4.html#oninit) and [HTTP](server-communication.html#oninit) chapter
|
||||
show how.
|
||||
|
||||
|
||||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||
That's a problem if you need to initialize the directive based on those properties.
|
||||
They'll have been set when `ngOninit` runs.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `ngOnChanges` method is your first opportunity to access those properties.
|
||||
Angular calls `ngOnChanges` before `ngOnInit` ... and many times after that.
|
||||
It only calls `ngOnInit` once.
|
||||
:marked
|
||||
You can count on Angular to call the `ngOnInit` method _soon_ after creating the component.
|
||||
That's where the heavy initialization logic belongs.
|
||||
|
||||
### OnDestroy
|
||||
|
||||
Put cleanup logic in `ngOnDestroy`, the logic that *must* run before Angular destroys the directive.
|
||||
|
||||
This is the time to notify another part of the application that the component is going away.
|
||||
|
||||
This is the place to free resources that won't be garbage collected automatically.
|
||||
Unsubscribe from observables and DOM events. Stop interval timers.
|
||||
Unregister all callbacks that this directive registered with global or application services.
|
||||
You risk memory leaks if you neglect to do so.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## OnChanges
|
||||
|
||||
Angular calls its `ngOnChanges` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
This example monitors the `OnChanges` hook.
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'ng-on-changes', 'OnChangesComponent (ngOnChanges)')(format=".")
|
||||
:marked
|
||||
The `ngOnChanges` method takes an object that maps each changed property name to a
|
||||
[SimpleChange](../api/core/index/SimpleChange-class.html) object holding the current and previous property values.
|
||||
This hook iterates over the changed properties and logs them.
|
||||
|
||||
The example component, `OnChangesComponent`, has two input properties: `hero` and `power`.
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'inputs')(format=".")
|
||||
:marked
|
||||
The host `OnChangesParentComponent` binds to them like this:
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes-parent.component.html', 'on-changes')
|
||||
:marked
|
||||
Here's the sample in action as the user makes changes.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges")
|
||||
|
||||
:marked
|
||||
The log entries appear as the string value of the *power* property changes.
|
||||
But the `ngOnChanges` does not catch changes to `hero.name`
|
||||
That's surprising at first.
|
||||
|
||||
Angular only calls the hook when the value of the input property changes.
|
||||
The value of the `hero` property is the *reference to the hero object*.
|
||||
Angular doesn't care that the hero's own `name` property changed.
|
||||
The hero object *reference* didn't change so, from Angular's perspective, there is no change to report!
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## DoCheck
|
||||
Use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Use this method to detect a change that Angular overlooked.
|
||||
:marked
|
||||
The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck` hook:
|
||||
+makeExample('lifecycle-hooks/ts/app/do-check.component.ts', 'ng-do-check', 'DoCheckComponent (ngDoCheck)')(format=".")
|
||||
:marked
|
||||
This code inspects certain _values-of-interest_, capturing and comparing their current state against previous values.
|
||||
It writes a special message to the log when there are no substantive changes to the `hero` or the `power`
|
||||
so you can see how often `DoCheck` is called. The results are illuminating:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck")
|
||||
:marked
|
||||
While the `ngDoCheck` hook can detect when the hero's `name` has changed, it has a frightful cost.
|
||||
This hook is called with enormous frequency —
|
||||
after _every_ change detection cycle no matter where the change occurred.
|
||||
It's called over twenty times in this example before the user can do anything.
|
||||
|
||||
Most of these initial checks are triggered by Angular's first rendering of *unrelated data elsewhere on the page*.
|
||||
Mere mousing into another input box triggers a call.
|
||||
Relatively few calls reveal actual changes to pertinent data.
|
||||
Clearly our implementation must be very lightweight or the user experience will suffer.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## AfterView
|
||||
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
||||
*after* it creates a component's child views.
|
||||
|
||||
Here's a child view that displays a hero's name in an input box:
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'child-view', 'ChildComponent')(format=".")
|
||||
:marked
|
||||
The `AfterViewComponent` displays this child view *within its template*:
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'template', 'AfterViewComponent (template)')(format=".")
|
||||
:marked
|
||||
The following hooks take action based on changing values *within the child view*
|
||||
which can only be reached by querying for the child view via the property decorated with
|
||||
[@ViewChild](../api/core/index/ViewChild-decorator.html).
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'hooks', 'AfterViewComponent (class excerpts)')(format=".")
|
||||
#wait-a-tick
|
||||
:marked
|
||||
### Abide by the unidirectional data flow rule
|
||||
The `doSomething` method updates the screen when the hero name exceeds 10 characters.
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'do-something', 'AfterViewComponent (doSomething)')(format=".")
|
||||
:marked
|
||||
Why does the `doSomething` method wait a tick before updating `comment`?
|
||||
|
||||
Angular's unidirectional data flow rule forbids updates to the view *after* it has been composed.
|
||||
Both of these hooks fire _after_ the component's view has been composed.
|
||||
|
||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||
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.
|
||||
|
||||
:marked
|
||||
Here's *AfterView* in action
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView")
|
||||
:marked
|
||||
Notice that Angular frequently calls `AfterViewChecked`, often when there are no changes of interest.
|
||||
Write lean hook methods to avoid performance problems.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## AfterContent
|
||||
The *AfterContent* sample explores the `AfterContentInit` and `AfterContentChecked` hooks that Angular calls
|
||||
*after* Angular projects external content into the component.
|
||||
|
||||
### Content projection
|
||||
*Content projection* is a way to import HTML content from outside the component and insert that content
|
||||
into the component's template in a designated spot.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular 1 developers know this technique as *transclusion*.
|
||||
|
||||
:marked
|
||||
Consider this variation on the [previous _AfterView_](#afterview) example.
|
||||
This time, instead of including the child view within the template, it imports the content from
|
||||
the `AfterContentComponent`'s parent. Here's the parent's template.
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'parent-template', 'AfterContentParentComponent (template excerpt)')(format=".")
|
||||
:marked
|
||||
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||||
Never put content between a component's element tags *unless you intend to project that content
|
||||
into the component*.
|
||||
|
||||
Now look at the component's template:
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
||||
:marked
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
It tells Angular where to insert that content.
|
||||
In this case, the projected content is the `<my-child>` from the parent.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content")
|
||||
:marked
|
||||
.l-sub-section
|
||||
:marked
|
||||
The tell-tale signs of *content projection* are (a) HTML between component element tags
|
||||
and (b) the presence of `<ng-content>` tags in the component's template.
|
||||
:marked
|
||||
### AfterContent hooks
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks.
|
||||
The key difference is in the child component
|
||||
|
||||
* The *AfterView* hooks concern `ViewChildren`, the child components whose element tags
|
||||
appear *within* the component's template.
|
||||
|
||||
* The *AfterContent* hooks concern `ContentChildren`, the child components that Angular
|
||||
projected into the component.
|
||||
|
||||
The following *AfterContent* hooks take action based on changing values in a *content child*
|
||||
which can only be reached by querying for it via the property decorated with
|
||||
[@ContentChild](../api/core/index/ContentChild-decorator.html).
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'hooks', 'AfterContentComponent (class excerpts)')(format=".")
|
||||
|
||||
a#no-unidirectional-flow-worries
|
||||
:marked
|
||||
### No unidirectional flow worries with _AfterContent..._
|
||||
|
||||
This component's `doSomething` method update's the component's data-bound `comment` property immediately.
|
||||
There's no [need to wait](#wait-a-tick).
|
||||
|
||||
Recall that Angular calls both *AfterContent* hooks before calling either of the *AfterView* hooks.
|
||||
Angular completes composition of the projected content *before* finishing the composition of this component's view.
|
||||
There is a small window between the `AfterContent...` and `AfterView...` hooks to modify the host view.
|
|
@ -1,487 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
||||
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
|
||||
|
||||
Once data arrive, we could push their raw `toString` values directly to the view.
|
||||
That rarely makes for a good user experience.
|
||||
E.g., almost everyone prefers a simple birthday date like
|
||||
<samp>April 15, 1988</samp> to the original raw string format
|
||||
— <samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>.
|
||||
|
||||
Clearly some values benefit from a bit of massage. We soon discover that we
|
||||
desire many of the same transformations repeatedly, both within and across many applications.
|
||||
We almost think of them as styles.
|
||||
In fact, we'd like to apply them in our HTML templates as we do styles.
|
||||
|
||||
Introducing Angular pipes, a way to write display-value transformations that we can declare in our HTML!
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Using Pipes
|
||||
|
||||
A pipe takes in data as input and transforms it to a desired output.
|
||||
We'll illustrate by transforming a component's birthday property into
|
||||
a human-friendly date.
|
||||
|
||||
+makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')(format='.')
|
||||
|
||||
:marked
|
||||
Focus on the component's template.
|
||||
|
||||
+makeExample('pipes/ts/app/app.component.html', 'hero-birthday-template')(format=".")
|
||||
|
||||
:marked
|
||||
Inside the interpolation expression we flow the component's `birthday` value through the
|
||||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-pipe.html)
|
||||
function on the right. All pipes work this way.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `Date` and `Currency` pipes need the **ECMAScript Internationalization API**.
|
||||
Safari and other older browsers don't support it. We can add support with a polyfill.
|
||||
|
||||
code-example(language="html").
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Built-in pipes
|
||||
Angular comes with a stock of pipes such as
|
||||
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
||||
They are all immediately available for use in any template.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about these and many other built-in pipes in the [API Reference](../api/#!?apiFilter=pipe);
|
||||
filter for entries that include the word "pipe".
|
||||
|
||||
Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in an [appendix below](#no-filter-pipe).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Parameterizing a Pipe
|
||||
|
||||
A pipe may accept any number of optional parameters to fine-tune its output.
|
||||
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
|
||||
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
|
||||
|
||||
We'll modify our birthday template to give the date pipe a format parameter.
|
||||
After formatting the hero's April 15th birthday, it should render as **<samp>04/15/88</samp>**:
|
||||
|
||||
+makeExample('pipes/ts/app/app.component.html', 'format-birthday')(format=".")
|
||||
|
||||
:marked
|
||||
The parameter value can be any valid
|
||||
[template expression](./template-syntax.html#template-expressions)
|
||||
such as a string literal or a component property.
|
||||
In other words, we can control the format through a binding the same way we control the birthday value through a binding.
|
||||
|
||||
Let's write a second component that *binds* the pipe's format parameter
|
||||
to the component's `format` property. Here's the template for that component:
|
||||
|
||||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'template', 'app/hero-birthday2.component.ts (template)')(format=".")
|
||||
|
||||
:marked
|
||||
We also added a button to the template and bound its click event to the component's `toggleFormat()` method.
|
||||
That method toggles the component's `format` property between a short form
|
||||
(`'shortDate'`) and a longer form (`'fullDate'`).
|
||||
|
||||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')(format='.')
|
||||
|
||||
:marked
|
||||
As we click the button, the displayed date alternates between
|
||||
"**<samp>04/15/1988</samp>**" and
|
||||
"**<samp>Friday, April 15, 1988</samp>**".
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle")
|
||||
:marked
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about the `DatePipes` format options in the [API Docs](../api/common/index/DatePipe-pipe.html).
|
||||
|
||||
:marked
|
||||
## Chaining pipes
|
||||
|
||||
We can chain pipes together in potentially useful combinations.
|
||||
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
|
||||
so we can display the birthday in uppercase. The following birthday displays as
|
||||
**<samp>APR 15, 1988</samp>**.
|
||||
|
||||
+makeExample('pipes/ts/app/app.component.html', 'chained-birthday')(format=".")
|
||||
|
||||
:marked
|
||||
This example — which displays **<samp>FRIDAY, APRIL 15, 1988</samp>** —
|
||||
chains the same pipes as above, but passes in a parameter to `date` as well.
|
||||
|
||||
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday')(format=".")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Custom Pipes
|
||||
|
||||
We can write our own custom pipes.
|
||||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||||
|
||||
+makeExample('pipes/ts/app/exponential-strength.pipe.ts', null, 'app/exponential-strength.pipe.ts')(format=".")
|
||||
|
||||
:marked
|
||||
This pipe definition reveals several key points:
|
||||
|
||||
* A pipe is a class decorated with pipe metadata.
|
||||
|
||||
* The pipe class implements the `PipeTransform` interface's `transform` method that
|
||||
accepts an input value followed by optional parameters and returns the transformed value.
|
||||
|
||||
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
||||
Our pipe has one such parameter: the `exponent`.
|
||||
|
||||
* We tell Angular that this is a pipe by applying the
|
||||
`@Pipe` #{_decorator} which we import from the core Angular library.
|
||||
|
||||
* The `@Pipe` #{_decorator} allows us to define the
|
||||
pipe name that we'll use within template expressions. It must be a valid JavaScript identifier.
|
||||
Our pipe's name is `exponentialStrength`.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### The *PipeTransform* Interface
|
||||
|
||||
The `transform` method is essential to a pipe.
|
||||
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
||||
It is technically optional; Angular looks for and executes the `transform` method regardless.
|
||||
|
||||
:marked
|
||||
Now we need a component to demonstrate our pipe.
|
||||
+makeExample('pipes/ts/app/power-booster.component.ts',null,'app/power-booster.component.ts')(format='.')
|
||||
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
|
||||
Two things to note:
|
||||
|
||||
1. We use our custom pipe the same way we use built-in pipes.
|
||||
1. We must include our pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
||||
|
||||
.callout.is-helpful
|
||||
header Remember the !{_decls} #{_array}!
|
||||
:marked
|
||||
Angular reports an error if we neglect to list our custom pipe.
|
||||
We didn't list the `DatePipe` in our previous example because all
|
||||
Angular built-in pipes are pre-registered.
|
||||
Custom pipes must be registered manually.
|
||||
|
||||
:marked
|
||||
If we try the <live-example></live-example>,
|
||||
we can probe its behavior by changing the value and the optional exponent in the template.
|
||||
|
||||
## Power Boost Calculator (extra-credit)
|
||||
|
||||
It's not much fun updating the template to test our custom pipe.
|
||||
We could upgrade the example to a "Power Boost Calculator" that combines
|
||||
our pipe and two-way data binding with `ngModel`.
|
||||
|
||||
+makeExample('app/power-boost-calculator.component.ts')
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator")
|
||||
|
||||
.l-main-section
|
||||
a#change-detection
|
||||
:marked
|
||||
## Pipes and Change Detection
|
||||
|
||||
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event:
|
||||
every keystroke, mouse move, timer tick, and server response. This could be expensive.
|
||||
Angular strives to lower the cost whenever possible and appropriate.
|
||||
|
||||
Angular picks a simpler, faster change detection algorithm when we use a pipe. Let's see how.
|
||||
|
||||
### No pipe
|
||||
|
||||
The component in our next example uses the default, aggressive change detection strategy to monitor and update
|
||||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
||||
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-1', 'app/flying-heroes.component.html (v1)')(format='.')
|
||||
|
||||
:marked
|
||||
The companion component class provides heroes, adds new heroes into the #{_array}, and can reset the #{_array}.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'v1', 'app/flying-heroes.component.ts (v1)')(format='.')
|
||||
|
||||
:marked
|
||||
We can add a new hero and Angular updates the display when we do.
|
||||
The `reset` button replaces `heroes` with a new #{_array} of the original heroes and Angular updates the display when we do.
|
||||
If we added the ability to remove or change a hero, Angular would detect those changes too and update the display as well.
|
||||
|
||||
### Flying Heroes pipe
|
||||
|
||||
Let's add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-flying-heroes', 'app/flying-heroes.component.html (flyers)')(format='.')
|
||||
:marked
|
||||
Here's the `FlyingHeroesPipe` implementation which follows the pattern for custom pipes we saw earlier.
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pure', 'app/flying-heroes.pipe.ts')(format='.')
|
||||
|
||||
:marked
|
||||
When we run the sample now we see odd behavior (try it in the <live-example></live-example>).
|
||||
Every hero we add is a flying hero but none of them are displayed.
|
||||
|
||||
Although we're not getting the behavior we want, Angular isn't broken.
|
||||
It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.
|
||||
|
||||
Look at how we're adding a new hero:
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'push')(format='.')
|
||||
:marked
|
||||
We're adding the new 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*.
|
||||
|
||||
We can fix that. Let's create a new #{_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 we **mutate** the #{_array}, no pipe is invoked and no display updated;
|
||||
if we **replace** the #{_array}, then the pipe executes and the display is updated*.
|
||||
The *Flying Heroes* extends the
|
||||
code with checkbox switches and additional displays to help us experience these effects.
|
||||
|
||||
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 to Angular that it should update the display.
|
||||
When do we replace the #{_array}? When the data change.
|
||||
That's an easy rule to follow in *this toy* example
|
||||
where the only way to change the data is by adding a new hero.
|
||||
|
||||
More often we don't know when the data have changed,
|
||||
especially in applications that mutate data in many ways,
|
||||
perhaps in application locations far away.
|
||||
A component in such an application usually can't know about those changes.
|
||||
Moreover, it's unwise to distort our component design to accommodate a pipe.
|
||||
We strive as much as possible to keep the component class independent of the HTML.
|
||||
The component should be unaware of pipes.
|
||||
|
||||
Perhaps we should consider a different kind of pipe for filtering flying heroes, an *impure pipe*.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Pure and Impure Pipes
|
||||
|
||||
There are two categories of pipes: **pure** and **impure**.
|
||||
Pipes are pure by default. Every pipe we've seen so far has been pure.
|
||||
We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe`
|
||||
impure like this:
|
||||
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
|
||||
|
||||
:marked
|
||||
Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe.
|
||||
|
||||
### Pure pipes
|
||||
|
||||
block pure-change
|
||||
:marked
|
||||
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
|
||||
A ***pure change*** is *either* a change to a primitive input value (`String`, `Number`, `Boolean`, `Symbol`)
|
||||
*or* a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||||
|
||||
:marked
|
||||
Angular ignores changes *within* (composite) objects.
|
||||
It won't call a pure pipe if we change an input month, add to an input #{_array}, or update an input object property.
|
||||
|
||||
This may seem restrictive but is is also fast.
|
||||
An object reference check is fast — much faster than a deep check for
|
||||
differences — so Angular can quickly determine if it can skip both the
|
||||
pipe execution and a view update.
|
||||
|
||||
For this reason, we prefer a pure pipe if we can live with the change detection strategy.
|
||||
When we can't, we *may* turn to the impure pipe.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Or we might not use a pipe at all.
|
||||
It may be better to pursue the pipe's purpose with a property of the component,
|
||||
a point we take up later.
|
||||
|
||||
:marked
|
||||
### Impure pipes
|
||||
|
||||
Angular executes an *impure pipe* during *every* component change detection cycle.
|
||||
An impure pipe will be called a lot, as often as every keystroke or mouse-move.
|
||||
|
||||
With that concern in mind, we must implement an impure pipe with great care.
|
||||
An expensive, long-running pipe could destroy the user experience.
|
||||
|
||||
<a id="impure-flying-heroes"></a>
|
||||
### An impure *FlyingHeroesPipe*
|
||||
|
||||
A flip of the switch turns our `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
||||
Here's the complete implementation:
|
||||
+makeTabs(
|
||||
'pipes/ts/app/flying-heroes.pipe.ts, pipes/ts/app/flying-heroes.pipe.ts',
|
||||
'impure, pure',
|
||||
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
|
||||
|
||||
:marked
|
||||
We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
|
||||
The only difference is the `pure` flag in the pipe metadata.
|
||||
|
||||
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
||||
|
||||
+makeExcerpt('app/flying-heroes.pipe.ts','filter', '')
|
||||
|
||||
:marked
|
||||
We 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('app/flying-heroes' + _fnSuffix + ' (excerpt)', _region)
|
||||
|
||||
:marked
|
||||
The only substantive change is the pipe in the template.
|
||||
We can confirm in the <live-example></live-example> that the _flying heroes_
|
||||
display updates as we enter new heroes even when we 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
|
||||
and subscribes to the input automatically, eventually returning the emitted value(s).
|
||||
|
||||
It is also stateful.
|
||||
The pipe maintains a subscription to the input `#{_Observable}` and
|
||||
keeps delivering values from that `#{_Observable}` as they arrive.
|
||||
|
||||
In this next example, we bind an `#{_Observable}` of message strings
|
||||
(`message#{_dollar}`) to a view with the `async` pipe.
|
||||
|
||||
+makeExample('pipes/ts/app/hero-async-message.component.ts', null, 'app/hero-async-message.component.ts')
|
||||
|
||||
:marked
|
||||
The Async pipe saves boilerplate in the component code.
|
||||
The component doesn't have to subscribe to the async data source,
|
||||
it doesn't extract the resolved values and expose them for binding,
|
||||
and the component doesn't have to unsubscribe when it is destroyed
|
||||
(a potent source of memory leaks).
|
||||
|
||||
### An impure caching pipe
|
||||
|
||||
Let's write one more impure pipe, a pipe that makes an HTTP request.
|
||||
|
||||
Remember that impure pipes are called every few milliseconds.
|
||||
If we're not careful, this pipe will punish the server with requests.
|
||||
|
||||
We are careful.
|
||||
The pipe only calls the server when the request URL changes and it caches the server response.
|
||||
Here's the code<span if-docs="ts">, which uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
||||
|
||||
+makeExample('app/fetch-json.pipe.ts')
|
||||
:marked
|
||||
Then we demonstrate it in a harness component whose template defines two bindings to this pipe,
|
||||
both requesting the heroes from the `heroes.json` file.
|
||||
|
||||
+makeExample('app/hero-list.component.ts')
|
||||
:marked
|
||||
The component renders like this:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
||||
|
||||
:marked
|
||||
A breakpoint on the pipe's request for data shows that
|
||||
* each binding gets its own pipe instance
|
||||
* each pipe instance caches its own url and data
|
||||
* each pipe instance only calls the server once
|
||||
|
||||
### *JsonPipe*
|
||||
|
||||
The second `fetch` pipe binding above demonstrates more pipe chaining.
|
||||
It displays the same hero data in JSON format by chaining through to the built-in `JsonPipe`.
|
||||
|
||||
.callout.is-helpful
|
||||
header Debugging with the json pipe
|
||||
:marked
|
||||
The [JsonPipe](../api/common/index/JsonPipe-pipe.html)
|
||||
provides an easy way to diagnosis a mysteriously failing data binding or
|
||||
inspect an object for future binding.
|
||||
|
||||
a#pure-pipe-pure-fn
|
||||
:marked
|
||||
### Pure pipes and pure functions
|
||||
|
||||
A pure pipe uses pure functions.
|
||||
Pure functions process inputs and return values without detectable side-effects.
|
||||
Given the same input they should always return the same output.
|
||||
|
||||
The pipes we saw earlier in this chapter were implemented with pure functions.
|
||||
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
||||
So is our `ExponentialStrengthPipe`.
|
||||
So is our `FlyingHeroesPipe`.
|
||||
A few steps back we reviewed the `FlyingHeroesImpurePipe` — *an impure pipe with a pure function*.
|
||||
|
||||
But a *pure pipe* must always be implemented with a *pure function*. Failure to heed this warning will bring about many a console errors regarding expressions that have changed after they were checked.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Next Steps
|
||||
|
||||
Pipes are a great way to encapsulate and share common display-value
|
||||
transformations. We use them like styles, dropping them
|
||||
into our templates expressions to enrich the appeal and usability
|
||||
of our views.
|
||||
|
||||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/#!?apiFilter=pipe).
|
||||
Try writing a custom pipe and perhaps contributing it to the community.
|
||||
|
||||
a(id="no-filter-pipe")
|
||||
.l-main-section
|
||||
:marked
|
||||
## No *FilterPipe* or *OrderByPipe*
|
||||
|
||||
Angular does not ship with pipes for filtering or sorting lists.
|
||||
Developers familiar with Angular 1 know these as `filter` and `orderBy`.
|
||||
There are no equivalents in Angular 2.
|
||||
|
||||
This is not an oversight. Angular 2 is unlikely to offer such pipes because
|
||||
(a) they perform poorly and (b) they prevent aggressive minification.
|
||||
Both `filter` and `orderBy` require parameters that reference object properties.
|
||||
We learned earlier that such pipes must be [*impure*](#pure-and-impure-pipes) and that
|
||||
Angular calls impure pipes in almost every change detection cycle.
|
||||
|
||||
Filtering and especially sorting are expensive operations.
|
||||
The user experience can degrade severely for even moderate sized lists when Angular calls these pipe methods many times per second.
|
||||
The `filter` and `orderBy` have often been abused in Angular 1 apps, leading to complaints that Angular itself is slow.
|
||||
That charge is fair in the indirect sense that Angular 1 prepared this performance trap
|
||||
by offering `filter` and `orderBy` in the first place.
|
||||
|
||||
The minification hazard is also compelling if less obvious. Imagine a sorting pipe applied to a list of heroes.
|
||||
We might sort the list by hero `name` and `planet` of origin properties something like this:
|
||||
code-example(language="html")
|
||||
<!-- NOT REAL CODE! -->
|
||||
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
|
||||
:marked
|
||||
We identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
||||
(e.g., `hero['name']`).
|
||||
Unfortunately, aggressive minification *munges* the `Hero` property names so that `Hero.name` and `Hero.planet`
|
||||
becomes something like `Hero.a` and `Hero.b`. Clearly `hero['name']` is not going to work.
|
||||
|
||||
Some of us may not care to minify this aggressively. That's *our* choice.
|
||||
But the Angular product should not prevent someone else from minifying aggressively.
|
||||
Therefore, the Angular team decided that everything shipped in Angular will minify safely.
|
||||
|
||||
The Angular team and many experienced Angular developers strongly recommend that you move
|
||||
filtering and sorting logic into the component itself.
|
||||
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
|
||||
over when and how often to execute the supporting logic.
|
||||
Any capabilities that you would have put in a pipe and shared across the app can be
|
||||
written in a filtering/sorting service and injected into the component.
|
||||
|
||||
If these performance and minification considerations do not apply to you, you can always create your own such pipes
|
||||
(along the lines of the [FlyingHeroesPipe](#impure-flying-heroes)) or find them in the community.
|
|
@ -1,263 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
:marked
|
||||
Web application security has many aspects. This chapter describes Angular's built in
|
||||
protections against common web application vulnerabilities and attacks, such as Cross Site
|
||||
Scripting Attacks. It does not cover application level security, such as authentication (_Who is
|
||||
this user?_) or authorization (_What can this user do?_).
|
||||
|
||||
The [Open Web Application Security Project (OWASP)](https://www.owasp.org/index.php/Category:OWASP_Guide_Project)
|
||||
has further information on the attacks and mitigations described below.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
# Table Of Contents
|
||||
|
||||
* [Reporting Vulnerabilities](#report-issues)
|
||||
* [Best Practices](#best-practices)
|
||||
* [Preventing Cross-Site Scripting (XSS)](#xss)
|
||||
* [Trusting Safe Values](#bypass-security-apis)
|
||||
* [HTTP-level Vulnerabilities](#http)
|
||||
* [Auditing Angular Applications](#code-review)
|
||||
|
||||
Try the <live-example></live-example> of the code shown in this chapter.
|
||||
|
||||
.l-main-section
|
||||
h2#report-issues Reporting Vulnerabilities
|
||||
:marked
|
||||
Email us at [security@angular.io](mailto:security@angular.io) to report vulnerabilities in
|
||||
Angular itself.
|
||||
|
||||
For further details on how Google handles security issues please refer to [Google's security
|
||||
philosophy](https://www.google.com/about/appsecurity/).
|
||||
|
||||
.l-main-section
|
||||
h2#best-practices Best Practices
|
||||
:marked
|
||||
* **Keep current with the latest Angular library releases.**
|
||||
We regularly update our Angular libraries and these updates may fix security defects discovered in
|
||||
previous version. Check the Angular [change
|
||||
log](https://github.com/angular/angular/blob/master/CHANGELOG.md) for security-related updates.
|
||||
|
||||
* **Don't modify your copy of Angular.**
|
||||
Private, customized versions of Angular tend to fall behind the current version and may neglect
|
||||
important security fixes and enhancements. Instead, share your Angular improvements with the
|
||||
community and make a pull request.
|
||||
|
||||
* **Avoid Angular APIs marked in the documentation as “[_Security Risk_](#bypass-security-apis)”.**
|
||||
|
||||
.l-main-section
|
||||
h2#xss Preventing Cross-Site Scripting (XSS)
|
||||
:marked
|
||||
[Cross-Site Scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) enables attackers
|
||||
to inject malicious code into web pages. Such code can then, for example, steal user's data (in
|
||||
particular their login data), or perform actions impersonating the user. This is one of the most
|
||||
common attacks on the web.
|
||||
|
||||
To block XSS attacks, we must prevent malicious code from entering the DOM. For example, if an
|
||||
attacker can trick us into inserting a `<script>` tag in the DOM, they can run arbitrary code on
|
||||
our website. The attack is not limited to `<script>` tags - many elements and properties in the
|
||||
DOM allow code execution, for example `<img onerror="...">`, `<a href="javascript:...">`. If
|
||||
attacker controlled data enters the DOM, we have to expect security vulnerabilities.
|
||||
|
||||
### Angular’s Cross-site Scripting Security Model
|
||||
|
||||
To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value
|
||||
is inserted into the DOM from a template, via property, attribute, style, or class binding, or via
|
||||
interpolation, Angular will sanitize and escape untrusted values.
|
||||
|
||||
**Angular templates are the same as executable code**: HTML, attributes, and binding expressions
|
||||
(but not the values bound!) in templates are trusted to be safe. That means applications must
|
||||
prevent potentially attacker controlled values from ever making it into the source code of a
|
||||
template. Never generate template source code by concatenating user input and templates! Using
|
||||
the [offline template compiler](#offline-template-compiler) is an effective way to prevent these
|
||||
vulnerabilities, also known as template injection.
|
||||
|
||||
### Sanitization and security contexts
|
||||
|
||||
Sanitization inspects an untrusted value and turns it into a value that is safe to insert into
|
||||
the DOM. In many cases, values do not get changed by this at all. Sanitization depends on context:
|
||||
a value that is harmless in CSS is potentially dangerous in a URL.
|
||||
|
||||
Angular defines four security contexts: HTML, style, URL, and resource URL.
|
||||
|
||||
* HTML is used when interpreting a value as HTML, e.g., when binding to `innerHtml`
|
||||
* Style is used when binding CSS into the `style` property
|
||||
* URL is used for URL properties such as `<a href>`
|
||||
* Resource URLs are URLs that will be loaded and executed as code, e.g., in `<script src>`
|
||||
|
||||
Angular sanitizes untrusted values for the first three items; sanitizing resource URLs is not
|
||||
possible as they contain arbitrary code. In development mode, Angular prints a console warning
|
||||
when it has to change a value during sanitization.
|
||||
|
||||
### Sanitization example
|
||||
|
||||
The template below binds the value of `htmlSnippet`, once by interpolating it into an element's
|
||||
content, and once by binding it to the `innerHTML` property of an element.
|
||||
|
||||
+makeExample('app/inner-html-binding.component.html')
|
||||
|
||||
:marked
|
||||
Interpolated content is always escaped - the HTML is not interpreted, and the browser displays
|
||||
angle brackets in the elements text content.
|
||||
|
||||
For the HTML to be interpreted, we must bind to an HTML property, such as `innerHTML`. But binding
|
||||
a potentially attacker controlled value into `innerHTML` would normally cause an XSS
|
||||
vulnerability. For example, code contained in a `<script>` tag would be executed.
|
||||
|
||||
+makeExcerpt('app/inner-html-binding.component.ts ()', 'inner-html-controller')
|
||||
|
||||
:marked
|
||||
Angular recognizes the value as unsafe, and automatically sanitizes it. It removes the `<script>`
|
||||
tag but keeps safe content, such as the text content of the `<script>` tag, or the `<b>` element.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/security/binding-inner-html.png'
|
||||
alt='A screenshot showing interpolated and bound HTML values')
|
||||
:marked
|
||||
### Avoid direct use of the DOM APIs
|
||||
|
||||
The built-in browser DOM APIs do not automatically protect you from security vulnerabilities.
|
||||
For example, `document`, the node available through `ElementRef`, and many third party APIs
|
||||
contain unsafe methods. Avoid directly interacting with the DOM, and instead use Angular
|
||||
templates where possible.
|
||||
|
||||
### Content Security Policy
|
||||
|
||||
A [Content Security Policy (CSP)](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is a defense-in-depth
|
||||
technique to prevent XSS. To enable CSP, configure your web server to return an appropriate
|
||||
`Content-Security-Policy` HTTP header.
|
||||
|
||||
<a id="offline-template-compiler"></a>
|
||||
### Use the Offline Template Compiler
|
||||
|
||||
The offline template compiler prevents a whole class of vulnerabilities called template injection,
|
||||
and also greatly improves application performance. Use the offline template compiler in production
|
||||
deployments. Do not dynamically generate templates. Angular trusts template code, so generating
|
||||
templates, in particular containing user data, circumvents Angular's built-in protections. See the
|
||||
[Dynamic Forms Cookbook](../cookbook/dynamic-form.html) on how to dynamically construct forms in a
|
||||
safe way.
|
||||
|
||||
### Server side XSS protection
|
||||
|
||||
HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an
|
||||
Angular application is the same as injecting executable code into the
|
||||
application; it gives the attacker full control over the application. To prevent this, make sure
|
||||
to use a templating language that automatically escapes values to prevent XSS vulnerabilities on
|
||||
the server. Do not generate Angular templates on the server side using a templating language, this
|
||||
carries a high risk of introducing template injection vulnerabilities.
|
||||
|
||||
.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 this situation,
|
||||
you can tell Angular that you inspected a value, checked how it is generated, and made sure it is
|
||||
always secure. But **be careful**! If you trust a value that can be malicious, you will likely
|
||||
introduce a security vulnerability into your application. If in doubt, find a professional
|
||||
security reviewer.
|
||||
|
||||
You can mark a value as trusted by injecting `DomSanitizer`, and calling one of the
|
||||
following methods.
|
||||
|
||||
* `bypassSecurityTrustHtml`
|
||||
* `bypassSecurityTrustScript`
|
||||
* `bypassSecurityTrustStyle`
|
||||
* `bypassSecurityTrustUrl`
|
||||
* `bypassSecurityTrustResourceUrl`
|
||||
|
||||
Remember, whether a value is safe depends on context, so you need to choose the right context for
|
||||
your intended use of the value. Imagine the following template needs to bind a URL to a
|
||||
`javascript:alert(...)` call.
|
||||
|
||||
+makeExcerpt('app/bypass-security.component.html ()', 'dangerous-url')
|
||||
|
||||
:marked
|
||||
Normally, Angular automatically sanitizes the URL, disables the dangerous code and,
|
||||
in development mode, logs this action to the console. To prevent
|
||||
this, we can mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
||||
|
||||
+makeExcerpt('app/bypass-security.component.ts ()', 'trust-url')
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/security/bypass-security-component.png'
|
||||
alt='A screenshot showing an alert box created from a trusted URL')
|
||||
|
||||
:marked
|
||||
If we need to convert user input into a trusted value, it can be convenient to do so in a
|
||||
controller method. The template below allows users to enter a YouTube video ID, and load the
|
||||
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
||||
context, because an untrusted source can, e.g., smuggle in file downloads that unsuspecting users
|
||||
would execute. So we call a method on the controller to construct a trusted video URL, which
|
||||
Angular then allows binding into `<iframe src>`.
|
||||
|
||||
+makeExcerpt('app/bypass-security.component.html ()', 'iframe-videoid')
|
||||
+makeExcerpt('app/bypass-security.component.ts ()', 'trust-video-url')
|
||||
|
||||
.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 (XSRF) and Cross-site Script Inclusion (XSSI). Both of these must be primarily mitigated
|
||||
on the server side, but Angular ships helpers to make integration on the client side easier.
|
||||
|
||||
h3#xsrf Cross-site Request Forgery (XSRF)
|
||||
:marked
|
||||
In a Cross-site Request Forgery (XSRF or CSRF), an attacker tricks the user into visiting a
|
||||
_different_ page, and has them, e.g., submit a form that sends a request to your application's
|
||||
web server. If the user is logged into your application, the browser will send authentication
|
||||
cookies, and the attacker could — for example — cause a bank transfer in the user's name with
|
||||
the right request.
|
||||
|
||||
To prevent this, your application must ensure that user requests originate in your own
|
||||
application, not on a different site. A common technique is that the server sends a randomly
|
||||
generated authentication token in a cookie, often with the name `XSRF-TOKEN`. Cookies can only
|
||||
be read by the website on which they are set, so only your own application can read this token. On
|
||||
each API request, the server then validates the client by checking that the token is sent back,
|
||||
usually in an HTTP header called `X-XSRF-TOKEN`.
|
||||
|
||||
The Angular `http` client has built-in support for this technique. The default
|
||||
`CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and sets an HTTP request header named
|
||||
`X-XSRF-TOKEN` with the value of that cookie on every request. The server must set the
|
||||
`XSRF-TOKEN` cookie, and validate the response header for each state modifying request.
|
||||
|
||||
XSRF tokens should be unique per user and session, have a large random value generated by a
|
||||
cryptographically secure random number generator, and expire.
|
||||
|
||||
Angular applications can customize cookie and header names by binding their own
|
||||
`CookieXSRFStrategy` value, or implement an entirely custom `XSRFStrategy` by providing a custom
|
||||
binding for that type, by adding either of the following to your providers list:
|
||||
|
||||
code-example(language="typescript").
|
||||
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name')}
|
||||
{ provide: XSRFStrategy, useClass: MyXSRFStrategy}
|
||||
|
||||
:marked
|
||||
Learn about Cross Site Request Forgery (XSRF) at the Open Web Application Security Project (OWASP)
|
||||
[here](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29) and
|
||||
[here](https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet). This [Stanford University
|
||||
paper](https://seclab.stanford.edu/websec/csrf/csrf.pdf) is also a rich source of detail.
|
||||
|
||||
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 browser by overriding native JavaScript
|
||||
object constructors, and then including an API URL using a `<script>` tag.
|
||||
|
||||
This attack is only successful if the returned JSON is executable as JavaScript. Servers can
|
||||
prevent it by prefixing all JSON responses to make them non-executable, by convention using the
|
||||
well-known string `")]}',\n"`.
|
||||
|
||||
Angular's `Http` library recognizes this convention and automatically strips the string
|
||||
`")]}',\n"` from all responses before further parsing.
|
||||
|
||||
Learn more in the XSSI section of this [Google web security blog
|
||||
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html)
|
||||
|
||||
.l-main-section
|
||||
h2#code-review Auditing Angular Applications
|
||||
:marked
|
||||
Angular applications should follow the same security principles as regular web applications, and
|
||||
should be audited as such. Angular specific APIs that should be audited in a security review,
|
||||
such as the [_bypassSecurityTrust_](#bypass-security-apis) APIs, are marked in the documentation
|
||||
as security sensitive.
|
|
@ -1,703 +0,0 @@
|
|||
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.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The [`WebSocket`](https://tools.ietf.org/html/rfc6455) protocol is another important communication technology;
|
||||
we won't cover it in this chapter.
|
||||
:marked
|
||||
Modern browsers support two HTTP-based APIs:
|
||||
[XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and
|
||||
[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 of the **XHR** and **JSONP** APIs
|
||||
as we'll learn in this chapter covering:
|
||||
|
||||
- [HTTP client sample overview](#http-client)
|
||||
- [Fetch data with http.get](#fetch-data)
|
||||
<li if-docs="ts"> [RxJS Observable of HTTP Responses](#rxjs)</li>
|
||||
<li if-docs="ts"> [Enabling RxJS Operators](#enable-rxjs-operators)</li>
|
||||
- [Extract JSON data](#extract-data)
|
||||
- [Error handling](#error-handling)
|
||||
- [Send data to the server](#update)
|
||||
<li if-docs="ts"> [Promises instead of observables](#promises)</li>
|
||||
- [Cross-origin requests: Wikipedia example](#cors)
|
||||
<ul if-docs="ts">
|
||||
<li> [Set query string parameters](#search-parameters)</li>
|
||||
<li> [Debounce search term input](#more-observables)</li>
|
||||
</ul>
|
||||
- [Appendix: the in-memory web api service](#in-mem-web-api)
|
||||
|
||||
We illustrate these topics with code that you can <live-example>run live</live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Demos
|
||||
|
||||
This chapter describes server communication with the help of the following demos
|
||||
|
||||
block demos-list
|
||||
:marked
|
||||
- [HTTP client: Tour of Heroes with Observables](#http-client)
|
||||
- [HTTP client: Tour of Heroes with !{_Promise}s](#promises)
|
||||
- [JSONP client: Wikipedia to fetch data from a service that does not support CORS](#cors)
|
||||
- [JSONP client: Wikipedia using observable operators to reduce server calls](#more-observables)
|
||||
|
||||
:marked
|
||||
These demos are orchestrated by the root `AppComponent`
|
||||
+makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts')
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
There is nothing remarkable here _except_ for the import of RxJS operators.
|
||||
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs')(format='.')
|
||||
:marked
|
||||
We'll talk about that [below](#rxjs) when we're ready to explore observables.
|
||||
:marked
|
||||
First, we have to configure our application to use server communication facilities.
|
||||
|
||||
.l-main-section#http-providers
|
||||
:marked
|
||||
# Providing HTTP Services
|
||||
|
||||
We use the !{_Angular_Http} client to communicate with a server using a familiar HTTP request/response protocol.
|
||||
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
:marked
|
||||
SystemJS knows how to load services from the !{_Angular_http_library} when we import from the `@angular/http` module
|
||||
because we registered that module name in the `system.config` file.
|
||||
|
||||
:marked
|
||||
Before we can use the `!{_Http}` client , we'll have to register it as a service provider with the Dependency Injection system.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about providers in the [Dependency Injection](dependency-injection.html) chapter.
|
||||
|
||||
:marked
|
||||
In this demo, we register providers in the `bootstrap()` method of
|
||||
<span ngio-ex>app/main.ts</span>.
|
||||
|
||||
+makeExample('server-communication/ts/app/main.ts', 'v1', 'app/main.ts (v1)')(format='.')
|
||||
|
||||
block http-providers
|
||||
:marked
|
||||
We begin by importing the symbols we need, most of them familiar by now. The newcomer is `HTTP_PROVIDERS`,
|
||||
a collection of service providers from the !{_Angular_http_library}.
|
||||
|
||||
We register HTTP providers in the bootstrap method by passing them in an array as the second parameter after the root component.
|
||||
|
||||
### Why register in *bootstrap*?
|
||||
|
||||
We prefer to register application-wide providers in the metadata `providers` array
|
||||
of the root `AppComponent` like this:
|
||||
+makeExample('server-communication/ts/app/app.component.ts','http-providers')(format='.')
|
||||
:marked
|
||||
Here we register the providers in the `bootstrap` method in the `main.ts` file. Why?
|
||||
|
||||
This is a *sample application* that doesn't talk to a real server.
|
||||
We're going to reconfigure the (typically-hidden) `XhrBackend` service with a fake provider
|
||||
that fetches and saves sample data from an in-memory data store.
|
||||
This replacement service is called the [*in-memory web api*](#in-mem-web-api).
|
||||
|
||||
Such sleight-of-hand is something the root application component should *not* know about.
|
||||
For this reason, and this reason *only*, we hide it *above* the `AppComponent` in `main.ts`.
|
||||
|
||||
.l-main-section#http-client
|
||||
:marked
|
||||
# The Tour of Heroes _HTTP_ Client Demo
|
||||
|
||||
Our 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 us add new heroes, and saves them to the server.
|
||||
We use the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
|
||||
|
||||
It works like this.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/server-communication/http-toh.gif' alt="ToH mini app" width="250")
|
||||
:marked
|
||||
This demo has a single component, the `HeroListComponent`. Here's its template:
|
||||
+makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (template)')
|
||||
:marked
|
||||
It presents the list of heroes with an `ngFor`.
|
||||
Below the list is an input box and an *Add Hero* button where we can enter the names of new heroes
|
||||
and add them to the database.
|
||||
We use a [template reference variable](template-syntax.html#ref-vars), `newHeroName`, to access the
|
||||
value of the input box in the `(click)` event binding.
|
||||
When the user clicks the button, we pass that value to the component's `addHero` method and then
|
||||
clear it to make it ready for a new hero name.
|
||||
|
||||
Below the button is an area for an error message.
|
||||
|
||||
a#oninit
|
||||
a#HeroListComponent
|
||||
:marked
|
||||
### The *HeroListComponent* class
|
||||
Here's the component class:
|
||||
+makeExample('server-communication/ts/app/toh/hero-list.component.ts','component', 'app/toh/hero-list.component.ts (class)')
|
||||
:marked
|
||||
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 doesn't know or care how we get the data.
|
||||
It delegates to the `HeroService`.
|
||||
|
||||
This is a golden rule: **always delegate data access to a supporting service class**.
|
||||
|
||||
Although _at runtime_ the component requests heroes immediately after creation,
|
||||
we do **not** call the service's `get` method in the component's constructor.
|
||||
We call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) instead
|
||||
and count on Angular to call `ngOnInit` when it instantiates this component.
|
||||
.l-sub-section
|
||||
:marked
|
||||
This is a *best practice*.
|
||||
Components are easier to test and debug when their constructors are simple and all real work
|
||||
(especially calling a remote server) is handled in a separate method.
|
||||
block getheroes-and-addhero
|
||||
:marked
|
||||
The service's `getHeroes()` and `addHero()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
|
||||
|
||||
*Observables* are a big topic, beyond the scope of this chapter.
|
||||
But we need to know a little about them to appreciate what is going on here.
|
||||
|
||||
We should think of an `Observable` as a stream of events published by some source.
|
||||
We listen for events in this stream by ***subscribing*** to the `Observable`.
|
||||
In these subscriptions we specify the actions to take when the web request
|
||||
produces a success event (with the hero data in the event payload) or a fail event (with the error in the payload).
|
||||
|
||||
:marked
|
||||
With our basic intuitions about the component squared away, we're ready to look inside the `HeroService`.
|
||||
|
||||
a#HeroService
|
||||
.l-main-section#fetch-data
|
||||
:marked
|
||||
## Fetch data with the **HeroService**
|
||||
|
||||
In many of our previous samples we faked the interaction with the server by
|
||||
returning mock heroes in a service like this one:
|
||||
+makeExample('toh-4/ts/app/hero.service.ts', 'just-get-heroes')(format=".")
|
||||
:marked
|
||||
In this chapter, we revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)')
|
||||
|
||||
:marked
|
||||
Notice that the !{_Angular_Http} client service is
|
||||
[injected](dependency-injection.html) into the `HeroService` constructor.
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor')
|
||||
:marked
|
||||
Look closely at how we call `!{_priv}http.get`
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'http-get', 'app/toh/hero.service.ts (getHeroes)')(format=".")
|
||||
:marked
|
||||
We pass the resource URL to `get` and it calls the server which should return heroes.
|
||||
.l-sub-section
|
||||
:marked
|
||||
It *will* return heroes once we've set up the [in-memory web api](#in-mem-web-api)
|
||||
described in the appendix below.
|
||||
Alternatively, we can (temporarily) target a JSON file by changing the endpoint URL:
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
<a id="rxjs"></a>
|
||||
The return value may surprise us.
|
||||
Many of us who are familiar with asynchronous methods in modern JavaScript would expect the `get` method to return a
|
||||
[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
We'd expect to chain a call to `then()` and extract the heroes.
|
||||
Instead we're calling a `map()` method.
|
||||
Clearly this is not a promise.
|
||||
|
||||
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
|
||||
and `map` is one of the RxJS *operators*.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## RxJS Library
|
||||
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular,
|
||||
that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern.
|
||||
|
||||
All of our Developer Guide samples have installed the RxJS npm package and loaded via `system.js`
|
||||
because observables are used widely in Angular applications.
|
||||
We certainly need it now when working with the HTTP client.
|
||||
And we must take a critical extra step to make RxJS observables usable.
|
||||
|
||||
### Enable RxJS Operators
|
||||
The RxJS library is quite large.
|
||||
Size matters when we build a production application and deploy it to mobile devices.
|
||||
We should include only those features that we actually need.
|
||||
|
||||
Accordingly, Angular exposes a stripped down version of `Observable` in the `rxjs/Observable` module,
|
||||
a version that lacks most of the operators including some we'd like to use here
|
||||
such as the `map` method we called above in `getHeroes`.
|
||||
|
||||
It's up to us to add the operators we need.
|
||||
|
||||
We could add _every_ RxJS operators with a single import statement.
|
||||
While that is the easiest thing to do, we'd pay a penalty in extended launch time and application size
|
||||
because the full library is so big. We only use a few operators in our app.
|
||||
|
||||
Instead, we'll import each `Observable` operator and static class method, one-by-one, until we have a custom *Observable* implementation tuned
|
||||
precisely to our requirements. We'll put the `import` statements in one `app/rxjs-operators.ts` file.
|
||||
+makeExample('server-communication/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
|
||||
:marked
|
||||
If we forget an operator, the TypeScript compiler will warn that it's missing and we'll update this file.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We don't need _all_ of these particular operators in the `HeroService` — just `map`, `catch` and `throw`.
|
||||
We'll need the other operators later, in a *Wiki* example [below](#more-observables).
|
||||
:marked
|
||||
Finally, we import `rxjs-operator`_itself_ in our `app.component.ts`:
|
||||
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs', 'app/app.component.ts (import rxjs)')(format=".")
|
||||
:marked
|
||||
Let's return to our study of the `HeroService`.
|
||||
|
||||
l-main-section
|
||||
a#extract-data
|
||||
:marked
|
||||
## Process the response object
|
||||
Remember that our `getHeroes()` method mapped the `!{_priv}http.get` response object to heroes with an `!{_priv}extractData` helper method:
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'extract-data', 'app/toh/hero.service.ts (excerpt)')(format=".")
|
||||
:marked
|
||||
The `response` object does not hold our data in a form we can use directly.
|
||||
To make it useful in our application we must parse the response data into a JSON object
|
||||
|
||||
#### Parse to JSON
|
||||
block parse-json
|
||||
:marked
|
||||
The response data are in JSON string form.
|
||||
We must parse that string into JavaScript objects which we do by calling `response.json()`.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
This is not Angular's own design.
|
||||
The Angular HTTP client follows the ES2015 specification for the
|
||||
[response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function.
|
||||
That spec defines a `json()` method that parses the response body into a JavaScript object.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We shouldn't expect the decoded JSON to be the heroes !{_array} directly.
|
||||
The server we're calling always wraps JSON results in an object with a `data`
|
||||
property. We have to unwrap it to get the heroes.
|
||||
This is conventional web api behavior, driven by
|
||||
[security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside).
|
||||
.alert.is-important
|
||||
:marked
|
||||
Make no assumptions about the server API.
|
||||
Not all servers return an object with a `data` property.
|
||||
:marked
|
||||
### Do not return the response object
|
||||
Our `getHeroes()` could have returned the HTTP response. Bad idea!
|
||||
The point of a data service is to hide the server interaction details from consumers.
|
||||
The component that calls the `HeroService` wants heroes.
|
||||
It has no interest in what we do to get them.
|
||||
It doesn't care where they come from.
|
||||
And it certainly doesn't want to deal with a response object.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.callout.is-important
|
||||
header HTTP GET is delayed
|
||||
:marked
|
||||
The `!{_priv}http.get` does **not send the request just yet!** This observable is
|
||||
[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables)
|
||||
which means the request won't go out until something *subscribes* to the observable.
|
||||
That *something* is the [HeroListComponent](#subscribe).
|
||||
|
||||
a#error-handling
|
||||
:marked
|
||||
### Always handle errors
|
||||
|
||||
Whenever we deal with I/O we must be prepared for something to go wrong as it surely will.
|
||||
We should catch errors in the `HeroService` and do something with them.
|
||||
We may also pass an error message back to the component for presentation to the user
|
||||
but only if we can say something the user can understand and act upon.
|
||||
|
||||
In this simple app we provide rudimentary error handling in both the service and the component.
|
||||
block error-handling
|
||||
:marked
|
||||
The eagle-eyed reader may have spotted our use of the `catch` operator in conjunction with a `handleError` method.
|
||||
We haven't discussed so far how that actually works.
|
||||
|
||||
We use the Observable `catch` operator on the service level.
|
||||
It takes an error handling function with an error object as the argument.
|
||||
Our service handler, `handleError`, logs the response to the console,
|
||||
transforms the error into a user-friendly message, and returns the message in a new, failed observable via `Observable.throw`.
|
||||
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'error-handling', 'app/toh/hero.service.ts (excerpt)')(format=".")
|
||||
|
||||
a#subscribe
|
||||
a#hero-list-component
|
||||
h4 #[b HeroListComponent] error handling
|
||||
block hlc-error-handling
|
||||
:marked
|
||||
Back in the `HeroListComponent`, where we called `!{_priv}heroService.getHeroes()`,
|
||||
we supply the `subscribe` function with a second function parameter to handle the error message.
|
||||
It sets an `errorMessage` variable which we've bound conditionally in the `HeroListComponent` template.
|
||||
|
||||
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'getHeroes', 'app/toh/hero-list.component.ts (getHeroes)')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Want to see it fail? Reset the api endpoint in the `HeroService` to a bad value. Remember to restore it!
|
||||
|
||||
|
||||
<a id="update"></a>
|
||||
<a id="post"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Send data to the server
|
||||
|
||||
So far we've seen how to retrieve data from a remote location using an HTTP service.
|
||||
Let's add the ability to create new heroes and save them in the backend.
|
||||
|
||||
We'll create an easy method for the `HeroListComponent` to call, an `addHero()` method that takes
|
||||
just the name of a new hero:
|
||||
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero-sig')(format=".")
|
||||
|
||||
:marked
|
||||
To implement it, we need to know some details about the server's api for creating heroes.
|
||||
|
||||
[Our data server](#server) follows typical REST guidelines.
|
||||
It expects a [`POST`](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) request
|
||||
at the same endpoint where we `GET` heroes.
|
||||
It expects the new hero data to arrive in the body of the request,
|
||||
structured like a `Hero` entity but without the `id` property.
|
||||
The body of the request should look like this:
|
||||
|
||||
code-example(format="." language="javascript").
|
||||
{ "name": "Windstorm" }
|
||||
:marked
|
||||
The server will generate the `id` and return the entire `JSON` representation
|
||||
of the new hero including its generated id. The hero arrives tucked inside a response object
|
||||
with its own `data` property.
|
||||
|
||||
Now that we know how the API works, we implement `addHero()`like this:
|
||||
|
||||
+ifDocsFor('ts')
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'import-request-options', 'app/toh/hero.service.ts (additional imports)')(format=".")
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'addhero', 'app/toh/hero.service.ts (addHero)')(format=".")
|
||||
|
||||
:marked
|
||||
### Headers
|
||||
|
||||
The `Content-Type` header allows us to inform the server that the body will represent JSON.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
[Headers](../api/http/index/Headers-class.html) are one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
|
||||
Compose the options object and pass it in as the *third* parameter of the `post` method, as shown above.
|
||||
|
||||
:marked
|
||||
### Body
|
||||
|
||||
Despite the content type being specified as JSON, the POST body must actually be a *string*.
|
||||
Hence, we explicitly encode the JSON hero content before passing it in as the body argument.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
:marked
|
||||
We may be able to skip the `JSON.stringify` step in the near future.
|
||||
|
||||
:marked
|
||||
### JSON results
|
||||
|
||||
As with `getHeroes()`, we [extract the data](#extract-data) from the response using the
|
||||
`!{_priv}extractData()` helper.
|
||||
|
||||
block hero-list-comp-add-hero
|
||||
:marked
|
||||
Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method.
|
||||
When the data, arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
||||
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
h2#promises Fall back to Promises
|
||||
:marked
|
||||
Although the Angular `http` client API returns an `Observable<Response>` we can turn it into a
|
||||
[Promise<Response>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer.
|
||||
It's easy to do and a promise-based version looks much like the observable-based version in simple cases.
|
||||
.l-sub-section
|
||||
:marked
|
||||
While promises may be more familiar, observables have many advantages.
|
||||
Don't rush to promises until you give observables a chance.
|
||||
:marked
|
||||
Let's rewrite the `HeroService` using promises , highlighting just the parts that are different.
|
||||
+makeTabs(
|
||||
'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts',
|
||||
'methods, methods',
|
||||
'app/toh/hero.service.promise.ts (promise-based), app/toh/hero.service.ts (observable-based)')
|
||||
:marked
|
||||
Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`.
|
||||
|
||||
We move the observable's `map` callback to the first *success* parameter and its `catch` callback to the second *fail* parameter
|
||||
and we're done!
|
||||
Or we can follow the promise `then.catch` pattern as we do in the second `addHero` example.
|
||||
|
||||
Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable.
|
||||
|
||||
The diagnostic *log to console* is just one more `then` in the promise chain.
|
||||
|
||||
We have to adjust the calling component to expect a `Promise` instead of an `Observable`.
|
||||
|
||||
+makeTabs(
|
||||
'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts',
|
||||
'methods, methods',
|
||||
'app/toh/hero-list.component.promise.ts (promise-based), app/toh/hero-list.component.ts (observable-based)')
|
||||
:marked
|
||||
The only obvious difference is that we call `then` on the returned promise instead of `subscribe`.
|
||||
We give both methods the same functional arguments.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The less obvious but critical difference is that these two methods return very different results!
|
||||
|
||||
The promise-based `then` returns another promise. We can keep chaining more `then` and `catch` calls, getting a new promise each time.
|
||||
|
||||
The `subscribe` method returns a `Subscription`. A `Subscription` is not another `Observable`.
|
||||
It's the end of the line for observables. We can't call `map` on it or call `subscribe` again.
|
||||
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
|
||||
|
||||
Learn more about observables to understand the implications and consequences of subscriptions.
|
||||
|
||||
h2#cors Cross-origin requests: Wikipedia example
|
||||
:marked
|
||||
We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
|
||||
This is the most common approach for server communication.
|
||||
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.
|
||||
The *origin* is the combination of URI scheme, hostname and port number.
|
||||
This is called the [Same-origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy).
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the
|
||||
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol.
|
||||
If the server requires user credentials, we'll enable them in the [request headers](#headers).
|
||||
|
||||
:marked
|
||||
Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP).
|
||||
Wikipedia is one such server.
|
||||
.l-sub-section
|
||||
:marked
|
||||
This [StackOverflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP.
|
||||
:marked
|
||||
### Search wikipedia
|
||||
|
||||
Let's build a simple search that shows suggestions from wikipedia as we type in a text box.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250")
|
||||
|
||||
block wikipedia-jsonp+
|
||||
:marked
|
||||
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. Let's use the latter for this example.
|
||||
The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts us to `GET` requests.
|
||||
All other HTTP methods throw an error because JSONP is a read-only facility.
|
||||
|
||||
As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
||||
|
||||
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts',null,'app/wiki/wikipedia.service.ts')
|
||||
:marked
|
||||
The constructor expects Angular to inject its `jsonp` service.
|
||||
We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`.
|
||||
|
||||
<a id="query-parameters"></a>
|
||||
:marked
|
||||
### Search parameters
|
||||
The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)
|
||||
expects four parameters (key/value pairs) to arrive in the request URL's query string.
|
||||
The keys are `search`, `action`, `format`, and `callback`.
|
||||
The value of the `search` key is the user-supplied search term to find in Wikipedia.
|
||||
The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `JSONP` technique requires that we pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`.
|
||||
The server uses that name to build a JavaScript wrapper function in its response which Angular ultimately calls to extract the data.
|
||||
All of this happens under the hood.
|
||||
:marked
|
||||
If we're looking for articles with the word "Angular", we could construct the query string by hand and call `jsonp` like this:
|
||||
+makeExample('server-communication/ts/app/wiki/wikipedia.service.1.ts','query-string')(format='.')
|
||||
:marked
|
||||
In more parameterized examples we might prefer to build the query string with the Angular `URLSearchParams` helper as shown here:
|
||||
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','search-parameters','app/wiki/wikipedia.service.ts (search parameters)')(format=".")
|
||||
:marked
|
||||
This time we call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object.
|
||||
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','call-jsonp','app/wiki/wikipedia.service.ts (call jsonp)')(format=".")
|
||||
:marked
|
||||
`Jsonp` flattens the `params` object into the same query string we saw earlier before putting the request on the wire.
|
||||
|
||||
<a id="wikicomponent"></a>
|
||||
:marked
|
||||
### The WikiComponent
|
||||
|
||||
Now that we have a service that can query the Wikipedia API,
|
||||
we turn to the component that takes user input and displays search results.
|
||||
|
||||
+makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts')
|
||||
:marked
|
||||
The `providers` array in the component metadata specifies the Angular `JSONP_PROVIDERS` collection that supports the `Jsonp` service.
|
||||
We register that collection at the component level to make `Jsonp` injectable in the `WikipediaService`.
|
||||
|
||||
The component presents an `<input>` element *search box* to gather search terms from the user.
|
||||
and calls a `search(term)` method after each `keyup` event.
|
||||
|
||||
The `search(term)` method delegates to our `WikipediaService` which returns an observable array of string results (`Observable<string[]>`).
|
||||
Instead of subscribing to the observable inside the component as we did in the `HeroListComponent`,
|
||||
we forward the observable result to the template (via `items`) where the [async pipe](pipes.html#async-pipe)
|
||||
in the `ngFor` handles the subscription.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We often use the [async pipe](pipes.html#async-pipe) in read-only components where the component has no need to interact with the data.
|
||||
We couldn't use the pipe in the `HeroListComponent` because the "add hero" feature pushes newly created heroes into the list.
|
||||
|
||||
:marked
|
||||
## Our wasteful app
|
||||
|
||||
Our wikipedia search makes too many calls to the server.
|
||||
It is inefficient and potentially expensive on mobile devices with limited data plans.
|
||||
|
||||
### 1. Wait for the user to stop typing
|
||||
At the moment we call the server after every key stroke.
|
||||
The app should only make requests when the user *stops typing* .
|
||||
Here's how it *should* work — and *will* work — when we're done refactoring:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250")
|
||||
:marked
|
||||
### 2. Search when the search term changes
|
||||
|
||||
Suppose the user enters the word *angular* in the search box and pauses for a while.
|
||||
The application issues a search request for *Angular*.
|
||||
|
||||
Then the user backspaces over the last three letters, *lar*, and immediately re-types *lar* before pausing once more.
|
||||
The search term is still "angular". The app shouldn't make another request.
|
||||
|
||||
### 3. Cope with out-of-order responses
|
||||
|
||||
The user enters *angular*, pauses, clears the search box, and enters *http*.
|
||||
The application issues two search requests, one for *angular* and one for *http*.
|
||||
|
||||
Which response will arrive first? We can't be sure.
|
||||
A load balancer could dispatch the requests to two different servers with different response times.
|
||||
The results from the first *angular* request might arrive after the later *http* results.
|
||||
The user will be confused if we display the *angular* results to the *http* query.
|
||||
|
||||
When there are multiple requests in-flight, the app should present the responses
|
||||
in the original request order. That won't happen if *angular* results arrive last.
|
||||
|
||||
<a id="more-observables"></a>
|
||||
## More fun with Observables
|
||||
We can address these problems and improve our app with the help of some nifty observable operators.
|
||||
|
||||
We could make our changes to the `WikipediaService`.
|
||||
But we sense that our concerns are driven by the user experience so we update the component class instead.
|
||||
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts')
|
||||
:marked
|
||||
We made no changes to the template or metadata, confining them all to the component class.
|
||||
Let's review those changes.
|
||||
|
||||
### Create a stream of search terms
|
||||
|
||||
We're binding to the search box `keyup` event and calling the component's `search` method after each keystroke.
|
||||
|
||||
We turn these events into an observable stream of search terms using a `Subject`
|
||||
which we import from the RxJS observable library:
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject')
|
||||
:marked
|
||||
Each search term is a string, so we create a new `Subject` of type `string` called `searchTermStream`.
|
||||
After every keystroke, the `search` method adds the search box value to that stream
|
||||
via the subject's `next` method.
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.')
|
||||
:marked
|
||||
### Listen for search terms
|
||||
|
||||
Earlier, we passed each search term directly to the service and bound the template to the service results.
|
||||
Now we listen to the *stream of terms*, manipulating the stream before it reaches the `WikipediaService`.
|
||||
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.')
|
||||
:marked
|
||||
We wait for the user to stop typing for at least 300 milliseconds
|
||||
([debounceTime](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)).
|
||||
Only changed search values make it through to the service
|
||||
([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)).
|
||||
|
||||
The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each request.
|
||||
We could have multiple requests *in flight*, all awaiting the server's reply,
|
||||
which means multiple *observables-of-strings* could arrive at any moment in any order.
|
||||
|
||||
The [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
|
||||
(formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables,
|
||||
re-arranges them in their original request order,
|
||||
and delivers to subscribers only the most recent search results.
|
||||
|
||||
The displayed list of search results stays in sync with the user's sequence of search terms.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class
|
||||
in `rxjs-operators` as [described above](#rxjs)
|
||||
|
||||
a#in-mem-web-api
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: Tour of Heroes in-memory server
|
||||
|
||||
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
||||
- var _heroesJsonPath = (_docsFor == 'dart' ? 'web' : 'app') + '/heroes.json';
|
||||
+makeJson('server-communication/' + _docsFor + '/' + _heroesJsonPath, null, _heroesJsonPath)(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
We wrap the heroes array in an object with a `data` property for the same reason that a data server does:
|
||||
to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk)
|
||||
posed by top-level JSON arrays.
|
||||
:marked
|
||||
We'd set the endpoint to the JSON file like this:
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
|
||||
|
||||
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
|
||||
:marked
|
||||
The *get heroes* scenario would work.
|
||||
But we want to *save* data too. We can't save changes to a JSON file. We need a web API server.
|
||||
We didn't want the hassle of setting up and maintaining a real server for this chapter.
|
||||
So we turned to an *in-memory web API simulator* instead.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The in-memory web api is not part of the Angular core.
|
||||
It's an optional service in its own `angular-in-memory-web-api` library
|
||||
that we installed with npm (see `package.json`) and
|
||||
registered for module loading by SystemJS (see `systemjs.config.js`)
|
||||
|
||||
:marked
|
||||
The in-memory web API gets its data from !{_a_ca_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.
|
||||
|
||||
Here's the class we created for this sample based on the JSON data:
|
||||
+makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".")
|
||||
:marked
|
||||
Ensure that the `HeroService` endpoint refers to the web API:
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".")
|
||||
:marked
|
||||
Finally, we need to redirect client HTTP requests to the in-memory web API.
|
||||
block redirect-to-web-api
|
||||
:marked
|
||||
This redirection is easy to configure because Angular's `http` service delegates the client/server communication tasks
|
||||
to a helper service called the `XHRBackend`.
|
||||
|
||||
To enable our server simulation, we replace the default `XHRBackend` service with
|
||||
the in-memory web API service using standard Angular provider registration techniques.
|
||||
We initialize the in-memory web API with *seed data* from the mock hero dataset at the same time.
|
||||
:marked
|
||||
Here is the revised (and final) version of <span ngio-ex>app/main.ts></span> demonstrating these steps.
|
||||
|
||||
+makeExcerpt('app/main.ts', 'final')
|
||||
|
||||
:marked
|
||||
See the full source code in the <live-example></live-example>.
|
|
@ -1,180 +0,0 @@
|
|||
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").
|
||||
|
||||
Make sure you have [!{_prereq} installed](#install-prerequisites "What if you don't have !{_prereq}?").
|
||||
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.
|
||||
|
||||
a#clone
|
||||
:marked
|
||||
### Clone
|
||||
|
||||
Perform the _clone-to-launch_ steps with these terminal commands.
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
git clone !{_qsRepo}.git quickstart
|
||||
cd quickstart
|
||||
!{_npm} !{_install}
|
||||
!{_npm} !{_start}
|
||||
|
||||
a#download
|
||||
:marked
|
||||
### Download
|
||||
<a href="!{_qsRepoZip}" 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}
|
||||
|
||||
.l-main-section#seed
|
||||
:marked
|
||||
## What's in the QuickStart seed?
|
||||
|
||||
block qs-seed
|
||||
:marked
|
||||
The **QuickStart seed** contains the same application as the QuickStart playground
|
||||
and even has <live-example>its own _playground_</live-example>
|
||||
that accomodates development of richer examples in a live coding environment.
|
||||
|
||||
But it's true purpose is to provide a solid foundation for _local_ development.
|
||||
Consequently, there are _many more files_ in the project folder on your machine,
|
||||
most of which you can [learn about later](setup-systemjs-anatomy.html "Setup Anatomy").
|
||||
|
||||
block core-files
|
||||
:marked
|
||||
Focus on the following three TypeScript (`.ts`) files in the **`/app`** folder.
|
||||
|
||||
.filetree
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file app.module.ts
|
||||
.file main.ts
|
||||
|
||||
+makeTabs(`
|
||||
setup/ts/app/app.component.ts,
|
||||
setup/ts/app/app.module.ts,
|
||||
setup/ts/app/main.ts
|
||||
`, '', `
|
||||
app/app.component.ts,
|
||||
app/app.module.ts,
|
||||
app/main.ts
|
||||
`)(format='.')
|
||||
|
||||
:marked
|
||||
All guides and cookbooks have _at least these core files_. Each file has a distinct purpose and evolves independently as the application grows.
|
||||
|
||||
style td, th {vertical-align: top}
|
||||
table(width="100%")
|
||||
col(width="20%")
|
||||
col(width="80%")
|
||||
tr
|
||||
th File
|
||||
th Purpose
|
||||
tr
|
||||
td <ngio-ex>app.component.ts</ngio-ex>
|
||||
td
|
||||
:marked
|
||||
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")
|
||||
td <code>app.module.ts</code>
|
||||
td
|
||||
:marked
|
||||
Defines `AppModule`, the [root module](appmodule.html "AppModule: the root module") that tells Angular how to assemble the application.
|
||||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
tr
|
||||
td <ngio-ex>main.ts</ngio-ex>
|
||||
td
|
||||
:marked
|
||||
Compiles the application with the [JIT compiler](../glossary.html#jit)
|
||||
and [bootstraps](appmodule.html#main "bootstrap the application") the application to run in the browser.
|
||||
That's a reasonable choice for the development of most projects and
|
||||
it's the only viable choice for a sample running in a _live-coding_ environment like Plunker.
|
||||
You'll learn about alternative compiling and deployment options later in the documentation.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Next Step
|
||||
|
||||
If you're new to Angular, we recommend staying on the [learning path](learning-angular.html).
|
||||
|
||||
br
|
||||
br
|
||||
|
||||
a#install-prerequisites
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: !{_prereq}
|
||||
block install-tooling
|
||||
:marked
|
||||
Node.js and npm are essential to modern web development with Angular and other platforms.
|
||||
Node powers client development and build tools.
|
||||
The _npm_ package manager, itself a _node_ application, installs JavaScript libraries.
|
||||
|
||||
<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">
|
||||
Get them now</a> if they're not already installed on your machine.
|
||||
|
||||
**Verify that you are running node `v4.x.x` or higher and npm `3.x.x` or higher**
|
||||
by running the commands `node -v` and `npm -v` in a terminal/console window.
|
||||
Older versions produce errors.
|
||||
|
||||
We recommend [nvm](https://github.com/creationix/nvm) for managing multiple versions of node and npm.
|
||||
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
|
||||
use other versions of node and npm.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
a#why-locally
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: Why develop locally
|
||||
|
||||
<live-example>Live coding</live-example> in the browser is a great way to explore Angular.
|
||||
|
||||
Links on almost every documentation page open completed samples in the browser.
|
||||
You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
|
||||
|
||||
The [QuickStart](../quickstart.html "Angular QuickStart Playground") shows just the `AppComponent` file.
|
||||
It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_.
|
||||
so the reader can discover Angular without distraction.
|
||||
The other samples are based on the QuickStart seed.
|
||||
|
||||
As much fun as this is ...
|
||||
* you can't ship your app in plunker
|
||||
* you aren't always online when writing code
|
||||
* transpiling TypeScript in the browser is slow
|
||||
* the type support, refactoring, and code completion only work in your local IDE
|
||||
|
||||
Use the <live-example><i>live coding</i></live-example> environment as a _playground_,
|
||||
a place to try the documentation samples and experiment on your own.
|
||||
It's the perfect place to reproduce a bug when you want to
|
||||
<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or
|
||||
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>.
|
||||
|
||||
For real development, we strongly recommend [developing locally](#develop-locally).
|
|
@ -1,336 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
One of the defining features of a single page application is its manipulation
|
||||
of the DOM tree. Instead of serving a whole new page every time a user
|
||||
navigates, whole sections of the DOM appear and disappear according
|
||||
to the application state. In this chapter we'll to look at how Angular
|
||||
manipulates the DOM and how we can do it ourselves in our own directives.
|
||||
|
||||
In this chapter we will
|
||||
- [learn what structural directives are](#definition)
|
||||
- [study *ngIf*](#ngIf)
|
||||
- [discover the <template> element](#template)
|
||||
- [understand the asterisk (\*) in **ngFor*](#asterisk)
|
||||
- [write our own structural directive](#unless)
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
<a id="definition"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## What are structural directives?
|
||||
|
||||
There are three kinds of Angular directives:
|
||||
1. Components
|
||||
1. Attribute directives
|
||||
1. Structural directives
|
||||
|
||||
The *Component* is really a directive with a template.
|
||||
It's the most common of the three directives and we write lots of them as we build our application.
|
||||
|
||||
The [*Attribute* directive](attribute-directives.html) changes the appearance or behavior of an element.
|
||||
The built-in [NgStyle](template-syntax.html#ngStyle) directive, for example,
|
||||
can change several element styles at the same time.
|
||||
We can use it to render text bold, italic, and lime green by binding to a
|
||||
component property that requests such a sickening result.
|
||||
|
||||
A *Structural* directive changes the DOM layout by adding and removing DOM elements.
|
||||
We've seen three of the built-in structural directives in other chapters: [ngIf](template-syntax.html#ngIf),
|
||||
[ngSwitch](template-syntax.html#ngSwitch) and [ngFor](template-syntax.html#ngFor).
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'structural-directives')(format=".")
|
||||
|
||||
|
||||
<a id="ngIf"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## NgIf Case Study
|
||||
|
||||
Let’s focus on `ngIf`. It's a great example of a structural
|
||||
directive: it takes a boolean and makes an entire chunk of DOM appear
|
||||
or disappear.
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngIf')(format=".")
|
||||
|
||||
:marked
|
||||
The `ngIf` directive does not hide the element.
|
||||
Using browser developer tools we can see that, when the condition is true, the top
|
||||
paragraph is in the DOM and the bottom disused paragraph is completely
|
||||
absent from the DOM! In its place are empty `<script>` tags.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/structural-directives/element-not-in-dom.png' alt="element not in dom")
|
||||
|
||||
:marked
|
||||
### Why *remove* rather than *hide*?
|
||||
We could hide the unwanted paragraph by setting its css `display` style to `none`.
|
||||
The element would remain in the DOM while invisible. Instead we removed it with `ngIf`.
|
||||
|
||||
The difference matters. When we hide an element,
|
||||
the component's behavior continues.
|
||||
It remains attached to its DOM element. It continues to listen to events.
|
||||
Angular keeps checking for changes that could affect data bindings.
|
||||
Whatever the component was doing it keeps doing.
|
||||
|
||||
Although invisible, the component — and all of its descendent components —
|
||||
tie up resources that might be more useful elsewhere.
|
||||
The performance and memory burden can be substantial and the user may not benefit at all.
|
||||
|
||||
On the positive side, showing the element again is very quick.
|
||||
The component's previous state is preserved and ready to display.
|
||||
The component doesn't re-initialize — an operation that could be expensive.
|
||||
|
||||
`ngIf` is different.
|
||||
Setting `ngIf` to false **does** affect the component's resource consumption.
|
||||
Angular removes the element from DOM, stops change detection for the associated component,
|
||||
detaches it from DOM events (the attachments that it made) and destroys the component.
|
||||
The component can be garbage-collected (we hope) and free up memory.
|
||||
|
||||
Components often have child components which themselves have children.
|
||||
All of them are destroyed when `ngIf` destroys the common ancestor.
|
||||
This cleanup effort is usually a good thing.
|
||||
|
||||
Of course it isn't *always* a good thing.
|
||||
It might be a bad thing if we need that particular component again soon.
|
||||
|
||||
The component's state might be expensive to re-construct.
|
||||
When `ngIf` becomes `true` again, Angular recreates the component and its subtree.
|
||||
Angular runs every component's initialization logic again. That could be expensive ... as when
|
||||
a component re-fetches data that had been in memory just moments ago.
|
||||
.l-sub-section
|
||||
:marked
|
||||
*Design thought*: minimize initialization effort and consider caching state in a
|
||||
companion service.
|
||||
:marked
|
||||
Although there are pros and cons to each approach,
|
||||
in general it is best to use `ngIf` to remove unwanted components rather than
|
||||
hide them.
|
||||
|
||||
**These same considerations apply to every structural directive, whether built-in or custom.**
|
||||
We should ask ourselves — and the users of our directives — to think carefully
|
||||
about the consequences of adding and removing elements and of creating and destroying components.
|
||||
|
||||
Let's see these dynamics at work. For fun, we'll stack the deck *against*
|
||||
our recommendation and consider a component called `heavy-loader` that
|
||||
***pretends*** to load a ton of data when initialized.
|
||||
|
||||
We'll display two instances of the component. We toggle the visibility of the first one with CSS.
|
||||
We toggle the second into and out of the DOM with `ngIf`.
|
||||
|
||||
+makeTabs(
|
||||
`structural-directives/ts/app/structural-directives.component.html,
|
||||
structural-directives/ts/app/heavy-loader.component.ts`,
|
||||
'message-log,',
|
||||
'template (excerpt), heavy-loader.component.ts')
|
||||
|
||||
:marked
|
||||
We also log when a component is created or destroyed
|
||||
using the built-in `ngOnInit` and `ngOnDestroy` [lifecycle hooks](lifecycle-hooks.html).
|
||||
Here it is in action:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/structural-directives/heavy-loader-toggle.gif' alt="heavy loader toggle")
|
||||
|
||||
:marked
|
||||
Both components are in the DOM at the start.
|
||||
First we toggle the component's visibility repeatedly. The component never leaves the DOM.
|
||||
When visible it's always the same instance and the log is quiet.
|
||||
|
||||
Then we toggle the second component with `ngIf`.
|
||||
We create a new instance every time and the log shows that we're paying
|
||||
a heavy price to create and destroy it.
|
||||
|
||||
If we really expected to "wink" the component like this, toggling visibility would be the better choice.
|
||||
In most UIs, when we "close" a component we're unlikely see it again for a long time, if ever.
|
||||
The `ngIf` would be preferred in that case.
|
||||
|
||||
<a id="template"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## The *<template>* tag
|
||||
|
||||
Structural directives, like `ngIf`, do their magic by using the
|
||||
[HTML 5 template tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
|
||||
|
||||
Outside of an Angular app, the `<template>` tag's default CSS `display` property is `none`.
|
||||
It's contents are ***invisible*** within
|
||||
a hidden [document fragment](https://developer.mozilla.org/en/docs/Web/API/DocumentFragment).
|
||||
|
||||
Inside of an app, Angular ***removes*** the`<template>` tags and their children.
|
||||
The contents are gone — but not forgotten as we'll see soon.
|
||||
|
||||
We can confirm these effects by wrapping the middle "hip" of the phrase "Hip! Hip! Hooray!" within a `<template>` tag.
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'template-tag')(format=".")
|
||||
:marked
|
||||
The display is a 'Hip! Hooray!', short of perfect enthusiasm. The DOM effects are different when Angular is in control.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/structural-directives/template-in-out-of-a2.png' alt="template outside angular")
|
||||
|
||||
:marked
|
||||
Evidently Angular replaces the `<template>` tag and its contents with empty `<script>` tags.
|
||||
That's just its default behavior.
|
||||
It can do something different as we saw when applying a variety of `ngSwitch` directives to `<template>` tags:
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngSwitch')(format=".")
|
||||
:marked
|
||||
When one of those `ngSwitch` conditions is true, Angular inserts the template's content into the DOM.
|
||||
|
||||
What does this have to do with `ngIf` and `ngFor`? We didn't use a `<template>` tag with those directives.
|
||||
|
||||
<a id="asterisk"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## The asterisk (\*) effect
|
||||
Here are those directives again. See the difference?
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'asterisk')(format=".")
|
||||
:marked
|
||||
We're prefixing these directive names with an asterisk (\*).
|
||||
|
||||
The asterisk is "syntactic sugar". It simplifies `ngIf` and `ngFor` for both the writer and the reader.
|
||||
Under the hood, Angular replaces the asterisk version with a more verbose `<template>` form.
|
||||
|
||||
The next two `ngIf` examples are effectively the same and we may write in either style:
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngIf-template')(format=".")
|
||||
|
||||
:marked
|
||||
Most of us would rather write in style (A).
|
||||
|
||||
It's worth knowing that Angular expands style (A) into style (B).
|
||||
It moves the paragraph and its contents inside a `<template>` tag.
|
||||
It moves the directive up to the `<template>` tag where it becomes a property binding,
|
||||
surrounded in square brackets. The boolean value of the host component's `condition` property
|
||||
determines whether the templated content is displayed or not.
|
||||
|
||||
Angular transforms `*ngFor` in a similar manner:
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngFor-template')(format=".")
|
||||
:marked
|
||||
The basic pattern is the same: create a `<template>`, relocate the content,
|
||||
and move the directive onto the `<template>`.
|
||||
|
||||
There are extra nuances stemming from
|
||||
Angular's [ngFor micro-syntax](template-syntax.html#ngForMicrosyntax) which expands
|
||||
into an additional `ngForOf` property binding (the iterable) and
|
||||
the `hero` template input variable (the current item in each iteration).
|
||||
|
||||
<a id="unless"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Make a structural directive
|
||||
Let's write our own structural directive, an `Unless` directive, the not-so-evil twin of `ngIf`.
|
||||
|
||||
Unlike `ngIf` which displays the template content when `true`,
|
||||
our directive displays the content when the condition is ***false***.
|
||||
|
||||
block unless-intro
|
||||
:marked
|
||||
Creating a directive is similar to creating a component.
|
||||
* import the `Directive` decorator.
|
||||
|
||||
* add a CSS **attribute selector** (in brackets) that identifies our directive.
|
||||
|
||||
* specify the name of the public `input` property for binding
|
||||
(typically the name of the directive itself).
|
||||
|
||||
* apply the decorator to our implementation class.
|
||||
|
||||
Here is how we begin:
|
||||
|
||||
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-declaration', 'unless.directive.ts (excerpt)')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Selector brackets [ ]
|
||||
The CSS syntax for selecting an attribute is a name in square brackets.
|
||||
We surround our directive name in square brackets. See *Directive configuration* on the
|
||||
[cheatsheet](cheatsheet.html).
|
||||
|
||||
### Selector name prefixes
|
||||
|
||||
We recommend picking a selector name with a prefix to ensure
|
||||
that it cannot conflict with any standard HTML attribute, now or in the future.
|
||||
|
||||
We do **not** prefix our `unless` directive name with **`ng`**.
|
||||
That prefix belongs to Angular and
|
||||
we don't want to confuse our directives with their directives.
|
||||
|
||||
Our prefix is `my`.
|
||||
:marked
|
||||
We'll need access to the template *and* something that can render its contents.
|
||||
We access the template with a `TemplateRef`. The renderer is a `ViewContainerRef`.
|
||||
We inject both into our constructor as private variables.
|
||||
|
||||
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-constructor')(format=".")
|
||||
|
||||
:marked
|
||||
The consumer of our directive will bind a boolean value to our directive's `myUnless` input property.
|
||||
The directive adds or removes the template based on that value.
|
||||
|
||||
Let's add the `myUnless` property now as a setter-only property.
|
||||
|
||||
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-set')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `@Input()` annotation marks this property as an input for the directive.
|
||||
:marked
|
||||
Nothing fancy here: if the condition is false,
|
||||
we render the template, otherwise we clear the element content.
|
||||
|
||||
The end result should look like this:
|
||||
|
||||
+makeExample('structural-directives/ts/app/unless.directive.ts', null, 'unless.directive.ts')
|
||||
|
||||
:marked
|
||||
Now we add it to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp} and try it.
|
||||
First we add some test HTML to the template:
|
||||
|
||||
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'myUnless')(format=".")
|
||||
:marked
|
||||
We run it and it behaves as expected, doing the opposite of `ngIf`.
|
||||
When `condition` is `true`, the top paragraph is removed (replaced by `<script>` tags) and the bottom paragraph appears.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/structural-directives/myUnless-is-true.png' alt="myUnless is true" )
|
||||
|
||||
:marked
|
||||
Our `myUnless` directive is dead simple. Surely we left something out.
|
||||
Surely `ngIf` is more complex?
|
||||
|
||||
[Look at the source code](https://github.com/angular/angular/blob/master/modules/%40angular/common/src/directives/ng_if.ts).
|
||||
It's well documented and we shouldn't be shy
|
||||
about consulting the source when we want to know how something works.
|
||||
|
||||
`ngIf` isn't much different! There are a few
|
||||
additional checks to improve performance (don't clear or recreate the
|
||||
view unless necessary) but otherwise it's much the same.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Wrap up
|
||||
Here is the pertinent source for this chapter.
|
||||
|
||||
+makeTabs(`
|
||||
structural-directives/ts/app/unless.directive.ts,
|
||||
structural-directives/ts/app/heavy-loader.component.ts,
|
||||
structural-directives/ts/app/structural-directives.component.ts,
|
||||
structural-directives/ts/app/structural-directives.component.html
|
||||
`,
|
||||
null,
|
||||
`unless.directive.ts,
|
||||
heavy-loader.component.ts,
|
||||
structural-directives.component.ts,
|
||||
structural-directives.component.html
|
||||
`)
|
||||
:marked
|
||||
We learned that we can manipulate our HTML layout with
|
||||
structural directives like `ngFor` and `ngIf` and we
|
||||
wrote our own structural directive, `myUnless`, to do something similar.
|
||||
|
||||
Angular offers more sophisticated techniques for managing layout
|
||||
such as *structural components* that can take external content
|
||||
and incorporate that content within their own templates.
|
||||
Tab and tab pane controls are good examples.
|
||||
|
||||
We'll learn about structural components in a future chapter.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,45 +0,0 @@
|
|||
block includes
|
||||
include _util-fns
|
||||
- var _on_Plunkr = 'on Plunkr';
|
||||
|
||||
:marked
|
||||
Angular applications are made up of _components_.
|
||||
A _component_ is the combination of an HTML template and a component class that controls a portion of the screen. Here is an example of a component that displays a simple string:
|
||||
|
||||
+makeExample('app/app.component.ts')(format='.')
|
||||
|
||||
:marked
|
||||
You can try this out without installing anything. Open the <live-example>QuickStart example !{_on_Plunkr}</live-example> in another tab
|
||||
and follow along.
|
||||
|
||||
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.
|
||||
|
||||
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
|
||||
+makeExample('index.html','my-app','index.html (inside <body>)')(format='.')
|
||||
:marked
|
||||
The `template` property defines a message inside an `<h1>` header.
|
||||
The message starts with "Hello" and ends with `{{name}}`,
|
||||
which is an Angular [interpolation binding](guide/displaying-data.html) expression.
|
||||
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
|
||||
Interpolation binding is one of many Angular features you'll discover in this documentation.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
|
||||
|
||||
.callout.is-helpful
|
||||
header A word about TypeScript
|
||||
p.
|
||||
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
|
||||
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Next step
|
||||
|
||||
To learn how to write a real application, your next step is to set up a local development
|
||||
environment and begin exploring with code. The [**Developer Guide**](guide/index.html)
|
||||
shows you how.
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
Our grand plan for this tutorial is to build an app to help a staffing agency manage its stable of heroes.
|
||||
Even heroes need to find work.
|
||||
|
||||
Of course we'll only make a little progress in this tutorial. What we do build will
|
||||
have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying
|
||||
a list of heroes, editing a selected hero's detail, and navigating among different
|
||||
views of heroic data.
|
||||
|
||||
The Tour of Heroes covers the core fundamentals of Angular.
|
||||
We’ll use built-in directives to show/hide elements and display lists of hero data.
|
||||
We’ll create a component to display hero details and another to show an array of heroes.
|
||||
We'll use one-way data binding for read-only data. We'll add editable fields to update a model
|
||||
with two-way data binding. We'll bind component methods to user events like key strokes and clicks.
|
||||
We’ll learn to select a hero from a master list and edit that hero in the details view. We'll
|
||||
format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components.
|
||||
|
||||
We’ll learn enough core Angular to get started and gain confidence that
|
||||
Angular can do whatever we need it to do.
|
||||
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
||||
to chapters with greater depth.
|
||||
|
||||
Run the <live-example name="toh-6"></live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The End Game
|
||||
|
||||
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||
view and our most heroic heroes:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard")
|
||||
|
||||
:marked
|
||||
Above the dashboard are two links ("Dashboard" and "Heroes").
|
||||
We could click them to navigate between this Dashboard and a Heroes view.
|
||||
|
||||
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
||||
of that hero where we can change the hero's name.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app")
|
||||
|
||||
:marked
|
||||
Clicking the "Back" button would return us to the "Dashboard".
|
||||
Links at the top can take us to either of the main views.
|
||||
We'll click "Heroes". The app takes to the "Heroes" master list view.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app")
|
||||
|
||||
:marked
|
||||
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
||||
|
||||
We click the "View Details" button to drill into the
|
||||
editable details of our selected hero.
|
||||
|
||||
The following diagram captures all of our navigation options.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||
|
||||
:marked
|
||||
Here's our app in action
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Up Next
|
||||
|
||||
We’ll build this Tour of Heroes together, step by step.
|
||||
We'll motivate each step with a requirement that we've
|
||||
met in countless applications. Everything has a reason.
|
||||
|
||||
And we’ll meet many of the core fundamentals of Angular along the way.
|
|
@ -1,966 +0,0 @@
|
|||
- var _example = 'toh-5';
|
||||
|
||||
block includes
|
||||
include ../_util-fns
|
||||
- var _appRoutingTsVsAppComp = 'app.module.ts'
|
||||
- var _RoutesVsAtRouteConfig = 'Routes'
|
||||
- var _RouterModuleVsRouterDirectives = 'RouterModule'
|
||||
- var _redirectTo = 'redirectTo'
|
||||
|
||||
:marked
|
||||
We received new requirements for our Tour of Heroes application:
|
||||
|
||||
* Add a *Dashboard* view.
|
||||
* Navigate between the *Heroes* and *Dashboard* views.
|
||||
* Clicking on a hero in either view navigates to a detail view of the selected hero.
|
||||
* Clicking a *deep link* in an email opens the detail view for a particular hero.
|
||||
|
||||
When we’re done, users will be able to navigate the app like this:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||
|
||||
:marked
|
||||
We'll add Angular’s *Router* to our app to satisfy these requirements.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The [Routing and Navigation](../guide/router.html) chapter covers the router
|
||||
in more detail than we will in this tutorial.
|
||||
|
||||
:marked
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
include ../../../_includes/_see-addr-bar
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Where We Left Off
|
||||
|
||||
Before we continue with our Tour of Heroes, let’s verify that
|
||||
we have the following structure after adding our hero service
|
||||
and hero detail component. If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
block intro-file-tree
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file app.module.ts
|
||||
.file hero.service.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.ts
|
||||
.file main.ts
|
||||
.file mock-heroes.ts
|
||||
.file node_modules ...
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file styles.css
|
||||
.file systemjs.config.js
|
||||
.file tsconfig.json
|
||||
|
||||
block keep-app-running
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
|
||||
Open a terminal/console window and enter the following command to
|
||||
start the TypeScript compiler, start the server, and watch for changes:
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
npm start
|
||||
|
||||
:marked
|
||||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||
|
||||
## Action plan
|
||||
|
||||
Here's our plan:
|
||||
|
||||
* Turn `AppComponent` into an application shell that only handles navigation
|
||||
* Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`
|
||||
* Add routing
|
||||
* Create a new `DashboardComponent`
|
||||
* Tie the *Dashboard* into the navigation structure
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
*Routing* is another name for *navigation*. The *router* is the mechanism for navigating from view to view.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Splitting the *AppComponent*
|
||||
|
||||
Our current app loads `AppComponent` and immediately displays the list of heroes.
|
||||
|
||||
Our revised app should present a shell with a choice of views (*Dashboard* and *Heroes*)
|
||||
and then default to one of them.
|
||||
|
||||
The `AppComponent` should only handle navigation.
|
||||
Let's move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`.
|
||||
|
||||
### *HeroesComponent*
|
||||
|
||||
`AppComponent` is already dedicated to *Heroes*.
|
||||
Instead of moving anything out of `AppComponent`, we'll just rename it `HeroesComponent`
|
||||
and create a new `AppComponent` shell separately.
|
||||
|
||||
The steps are to rename:
|
||||
* <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>
|
||||
* `AppComponent` class to `HeroesComponent`
|
||||
* Selector `my-app` to `my-heroes`
|
||||
|
||||
+makeExcerpt('app/heroes.component.ts (showing renamings only)', 'renaming')
|
||||
|
||||
:marked
|
||||
## Create *AppComponent*
|
||||
|
||||
The new `AppComponent` will be the application shell.
|
||||
It will have some navigation links at the top and a display area below for the pages we navigate to.
|
||||
|
||||
The initial steps are:
|
||||
|
||||
* Create the file <span ngio-ex>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.
|
||||
* 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 we 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 we'll need it in every other view.
|
||||
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it has been promoted.
|
||||
* Add the supporting `import` statements for `AppComponent`.
|
||||
|
||||
Our first draft looks like this:
|
||||
|
||||
block app-comp-v1
|
||||
+makeTabs(
|
||||
`toh-5/ts/app/app.component.1.ts,
|
||||
toh-5/ts/app/app.module.1.ts`,
|
||||
',',
|
||||
`app/app.component.ts (v1),
|
||||
app/app.module.ts (v1)`)
|
||||
|
||||
:marked
|
||||
The app still runs and still displays heroes.
|
||||
Our refactoring of `AppComponent` into a new `AppComponent` and a `HeroesComponent` worked!
|
||||
We have done no harm.
|
||||
|
||||
:marked
|
||||
## Add Routing
|
||||
|
||||
We're ready to take the next step.
|
||||
Instead of displaying heroes automatically, we'd like to show them *after* the user clicks a button.
|
||||
In other words, we'd like to navigate to the list of heroes.
|
||||
|
||||
We'll need the Angular *Router*.
|
||||
|
||||
block angular-router
|
||||
:marked
|
||||
The Angular router is an external, optional Angular NgModule called `RouterModule`.
|
||||
The router is a combination of multiple provided services (`RouterModule`),
|
||||
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
|
||||
and a configuration (`Routes`). We'll configure our routes first.
|
||||
|
||||
:marked
|
||||
### Add the base tag
|
||||
|
||||
Open `index.html` and add `<base href="/">` at the top of the `<head>` section.
|
||||
|
||||
+makeExcerpt('index.html', 'base-href')
|
||||
|
||||
.callout.is-important
|
||||
header base href is essential
|
||||
:marked
|
||||
See the *base href* section of the [Router](../guide/router.html#base-href)
|
||||
chapter to learn why this matters.
|
||||
|
||||
a#configure-routes
|
||||
block router-config-intro
|
||||
:marked
|
||||
### Configure routes
|
||||
|
||||
Our application doesn't have any routes yet.
|
||||
We'll start by creating a configuration for the application routes.
|
||||
|
||||
:marked
|
||||
*Routes* tell the router which views to display when a user clicks a link or
|
||||
pastes a URL into the browser address bar.
|
||||
|
||||
Let's define our first route as a route to the heroes component:
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts'
|
||||
+makeExcerpt('app/' + _file + ' (heroes route)', 'heroes')
|
||||
|
||||
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
|
||||
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
|
||||
:marked
|
||||
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
||||
We have only one route definition at the moment but rest assured, we'll add more.
|
||||
|
||||
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>
|
||||
- **component**: the component that the router should create when navigating to this route (`HeroesComponent`).
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing](../guide/router.html) chapter.
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
### Make the router available
|
||||
|
||||
We've setup the initial route configuration. Now we'll add it to our `AppModule`.
|
||||
We'll add our configured `RouterModule` to the `AppModule` imports !{_array}.
|
||||
|
||||
+makeExcerpt('app/app.module.2.ts (app routing)', '')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We use the `forRoot` method because we're providing a configured router at the _root_ of the application.
|
||||
The `forRoot` method gives us 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 we paste the path, `/heroes`, into the browser address bar,
|
||||
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
||||
But where?
|
||||
|
||||
We have to ***tell it where*** by adding a `<router-outlet>` element to the bottom of the template.
|
||||
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
||||
The router displays each component immediately below the `<router-outlet>` as we navigate through the application.
|
||||
|
||||
### Router Links
|
||||
|
||||
We don't really expect users to paste a route URL into the address bar.
|
||||
We add an anchor tag to the template which, when clicked, triggers navigation to the `HeroesComponent`.
|
||||
|
||||
The revised template looks like this:
|
||||
|
||||
+makeExcerpt('app/app.component.1.ts', 'template-v2')
|
||||
|
||||
block routerLink
|
||||
:marked
|
||||
Notice the `routerLink` binding in the anchor tag.
|
||||
We bind the `RouterLink` directive (another of the `RouterModule` directives) to a string
|
||||
that tells the router where to navigate when the user clicks the link.
|
||||
|
||||
Since our link is not dynamic, we define a *routing instruction* with a **one-time binding** to our route **path**.
|
||||
Looking back at the route configuration, we confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about dynamic router links and the *link parameters array*
|
||||
in the [Routing](../guide/router.html#link-parameters-array) chapter.
|
||||
|
||||
:marked
|
||||
Refresh the browser. We see only the app title and heroes link. We don't see the heroes list.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The browser's address bar shows `/`.
|
||||
The route path to `HeroesComponent` is `/heroes`, not `/`.
|
||||
We don't have a route that matches the path `/`, so there is nothing to show.
|
||||
That's something we'll want to fix.
|
||||
|
||||
:marked
|
||||
We click the *Heroes* navigation link, the browser bar updates to `/heroes`,
|
||||
and now we see the list of heroes. We are navigating at last!
|
||||
|
||||
At this stage, our `AppComponent` looks like this.
|
||||
|
||||
+makeExample('app/app.component.1.ts', 'v2', 'app/app.component.ts (v2)')
|
||||
|
||||
:marked
|
||||
The *AppComponent* is now attached to a router and displaying routed views.
|
||||
For this reason and to distinguish it from other kinds of components,
|
||||
we call this type of component a *Router Component*.
|
||||
|
||||
:marked
|
||||
## Add a *Dashboard*
|
||||
|
||||
Routing only makes sense when we have multiple views. We need another view.
|
||||
|
||||
Create a placeholder `DashboardComponent` that gives us something to navigate to and from.
|
||||
|
||||
+makeExcerpt('app/dashboard.component.1.ts (v1)', '')
|
||||
|
||||
:marked
|
||||
We’ll come back and make it more useful later.
|
||||
|
||||
### Configure the dashboard route
|
||||
|
||||
Go back to `!{_appRoutingTsVsAppComp}` and teach it to navigate to the dashboard.
|
||||
|
||||
Import the dashboard component and
|
||||
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
Also import and add `DashboardComponent` to our `AppModule`'s `declarations`.
|
||||
|
||||
+makeExcerpt('app/app.module.ts', 'dashboard')
|
||||
|
||||
:marked
|
||||
#### !{_redirectTo}
|
||||
|
||||
We want the app to show the dashboard when it starts and
|
||||
we want to see a nice URL in the browser address bar that says `/dashboard`.
|
||||
Remember that the browser launches with `/` in the address bar.
|
||||
|
||||
block redirect-vs-use-as-default
|
||||
:marked
|
||||
We can use a redirect route to make this happen. Add the following
|
||||
to our array of route definitions:
|
||||
|
||||
+makeExcerpt('app/app.module.3.ts','redirect')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about the *redirects* in the [Routing](../guide/router.html#redirect) chapter.
|
||||
|
||||
:marked
|
||||
#### Add navigation to the template
|
||||
|
||||
Finally, add a dashboard navigation link to the template, just above the *Heroes* link.
|
||||
|
||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||||
+makeExcerpt('app/app.component' + _vers + '.ts', 'template-v3')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We nested the two links within `<nav>` tags.
|
||||
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
|
||||
|
||||
:marked
|
||||
To see these changes in your browser, go to the application root (`/`) and reload.
|
||||
The app displays the dashboard and we can navigate between the dashboard and the heroes.
|
||||
|
||||
## Dashboard Top Heroes
|
||||
|
||||
Let’s spice up the dashboard by displaying the top four heroes at a glance.
|
||||
|
||||
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||||
template file.
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
|
||||
|
||||
+makeExcerpt('app/dashboard.component.ts', 'metadata')
|
||||
|
||||
block templateUrl-path-resolution
|
||||
//- N/A for TS
|
||||
|
||||
:marked
|
||||
Create that file with this content:
|
||||
|
||||
+makeExample('app/dashboard.component.1.html', '', 'app/dashboard.component.html')
|
||||
|
||||
:marked
|
||||
We use `*ngFor` once again to iterate over a list of heroes and display their names.
|
||||
We added extra `<div>` elements to help with styling later in this chapter.
|
||||
|
||||
### Share the *HeroService*
|
||||
|
||||
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}.
|
||||
|
||||
Recall earlier in the chapter that we removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
||||
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
||||
|
||||
That move created a singleton `HeroService` instance, available to *all* components of the application.
|
||||
Angular will inject `HeroService` and we'll use it here in the `DashboardComponent`.
|
||||
|
||||
### Get heroes
|
||||
|
||||
Open <span ngio-ex>dashboard.component.ts</span> and add the requisite `import` statements.
|
||||
|
||||
+makeExcerpt('app/dashboard.component.ts','imports')
|
||||
|
||||
:marked
|
||||
Now implement the `DashboardComponent` class like this:
|
||||
|
||||
+makeExcerpt('app/dashboard.component.ts (class)', 'class')
|
||||
|
||||
:marked
|
||||
We've seen this kind of logic before in the `HeroesComponent`:
|
||||
|
||||
* Define a `heroes` !{_array} property.
|
||||
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
||||
* Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
|
||||
|
||||
In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice` method</span>.
|
||||
|
||||
Refresh the browser and see four heroes in the new dashboard.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Navigate to Hero Details
|
||||
|
||||
Although we display the details of a selected hero at the bottom of the `HeroesComponent`,
|
||||
we don't yet *navigate* to the `HeroDetailComponent` in the three ways specified in our requirements:
|
||||
|
||||
1. from the *Dashboard* to a selected hero.
|
||||
1. from the *Heroes* list to a selected hero.
|
||||
1. from a "deep link" URL pasted into the browser address bar.
|
||||
|
||||
Adding a hero-detail route seems like an obvious place to start.
|
||||
|
||||
### Routing to a hero detail
|
||||
|
||||
We'll add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}` where our other routes are configured.
|
||||
|
||||
The new route is a bit unusual in that we must tell the `HeroDetailComponent` *which hero to show*.
|
||||
We didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||||
|
||||
At the moment the parent `HeroesComponent` sets the component's `hero` property to a
|
||||
hero object with a binding like this.
|
||||
|
||||
code-example(language="html").
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
|
||||
:marked
|
||||
That clearly won't work in any of our routing scenarios.
|
||||
Certainly not the last one; we can't embed an entire hero object in the URL! Nor would we want to.
|
||||
|
||||
### Parameterized route
|
||||
|
||||
We *can* add the hero's `id` to the URL. When routing to the hero whose `id` is 11,
|
||||
we could expect to see an URL such as this:
|
||||
|
||||
code-example(format="nocode").
|
||||
/detail/11
|
||||
|
||||
:marked
|
||||
The `/detail/` part of that URL is constant. The trailing numeric `id` part changes from hero to hero.
|
||||
We need to represent that variable part of the route with a *parameter* (or *token*) that stands for the hero's `id`.
|
||||
|
||||
### Configure a Route with a Parameter
|
||||
|
||||
Here's the *route definition* we'll use.
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (hero detail)','hero-detail')
|
||||
|
||||
:marked
|
||||
The colon (:) in the path indicates that `:id` is a placeholder to be filled with a specific hero `id`
|
||||
when navigating to the `HeroDetailComponent`.
|
||||
|
||||
+ifDocsFor('dart')
|
||||
.l-sub-section
|
||||
:marked
|
||||
Remember to import the hero detail component before creating this route.
|
||||
|
||||
:marked
|
||||
We're finished with the application routes.
|
||||
|
||||
We won't add a `'Hero Detail'` link to the template because users
|
||||
don't click a navigation *link* to view a particular hero.
|
||||
They click a *hero* whether that hero is displayed on the dashboard or in the heroes list.
|
||||
|
||||
We'll get to those *hero* clicks later in the chapter.
|
||||
There's no point in working on them until the `HeroDetailComponent`
|
||||
is ready to be navigated *to*.
|
||||
|
||||
That will require an `HeroDetailComponent` overhaul.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Revise the *HeroDetailComponent*
|
||||
|
||||
Before we rewrite the `HeroDetailComponent`, let's review what it looks like now:
|
||||
|
||||
+makeExample('toh-4/ts/app/hero-detail.component.ts', null, 'app/hero-detail.component.ts (current)')
|
||||
|
||||
:marked
|
||||
The template won't change. We'll display a hero the same way.
|
||||
The big changes are driven by how we get the hero.
|
||||
|
||||
block route-params
|
||||
:marked
|
||||
We will no longer receive the hero in a parent component property binding.
|
||||
The new `HeroDetailComponent` should take the `id` parameter from the `params` observable
|
||||
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
|
||||
|
||||
:marked
|
||||
First, add the requisite imports:
|
||||
|
||||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||||
+makeExcerpt('app/hero-detail.component' + _vers + '.ts', 'added-imports', '')
|
||||
|
||||
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
|
||||
:marked
|
||||
Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
|
||||
into the constructor, saving their values in private fields:
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts (constructor)', 'ctor')
|
||||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
Also import the `switchMap` operator to use later with the route parameters `Observable`.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts (switchMap import)', 'rxjs-import')
|
||||
|
||||
:marked
|
||||
We tell the class that we want to implement the `OnInit` interface.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts', 'implement', '')
|
||||
|
||||
block ngOnInit
|
||||
:marked
|
||||
Inside the `ngOnInit` lifecycle hook, we 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('app/hero-detail.component.ts', 'ngOnInit')
|
||||
|
||||
block extract-id
|
||||
:marked
|
||||
Note how the `switchMap` operator maps the id in the observable route parameters
|
||||
to a new `Observable`, the result of the `HeroService.getHero` method.
|
||||
|
||||
If the user re-navigates to this component while a getHero request is still inflight,
|
||||
switchMap cancels that old request before calling `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 we convert the route parameter value to a number with the !{_str2int}.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Do I need to unsubscribe?
|
||||
|
||||
The `Router` manages the [observables](../guide/router.html#activated-route) it provides and localizes
|
||||
the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against
|
||||
memory leaks, so we don't need to _unsubscribe_ from the route params `Observable`.
|
||||
|
||||
:marked
|
||||
### Add *HeroService.getHero*
|
||||
|
||||
The problem with this bit of code is that `HeroService` doesn't have a `getHero` method!
|
||||
We better fix that quickly before someone notices that we broke the app.
|
||||
|
||||
Open `HeroService` and add a `getHero` method that filters the heroes list from `getHeroes` by `id`:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'getHero')
|
||||
|
||||
:marked
|
||||
Let's return to the `HeroDetailComponent` to clean up loose ends.
|
||||
|
||||
### Find our way back
|
||||
|
||||
We can navigate *to* the `HeroDetailComponent` in several ways.
|
||||
How do we navigate somewhere else when we're done?
|
||||
|
||||
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
|
||||
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
|
||||
using the `Location` service we injected previously.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts', 'goBack')
|
||||
|
||||
- var _CanDeactivateGuard = _docsFor == 'dart' ? '<em>routerCanDeactivate</em> hook' : '<em>CanDeactivate</em> guard'
|
||||
- var _CanDeactivateGuardUri = _docsFor == 'dart' ? 'angular2.router/CanDeactivate-class' : 'router/index/CanDeactivate-interface'
|
||||
.l-sub-section
|
||||
:marked
|
||||
Going back too far could take us out of the application.
|
||||
That's acceptable in a demo. We'd guard against it in a real application,
|
||||
perhaps with the [!{_CanDeactivateGuard}](../api/!{_CanDeactivateGuardUri}.html).
|
||||
|
||||
:marked
|
||||
Then we wire this method with an event binding to a *Back* button that we
|
||||
add to the bottom of the component template.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.html', 'back-button', '')
|
||||
|
||||
:marked
|
||||
Modifying the template to add this button spurs us to take one more
|
||||
incremental improvement and migrate the template to its own file,
|
||||
called <span ngio-ex>hero-detail.component.html</span>:
|
||||
|
||||
+makeExample('app/hero-detail.component.html')
|
||||
|
||||
:marked
|
||||
We update the component metadata with a <span if-docs="ts">`moduleId` and a </span>`templateUrl` pointing to the template file that we just created.
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts', 'metadata')
|
||||
|
||||
:marked
|
||||
Refresh the browser and see the results.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Select a *Dashboard* Hero
|
||||
|
||||
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
|
||||
|
||||
Although the dashboard heroes are presented as button-like blocks, they should behave like anchor tags.
|
||||
When hovering over a hero block, the target URL should display in the browser status bar
|
||||
and the user should be able to copy the link or open the hero detail view in a new tab.
|
||||
|
||||
To achieve this effect, reopen the `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
||||
with `<a>` tags. The opening `<a>` tag looks like this:
|
||||
|
||||
+makeExample('app/dashboard.component.html', 'click', 'app/dashboard.component.html (repeated <a> tag)')
|
||||
|
||||
+ifDocsFor('dart')
|
||||
.alert.is-important
|
||||
:marked
|
||||
Router links in the dashboard are currently not operational, as reported in issue
|
||||
[dart-lang/angular2/issues/186](https://github.com/dart-lang/angular2/issues/186).
|
||||
|
||||
- var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
|
||||
:marked
|
||||
Notice the `[routerLink]` binding.
|
||||
|
||||
Top level navigation in the [`AppComponent`
|
||||
template](#router-links) has router links set to fixed !{_pathVsName}s of the
|
||||
destination routes, "/dashboard" and "/heroes".
|
||||
|
||||
This time, we're binding to an expression containing a **link parameters !{_array}**.
|
||||
The !{_array} has two elements, the ***!{_pathVsName}*** 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***
|
||||
token in the parameterized hero detail route definition we added to
|
||||
`!{_appRoutingTsVsAppComp}` earlier in the chapter:
|
||||
|
||||
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3.ts'
|
||||
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
|
||||
|
||||
:marked
|
||||
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’s details.
|
||||
|
||||
+ifDocsFor('ts')
|
||||
.l-main-section
|
||||
:marked
|
||||
## Refactor routes to a _Routing Module_
|
||||
|
||||
Almost 20 lines of `AppModule` are devoted to configuring four routes.
|
||||
Most applications have many more routes and they [add guard services](../guide/router.html#guards)
|
||||
to protect against unwanted or unauthorized navigations.
|
||||
Routing considerations could quickly dominate this module and obscure its primary purpose which is to
|
||||
establish key facts about the entire app for the Angular compiler.
|
||||
|
||||
We should refactor the routing configuration into its own class.
|
||||
What kind of class?
|
||||
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders` which suggests that a
|
||||
class dedicated to routing should be some kind of module.
|
||||
It should be a [_Routing Module_](../guide/router.html#routing-module).
|
||||
|
||||
By convention the name of a _Routing Module_ contains the word "Routing" and
|
||||
aligns with the name of the module that declares the components navigated to.
|
||||
|
||||
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`. Give it the following contents extracted from the `AppModule` class:
|
||||
|
||||
+makeExample('app/app-routing.module.ts')
|
||||
:marked
|
||||
Noteworthy points, typical of _Routing Modules_:
|
||||
* Pulls the routes into a variable. You might export it in future and it clarifies the _Routing Module_ pattern.
|
||||
|
||||
* Adds `RouterModule.forRoot(routes)` to `imports`.
|
||||
|
||||
* Adds `RouterModule` to `exports` so that the components in the companion module have access to Router declarables
|
||||
such as `RouterLink` and `RouterOutlet`.
|
||||
|
||||
* No `declarations`! Declarations are the responsibility of the companion module.
|
||||
|
||||
* Adds module `providers` for guard services if you have them; there are none in this example.
|
||||
|
||||
### Update _AppModule_
|
||||
|
||||
Now delete the routing configuration from `AppModule` and import the `AppRoutingModule`
|
||||
(_both_ with an ES `import` statement _and_ by adding it to the `NgModule.imports` list).
|
||||
|
||||
Here is the revised `AppModule`, compared to its pre-refactor state:
|
||||
+makeTabs(
|
||||
`toh-5/ts/app/app.module.ts, toh-5/ts/app/app.module.3.ts`,
|
||||
null,
|
||||
`app/app.module.ts (after), app/app.module.ts (before)`)
|
||||
:marked
|
||||
It's simpler and focused on identifying the key pieces of the application.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Select a Hero in the *HeroesComponent*
|
||||
|
||||
Earlier we added the ability to select a hero from the dashboard.
|
||||
We'll do something similar in the `HeroesComponent`.
|
||||
|
||||
The `HeroesComponent` template exhibits a "master/detail" style with the list of heroes
|
||||
at the top and details of the selected hero below.
|
||||
|
||||
+makeExample('toh-4/ts/app/app.component.ts','template', 'app/heroes.component.ts (current template)')(format=".")
|
||||
|
||||
:marked
|
||||
Our goal is to move the detail to its own view and navigate to it when the user decides to edit a selected hero.
|
||||
|
||||
Delete the `<h1>` at the top (we forgot about it during the `AppComponent`-to-`HeroesComponent` conversion).
|
||||
|
||||
Delete the last line of the template with the `<my-hero-detail>` tags.
|
||||
|
||||
We'll no longer show the full `HeroDetailComponent` here.
|
||||
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
|
||||
|
||||
We'll throw in a small twist for variety.
|
||||
We are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
|
||||
When the user selects a hero from the list, we *don't* go to the detail page.
|
||||
We show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
|
||||
|
||||
### Add the *mini-detail*
|
||||
|
||||
Add the following HTML fragment at the bottom of the template where the `<my-hero-detail>` used to be:
|
||||
|
||||
+makeExcerpt('app/heroes.component.html', 'mini-detail', '')
|
||||
|
||||
:marked
|
||||
After clicking a hero, the user should see something like this below the hero list:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/mini-hero-detail.png' alt="Mini Hero Detail" height="70")
|
||||
|
||||
:marked
|
||||
### Format with the *uppercase* pipe
|
||||
|
||||
Notice that the hero's name is displayed in CAPITAL LETTERS. That's the effect of the `uppercase` pipe
|
||||
that we slipped into the interpolation binding. Look for it right after the pipe operator ( | ).
|
||||
|
||||
+makeExcerpt('app/heroes.component.html', 'pipe', '')
|
||||
|
||||
:marked
|
||||
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||||
Angular ships with several pipes and we can write our own.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about pipes in the [Pipes](../guide/pipes.html) chapter.
|
||||
|
||||
:marked
|
||||
### Move content out of the component file
|
||||
|
||||
We are not done. We still have to update the component class to support navigation to the
|
||||
`HeroDetailComponent` when the user clicks the *View Details* button.
|
||||
|
||||
This component file is really big. Most of it is either template or CSS styles.
|
||||
It's difficult to find the component logic amidst the noise of HTML and CSS.
|
||||
|
||||
Let's migrate the template and the styles to their own files before we make any more changes:
|
||||
|
||||
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
|
||||
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
||||
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
|
||||
<li if-docs="ts">. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component.</li>
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
||||
We could list multiple style files from different locations if we needed them.
|
||||
|
||||
block heroes-component-cleanup
|
||||
//- Only relevant for Dart.
|
||||
|
||||
+makeExcerpt('app/heroes.component.ts (revised metadata)', 'metadata')
|
||||
|
||||
:marked
|
||||
### Update the _HeroesComponent_ class.
|
||||
|
||||
The `HeroesComponent` navigates to the `HeroDetailComponent` in response to a button click.
|
||||
The button's _click_ event is bound to a `gotoDetail` method that navigates _imperatively_
|
||||
by telling the router where to go.
|
||||
|
||||
This approach requires some changes to the component class:
|
||||
|
||||
1. Import the `router` from the Angular router library
|
||||
1. Inject the `router` in the constructor (along with the `HeroService`)
|
||||
1. Implement `gotoDetail` by calling the `router.navigate` method
|
||||
|
||||
+makeExcerpt('app/heroes.component.ts', 'gotoDetail')
|
||||
|
||||
:marked
|
||||
Note that we're passing a two-element **link parameters !{_array}**
|
||||
— a path and the route parameter — to
|
||||
the `router.navigate` method just as we did in the `[routerLink]` binding
|
||||
back in the `DashboardComponent`.
|
||||
Here's the fully revised `HeroesComponent` class:
|
||||
|
||||
+makeExcerpt('app/heroes.component.ts', 'class')
|
||||
|
||||
:marked
|
||||
Refresh the browser and start clicking.
|
||||
We can navigate around the app, from the dashboard to hero details and back,
|
||||
for heroes list to the mini-detail to the hero details and back to the heroes again.
|
||||
We can jump back and forth between the dashboard and the heroes.
|
||||
|
||||
We've met all of the navigational requirements that propelled this chapter.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Styling the App
|
||||
|
||||
The app is functional but pretty ugly.
|
||||
Our creative designer team provided some CSS files to make it look better.
|
||||
|
||||
### A Dashboard with Style
|
||||
|
||||
The designers think we should display the dashboard heroes in a row of rectangles.
|
||||
They've given us ~60 lines of CSS for this purpose including some simple media queries for responsive design.
|
||||
|
||||
If we paste these ~60 lines into the component `styles` metadata,
|
||||
they'll completely obscure the component logic.
|
||||
Let's not do that. It's easier to edit CSS in a separate `*.css` file anyway.
|
||||
|
||||
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:
|
||||
|
||||
+makeExcerpt('app/dashboard.component.ts (styleUrls)', 'css')
|
||||
|
||||
:marked
|
||||
### Stylish Hero Details
|
||||
|
||||
The designers also gave us CSS styles specifically for the `HeroDetailComponent`.
|
||||
|
||||
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
||||
folder and refer to that file inside
|
||||
the `styleUrls` !{_array} as we did for `DashboardComponent`.
|
||||
Let's also remove the `hero` property `@Input` !{_decorator}
|
||||
<span if-docs="ts">and its import</span>
|
||||
while we are at it.
|
||||
|
||||
Here's the content for the aforementioned component CSS files.
|
||||
|
||||
block css-files
|
||||
+makeTabs(
|
||||
`toh-5/ts/app/hero-detail.component.css,
|
||||
toh-5/ts/app/dashboard.component.css`,
|
||||
null,
|
||||
`app/hero-detail.component.css,
|
||||
app/dashboard.component.css`)
|
||||
|
||||
:marked
|
||||
### Style the Navigation Links
|
||||
|
||||
The designers gave us CSS to make the navigation links in our `AppComponent` look more like selectable buttons.
|
||||
We cooperated by surrounding those links in `<nav>` tags.
|
||||
|
||||
Add a <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
||||
|
||||
+makeExcerpt('app/app.component.css (navigation styles)', '')
|
||||
|
||||
.l-sub-section
|
||||
block router-link-active
|
||||
:marked
|
||||
**The *routerLinkActive* directive**
|
||||
|
||||
The Angular Router provides a `routerLinkActive` directive we can use to
|
||||
add a class to the HTML navigation element whose route matches the active route.
|
||||
All we have to do is define the style for it. Sweet!
|
||||
|
||||
+makeExcerpt('app/app.component.ts (active router links)', 'template')
|
||||
|
||||
:marked
|
||||
Set the `AppComponent`’s `styleUrls` property to this CSS file.
|
||||
|
||||
+makeExcerpt('app/app.component.ts','styleUrls')
|
||||
|
||||
:marked
|
||||
### Global application styles
|
||||
|
||||
When we add styles to a component, we're keeping everything a component needs
|
||||
— HTML, the CSS, the code — together in one convenient place.
|
||||
It's pretty easy to package it all up and re-use the component somewhere else.
|
||||
|
||||
We can also create styles at the *application level* outside of any component.
|
||||
|
||||
Our designers provided some basic styles to apply to elements across the entire app.
|
||||
These correspond to the full set of master styles that we installed earlier during [setup](../guide/setup.html).
|
||||
Here is an excerpt:
|
||||
|
||||
+makeExcerpt('styles.css (excerpt)', 'toh')
|
||||
|
||||
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/styles.css'
|
||||
|
||||
:marked
|
||||
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already.
|
||||
Ensure that it contains the [master styles given here](!{styles_css}).
|
||||
|
||||
If necessary, also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
||||
|
||||
+makeExcerpt('index.html (link ref)', 'css')
|
||||
|
||||
:marked
|
||||
Look at the app now. Our dashboard, heroes, and navigation links are styling!
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Application structure and code
|
||||
|
||||
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||
Verify that we have the following structure:
|
||||
|
||||
block file-tree-end
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file app
|
||||
.children
|
||||
.file app.component.css
|
||||
.file app.component.ts
|
||||
.file app.module.ts
|
||||
.file app-routing.module.ts
|
||||
.file dashboard.component.css
|
||||
.file dashboard.component.html
|
||||
.file dashboard.component.ts
|
||||
.file hero.service.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.css
|
||||
.file hero-detail.component.html
|
||||
.file hero-detail.component.ts
|
||||
.file heroes.component.css
|
||||
.file heroes.component.html
|
||||
.file heroes.component.ts
|
||||
.file main.ts
|
||||
.file mock-heroes.ts
|
||||
.file node_modules ...
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file styles.css
|
||||
.file systemjs.config.js
|
||||
.file tsconfig.json
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Recap
|
||||
|
||||
### The Road Behind
|
||||
|
||||
We travelled a great distance in this chapter
|
||||
|
||||
- We added the Angular *Router* to navigate among different components.
|
||||
- We learned how to create router links to represent navigation menu items.
|
||||
- We used router link parameters to navigate to the details of user selected hero.
|
||||
- We shared the `HeroService` among multiple components.
|
||||
- We moved HTML and CSS out of the component file and into their own files.
|
||||
- We added the `uppercase` pipe to format data.
|
||||
<li if-docs="ts"> We refactored routes into a `Routing Module` that we import.</li>
|
||||
|
||||
### The Road Ahead
|
||||
|
||||
We have much of the foundation we need to build an application.
|
||||
We're still missing a key piece: remote data access.
|
||||
|
||||
In the next chapter,
|
||||
we’ll replace our mock data with data retrieved from a server using http.
|
|
@ -1,643 +0,0 @@
|
|||
- 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
|
||||
Our stakeholders appreciate our progress.
|
||||
Now they want to get the hero data from a server, let users add, edit, and delete heroes,
|
||||
and save these changes back to the server.
|
||||
|
||||
In this chapter we teach our application to make the corresponding HTTP calls to a remote server's web API.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Where We Left Off
|
||||
|
||||
In the [previous chapter](toh-pt5.html), we learned to navigate between the dashboard and the fixed heroes list, editing a selected hero along the way.
|
||||
That's our starting point for this chapter.
|
||||
|
||||
block start-server-and-watch
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
|
||||
Open a terminal/console window and enter the following command to
|
||||
start the TypeScript compiler, start the server, and watch for changes:
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
npm start
|
||||
|
||||
:marked
|
||||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||
|
||||
.l-main-section#http-providers
|
||||
h1 Providing HTTP Services
|
||||
block http-library
|
||||
:marked
|
||||
The `HttpModule` is ***not*** a core Angular module.
|
||||
It's Angular's optional approach to web access and it exists as a separate add-on module called `@angular/http`,
|
||||
shipped in a separate script file as part of the Angular npm package.
|
||||
|
||||
Fortunately we're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when we need it.
|
||||
|
||||
:marked
|
||||
### Register for HTTP services
|
||||
|
||||
block http-providers
|
||||
:marked
|
||||
Our app will depend upon the Angular `http` service which itself depends upon other supporting services.
|
||||
The `HttpModule` from `@angular/http` library holds providers for a complete set of HTTP services.
|
||||
|
||||
We should be able to access these services from anywhere in the application.
|
||||
So we register them all by adding `HttpModule` to the `imports` list of the `AppModule` where we
|
||||
bootstrap the application and its root `AppComponent`.
|
||||
|
||||
+makeExample('app/app.module.ts', 'v1','app/app.module.ts (v1)')
|
||||
|
||||
:marked
|
||||
Notice that we supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Simulating the web API
|
||||
|
||||
We recommend registering application-wide services in the root
|
||||
`!{_AppModuleVsAppComp}` *providers*. <span if-docs="dart">Here we're
|
||||
registering in `main` for a special reason.</span>
|
||||
|
||||
Our application is in the early stages of development and far from ready for production.
|
||||
We don't even have a web server that can handle requests for heroes.
|
||||
Until we do, *we'll have to fake it*.
|
||||
|
||||
We're going to *trick* the HTTP client into fetching and saving data from
|
||||
a mock service, the *in-memory web API*.
|
||||
<span if-docs="dart"> The application itself doesn't need to know and
|
||||
shouldn't know about this. So we'll slip the in-memory web API into the
|
||||
configuration *above* the `AppComponent`.</span>
|
||||
|
||||
Here is a version of <span ngio-ex>!{_appModuleTsVsMainTs}</span> that performs this trick:
|
||||
|
||||
+makeExcerpt(_appModuleTsVsMainTs, 'v2')
|
||||
|
||||
block backend
|
||||
:marked
|
||||
Rather than require a real API server, this example simulates communication with the remote server by adding the
|
||||
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API"><i>InMemoryWebApiModule</i></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', '')
|
||||
|
||||
:marked
|
||||
The `forRoot` configuration method takes an `InMemoryDataService` class
|
||||
that primes the in-memory database as follows:
|
||||
|
||||
+makeExample('app/in-memory-data.service.ts', 'init')(format='.')
|
||||
|
||||
p This file replaces the #[code #[+adjExPath('mock-heroes.ts')]] which is now safe to delete.
|
||||
|
||||
block dont-be-distracted-by-backend-subst
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
This chapter is an introduction to the !{_Angular_http_library}.
|
||||
Please don't be distracted by the details of this backend substitution. Just follow along with the example.
|
||||
|
||||
Learn more later about the in-memory web API in the [HTTP client chapter](../guide/server-communication.html#in-mem-web-api).
|
||||
Remember, the in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
|
||||
Skip it when you have a real web API server.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Heroes and HTTP
|
||||
|
||||
Look at our current `HeroService` implementation
|
||||
|
||||
+makeExcerpt('toh-4/ts/app/hero.service.ts (old getHeroes)', 'get-heroes')
|
||||
|
||||
:marked
|
||||
We returned a !{_Promise} resolved with mock heroes.
|
||||
It may have seemed like overkill at the time, but we were anticipating the
|
||||
day when we fetched heroes with an HTTP client and we knew that would have to be an asynchronous operation.
|
||||
|
||||
That day has arrived! Let's convert `getHeroes()` to use HTTP.
|
||||
|
||||
+makeExcerpt('app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes')
|
||||
|
||||
:marked
|
||||
Our updated import statements are now:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts (updated imports)', 'imports')
|
||||
|
||||
- var _h3id = `http-${_promise}`
|
||||
:marked
|
||||
Refresh the browser, and the hero data should be successfully loaded from the
|
||||
mock server.
|
||||
|
||||
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
|
||||
|
||||
We're still returning a !{_Promise} but we're creating it differently.
|
||||
|
||||
block get-heroes-details
|
||||
:marked
|
||||
The Angular `http.get` returns an RxJS `Observable`.
|
||||
*Observables* are a powerful way to manage asynchronous data flows.
|
||||
We'll learn about [Observables](#observables) later in this chapter.
|
||||
|
||||
For *now* we get back on familiar ground by immediately
|
||||
converting that `Observable` to a `Promise` using the `toPromise` operator.
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'to-promise', '')
|
||||
|
||||
:marked
|
||||
Unfortunately, the Angular `Observable` doesn't have a `toPromise` operator ...
|
||||
not out of the box.
|
||||
The Angular `Observable` is a bare-bones implementation.
|
||||
|
||||
There are scores of operators like `toPromise` that extend `Observable` with useful capabilities.
|
||||
If we want those capabilities, we have to add the operators ourselves.
|
||||
That's as easy as importing them from the RxJS library like this:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'rxjs', '')
|
||||
|
||||
:marked
|
||||
### Extracting the data in the *then* callback
|
||||
|
||||
In the *promise*'s `then` callback we call the `json` method of the HTTP `Response` to extract the
|
||||
data within the response.
|
||||
+makeExcerpt('app/hero.service.ts', 'to-data', '')
|
||||
|
||||
:marked
|
||||
That response JSON has a single `data` property.
|
||||
The `data` property holds the !{_array} of *heroes* that the caller really wants.
|
||||
So we grab that !{_array} and return it as the resolved !{_Promise} value.
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Pay close attention to the shape of the data returned by the server.
|
||||
This particular *in-memory web API* example happens to return an object with a `data` property.
|
||||
Your API might return something else. Adjust the code to match *your web API*.
|
||||
|
||||
:marked
|
||||
The caller is unaware of these machinations. It receives a !{_Promise} of *heroes* just as it did before.
|
||||
It has no idea that we fetched the heroes from the (mock) server.
|
||||
It knows nothing of the twists and turns required to convert the HTTP response into heroes.
|
||||
Such is the beauty and purpose of delegating data access to a service like this `HeroService`.
|
||||
|
||||
### Error Handling
|
||||
|
||||
At the end of `getHeroes()` we `catch` server failures and pass them to an error handler:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'catch', '')
|
||||
|
||||
:marked
|
||||
This is a critical step!
|
||||
We must anticipate HTTP failures as they happen frequently for reasons beyond our control.
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'handleError', '')
|
||||
|
||||
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
|
||||
:marked
|
||||
In this demo service we log the error to the console; we would do better in real life.
|
||||
|
||||
We've also decided to return a user friendly form of the error to
|
||||
the caller in a !{rejected_promise} so that the caller can display a proper error message to the user.
|
||||
|
||||
### Get hero by id
|
||||
The `HeroDetailComponent` asks the `HeroService` to fetch a single hero to edit.
|
||||
|
||||
The `HeroService` currently fetches all heroes and then finds the desired hero
|
||||
by filtering for the one with the matching `id`.
|
||||
That's fine in a simulation. It's wasteful to ask a real server for _all_ heroes when we only want one.
|
||||
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (e.g., `api/hero/11`).
|
||||
|
||||
Update the `HeroService.getHero` method to make a _get-by-id_ request,
|
||||
applying what we just learned to write `getHeroes`:
|
||||
+makeExcerpt('app/hero.service.ts', 'getHero', '')
|
||||
:marked
|
||||
It's almost the same as `getHeroes`.
|
||||
The URL identifies _which_ hero the server should update by encoding the hero id into the URL
|
||||
to match the `api/hero/:id` pattern.
|
||||
|
||||
We also adjust to the fact that the `data` in the response is a single hero object rather than !{_an} !{_array}.
|
||||
|
||||
### Unchanged _getHeroes_ API
|
||||
|
||||
Although we made significant *internal* changes to `getHeroes()` and `getHero()`,
|
||||
the public signatures did not change.
|
||||
We still return a !{_Promise} from both methods.
|
||||
We won't have to update any of the components that call them.
|
||||
|
||||
Our stakeholders are thrilled with the web API integration so far.
|
||||
Now they want the ability to create and delete heroes.
|
||||
|
||||
Let's see first what happens when we try to update a hero's details.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Update hero details
|
||||
|
||||
We can edit a hero's name already in the hero detail view. Go ahead and try
|
||||
it. As we type, the hero name is updated in the view heading.
|
||||
But when we hit the `Back` button, the changes are lost!
|
||||
|
||||
Updates weren't lost before. What changed?
|
||||
When the app used a list of mock heroes, updates were applied directly to the
|
||||
hero objects within the single, app-wide, shared list. Now that we are fetching data
|
||||
from a server, if we want changes to persist, we'll need to write them back to
|
||||
the server.
|
||||
|
||||
### Save hero details
|
||||
|
||||
Let's ensure that edits to a hero's name aren't lost. Start by adding,
|
||||
to the end of the hero detail template, a save button with a `click` event
|
||||
binding that invokes a new component method named `save`:
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.html', 'save')
|
||||
|
||||
:marked
|
||||
The `save` method persists hero name changes using the hero service
|
||||
`update` method and then navigates back to the previous view:
|
||||
|
||||
+makeExcerpt('app/hero-detail.component.ts', 'save')
|
||||
|
||||
:marked
|
||||
### Hero service `update` method
|
||||
|
||||
The overall structure of the `update` method is similar to that of
|
||||
`getHeroes`, although we'll use an HTTP _put_ to persist changes
|
||||
server-side:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'update')
|
||||
|
||||
:marked
|
||||
We identify _which_ hero the server should update by encoding the hero id in
|
||||
the URL. The put body is the JSON string encoding of the hero, obtained by
|
||||
calling `!{_JSON_stringify}`. We identify the body content type
|
||||
(`application/json`) in the request header.
|
||||
|
||||
Refresh the browser and give it a try. Changes to hero names should now persist.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Add a hero
|
||||
|
||||
To add a new hero we need to know the hero's name. Let's use an input
|
||||
element for that, paired with an add button.
|
||||
|
||||
Insert the following into the heroes component HTML, first thing after
|
||||
the heading:
|
||||
|
||||
+makeExcerpt('app/heroes.component.html', 'add')
|
||||
|
||||
:marked
|
||||
In response to a click event, we call the component's click handler and then
|
||||
clear the input field so that it will be ready to use for another name.
|
||||
|
||||
+makeExcerpt('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 our !{_array}.
|
||||
|
||||
Finally, we implement the `create` method in the `HeroService` class.
|
||||
+makeExcerpt('app/hero.service.ts', 'create')
|
||||
|
||||
:marked
|
||||
Refresh the browser and create some new heroes!
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Delete a hero
|
||||
|
||||
Too many heroes?
|
||||
Let's add a delete button to each hero in the heroes view.
|
||||
|
||||
Add this button element to the heroes component HTML, right after the hero
|
||||
name in the repeated `<li>` tag:
|
||||
|
||||
+makeExcerpt('app/heroes.component.html', 'delete', '')
|
||||
|
||||
:marked
|
||||
The `<li>` element should now look like this:
|
||||
|
||||
+makeExcerpt('app/heroes.component.html', 'li-element')
|
||||
|
||||
:marked
|
||||
In addition to calling the component's `delete` method, the delete button
|
||||
click handling code stops the propagation of the click event — we
|
||||
don't want the `<li>` click handler to be triggered because that would
|
||||
select the hero that we are going to delete!
|
||||
|
||||
The logic of the `delete` handler is a bit trickier:
|
||||
|
||||
+makeExcerpt('app/heroes.component.ts', 'delete')
|
||||
|
||||
:marked
|
||||
Of course, we 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.
|
||||
|
||||
:marked
|
||||
We want our delete button to be placed at the far right of the hero entry.
|
||||
This extra CSS accomplishes that:
|
||||
|
||||
+makeExcerpt('app/heroes.component.css', 'additions')
|
||||
|
||||
:marked
|
||||
### Hero service `delete` method
|
||||
|
||||
The hero service's `delete` method uses the _delete_ HTTP method to remove the hero from the server:
|
||||
|
||||
+makeExcerpt('app/hero.service.ts', 'delete')
|
||||
|
||||
:marked
|
||||
Refresh the browser and try the new delete functionality.
|
||||
|
||||
#observables
|
||||
:marked
|
||||
## !{_Observable}s
|
||||
|
||||
block observables-section-intro
|
||||
:marked
|
||||
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
|
||||
|
||||
Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
||||
In this section we learn to return the `Observable` directly and discuss when and why that might be
|
||||
a good thing to do.
|
||||
|
||||
### Background
|
||||
An *observable* is a stream of events that we can process with array-like operators.
|
||||
|
||||
Angular core has basic support for observables. We developers augment that support with
|
||||
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
|
||||
We'll see how shortly.
|
||||
|
||||
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
|
||||
That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
|
||||
|
||||
Converting to a promise is often a good choice. We typically ask `http.get` to fetch a single chunk of data.
|
||||
When we receive the data, we're done.
|
||||
A single result in the form of a promise is easy for the calling component to consume
|
||||
and it helps that promises are widely understood by JavaScript programmers.
|
||||
|
||||
:marked
|
||||
But requests aren't always "one and done". We may start one request,
|
||||
then cancel it, and make a different request before the server has responded to the first request.
|
||||
Such a _request-cancel-new-request_ sequence is difficult to implement with *!{_Promise}s*.
|
||||
It's easy with *!{_Observable}s* as we'll see.
|
||||
|
||||
### Search-by-name
|
||||
We're going to add a *hero search* feature to the Tour of Heroes.
|
||||
As the user types a name into a search box, we'll make repeated HTTP requests for heroes filtered by that name.
|
||||
|
||||
We start by creating `HeroSearchService` that sends search queries to our server's web api.
|
||||
|
||||
+makeExample('app/hero-search.service.ts')
|
||||
|
||||
:marked
|
||||
The `!{_priv}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">Another notable difference: we no longer call `toPromise`,
|
||||
we simply return the *observable* instead.</span>
|
||||
|
||||
### HeroSearchComponent
|
||||
|
||||
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
|
||||
|
||||
The component template is simple — just a text box and a list of matching search results.
|
||||
|
||||
+makeExample('app/hero-search.component.html')
|
||||
:marked
|
||||
We'll also want to add styles for the new component.
|
||||
+makeExample('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.
|
||||
|
||||
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
|
||||
|
||||
But, as we'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 we flow it through the `async` pipe (`AsyncPipe`).
|
||||
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
|
||||
|
||||
Time to create the `HeroSearchComponent` class and metadata.
|
||||
|
||||
+makeExample('app/hero-search.component.ts')
|
||||
|
||||
:marked
|
||||
#### Search terms
|
||||
|
||||
Let's focus on the `!{_priv}searchTerms`:
|
||||
|
||||
+makeExcerpt('app/hero-search.component.ts', 'searchTerms', '')
|
||||
|
||||
block search-criteria-intro
|
||||
:marked
|
||||
A `Subject` is a producer of an _observable_ event stream;
|
||||
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
|
||||
|
||||
Each call to `search` puts a new string into this subject's _observable_ stream by calling `next`.
|
||||
|
||||
:marked
|
||||
<a id="ngoninit"></a>
|
||||
#### Initialize the _**heroes**_ property (_**ngOnInit**_)
|
||||
|
||||
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
|
||||
We're going to turn the stream
|
||||
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
|
||||
|
||||
+makeExcerpt('app/hero-search.component.ts', 'search', '')
|
||||
|
||||
:marked
|
||||
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of HTTP requests.
|
||||
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
|
||||
|
||||
block observable-transformers
|
||||
:marked
|
||||
Fortunately, we can chain `Observable` operators to the string `Observable` that reduce the request flow.
|
||||
We'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. We'll never make requests more frequently than 300ms.
|
||||
|
||||
* `distinctUntilChanged` ensures that we only send a request if the filter text changed.
|
||||
There's no point in repeating a request for the same search term.
|
||||
|
||||
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
|
||||
It cancels and discards previous search observables, returning only the latest search service observable.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
|
||||
(formerly known as "flatMapLatest") is very clever.
|
||||
|
||||
Every qualifying key event can trigger an `http` method call.
|
||||
Even with a 300ms pause between requests, we could have multiple HTTP requests in flight
|
||||
and they may not return in the order sent.
|
||||
|
||||
`switchMap` preserves the original request order while returning
|
||||
only the observable from the most recent `http` method call.
|
||||
Results from prior calls are canceled and discarded.
|
||||
|
||||
We also short-circuit the `http` method call and return an observable containing an empty array
|
||||
if the search text is empty.
|
||||
|
||||
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending HTTP request
|
||||
until the service supports that feature, a topic for another day.
|
||||
We are content for now to discard unwanted results.
|
||||
:marked
|
||||
* `catch` intercepts a failed observable.
|
||||
Our simple example prints the error to the console; a real life application should do better.
|
||||
Then we return an observable containing an empty array to clear the search result.
|
||||
|
||||
### Import RxJS operators
|
||||
The RxJS operators are not available in Angular's base `Observable` implementation.
|
||||
We have to extend `Observable` by *importing* them.
|
||||
|
||||
We could extend `Observable` with just the operators we need here by
|
||||
including the pertinent `import` statements at the top of this file.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Many authorities say we should do just that.
|
||||
:marked
|
||||
We take a different approach in this example.
|
||||
We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
|
||||
|
||||
+makeExample('app/rxjs-extensions.ts')(format='.')
|
||||
|
||||
:marked
|
||||
We load them all at once by importing `rxjs-extensions` at the top of `AppModule`.
|
||||
|
||||
+makeExcerpt('app/app.module.ts', 'rxjs-extensions')(format='.')
|
||||
|
||||
:marked
|
||||
### Add the search component to the dashboard
|
||||
|
||||
We add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
||||
|
||||
+makeExample('app/dashboard.component.html')(format='.')
|
||||
|
||||
- var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
|
||||
- var declFile = _docsFor == 'dart' ? 'app/dashboard.component.ts' : 'app/app.module.ts'
|
||||
:marked
|
||||
Finally, we import `HeroSearchComponent` from
|
||||
<span ngio-ex>hero-search.component.ts</span>
|
||||
and add it to the `!{_declarations}` !{_array}:
|
||||
|
||||
+makeExcerpt(declFile, 'search')
|
||||
|
||||
:marked
|
||||
Run the app again, go to the *Dashboard*, and enter some text in the search box.
|
||||
At some point it might look like this.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Application structure and code
|
||||
|
||||
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||
Verify that we have the following structure:
|
||||
|
||||
block filetree
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
.children
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file app.component.css
|
||||
.file app.module.ts
|
||||
.file app-routing.module.ts
|
||||
.file dashboard.component.css
|
||||
.file dashboard.component.html
|
||||
.file dashboard.component.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.css
|
||||
.file hero-detail.component.html
|
||||
.file hero-detail.component.ts
|
||||
.file hero-search.component.html (new)
|
||||
.file hero-search.component.css (new)
|
||||
.file hero-search.component.ts (new)
|
||||
.file hero-search.service.ts (new)
|
||||
.file rxjs-extensions.ts
|
||||
.file hero.service.ts
|
||||
.file heroes.component.css
|
||||
.file heroes.component.html
|
||||
.file heroes.component.ts
|
||||
.file main.ts
|
||||
.file in-memory-data.service.ts (new)
|
||||
.file node_modules ...
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file styles.css
|
||||
.file systemjs.config.js
|
||||
.file tsconfig.json
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Home Stretch
|
||||
|
||||
We are at the end of our journey for now, but we have accomplished a lot.
|
||||
- We added the necessary dependencies to use HTTP in our application.
|
||||
- We refactored `HeroService` to load heroes from a web API.
|
||||
- We extended `HeroService` to support post, put and delete methods.
|
||||
- We updated our components to allow adding, editing and deleting of heroes.
|
||||
- We configured an in-memory web API.
|
||||
- We learned how to use !{_Observable}s.
|
||||
|
||||
Here are the files we _added or changed_ in this chapter.
|
||||
|
||||
block file-summary
|
||||
+makeTabs(
|
||||
`toh-6/ts/app/app.component.ts,
|
||||
toh-6/ts/app/app.module.ts,
|
||||
toh-6/ts/app/heroes.component.ts,
|
||||
toh-6/ts/app/heroes.component.html,
|
||||
toh-6/ts/app/heroes.component.css,
|
||||
toh-6/ts/app/hero-detail.component.ts,
|
||||
toh-6/ts/app/hero-detail.component.html,
|
||||
toh-6/ts/app/hero.service.ts,
|
||||
toh-6/ts/app/in-memory-data.service.ts`,
|
||||
',,,,,,,,',
|
||||
`app.comp...ts,
|
||||
app.mod...ts,
|
||||
heroes.comp...ts,
|
||||
heroes.comp...html,
|
||||
heroes.comp...css,
|
||||
hero-detail.comp...ts,
|
||||
hero-detail.comp...html,
|
||||
hero.service.ts,
|
||||
in-memory-data.service.ts`
|
||||
)
|
||||
|
||||
+makeTabs(
|
||||
`toh-6/ts/app/hero-search.service.ts,
|
||||
toh-6/ts/app/hero-search.component.ts,
|
||||
toh-6/ts/app/hero-search.component.html,
|
||||
toh-6/ts/app/hero-search.component.css,
|
||||
toh-6/ts/app/rxjs-extensions.ts`,
|
||||
null,
|
||||
`hero-search.service.ts,
|
||||
hero-search.component.ts,
|
||||
hero-search.component.html,
|
||||
hero-search.component.css,
|
||||
rxjs-extensions.ts`
|
||||
)
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Next Step
|
||||
|
||||
Return to the [learning path](../guide/learning-angular.html#architecture) where
|
||||
you can read about the concepts and practices you discovered in this tutorial.
|
|
@ -1,95 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
cd `dirname $0`/..
|
||||
|
||||
BASE="public/docs/ts"
|
||||
LATEST="$BASE/latest"
|
||||
CACHE="$BASE/_cache"
|
||||
|
||||
# setup-systemjs-anatomy.jade?
|
||||
FILES="
|
||||
guide/architecture.jade
|
||||
guide/attribute-directives.jade
|
||||
guide/component-styles.jade
|
||||
guide/dependency-injection.jade
|
||||
guide/displaying-data.jade
|
||||
guide/hierarchical-dependency-injection.jade
|
||||
guide/index.jade
|
||||
guide/learning-angular.jade
|
||||
guide/lifecycle-hooks.jade
|
||||
guide/pipes.jade
|
||||
guide/security.jade
|
||||
guide/server-communication.jade
|
||||
guide/setup.jade
|
||||
guide/structural-directives.jade
|
||||
guide/template-syntax.jade
|
||||
glossary.jade
|
||||
quickstart.jade
|
||||
_quickstart_repo.jade
|
||||
tutorial/index.jade
|
||||
tutorial/toh-pt5.jade
|
||||
tutorial/toh-pt6.jade"
|
||||
|
||||
function cacheRefresh() {
|
||||
local FILE_PATTERN="*"
|
||||
if [[ -n "$1" ]]; then
|
||||
FILE_PATTERN="$1"
|
||||
else
|
||||
echo "Argument missing: specify shell file glob pattern of files to be refreshed."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
local allFound=true;
|
||||
|
||||
for f in $FILES; do
|
||||
local srcPath="$LATEST/$f";
|
||||
local destPath="$CACHE/$f";
|
||||
local destDir=`dirname $destPath`;
|
||||
if [[ -e $srcPath ]]; then
|
||||
[[ -d "$destDir" ]] || (set -x; mkdir $destDir);
|
||||
case "$f" in
|
||||
(*$FILE_PATTERN*)
|
||||
(set -x; cp $srcPath $destPath);;
|
||||
(*)
|
||||
echo "SKIPPED $f";;
|
||||
esac
|
||||
else
|
||||
echo Cannot find $srcPath
|
||||
allFound=false;
|
||||
fi
|
||||
done
|
||||
|
||||
[[ $allFound ]] || exit 1;
|
||||
}
|
||||
|
||||
function cacheDiffSummary() {
|
||||
diff -qr -x "_util*.jade" "$CACHE/" "$LATEST/" | \
|
||||
grep -v "^Only in"
|
||||
}
|
||||
|
||||
function cacheDiff() {
|
||||
local FILES="*$1*"
|
||||
cd $CACHE;
|
||||
# List files
|
||||
find . -name "$FILES" ! -name "*~" -exec diff -q {} ../latest/{} \;
|
||||
# Show differences
|
||||
find . -name "$FILES" ! -name "*~" -exec diff {} ../latest/{} \;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
echo "Usage: cache.sh [options]"
|
||||
echo " (-ds|--diff-summary) list names of cache files that differ from ts/latest"
|
||||
echo " (-d|--diff) pat diff cache and latest subdirectories"
|
||||
echo " (-l|--list) list files subject to caching"
|
||||
echo " (-r|--refresh) pat refresh files in cache matching pattern"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
(-ds|--diff-summary) shift; cacheDiffSummary $@;;
|
||||
(-d|--diff) shift; cacheDiff $@;;
|
||||
(-l|--list) shift; printf "$FILES\n\n";;
|
||||
(-r|--refresh) shift; cacheRefresh $@;;
|
||||
(*) usage;
|
||||
esac
|
|
@ -1,43 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script currently requires that the site have been generated
|
||||
# under $SITE and that it is being served via http://localhost:8080.
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
[[ -z "$NGIO_ENV_DEFS" ]] && . ./scripts/env-set.sh > /dev/null
|
||||
if [[ "x$1" == "x-v" ]]; then VERBOSE=1; shift; fi
|
||||
|
||||
SITE=./www
|
||||
|
||||
CHECK_FOR=dart-bad-api-links
|
||||
|
||||
LOGFILE_PREFIX=$CHECK_FOR-log
|
||||
LOGFILE_FULL=$TMP/$LOGFILE_PREFIX-full.txt
|
||||
LOGFILE=$TMP/$LOGFILE_PREFIX.txt
|
||||
|
||||
if [[ ! -d $SITE ]]; then
|
||||
echo "Missing site folder $SITE"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
cd $SITE
|
||||
echo "" > $LOGFILE_FULL
|
||||
|
||||
# We don't check cookbook pages since they are all empty.
|
||||
# We don't check api pages because there are currently too many broken links.
|
||||
for f in docs/dart/latest/{,guide/,tutorial/}*.html; do
|
||||
echo "Checking links in $f";
|
||||
$(npm bin)/blc -e --get http://localhost:8080/$f >> $LOGFILE_FULL
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "Listing broken links, if any:"
|
||||
grep -i broken $LOGFILE_FULL | grep -v Finished || true
|
||||
echo ""
|
||||
|
||||
echo "Listing links to TS api pages from Dart docs pages, if any:"
|
||||
grep /api/ $LOGFILE_FULL | grep -v '/api/$' | grep -v /angular2. || true
|
||||
echo ""
|
||||
|
||||
echo "For details consult the full log $LOGFILE_FULL"
|
Loading…
Reference in New Issue