docs(aio): update migrated content from anguar.io
This commit is contained in:

committed by
Pete Bacon Darwin

parent
ff82756415
commit
fd72fad8fd
@ -79,9 +79,11 @@ The following table lists some of the key AngularJS template features with their
|
||||
<td>
|
||||
### Bindings/interpolation
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='interpolation'}
|
||||
|
||||
In Angular, a template expression in curly braces still denotes one-way binding.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="interpolation" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, a template expression in curly braces still denotes one-way binding.
|
||||
This binds the value of the element to a property of the component.
|
||||
The context of the binding is implied and is always the
|
||||
associated component, so it needs no reference variable.
|
||||
@ -111,9 +113,11 @@ The following table lists some of the key AngularJS template features with their
|
||||
<td>
|
||||
### Pipes
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='uppercase'}
|
||||
|
||||
In Angular you use similar syntax with the pipe (|) character to filter output, but now you call them **pipes**.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="uppercase" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular you use similar syntax with the pipe (|) character to filter output, but now you call them **pipes**.
|
||||
Many (but not all) of the built-in filters from AngularJS are
|
||||
built-in pipes in Angular.
|
||||
|
||||
@ -141,9 +145,11 @@ The following table lists some of the key AngularJS template features with their
|
||||
<td>
|
||||
### Input variables
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='local'}
|
||||
|
||||
Angular has true template input variables that are explicitly defined using the `let` keyword.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="local" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular has true template input variables that are explicitly defined using the `let` keyword.
|
||||
|
||||
For more information, see the [ngFor micro-syntax](guide/template-syntax)
|
||||
section of the [Template Syntax](guide/template-syntax) page.
|
||||
@ -208,13 +214,17 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Bootstrapping
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/main.ts'}
|
||||
<code-example path="cb-ajs-quick-reference/src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
<br>
|
||||
|
||||
<br>
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.module.1.ts'}
|
||||
|
||||
Angular doesn't have a bootstrap directive.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.module.1.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular doesn't have a bootstrap directive.
|
||||
To launch the app in code, explicitly bootstrap the application's root module (`AppModule`)
|
||||
in `main.ts`
|
||||
and the application's root component (`AppComponent`) in `app.module.ts`.
|
||||
@ -250,9 +260,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### ngClass
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='ngClass'}
|
||||
|
||||
In Angular, the `ngClass` directive works similarly.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="ngClass" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, the `ngClass` directive works similarly.
|
||||
It includes/excludes CSS classes based on an expression.
|
||||
|
||||
In the first example, the `active` class is applied to the element if `isActive` is true.
|
||||
@ -292,9 +304,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Bind to the `click` event
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='event-binding'}
|
||||
|
||||
AngularJS event-based directives do not exist in Angular.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="event-binding" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
AngularJS event-based directives do not exist in Angular.
|
||||
Rather, define one-way binding from the template view to the component using **event binding**.
|
||||
|
||||
For event binding, define the name of the target event within parenthesis and
|
||||
@ -335,9 +349,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Component decorator
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.ts' region='component'}
|
||||
|
||||
In Angular, the template no longer specifies its associated controller.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.ts" region="component" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, the template no longer specifies its associated controller.
|
||||
Rather, the component specifies its associated template as part of the component class decorator.
|
||||
|
||||
For more information, see [Architecture Overview](guide/architecture).
|
||||
@ -391,9 +407,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Bind to the `href` property
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='href'}
|
||||
|
||||
Angular uses property binding; there is no built-in *href* directive.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="href" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular uses property binding; there is no built-in *href* directive.
|
||||
Place the element's `href` property in square brackets and set it to a quoted template expression.
|
||||
|
||||
For more information see the [Property binding](guide/template-syntax)
|
||||
@ -401,9 +419,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
|
||||
In Angular, `href` is no longer used for routing. Routing uses `routerLink`, as shown in the following example.
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='router-link'}
|
||||
|
||||
For more information on routing, see the [RouterLink binding](guide/router)
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="router-link" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
For more information on routing, see the [RouterLink binding](guide/router)
|
||||
section of the [Routing & Navigation](guide/router) page.
|
||||
|
||||
</td>
|
||||
@ -430,9 +450,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### *ngIf
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='ngIf'}
|
||||
|
||||
The `*ngIf` directive in Angular works the same as the `ng-if` directive in AngularJS. It removes
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="ngIf" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `*ngIf` directive in Angular works the same as the `ng-if` directive in AngularJS. It removes
|
||||
or recreates a portion of the DOM based on an expression.
|
||||
|
||||
In this example, the `<table>` element is removed from the DOM unless the `movies` array has a length.
|
||||
@ -461,9 +483,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### ngModel
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='ngModel'}
|
||||
|
||||
In Angular, **two-way binding** is denoted by `[()]`, descriptively referred to as a "banana in a box". This syntax is a shortcut for defining both property binding (from the component to the view)
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="ngModel" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, **two-way binding** is denoted by `[()]`, descriptively referred to as a "banana in a box". This syntax is a shortcut for defining both property binding (from the component to the view)
|
||||
and event binding (from the view to the component), thereby providing two-way binding.
|
||||
|
||||
For more information on two-way binding with `ngModel`, see the [NgModel—Two-way binding to
|
||||
@ -493,9 +517,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### *ngFor
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='ngFor'}
|
||||
|
||||
The `*ngFor` directive in Angular is similar to the `ng-repeat` directive in AngularJS. It repeats
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="ngFor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `*ngFor` directive in Angular is similar to the `ng-repeat` directive in AngularJS. It repeats
|
||||
the associated DOM element for each item in the specified collection.
|
||||
More accurately, it turns the defined element (`<tr>` in this example) and its contents into a template and
|
||||
uses that template to instantiate a view for each item in the list.
|
||||
@ -532,9 +558,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Bind to the `hidden` property
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='hidden'}
|
||||
|
||||
Angular uses property binding; there is no built-in *show* directive.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="hidden" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular uses property binding; there is no built-in *show* directive.
|
||||
For hiding and showing elements, bind to the HTML `hidden` property.
|
||||
|
||||
To conditionally display an element, place the element's `hidden` property in square brackets and
|
||||
@ -567,9 +595,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### Bind to the `src` property
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='src'}
|
||||
|
||||
Angular uses property binding; there is no built-in *src* directive.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="src" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular uses property binding; there is no built-in *src* directive.
|
||||
Place the `src` property in square brackets and set it to a quoted template expression.
|
||||
|
||||
For more information on property binding, see the [Property binding](guide/template-syntax)
|
||||
@ -600,9 +630,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### ngStyle
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='ngStyle'}
|
||||
|
||||
In Angular, the `ngStyle` directive works similarly. It sets a CSS style on an HTML element based on an expression.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="ngStyle" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, the `ngStyle` directive works similarly. It sets a CSS style on an HTML element based on an expression.
|
||||
|
||||
In the first example, the `color` style is set to the current value of the `colorPreference` variable.
|
||||
|
||||
@ -651,9 +683,11 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
<td>
|
||||
### ngSwitch
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.html' region='ngSwitch'}
|
||||
|
||||
In Angular, the `ngSwitch` directive works similarly.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.html" region="ngSwitch" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, the `ngSwitch` directive works similarly.
|
||||
It displays an element whose `*ngSwitchCase` matches the current `ngSwitch` expression value.
|
||||
|
||||
In this example, if `favoriteHero` is not set, the `ngSwitch` value is `null`
|
||||
@ -726,9 +760,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### currency
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='currency'}
|
||||
|
||||
The Angular `currency` pipe is similar although some of the parameters have changed.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="currency" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Angular `currency` pipe is similar although some of the parameters have changed.
|
||||
</td>
|
||||
|
||||
|
||||
@ -740,7 +776,7 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### date
|
||||
<code-example>
|
||||
<td>{{movie.releaseDate | date}}</td>
|
||||
<td>{{movie.releaseDate | date}}</td>
|
||||
</code-example>
|
||||
|
||||
Formats a date to a string based on the requested format.
|
||||
@ -750,9 +786,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### date
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='date'}
|
||||
|
||||
The Angular `date` pipe is similar.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="date" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Angular `date` pipe is similar.
|
||||
|
||||
</td>
|
||||
|
||||
@ -797,9 +835,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### json
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='json'}
|
||||
|
||||
The Angular `json` pipe does the same thing.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Angular `json` pipe does the same thing.
|
||||
</td>
|
||||
|
||||
|
||||
@ -822,9 +862,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### slice
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='slice'}
|
||||
|
||||
The `SlicePipe` does the same thing but the *order of the parameters is reversed*, in keeping
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="slice" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `SlicePipe` does the same thing but the *order of the parameters is reversed*, in keeping
|
||||
with the JavaScript `Slice` method.
|
||||
The first parameter is the starting index; the second is the limit.
|
||||
As in AngularJS, coding this operation within the component instead could improve performance.
|
||||
@ -849,9 +891,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### lowercase
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='lowercase'}
|
||||
|
||||
The Angular `lowercase` pipe does the same thing.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="lowercase" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Angular `lowercase` pipe does the same thing.
|
||||
</td>
|
||||
|
||||
|
||||
@ -863,7 +907,7 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### number
|
||||
<code-example>
|
||||
<td>{{movie.starRating | number}}</td>
|
||||
<td>{{movie.starRating | number}}</td>
|
||||
</code-example>
|
||||
|
||||
Formats a number as text.
|
||||
@ -873,9 +917,11 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
<td>
|
||||
### number
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.component.html' region='number'}
|
||||
|
||||
The Angular `number` pipe is similar.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.component.html" region="number" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Angular `number` pipe is similar.
|
||||
It provides more functionality when defining
|
||||
the decimal places, as shown in the second example above.
|
||||
|
||||
@ -999,9 +1045,11 @@ The Angular code is shown using TypeScript.
|
||||
<td>
|
||||
### Angular modules
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/app.module.1.ts'}
|
||||
|
||||
Angular modules, defined with the `NgModule` decorator, serve the same purpose:
|
||||
<code-example path="cb-ajs-quick-reference/src/app/app.module.1.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular modules, defined with the `NgModule` decorator, serve the same purpose:
|
||||
- `imports`: specifies the list of other modules that this module depends upon
|
||||
- `declaration`: keeps track of your components, pipes, and directives.
|
||||
|
||||
@ -1035,9 +1083,11 @@ The Angular code is shown using TypeScript.
|
||||
<td>
|
||||
### Component decorator
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.ts' region='component'}
|
||||
|
||||
Angular adds a decorator to the component class to provide any required metadata.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.ts" region="component" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular adds a decorator to the component class to provide any required metadata.
|
||||
The `@Component` decorator declares that the class is a component and provides metadata about
|
||||
that component such as its selector (or tag) and its template.
|
||||
|
||||
@ -1067,9 +1117,11 @@ The Angular code is shown using TypeScript.
|
||||
<td>
|
||||
### Component class
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.ts' region='class'}
|
||||
|
||||
In Angular, you create a component class.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.ts" region="class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, you create a component class.
|
||||
|
||||
NOTE: If you are using TypeScript with AngularJS, you must use the `export` keyword to export the component class.
|
||||
|
||||
@ -1102,9 +1154,11 @@ The Angular code is shown using TypeScript.
|
||||
<td>
|
||||
### Dependency injection
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.ts' region='di'}
|
||||
|
||||
In Angular, you pass in dependencies as arguments to the component class constructor.
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.ts" region="di" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, you pass in dependencies as arguments to the component class constructor.
|
||||
This example injects a `MovieService`.
|
||||
The first parameter's TypeScript type tells Angular what to inject, even after minification.
|
||||
|
||||
@ -1173,16 +1227,20 @@ also encapsulate a style sheet within a specific component.
|
||||
<td>
|
||||
### Link tag
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/index.html' region='style'}
|
||||
|
||||
In Angular, you can continue to use the link tag to define the styles for your application in the `index.html` file.
|
||||
<code-example path="cb-ajs-quick-reference/src/index.html" region="style" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In Angular, you can continue to use the link tag to define the styles for your application in the `index.html` file.
|
||||
But now you can also encapsulate styles for your components. ### StyleUrls
|
||||
In Angular, you can use the `styles` or `styleUrls` property of the `@Component` metadata to define
|
||||
a style sheet for a particular component.
|
||||
|
||||
{@example 'cb-ajs-quick-reference/ts/src/app/movie-list.component.ts' region='style-url'}
|
||||
|
||||
This allows you to set appropriate styles for individual components that won’t leak into
|
||||
<code-example path="cb-ajs-quick-reference/src/app/movie-list.component.ts" region="style-url" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This allows you to set appropriate styles for individual components that won’t leak into
|
||||
other parts of the application.
|
||||
</td>
|
||||
|
||||
|
@ -41,9 +41,16 @@ add it to your page.
|
||||
* [Parallel animation groups](guide/animations#parallel-animation-groups).
|
||||
* [Animation callbacks](guide/animations#animation-callbacks).
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The examples in this page are available as a <live-example></live-example>.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a example-transitioning-between-states}
|
||||
|
||||
## Quickstart example: Transitioning between two states
|
||||
@ -55,17 +62,27 @@ You can build a simple animation that transitions an element between two states
|
||||
driven by a model attribute.
|
||||
|
||||
Animations are defined inside `@Component` metadata. Before you can add animations, you need
|
||||
to import a few animation-specific functions:
|
||||
to import a few animation-specific imports and functions:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='imports'}
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
With these, you can define an *animation trigger* called `heroState` in the component
|
||||
metadata. It uses animations to transition between two states: `active` and `inactive`. When a
|
||||
hero is active, the element appears in a slightly larger size and lighter color.
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -81,7 +98,9 @@ Now, using the `[@triggerName]` syntax, attach the animation that you just defin
|
||||
one or more elements in the component's template.
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='template'}
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here, the animation trigger applies to every element repeated by an `ngFor`. Each of
|
||||
the repeated elements animates independently. The value of the
|
||||
@ -91,7 +110,9 @@ With this setup, an animated transition appears whenever a hero object changes s
|
||||
Here's the full component implementation:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts'}
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
## States and transitions
|
||||
|
||||
@ -107,7 +128,9 @@ component's template.
|
||||
You can define *styles* for each animation state:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='states'}
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
These `state` definitions specify the *end styles* of each state.
|
||||
They are applied to the element once it has transitioned to that state, and stay
|
||||
@ -117,7 +140,9 @@ After you define states, you can define *transitions* between the states. Each t
|
||||
controls the timing of switching between one set of styles and the next:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='transitions'}
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -128,13 +153,17 @@ If several transitions have the same timing configuration, you can combine
|
||||
them into the same `transition` definition:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-combined-transitions.component.ts' region='transitions'}
|
||||
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
When both directions of a transition have the same timing, as in the previous
|
||||
example, you can use the shorthand syntax `<=>`:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-twoway.component.ts' region='transitions'}
|
||||
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can also apply a style during an animation but not keep it around
|
||||
after the animation finishes. You can define such styles inline, in the `transition`. In this example,
|
||||
@ -143,7 +172,9 @@ When the transition finishes, none of these styles are kept because they're not
|
||||
defined in a `state`.
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-inline-styles.component.ts' region='transitions'}
|
||||
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### The wildcard state `*`
|
||||
|
||||
@ -187,13 +218,18 @@ entering and leaving of elements:
|
||||
For example, in the `animations` !{_array} below there are two transitions that use
|
||||
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-enter-leave.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Note that in this case the styles are applied to the void state directly in the
|
||||
transition definitions, and not in a separate `state(void)` definition. Thus, the transforms
|
||||
are different on enter and leave: the element enters from the left
|
||||
and leaves to the right.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
These two common animations have their own aliases:
|
||||
<code-example language="typescript">
|
||||
transition(':enter', [ ... ]); // void => *
|
||||
@ -201,6 +237,10 @@ These two common animations have their own aliases:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
## Example: Entering and leaving from different states
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" align="right" style="width:200px"> </img>
|
||||
@ -224,7 +264,9 @@ This gives you fine-grained control over each transition:
|
||||
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-enter-leave-states.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-enter-leave-states.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
## Animatable properties and units
|
||||
|
||||
@ -262,7 +304,9 @@ In this example, the leave animation takes whatever height the element has befor
|
||||
leaves and animates from that height to zero:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-auto.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
## Animation timing
|
||||
|
||||
@ -310,7 +354,9 @@ slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
|
||||
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-timings.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
## Multi-step animations with keyframes
|
||||
<figure>
|
||||
@ -328,7 +374,9 @@ This example adds some "bounce" to the enter and leave animations with
|
||||
keyframes:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-multistep.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Note that the offsets are *not* defined in terms of absolute time. They are relative
|
||||
measures from zero to one. The final timeline of the animation is based on the combination
|
||||
@ -354,7 +402,9 @@ enter and leave allows for two different timing configurations. Both
|
||||
are applied to the same element in parallel, but run independently of each other:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-groups.component.ts' region='animationdef'}
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
One group animates the element transform and width; the other group animates the opacity.
|
||||
## Animation callbacks
|
||||
@ -365,9 +415,11 @@ In the keyframes example, you have a `trigger` called `@flyInOut`. You can hook
|
||||
those callbacks like this:
|
||||
|
||||
|
||||
{@example 'animations/ts/src/app/hero-list-multistep.component.ts' region='template'}
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" linenums="false">
|
||||
|
||||
The callbacks receive an `AnimationTransitionEvent` which contains useful properties such as `fromState`,
|
||||
`toState` and `totalTime`.
|
||||
</code-example>
|
||||
|
||||
The callbacks receive an `AnimationEvent` that contains contains useful properties such as
|
||||
`fromState`, `toState` and `totalTime`.
|
||||
|
||||
Those callbacks will fire whether or not an animation is picked up.
|
@ -2,37 +2,54 @@
|
||||
Ahead-of-Time Compilation
|
||||
|
||||
@intro
|
||||
Learn how to use Ahead-of-time compilation.
|
||||
Learn how to use ahead-of-time compilation.
|
||||
|
||||
@description
|
||||
This cookbook describes how to radically improve performance by compiling _Ahead of Time_ (AOT)
|
||||
This cookbook describes how to radically improve performance by compiling _ahead-of-time_ (AOT)
|
||||
during a build process.
|
||||
|
||||
|
||||
{@a toc}
|
||||
## Table of Contents
|
||||
* [Overview](guide/aot-compiler#overview)
|
||||
* [_Ahead-of-Time_ vs _Just-in-Time_](guide/aot-compiler#aot-jit)
|
||||
* [Compile with AOT](guide/aot-compiler#compile)
|
||||
* [Bootstrap](guide/aot-compiler#bootstrap)
|
||||
* [Tree Shaking](guide/aot-compiler#tree-shaking)
|
||||
* [Load the bundle](guide/aot-compiler#load)
|
||||
* [Serve the app](guide/aot-compiler#serve)
|
||||
* [Workflow and convenience script](guide/aot-compiler#workflow)
|
||||
* [Source Code](guide/aot-compiler#source-code)
|
||||
* [Tour of Heroes](guide/aot-compiler#toh)
|
||||
# Contents
|
||||
- [Overview](guide/overview)
|
||||
- [Ahead-of-time (AOT) vs just-in-time (JIT)](guide/aot-compiler#aot-jit)
|
||||
- [Why do AOT compilation?](guide/aot-compiler#why-aot)
|
||||
- [Compile with AOT](guide/aot-compiler#compile)
|
||||
- [Bootstrap](guide/aot-compiler#bootstrap)
|
||||
- [Tree shaking](guide/aot-compiler#tree-shaking)
|
||||
- [Rollup](guide/aot-compiler#rollup)
|
||||
- [Rollup Plugins](guide/aot-compiler#rollup-plugins)
|
||||
- [Run Rollup](guide/aot-compiler#run-rollup)
|
||||
- [Load the bundle](guide/aot-compiler#load)
|
||||
- [Serve the app](guide/aot-compiler#serve)
|
||||
- [AOT QuickStart source code](guide/aot-compiler#source-code)
|
||||
- [Workflow and convenience script](guide/aot-compiler#workflow)
|
||||
- [Develop JIT along with AOT](guide/aot-compiler#run-jit)
|
||||
- [Tour of Heroes](guide/aot-compiler#toh)
|
||||
- [JIT in development, AOT in production](guide/aot-compiler#jit-dev-aot-prod)
|
||||
- [Tree shaking](guide/aot-compiler#shaking)
|
||||
- [Running the application](guide/aot-compiler#running-app)
|
||||
- [Inspect the Bundle](guide/aot-compiler#inspect-bundle)
|
||||
|
||||
|
||||
|
||||
{@a overview}
|
||||
|
||||
## Overview
|
||||
|
||||
An Angular application consist largely of components and their HTML templates.
|
||||
An Angular application consists largely of components and their HTML templates.
|
||||
Before the browser can render the application,
|
||||
the components and templates must be converted to executable JavaScript by the _Angular compiler_.
|
||||
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo" target="_blank">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.You can compile the app in the browser, at runtime, as the application loads, using the **_Just-in-Time_ (JIT) compiler**.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo" target="_blank">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
|
||||
|
||||
~~~
|
||||
|
||||
You can compile the app in the browser, at runtime, as the application loads, using the **_just-in-time_ (JIT) compiler**.
|
||||
This is the standard development approach shown throughout the documentation.
|
||||
It's great .. but it has shortcomings.
|
||||
It's great but it has shortcomings.
|
||||
|
||||
JIT compilation incurs a runtime performance penalty.
|
||||
Views take longer to render because of the in-browser compilation step.
|
||||
@ -41,22 +58,23 @@ and a lot of library code that the application won't actually need.
|
||||
Bigger apps take longer to transmit and are slower to load.
|
||||
|
||||
Compilation can uncover many component-template binding errors.
|
||||
JIT compilation discovers them at runtime which is later than we'd like.
|
||||
JIT compilation discovers them at runtime, which is late in the process.
|
||||
|
||||
The **_Ahead-of-Time_ (AOT) compiler** can catch template errors early and improve performance
|
||||
by compiling at build time as you'll learn in this chapter.
|
||||
The **_ahead-of-time_ (AOT) compiler** can catch template errors early and improve performance
|
||||
by compiling at build time.
|
||||
|
||||
|
||||
|
||||
{@a aot-jit}
|
||||
|
||||
## _Ahead-of-time_ (AOT) vs _Just-in-time_ (JIT)
|
||||
## _Ahead-of-time_ (AOT) vs _just-in-time_ (JIT)
|
||||
|
||||
There is actually only one Angular compiler. The difference between AOT and JIT is a matter of timing and tooling.
|
||||
With AOT, the compiler runs once at build time using one set of libraries;
|
||||
With JIT it runs every time for every user at runtime using a different set of libraries.
|
||||
with JIT it runs every time for every user at runtime using a different set of libraries.
|
||||
|
||||
### Why do AOT compilation?
|
||||
{@a why-aot}
|
||||
## Why do AOT compilation?
|
||||
|
||||
*Faster rendering*
|
||||
|
||||
@ -65,7 +83,7 @@ The browser loads executable code so it can render the application immediately,
|
||||
|
||||
*Fewer asynchronous requests*
|
||||
|
||||
The compiler _inlines_ external html templates and css style sheets within the application JavaScript,
|
||||
The compiler _inlines_ external HTML templates and CSS style sheets within the application JavaScript,
|
||||
eliminating separate ajax requests for those source files.
|
||||
|
||||
*Smaller Angular framework download size*
|
||||
@ -91,23 +109,24 @@ there are fewer opportunities for injection attacks.
|
||||
|
||||
## Compile with AOT
|
||||
|
||||
### Prepare for offline compilation
|
||||
Preparing for offline compilation takes a few simple steps.
|
||||
Take the <a href='../guide/setup.html'>Setup</a> as a starting point.
|
||||
A few minor changes to the lone `app.component` lead to these two class and html files:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.component.html">
|
||||
{@example 'cb-aot-compiler/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
A few minor changes to the lone `app.component` lead to these two class and HTML files:
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'cb-aot-compiler/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.html" path="cb-aot-compiler/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
<code-pane title="src/app/app.component.ts" path="cb-aot-compiler/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</code-tabs>
|
||||
|
||||
Install a few new npm dependencies with the following command:
|
||||
<code-example language="none" class="code-shell">
|
||||
@ -120,39 +139,32 @@ instead of the TypeScript compiler (`tsc`).
|
||||
`ngc` is a drop-in replacement for `tsc` and is configured much the same way.
|
||||
|
||||
`ngc` requires its own `tsconfig.json` with AOT-oriented settings.
|
||||
Copy the original `src/tsconfig.json` to a file called `tsconfig-aot.json` (on the project root),
|
||||
then modify it to look as follows.
|
||||
Copy the original `src/tsconfig.json` to a file called `tsconfig-aot.json` on the project root,
|
||||
then modify it as follows.
|
||||
|
||||
|
||||
{@example 'cb-aot-compiler/ts/tsconfig-aot.json'}
|
||||
<code-example path="cb-aot-compiler/tsconfig-aot.json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `compilerOptions` section is unchanged except for one property.
|
||||
**Set the `module` to `es2015`**.
|
||||
This is important as explained later in the [Tree Shaking](guide/aot-compiler#tree-shaking) section.
|
||||
|
||||
What's really new is the `ngc` section at the bottom called `angularCompilerOptions`.
|
||||
Its `"genDir"` property tells the compiler
|
||||
Its `genDir` property tells the compiler
|
||||
to store the compiled output files in a new `aot` folder.
|
||||
|
||||
The `"skipMetadataEmit" : true` property prevents the compiler from generating metadata files with the compiled application.
|
||||
Metadata files are not necessary when targeting TypeScript files, so there is no reason to include them.
|
||||
***Component-relative Template URLS***
|
||||
***Component-relative template URLS***
|
||||
|
||||
The AOT compiler requires that `@Component` URLS for external templates and css files be _component-relative_.
|
||||
The AOT compiler requires that `@Component` URLS for external templates and CSS files be _component-relative_.
|
||||
That means that the value of `@Component.templateUrl` is a URL value _relative_ to the component class file.
|
||||
For example, an `'app.component.html'` URL means that the template file is a sibling of its companion `app.component.ts` file.
|
||||
|
||||
While JIT app URLs are more flexible, stick with _component-relative_ URLs for compatibility with AOT compilation.
|
||||
|
||||
JIT-compiled applications that use the SystemJS loader and _component-relative_ URLs *must set the* `@Component.moduleId` *property to* `module.id`.
|
||||
The `module` object is undefined when an AOT-compiled app runs.
|
||||
The app fails with a null reference error unless you assign a global `module` value in the `index.html` like this:
|
||||
|
||||
{@example 'cb-aot-compiler/ts/src/index.html' region='moduleId'}
|
||||
|
||||
|
||||
Setting a global `module` is a temporary expedient.
|
||||
### Compiling the application
|
||||
***Compiling the application***
|
||||
|
||||
Initiate AOT compilation from the command line using the previously installed `ngc` compiler by executing:
|
||||
<code-example language="none" class="code-shell">
|
||||
@ -160,26 +172,41 @@ Initiate AOT compilation from the command line using the previously installed `n
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Windows users should surround the `ngc` command in double quotes:
|
||||
<code-example format='.'>
|
||||
"node_modules/.bin/ngc" -p tsconfig-aot.json
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
`ngc` expects the `-p` switch to point to a `tsconfig.json` file or a folder containing a `tsconfig.json` file.
|
||||
|
||||
After `ngc` completes, look for a collection of _NgFactory_ files in the `aot` folder (the folder specified as `genDir` in `tsconfig-aot.json`).
|
||||
After `ngc` completes, look for a collection of _NgFactory_ files in the `aot` folder.
|
||||
The `aot` folder is the directory specified as `genDir` in `tsconfig-aot.json`.
|
||||
|
||||
These factory files are essential to the compiled application.
|
||||
Each component factory creates an instance of the component at runtime by combining the original class file
|
||||
and a JavaScript representation of the component's template.
|
||||
Note that the original component class is still referenced internally by the generated factory.
|
||||
The curious can open the `aot/app.component.ngfactory.ts` to see the original Angular template syntax
|
||||
in its intermediate, compiled-to-TypeScript form.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The curious can open `aot/app.component.ngfactory.ts` to see the original Angular template syntax
|
||||
compiled to TypeScript, its intermediate form.
|
||||
|
||||
JIT compilation generates these same _NgFactories_ in memory where they are largely invisible.
|
||||
AOT compilation reveals them as separate, physical files.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
Do not edit the _NgFactories_! Re-compilation replaces these files and all edits will be lost.
|
||||
@ -193,7 +220,7 @@ Do not edit the _NgFactories_! Re-compilation replaces these files and all edits
|
||||
|
||||
## Bootstrap
|
||||
|
||||
The AOT path changes application bootstrapping.
|
||||
The AOT approach changes application bootstrapping.
|
||||
|
||||
Instead of bootstrapping `AppModule`, you bootstrap the application with the generated module factory, `AppModuleNgFactory`.
|
||||
|
||||
@ -206,54 +233,64 @@ Switch from the `platformBrowserDynamic.bootstrap` used in JIT compilation to
|
||||
|
||||
Here is AOT bootstrap in `main.ts` next to the original JIT version:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/main.ts">
|
||||
{@example 'cb-aot-compiler/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/main.ts" path="cb-aot-compiler/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/main-jit.ts">
|
||||
{@example 'cb-aot-compiler/ts/src/main-jit.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/main-jit.ts" path="cb-aot-compiler/src/main-jit.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Be sure to recompile with `ngc`!
|
||||
Be sure to [recompile](guide/aot-compiler#compiling-aot) with `ngc`!
|
||||
|
||||
|
||||
{@a tree-shaking}
|
||||
## Tree Shaking
|
||||
## Tree shaking
|
||||
|
||||
AOT compilation sets the stage for further optimization through a process called _Tree Shaking_.
|
||||
A Tree Shaker walks the dependency graph, top to bottom, and _shakes out_ unused code like
|
||||
dead needles in a Christmas tree.
|
||||
AOT compilation sets the stage for further optimization through a process called _tree shaking_.
|
||||
A tree shaker walks the dependency graph, top to bottom, and _shakes out_ unused code like
|
||||
dead leaves in a tree.
|
||||
|
||||
Tree Shaking can greatly reduce the downloaded size of the application
|
||||
Tree shaking can greatly reduce the downloaded size of the application
|
||||
by removing unused portions of both source and library code.
|
||||
In fact, most of the reduction in small apps comes from removing unreferenced Angular features.
|
||||
|
||||
For example, this demo application doesn't use anything from the `@angular/forms` library.
|
||||
There is no reason to download Forms-related Angular code and tree shaking ensures that you don't.
|
||||
There is no reason to download forms-related Angular code and tree shaking ensures that you don't.
|
||||
|
||||
Tree Shaking and AOT compilation are separate steps.
|
||||
Tree Shaking can only target JavaScript code.
|
||||
Tree shaking and AOT compilation are separate steps.
|
||||
Tree shaking can only target JavaScript code.
|
||||
AOT compilation converts more of the application to JavaScript,
|
||||
which in turn makes more of the application "Tree Shakable".
|
||||
which in turn makes more of the application "tree shakable".
|
||||
|
||||
|
||||
{@a rollup}
|
||||
### Rollup
|
||||
|
||||
This cookbook illustrates a Tree Shaking utility called _Rollup_.
|
||||
This cookbook illustrates a tree shaking utility called _Rollup_.
|
||||
|
||||
Rollup statically analyzes the application by following the trail of `import` and `export` statements.
|
||||
It produces a final code _bundle_ that excludes code that is exported, but never imported.
|
||||
|
||||
Rollup can only Tree Shake `ES2015` modules which have `import` and `export` statements.
|
||||
Rollup can only tree shake `ES2015` modules which have `import` and `export` statements.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Recall that `tsconfig-aot.json` is configured to produce `ES2015` modules.
|
||||
It's not important that the code itself be written with `ES2015` syntax such as `class` and `const`.
|
||||
What matters is that the code uses ES `import` and `export` statements rather than `require` statements.Install the Rollup dependencies with this command:
|
||||
<code-example format='.'>
|
||||
What matters is that the code uses ES `import` and `export` statements rather than `require` statements.
|
||||
|
||||
~~~
|
||||
|
||||
In the terminal window, install the Rollup dependencies with this command:
|
||||
<code-example language="none" class="code-shell">
|
||||
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
|
||||
</code-example>
|
||||
|
||||
@ -262,66 +299,98 @@ in the project root directory to tell Rollup how to process the application.
|
||||
The cookbook configuration file looks like this.
|
||||
|
||||
|
||||
{@example 'cb-aot-compiler/ts/rollup-config.js'}
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" linenums="false">
|
||||
|
||||
It tells Rollup that the app entry point is `src/app/main.js` .
|
||||
</code-example>
|
||||
|
||||
This config file tells Rollup that the app entry point is `src/app/main.js` .
|
||||
The `dest` attribute tells Rollup to create a bundle called `build.js` in the `dist` folder.
|
||||
It overrides the default `onwarn` method in order to skip annoying messages about the AOT compiler's use of the `this` keyword.
|
||||
|
||||
Then there are plugins.
|
||||
The next section covers the plugins in more depth.
|
||||
|
||||
|
||||
{@a rollup-plugins}
|
||||
### Rollup Plugins
|
||||
|
||||
Optional plugins filter and transform the Rollup inputs and output.
|
||||
|
||||
*RxJS*
|
||||
|
||||
Rollup expects application source code to use `ES2015` modules.
|
||||
Not all external dependencies are published as `ES2015` modules.
|
||||
In fact, most are not. Many of them are published as _CommonJS_ modules.
|
||||
|
||||
The _RxJs_ observable library is an essential Angular dependency published as an ES5 JavaScript _CommonJS_ module.
|
||||
The _RxJs_ Observable library is an essential Angular dependency published as an ES5 JavaScript _CommonJS_ module.
|
||||
|
||||
Luckily there is a Rollup plugin that modifies _RxJs_
|
||||
Luckily, there is a Rollup plugin that modifies _RxJs_
|
||||
to use the ES `import` and `export` statements that Rollup requires.
|
||||
Rollup then preserves in the final bundle the parts of `RxJS` referenced by the application.
|
||||
Rollup then preserves the parts of `RxJS` referenced by the application
|
||||
in the final bundle. Using it is straigthforward. Add the following to
|
||||
the `plugins` !{_array} in `rollup-config.js`:
|
||||
|
||||
|
||||
{@example 'cb-aot-compiler/ts/rollup-config.js' region='commonjs'}
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="commonjs" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
*Minification*
|
||||
|
||||
Rollup Tree Shaking reduces code size considerably. Minification makes it smaller still.
|
||||
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
|
||||
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
|
||||
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
|
||||
Add the following to the `plugins` !{_array}:
|
||||
|
||||
|
||||
{@example 'cb-aot-compiler/ts/rollup-config.js' region='uglify'}
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="uglify" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
In a production setting, you would also enable gzip on the web server to compress
|
||||
the code into an even smaller package going over the wire.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a run-rollup}
|
||||
### Run Rollup
|
||||
Execute the Rollup process with this command:
|
||||
<code-example format='.'>
|
||||
<code-example language="none" class="code-shell">
|
||||
node_modules/.bin/rollup -c rollup-config.js
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Windows users should surround the `rollup` command in double quotes:
|
||||
<code-example format='.'>
|
||||
<code-example language="none" class="code-shell">
|
||||
"node_modules/.bin/rollup" -c rollup-config.js
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a load}
|
||||
|
||||
## Load the Bundle
|
||||
## Load the bundle
|
||||
|
||||
Loading the generated application bundle does not require a module loader like SystemJS.
|
||||
Remove the scripts that concern SystemJS.
|
||||
Instead, load the bundle file using a single `script` tag **_after_** the `</body>` tag:
|
||||
Instead, load the bundle file using a single `<script>` tag **_after_** the `</body>` tag:
|
||||
|
||||
|
||||
{@example 'cb-aot-compiler/ts/src/index.html' region='bundle'}
|
||||
<code-example path="cb-aot-compiler/src/index.html" region="bundle" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -330,7 +399,7 @@ Instead, load the bundle file using a single `script` tag **_after_** the `</bod
|
||||
## Serve the app
|
||||
|
||||
You'll need a web server to host the application.
|
||||
Use the same _Lite Server_ employed elsewhere in the documentation:
|
||||
Use the same `lite-server` employed elsewhere in the documentation:
|
||||
<code-example language="none" class="code-shell">
|
||||
npm run lite
|
||||
</code-example>
|
||||
@ -340,42 +409,43 @@ The server starts, launches a browser, and the app should appear.
|
||||
|
||||
{@a source-code}
|
||||
|
||||
## AOT QuickStart Source Code
|
||||
## AOT QuickStart source code
|
||||
|
||||
Here's the pertinent source code:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.component.html">
|
||||
{@example 'cb-aot-compiler/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.html" path="cb-aot-compiler/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'cb-aot-compiler/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.ts" path="cb-aot-compiler/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/main.ts">
|
||||
{@example 'cb-aot-compiler/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/main.ts" path="cb-aot-compiler/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/index.html">
|
||||
{@example 'cb-aot-compiler/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/index.html" path="cb-aot-compiler/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="tsconfig-aot.json">
|
||||
{@example 'cb-aot-compiler/ts/tsconfig-aot.json'}
|
||||
</md-tab>
|
||||
<code-pane title="tsconfig-aot.json" path="cb-aot-compiler/tsconfig-aot.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="rollup-config.js">
|
||||
{@example 'cb-aot-compiler/ts/rollup-config.js'}
|
||||
</md-tab>
|
||||
<code-pane title="rollup-config.js" path="cb-aot-compiler/rollup-config.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -395,7 +465,7 @@ Add the following _npm_ convenience script to the `package.json` so you can comp
|
||||
|
||||
|
||||
{@a run-jit}
|
||||
### And JIT too!
|
||||
### Develop JIT along with AOT
|
||||
|
||||
AOT compilation and rollup together take several seconds.
|
||||
You may be able to develop iteratively a little faster with SystemJS and JIT.
|
||||
@ -405,24 +475,28 @@ The same source code can be built both ways. Here's one way to do that.
|
||||
* Delete the script at the bottom of `index-jit.html` that loads `bundle.js`
|
||||
* Restore the SystemJS scripts like this:
|
||||
|
||||
{@example 'cb-aot-compiler/ts/src/index-jit.html' region='jit'}
|
||||
<code-example path="cb-aot-compiler/src/index-jit.html" region="jit" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice the slight change to the `system.import` which now specifies `src/app/main-jit`.
|
||||
That's the JIT version of the bootstrap file that we preserved [above](guide/aot-compiler#bootstrap)
|
||||
Open a _different_ terminal window and enter.
|
||||
That's the JIT version of the bootstrap file that we preserved [above](guide/aot-compiler#bootstrap).
|
||||
Open a _different_ terminal window and enter `npm start`.
|
||||
<code-example language="none" class="code-shell">
|
||||
npm start
|
||||
</code-example>
|
||||
|
||||
That compiles the app with JIT and launches the server.
|
||||
The server loads `index.html` which is still the AOT version (confirm in the browser console).
|
||||
Change the address bar to `index-jit.html` and it loads the JIT version (confirm in the browser console).
|
||||
The server loads `index.html` which is still the AOT version, which you can confirm in the browser console.
|
||||
Change the address bar to `index-jit.html` and it loads the JIT version.
|
||||
This is also evident in the browser console.
|
||||
|
||||
Develop as usual.
|
||||
The server and TypeScript compiler are in "watch mode" so your changes are reflected immediately in the browser.
|
||||
|
||||
To see those changes in AOT, switch to the original terminal and re-run `npm run build:aot`.
|
||||
When it finishes, go back to the browser and back-button to the AOT version in the (default) `index.html`.
|
||||
When it finishes, go back to the browser and use the back button to
|
||||
return to the AOT version in the default `index.html`.
|
||||
|
||||
Now you can develop JIT and AOT, side-by-side.
|
||||
|
||||
@ -432,13 +506,15 @@ Now you can develop JIT and AOT, side-by-side.
|
||||
|
||||
## Tour of Heroes
|
||||
|
||||
The sample above is a trivial variation of the QuickStart app.
|
||||
In this section you apply what you've learned about AOT compilation and Tree Shaking
|
||||
to an app with more substance, the tutorial [_Tour of Heroes_](tutorial/toh-pt6).
|
||||
The sample above is a trivial variation of the QuickStart application.
|
||||
In this section you apply what you've learned about AOT compilation and tree shaking
|
||||
to an app with more substance, the [_Tour of Heroes_](tutorial/toh-pt6) application.
|
||||
|
||||
|
||||
{@a jit-dev-aot-prod}
|
||||
### JIT in development, AOT in production
|
||||
|
||||
Today AOT compilation and Tree Shaking take more time than is practical for development. That will change soon.
|
||||
Today AOT compilation and tree shaking take more time than is practical for development. That will change soon.
|
||||
For now, it's best to JIT compile in development and switch to AOT compilation before deploying to production.
|
||||
|
||||
Fortunately, the source code can be compiled either way without change _if_ you account for a few key differences.
|
||||
@ -449,19 +525,20 @@ The JIT and AOT apps require their own `index.html` files because they setup and
|
||||
|
||||
Here they are for comparison:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="aot/index.html (AOT)">
|
||||
{@example 'toh-6/ts/aot/index.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="aot/index.html (AOT)" path="toh-6/aot/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/index.html (JIT)">
|
||||
{@example 'toh-6/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/index.html (JIT)" path="toh-6/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The JIT version relies on `SystemJS` to load individual modules.
|
||||
Its scripts appear in its `index.html`.
|
||||
@ -475,41 +552,43 @@ JIT and AOT applications boot in much the same way but require different Angular
|
||||
The key differences, covered in the [Bootstrap](guide/aot-compiler#bootstrap) section above,
|
||||
are evident in these `main` files which can and should reside in the same folder:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="main-aot.ts (AOT)">
|
||||
{@example 'toh-6/ts/src/main-aot.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="main-aot.ts (AOT)" path="toh-6/src/main-aot.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="main.ts (JIT)">
|
||||
{@example 'toh-6/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="main.ts (JIT)" path="toh-6/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
***TypeScript configuration***
|
||||
|
||||
JIT-compiled applications transpile to `commonjs` modules.
|
||||
AOT-compiled applications transpile to _ES2015_/_ES6_ modules to facilitate Tree Shaking.
|
||||
AOT-compiled applications transpile to _ES2015_/_ES6_ modules to facilitate tree shaking.
|
||||
AOT requires its own TypeScript configuration settings as well.
|
||||
|
||||
You'll need separate TypeScript configuration files such as these:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="tsconfig-aot.json (AOT)">
|
||||
{@example 'toh-6/ts/tsconfig-aot.json'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="tsconfig-aot.json (AOT)" path="toh-6/tsconfig-aot.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/tsconfig.json (JIT)">
|
||||
{@example 'toh-6/ts/src/tsconfig.1.json'}
|
||||
</md-tab>
|
||||
<code-pane title="src/tsconfig.json (JIT)" path="toh-6/src/tsconfig.1.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -528,16 +607,23 @@ In a more typical project, `node_modules` would be a sibling of `tsconfig-aot.js
|
||||
and `"typeRoots"` would be set to `"node_modules/@types/"`.
|
||||
Edit your `tsconfig-aot.json` to fit your project's file structure.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Tree Shaking
|
||||
|
||||
Rollup does the Tree Shaking as before.
|
||||
|
||||
|
||||
{@example 'toh-6/ts/rollup-config.js'}
|
||||
{@a shaking}
|
||||
### Tree shaking
|
||||
|
||||
Rollup does the tree shaking as before.
|
||||
|
||||
|
||||
<code-example path="toh-6/rollup-config.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a running-app}
|
||||
### Running the application
|
||||
|
||||
|
||||
@ -556,29 +642,30 @@ The _Tour of Heroes_ source code is in the `public/docs/_examples/toh-6/ts` fold
|
||||
Run the JIT-compiled app with `npm start` as for all other JIT examples.
|
||||
|
||||
Compiling with AOT presupposes certain supporting files, most of them discussed above.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/index.html">
|
||||
{@example 'toh-6/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/index.html" path="toh-6/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="copy-dist-files.js">
|
||||
{@example 'toh-6/ts/copy-dist-files.js'}
|
||||
</md-tab>
|
||||
<code-pane title="copy-dist-files.js" path="toh-6/copy-dist-files.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="rollup-config.js">
|
||||
{@example 'toh-6/ts/rollup-config.js'}
|
||||
</md-tab>
|
||||
<code-pane title="rollup-config.js" path="toh-6/rollup-config.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="tsconfig-aot.json">
|
||||
{@example 'toh-6/ts/tsconfig-aot.json'}
|
||||
</md-tab>
|
||||
<code-pane title="tsconfig-aot.json" path="toh-6/tsconfig-aot.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Extend the `scripts` section of the `package.json` with these npm scripts:Copy the AOT distribution files into the `/aot` folder with the node script:
|
||||
<code-example language="none" class="code-shell">
|
||||
@ -586,12 +673,22 @@ Extend the `scripts` section of the `package.json` with these npm scripts:Copy t
|
||||
</code-example>
|
||||
|
||||
|
||||
You won't do that again until there are updates to `zone.js` or the `core-js` shim for old browsers.Now AOT-compile the app and launch it with the `lite` server:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You won't do that again until there are updates to `zone.js` or the `core-js` shim for old browsers.
|
||||
|
||||
~~~
|
||||
|
||||
Now AOT-compile the app and launch it with the `lite-server`:
|
||||
<code-example language="none" class="code-shell">
|
||||
npm run build:aot && npm run serve:aot
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a inspect-bundle}
|
||||
### Inspect the Bundle
|
||||
|
||||
It's fascinating to see what the generated JavaScript bundle looks like after Rollup.
|
||||
|
@ -14,7 +14,9 @@ The [setup](guide/setup) instructions produce a new project with the following m
|
||||
You'll evolve this module as your application grows.
|
||||
|
||||
|
||||
{@example 'setup/ts/src/app/app.module.ts'}
|
||||
<code-example path="setup/src/app/app.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
After the `import` statements, you come to a class adorned with the
|
||||
**`@NgModule`** [_decorator_](guide/glossary).
|
||||
@ -54,6 +56,9 @@ Other guide and cookbook pages will tell you when you need to add additional mod
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `import` statements at the top of the file and the Angular module's `imports` array
|
||||
are unrelated and have completely different jobs.
|
||||
|
||||
@ -66,6 +71,10 @@ The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata
|
||||
It tells Angular about specific _other_ Angular modules — all of them classes decorated with `@NgModule` —
|
||||
that the application needs to function properly.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a declarations}
|
||||
### The _declarations_ array
|
||||
|
||||
@ -125,7 +134,9 @@ and you'll run it in a browser. You can learn about other options later.
|
||||
The recommended place to bootstrap a JIT-compiled browser application is in a separate file
|
||||
in the `src` folder named `src/main.ts`
|
||||
|
||||
{@example 'setup/ts/src/main.ts'}
|
||||
<code-example path="setup/src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This code creates a browser platform for dynamic (JIT) compilation and
|
||||
bootstraps the `AppModule` described above.
|
||||
@ -137,7 +148,9 @@ creates an instance of the component and inserts it within the element tag ident
|
||||
The `AppComponent` selector — here and in most documentation samples — is `my-app`
|
||||
so Angular looks for a `<my-app>` tag in the `index.html` like this one ...
|
||||
|
||||
{@example 'setup/ts/src/index.html' region='my-app'}
|
||||
<code-example path="setup/src/index.html" region="my-app" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
... and displays the `AppComponent` there.
|
||||
|
||||
|
@ -5,6 +5,12 @@ Architecture Overview
|
||||
The basic building blocks of Angular applications.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
Angular is a framework for building client applications in HTML and
|
||||
either JavaScript or a language like TypeScript that compiles to JavaScript.
|
||||
|
||||
The framework consists of several libraries, some of them core and some optional.
|
||||
You write Angular applications by composing HTML *templates* with Angularized markup,
|
||||
writing *component* classes to manage those templates, adding application logic in *services*,
|
||||
and boxing components and services in *modules*.
|
||||
@ -34,16 +40,115 @@ The architecture diagram identifies the eight main building blocks of an Angular
|
||||
Learn these building blocks, and you're on your way.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
<p>
|
||||
The code referenced on this page is available as a <live-example></live-example>.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Modules
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
|
||||
|
||||
_Angular modules_ are a big deal.
|
||||
This page introduces modules; the [Angular modules](guide/ngmodule) page covers them in depth.
|
||||
<br class="l-clear-both"><br>Every Angular app has at least one Angular module class, [the _root module_](guide/appmodule),
|
||||
conventionally named `AppModule`.
|
||||
|
||||
While the _root module_ may be the only module in a small application, most apps have many more
|
||||
_feature modules_, each a cohesive block of code dedicated to an application domain,
|
||||
a workflow, or a closely related set of capabilities.
|
||||
|
||||
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Decorators are functions that modify JavaScript classes.
|
||||
Angular has many decorators that attach metadata to classes so that it knows
|
||||
what those classes mean and how they should work.
|
||||
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0" target="_blank">
|
||||
Learn more</a> about decorators on the web.
|
||||
|
||||
~~~
|
||||
|
||||
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
|
||||
The most important properties are:
|
||||
* `declarations` - the _view classes_ that belong to this module.
|
||||
Angular has three kinds of view classes: [components](guide/architecture#components), [directives](guide/architecture#directives), and [pipes](guide/pipes).
|
||||
|
||||
* `exports` - the subset of declarations that should be visible and usable in the component [templates](guide/architecture#templates) of other modules.
|
||||
|
||||
* `imports` - other modules whose exported classes are needed by component templates declared in _this_ module.
|
||||
|
||||
* `providers` - creators of [services](guide/architecture#services) that this module contributes to
|
||||
the global collection of services; they become accessible in all parts of the app.
|
||||
|
||||
* `bootstrap` - the main application view, called the _root component_,
|
||||
that hosts all other app views. Only the _root module_ should set this `bootstrap` property.
|
||||
|
||||
Here's a simple root module:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
|
||||
|
||||
~~~
|
||||
|
||||
Launch an application by _bootstrapping_ its root module.
|
||||
During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
|
||||
|
||||
|
||||
<code-example path="architecture/src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Angular modules vs. JavaScript modules
|
||||
|
||||
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
||||
|
||||
JavaScript also has its own module system for managing collections of JavaScript objects.
|
||||
It's completely different and unrelated to the Angular module system.
|
||||
|
||||
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
||||
The module declares some objects to be public by marking them with the `export` key word.
|
||||
Other JavaScript modules use *import statements* to access public objects from other modules.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/app.module.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/app.module.ts" region="export" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
|
||||
|
||||
~~~
|
||||
|
||||
These are two different and _complementary_ module systems. Use them both to write your apps.
|
||||
### Angular libraries
|
||||
|
||||
<figure>
|
||||
@ -51,6 +156,45 @@ Learn these building blocks, and you're on your way.
|
||||
</figure>
|
||||
|
||||
|
||||
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
|
||||
|
||||
Each Angular library name begins with the `!{_at_angular}` prefix.
|
||||
|
||||
You install them with the **npm** package manager and import parts of them with JavaScript `import` statements.
|
||||
<br class="l-clear-both"><br>
|
||||
|
||||
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="ngmodule-imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In this way you're using both the Angular and JavaScript module systems _together_.
|
||||
|
||||
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
|
||||
Hang in there. The confusion yields to clarity with time and experience.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Learn more from the [Angular modules](guide/ngmodule) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
|
||||
</div>
|
||||
@ -77,6 +221,12 @@ The class interacts with the view through an API of properties and methods.
|
||||
For example, this `HeroListComponent` has a `heroes` property that returns !{_an} !{_array} of heroes
|
||||
that it acquires from a service.
|
||||
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular creates, updates, and destroys components as the user moves through the application.
|
||||
Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()` declared above.
|
||||
|
||||
@ -97,7 +247,9 @@ A template looks like regular HTML, except for a few differences. Here is a
|
||||
template for our `HeroListComponent`:
|
||||
|
||||
|
||||
{@example 'architecture/ts/src/app/hero-list.component.html'}
|
||||
<code-example path="architecture/src/app/hero-list.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<hero-detail>` uses Angular's [template syntax](guide/template-syntax).
|
||||
|
||||
@ -135,16 +287,26 @@ To tell Angular that `HeroListComponent` is a component, attach **metadata** to
|
||||
|
||||
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**.
|
||||
Here's some metadata for `HeroListComponent`:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (metadata)" region="metadata">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here is the `@Component` !{_decorator}, which identifies the class
|
||||
immediately below it as a component class.
|
||||
<ul if-docs="ts"><li>`moduleId`: sets the source of the base address (`module.id`) for module-relative URLs such as the `templateUrl`.</ul>
|
||||
|
||||
The `@Component` decorator takes a required configuration object with the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
Here are a few of the most useful `@Component` configuration options:
|
||||
- `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`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates).
|
||||
|
||||
- `providers`: !{_array} of **dependency injection providers** for services that the component requires.
|
||||
This is one way to tell Angular that the component's constructor requires a `HeroService`
|
||||
so it can get the list of heroes to display.
|
||||
@ -179,6 +341,12 @@ a mechanism for coordinating parts of a template with parts of a component.
|
||||
Add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
|
||||
As the diagram shows, there are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions.<br class="l-clear-both">The `HeroListComponent` [example](guide/architecture#templates) template has three forms:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (binding)" region="binding">
|
||||
|
||||
</code-example>
|
||||
|
||||
* The `{{hero.name}}` [*interpolation*](guide/displaying-data)
|
||||
displays the component's `hero.name` property value within the `<li>` element.
|
||||
|
||||
@ -190,6 +358,12 @@ the parent `HeroListComponent` to the `hero` property of the child `HeroDetailCo
|
||||
**Two-way data binding** is an important fourth form
|
||||
that combines property and event binding in a single notation, using the `ngModel` directive.
|
||||
Here's an example from the `HeroDetailComponent` template:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel">
|
||||
|
||||
</code-example>
|
||||
|
||||
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.
|
||||
@ -226,8 +400,15 @@ A component is a *directive-with-a-template*;
|
||||
a `@Component` !{_decorator} is actually a `@Directive` !{_decorator} extended with template-oriented features.
|
||||
<br class="l-clear-both">
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
While **a component is technically a directive**,
|
||||
components are so distinctive and central to Angular applications that this architectural overview separates components from directives.Two *other* kinds of directives exist: _structural_ and _attribute_ directives.
|
||||
components are so distinctive and central to Angular applications that this architectural overview separates components from directives.
|
||||
|
||||
~~~
|
||||
|
||||
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.
|
||||
@ -235,8 +416,15 @@ 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.
|
||||
|
||||
The [example template](guide/architecture#templates) uses two built-in structural directives:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (structural)" region="structural">
|
||||
|
||||
</code-example>
|
||||
|
||||
* [`*ngFor`](guide/displaying-data) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data) includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
|
||||
@ -244,6 +432,12 @@ 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.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular has a few more directives that either alter the layout structure
|
||||
(for example, [ngSwitch](guide/template-syntax))
|
||||
or modify aspects of DOM elements and components
|
||||
@ -279,8 +473,20 @@ There is no service base class, and no place to register a service.
|
||||
Yet services are fundamental to any Angular application. Components are big consumers of services.
|
||||
|
||||
Here's an example of a service class that logs to the browser console:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/logger.service.ts" linenums="false" title="src/app/logger.service.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's a `HeroService` that uses a !{_PromiseLinked} to fetch heroes.
|
||||
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero.service.ts" linenums="false" title="src/app/hero.service.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Services are everywhere.
|
||||
|
||||
Component classes should be lean. They don't fetch data from the server,
|
||||
@ -312,6 +518,12 @@ _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">Angular can tell which services a component needs by looking at the types of its constructor parameters.
|
||||
For example, the constructor of your `HeroListComponent` needs a `HeroService`:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (constructor)" region="ctor">
|
||||
|
||||
</code-example>
|
||||
|
||||
When Angular creates a component, it first asks an **injector** for
|
||||
the services that the component requires.
|
||||
|
||||
@ -331,7 +543,24 @@ If the injector doesn't have a `HeroService`, how does it know how to make one?
|
||||
|
||||
In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
|
||||
A provider is something that can create or return a service, typically the service class itself.
|
||||
|
||||
You can register providers in modules or in components.
|
||||
|
||||
In general, add providers to the [root module](guide/architecture#module) so that
|
||||
the same instance of a service is available everywhere.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (module providers)" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (component providers)" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
Registering at a component level means you get a new instance of the
|
||||
service with each new instance of that component.
|
||||
|
||||
@ -399,3 +628,6 @@ by implementing the lifecycle hook interfaces.
|
||||
|
||||
> [**Router**](guide/router): Navigate from page to page within the client
|
||||
application and never leave the browser.
|
||||
|
||||
> [**Testing**](guide/testing): Run unit tests on your application parts as they interact with the Angular framework
|
||||
using the _Angular Testing Platform_.
|
@ -5,6 +5,7 @@ Attribute Directives
|
||||
Attribute directives attach behavior to elements.
|
||||
|
||||
@description
|
||||
|
||||
An **Attribute** directive changes the appearance or behavior of a DOM element.
|
||||
|
||||
# Contents
|
||||
@ -48,6 +49,12 @@ The controller class implements the desired directive behavior.
|
||||
This page demonstrates building a simple _myHighlight_ attribute
|
||||
directive to set an element's background color
|
||||
when the user hovers over that element. You can apply it like this:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (applied)" region="applied">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Write the directive code
|
||||
|
||||
Follow the [setup](guide/setup) instructions for creating a new local project
|
||||
@ -56,7 +63,9 @@ named <span ngio-ex>attribute-directives</span>.
|
||||
Create the following source file in the indicated folder:
|
||||
|
||||
|
||||
{@example 'attribute-directives/ts/src/app/highlight.directive.1.ts'}
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `import` statement specifies symbols from the Angular `core`:
|
||||
|
||||
@ -74,6 +83,9 @@ is the attribute name in square brackets.
|
||||
Here, the directive's selector is `[myHighlight]`.
|
||||
Angular locates all elements in the template that have an attribute named `myHighlight`.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Why not call it "highlight"?
|
||||
|
||||
Though *highlight* is a more concise name than *myHighlight* and would work,
|
||||
@ -84,6 +96,10 @@ This also reduces the risk of colliding with third-party directive names.
|
||||
Make sure you do **not** prefix the `highlight` directive name with **`ng`** because
|
||||
that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose.
|
||||
For a simple demo, the short prefix, `my`, helps distinguish your custom directive.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
After the `@Directive` metadata comes the directive's controller class,
|
||||
called `HighlightDirective`, which contains the logic for the directive.
|
||||
<span if-docs="ts">Exporting `HighlightDirective` makes it accessible to other components.</span>
|
||||
@ -104,19 +120,25 @@ Put the template in its own <span ngio-ex>app.component.html</span>
|
||||
file that looks like this:
|
||||
|
||||
|
||||
{@example 'attribute-directives/ts/src/app/app.component.1.html'}
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now reference this template in the `AppComponent`:
|
||||
|
||||
|
||||
{@example 'attribute-directives/ts/src/app/app.component.ts'}
|
||||
<code-example path="attribute-directives/src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Next, add an `import` statement to fetch the `Highlight` directive and
|
||||
add that class to the `declarations` NgModule metadata. This way Angular
|
||||
recognizes the directive when it encounters `myHighlight` in the template.
|
||||
|
||||
|
||||
{@example 'attribute-directives/ts/src/app/app.module.ts'}
|
||||
<code-example path="attribute-directives/src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
|
||||
|
||||
@ -125,6 +147,9 @@ Now when the app runs, the `myHighlight` directive highlights the paragraph text
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Your directive isn't working?
|
||||
|
||||
Did you remember to add the directive to the `declarations` attribute of `@NgModule`?
|
||||
@ -141,6 +166,10 @@ Angular detects that you're trying to bind to *something* but it can't find this
|
||||
in the module's `declarations` array.
|
||||
After specifying `HighlightDirective` in the `declarations` array,
|
||||
Angular knows it can apply the directive to components declared in this module.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
To summarize, Angular found the `myHighlight` attribute on the `<p>` element.
|
||||
It created an instance of the `HighlightDirective` class and
|
||||
injected a reference to the `<p>` element into the directive's constructor
|
||||
@ -155,23 +184,50 @@ and respond by setting or clearing the highlight color.
|
||||
|
||||
Begin by adding `HostListener` to the list of imported symbols;
|
||||
add the `Input` symbol as well because you'll need it soon.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports">
|
||||
|
||||
</code-example>
|
||||
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` !{_decorator}.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-methods)" region="mouse-methods">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `@HostListener` !{_decorator} lets you subscribe to events of the DOM
|
||||
element that hosts an attribute directive, the `<p>` in this case.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Of course you could reach into the DOM with standard JavaScript and and attach event listeners manually.
|
||||
There are at least three problems with _that_ approach:
|
||||
|
||||
1. You have to write the listeners correctly.
|
||||
1. The code must *detach* the listener when the directive is destroyed to avoid memory leaks.
|
||||
1. Talking to DOM API directly isn't a best practice.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The handlers delegate to a helper method that sets the color on the DOM element, `#{_priv}el`,
|
||||
which you declare and initialize in the constructor.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the updated directive in full:
|
||||
|
||||
|
||||
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts'}
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Run the app and confirm that the background color appears when
|
||||
the mouse hovers over the `p` and disappears as it moves out.
|
||||
@ -189,6 +245,12 @@ In this section, you give the developer the power to set the highlight color whi
|
||||
Start by adding a `highlightColor` property to the directive class like this:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a input}
|
||||
### Binding to an _@Input_ property
|
||||
|
||||
@ -198,15 +260,45 @@ It's called an *input* property because data flows from the binding expression _
|
||||
Without that input metadata, Angular rejects the binding; see [below](guide/attribute-directives#why-input "Why add @Input?") for more about that.
|
||||
|
||||
Try it by adding the following directive binding variations to the `AppComponent` template:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
Add a `color` property to the `AppComponent`.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Let it control the highlight color with a property binding.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `[myHighlight]` attribute binding both applies the highlighting directive to the `<p>` element
|
||||
and sets the directive's highlight color with a property binding.
|
||||
You're re-using the directive's attribute selector (`[myHighlight]`) to do both jobs.
|
||||
That's a crisp, compact syntax.
|
||||
|
||||
You'll have to rename the directive's `highlightColor` property to `myHighlight` because that's now the color property binding name.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||
|
||||
|
||||
@ -216,13 +308,37 @@ This is disagreeable. The word, `myHighlight`, is a terrible property name and i
|
||||
Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes.
|
||||
|
||||
Restore the original property name and specify the selector as the alias in the argument to `@Input`.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
_Inside_ the directive the property is known as `highlightColor`.
|
||||
_Outside_ the directive, where you bind to it, it's known as `myHighlight`.
|
||||
|
||||
You get the best of both worlds: the property name you want and the binding syntax you want:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
|
||||
If someone neglects to bind to `highlightColor`, highlight in red:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the latest version of the directive class.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
## Write a harness to try it
|
||||
|
||||
It may be difficult to imagine how this directive actually works.
|
||||
@ -230,7 +346,19 @@ In this section, you'll turn `AppComponent` into a harness that
|
||||
lets you pick the highlight color with a radio button and bind your color choice to the directive.
|
||||
|
||||
Update <span ngio-ex>app.component.html</span> as follows:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (v2)" region="v2">
|
||||
|
||||
</code-example>
|
||||
|
||||
Revise the `AppComponent.color` so that it has no initial value.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here are the harness and directive in action.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -247,13 +375,31 @@ the user picks a highlight color—is hard-coded as "red".
|
||||
Let the template developer set the default color.
|
||||
|
||||
Add a second **input** property to `HighlightDirective` called `defaultColor`:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (defaultColor)" region="defaultColor">
|
||||
|
||||
</code-example>
|
||||
|
||||
Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`,
|
||||
then with the `defaultColor`, and falls back to "red" if both properties are undefined.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter">
|
||||
|
||||
</code-example>
|
||||
|
||||
How do you bind to a second property when you're already binding to the `myHighlight` attribute name?
|
||||
|
||||
As with components, you can add as many directive property bindings as you need by stringing them along in the template.
|
||||
The developer should be able to write the following template HTML to both bind to the `AppComponent.color`
|
||||
and fall back to "violet" as the default color.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (defaultColor)" region="defaultColor">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||
because you made it _public_ with the `@Input` !{_decorator}.
|
||||
|
||||
@ -275,39 +421,40 @@ This page covered how to:
|
||||
|
||||
The final source code follows:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="app/app.component.ts">
|
||||
{@example 'attribute-directives/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app/app.component.ts" path="attribute-directives/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/app.component.html">
|
||||
{@example 'attribute-directives/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
<code-pane title="app/app.component.html" path="attribute-directives/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/highlight.directive.ts">
|
||||
{@example 'attribute-directives/ts/src/app/highlight.directive.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/highlight.directive.ts" path="attribute-directives/src/app/highlight.directive.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/app.module.ts">
|
||||
{@example 'attribute-directives/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/app.module.ts" path="attribute-directives/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="main.ts">
|
||||
{@example 'attribute-directives/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="main.ts" path="attribute-directives/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="index.html">
|
||||
{@example 'attribute-directives/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-pane title="index.html" path="attribute-directives/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
You can also experience and download the <live-example title="Attribute Directive example"></live-example>.
|
||||
|
||||
@ -315,7 +462,19 @@ You can also experience and download the <live-example title="Attribute Directiv
|
||||
|
||||
In this demo, the `hightlightColor` property is an ***input*** property of
|
||||
the `HighlightDirective`. You've seen it applied without an alias:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
You've seen it with an alias:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
Either way, the `@Input` !{_decorator} tells Angular that this property is
|
||||
_public_ and available for binding by a parent component.
|
||||
Without `@Input`, Angular refuses to bind to the property.
|
||||
@ -345,6 +504,12 @@ You can tell if `@Input` is needed by the position of the property name in a bin
|
||||
that property must be adorned with the `@Input` !{_decorator}.
|
||||
|
||||
Now apply that reasoning to the following example:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
|
||||
|
||||
</code-example>
|
||||
|
||||
* The `color` property in the expression on the right belongs to the template's component.
|
||||
The template and its component trust each other.
|
||||
The `color` property doesn't require the `@Input` !{_decorator}.
|
||||
|
@ -5,6 +5,7 @@ Browser support
|
||||
Browser support and polyfills guide.
|
||||
|
||||
@description
|
||||
|
||||
Angular supports most recent browsers. This includes the following specific versions:
|
||||
|
||||
<table>
|
||||
@ -237,9 +238,16 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
|
||||
using <a href="https://saucelabs.com/" target="_blank">SauceLabs</a> and
|
||||
<a href="https://www.browserstack.com" target="_blank">Browserstack</a>.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
## Polyfills #
|
||||
Angular is built on the latest standards of the web platform.
|
||||
Targeting such a wide range of browsers is challenging because they do not support all features of modern browsers.
|
||||
@ -247,7 +255,9 @@ Targeting such a wide range of browsers is challenging because they do not suppo
|
||||
You can compensate by loading polyfill scripts ("polyfills") on the host web page (`index.html`)
|
||||
that implement missing features in JavaScript.
|
||||
|
||||
{@example 'quickstart/ts/src/index.html' region='polyfills'}
|
||||
<code-example path="quickstart/src/index.html" region="polyfills" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||
You may need additional polyfills for specific features.
|
||||
|
@ -55,21 +55,34 @@ In the following example, we import and register several services
|
||||
in the `@Component` metadata `providers` array.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/app.component.ts' region='import-services'}
|
||||
<code-example path="cb-dependency-injection/src/app/app.component.ts" region="import-services" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
All of these services are implemented as classes.
|
||||
Service classes can act as their own providers which is why listing them in the `providers` array
|
||||
is all the registration we need.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
A *provider* is something that can create or deliver a service.
|
||||
Angular creates a service instance from a class provider by "new-ing" it.
|
||||
Learn more about providers [below](guide/cb-dependency-injection#providers).Now that we've registered these services,
|
||||
Learn more about providers [below](guide/cb-dependency-injection#providers).
|
||||
|
||||
~~~
|
||||
|
||||
Now that we've registered these services,
|
||||
Angular can inject them into the constructor of *any* component or service, *anywhere* in the application.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='ctor'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/user-context.service.ts' region='ctor'}
|
||||
<code-example path="cb-dependency-injection/src/app/user-context.service.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="external-module-configuration"></a>
|
||||
## External module configuration
|
||||
@ -83,7 +96,9 @@ We see an example of the second case here, where we configure the Component Rout
|
||||
in the `providers` list of the `AppModule`.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/app.module.ts' region='providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/app.module.ts" region="providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -103,13 +118,17 @@ At each step, the consumer of dependencies simply declares what it requires in i
|
||||
|
||||
For example, we inject both the `LoggerService` and the `UserContext` in the `AppComponent`.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/app.component.ts' region='ctor'}
|
||||
<code-example path="cb-dependency-injection/src/app/app.component.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `UserContext` in turn has dependencies on both the `LoggerService` (again) and
|
||||
a `UserService` that gathers information about a particular user.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/user-context.service.ts' region='injectables'}
|
||||
<code-example path="cb-dependency-injection/src/app/user-context.service.ts" region="injectables" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
When Angular creates an`AppComponent`, the dependency injection framework creates an instance of the `LoggerService` and
|
||||
starts to create the `UserContextService`.
|
||||
@ -128,7 +147,9 @@ Once all the dependencies are in place, the `AppComponent` displays the user inf
|
||||
### *@Injectable()*
|
||||
Notice the `@Injectable()`decorator on the `UserContextService` class.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/user-context.service.ts' region='injectable'}
|
||||
<code-example path="cb-dependency-injection/src/app/user-context.service.ts" region="injectable" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
|
||||
|
||||
@ -149,10 +170,17 @@ Some developers prefer to add it only where needed and that's a reasonable polic
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `AppComponent` class had two dependencies as well but no `@Injectable()`.
|
||||
It didn't need `@Injectable()` because that component class has the `@Component` decorator.
|
||||
In Angular with TypeScript, a *single* decorator — *any* decorator — is sufficient to identify dependency types.
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
<a id="service-scope"></a>
|
||||
## Limit service scope to a component subtree
|
||||
|
||||
@ -175,18 +203,27 @@ We can limit the scope of an injected service to a *branch* of the application h
|
||||
by providing that service *at the sub-root component for that branch*.
|
||||
Here we provide the `HeroService` to the `HeroesBaseComponent` by listing it in the `providers` array:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/sorted-heroes.component.ts' region='injection'}
|
||||
<code-example path="cb-dependency-injection/src/app/sorted-heroes.component.ts" region="injection">
|
||||
|
||||
</code-example>
|
||||
|
||||
When Angular creates the `HeroesBaseComponent`, it also creates a new instance of `HeroService`
|
||||
that is visible only to the component and its children (if any).
|
||||
|
||||
We could also provide the `HeroService` to a *different* component elsewhere in the application.
|
||||
That would result in a *different* instance of the service, living in a *different* injector.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
We examples of such scoped `HeroService` singletons appear throughout the accompanying sample code,
|
||||
including the `HeroBiosComponent`, `HeroOfTheMonthComponent`, and `HeroesBaseComponent`.
|
||||
Each of these components has its own `HeroService` instance managing its own independent collection of heroes.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
@ -210,12 +247,16 @@ We call this *sandboxing* because each service and component instance has its ow
|
||||
<a id="hero-bios-component"></a>
|
||||
Imagine a `HeroBiosComponent` that presents three instances of the `HeroBioComponent`.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='simple'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="simple">
|
||||
|
||||
</code-example>
|
||||
|
||||
Each `HeroBioComponent` can edit a single hero's biography.
|
||||
A `HeroBioComponent` relies on a `HeroCacheService` to fetch, cache, and perform other persistence operations on that hero.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-cache.service.ts' region='service'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-cache.service.ts" region="service">
|
||||
|
||||
</code-example>
|
||||
|
||||
Clearly the three instances of the `HeroBioComponent` can't share the same `HeroCacheService`.
|
||||
They'd be competing with each other to determine which hero to cache.
|
||||
@ -223,7 +264,9 @@ They'd be competing with each other to determine which hero to cache.
|
||||
Each `HeroBioComponent` gets its *own* `HeroCacheService` instance
|
||||
by listing the `HeroCacheService` in its metadata `providers` array.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bio.component.ts' region='component'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bio.component.ts" region="component">
|
||||
|
||||
</code-example>
|
||||
|
||||
The parent `HeroBiosComponent` binds a value to the `heroId`.
|
||||
The `ngOnInit` pass that `id` to the service which fetches and caches the hero.
|
||||
@ -266,17 +309,23 @@ We look at this second, more interesting case in our next example.
|
||||
### Demonstration
|
||||
The `HeroBiosAndContactsComponent` is a revision of the `HeroBiosComponent` that we looked at [above](guide/cb-dependency-injection#hero-bios-component).
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='hero-bios-and-contacts'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="hero-bios-and-contacts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Focus on the template:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='template'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We've inserted a `<hero-contact>` element between the `<hero-bio>` tags.
|
||||
Angular *projects* (*transcludes*) the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
|
||||
placing it in the `<ng-content>` slot of the `HeroBioComponent` template:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bio.component.ts' region='template'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bio.component.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
It looks like this, with the hero's telephone number from `HeroContactComponent` projected above the hero description:
|
||||
<figure class='image-display'>
|
||||
@ -285,11 +334,15 @@ It looks like this, with the hero's telephone number from `HeroContactComponent`
|
||||
|
||||
Here's the `HeroContactComponent` which demonstrates the qualifying decorators that we're talking about in this section:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-contact.component.ts' region='component'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-contact.component.ts" region="component">
|
||||
|
||||
</code-example>
|
||||
|
||||
Focus on the constructor parameters
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-contact.component.ts' region='ctor-params'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-contact.component.ts" region="ctor-params" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `@Host()` function decorating the `heroCache` property ensures that
|
||||
we get a reference to the cache service from the parent `HeroBioComponent`.
|
||||
@ -302,7 +355,14 @@ The host `HeroBioComponent` doesn't have its own `LoggerService` provider.
|
||||
Angular would throw an error if we hadn't also decorated the property with the `@Optional()` function.
|
||||
Thanks to `@Optional()`, Angular sets the `loggerService` to null and the rest of the component adapts.
|
||||
|
||||
We'll come back to the `elementRef` property shortly.Here's the `HeroBiosAndContactsComponent` in action.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
We'll come back to the `elementRef` property shortly.
|
||||
|
||||
~~~
|
||||
|
||||
Here's the `HeroBiosAndContactsComponent` in action.
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into"> </img>
|
||||
</figure>
|
||||
@ -327,7 +387,9 @@ require DOM access.
|
||||
To illustrate, we've written a simplified version of the `HighlightDirective` from
|
||||
the [Attribute Directives](guide/attribute-directives) chapter.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/highlight.directive.ts'}
|
||||
<code-example path="cb-dependency-injection/src/app/highlight.directive.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The directive sets the background to a highlight color when the user mouses over the
|
||||
DOM element to which it is applied.
|
||||
@ -339,7 +401,9 @@ Its `nativeElement` property exposes the DOM element for the directive to manipu
|
||||
The sample code applies the directive's `myHighlight` attribute to two `<div>` tags,
|
||||
first without a value (yielding the default color) and then with an assigned color value.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/app.component.html' region='highlight'}
|
||||
<code-example path="cb-dependency-injection/src/app/app.component.html" region="highlight" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The following image shows the effect of mousing over the `<hero-bios-and-contacts>` tag.
|
||||
<figure class='image-display'>
|
||||
@ -360,7 +424,9 @@ Angular passes this token to the injector and assigns the result to the paramete
|
||||
Here's a typical example:
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='ctor'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular asks the injector for the service associated with the `LoggerService`
|
||||
and assigns the returned value to the `logger` parameter.
|
||||
@ -369,23 +435,34 @@ Where did the injector get that value?
|
||||
It may already have that value in its internal container.
|
||||
If it doesn't, it may be able to make one with the help of a ***provider***.
|
||||
A *provider* is a recipe for delivering a service associated with a *token*.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
If the injector doesn't have a provider for the requested *token*, it delegates the request
|
||||
to its parent injector, where the process repeats until there are no more injectors.
|
||||
If the search is futile, the injector throws an error ... unless the request was [optional](guide/cb-dependency-injection#optional).
|
||||
|
||||
Let's return our attention to providers themselves.A new injector has no providers.
|
||||
Let's return our attention to providers themselves.
|
||||
|
||||
~~~
|
||||
|
||||
A new injector has no providers.
|
||||
Angular initializes the injectors it creates with some providers it cares about.
|
||||
We have to register our _own_ application providers manually,
|
||||
usually in the `providers` array of the `Component` or `Directive` metadata:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/app.component.ts' region='providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/app.component.ts" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Defining providers
|
||||
|
||||
The simple class provider is the most typical by far.
|
||||
We mention the class in the `providers` array and we're done.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='class-provider'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-bios.component.ts" region="class-provider" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
It's that simple because the most common injected service is an instance of a class.
|
||||
But not every dependency can be satisfied by creating a new instance of a class.
|
||||
@ -399,7 +476,9 @@ The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and
|
||||
|
||||
It's visually simple: a few properties and the output of a logger. The code behind it gives us plenty to talk about.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='hero-of-the-month'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="hero-of-the-month">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -427,7 +506,9 @@ The `HeroOfTheMonthComponent` example has two *value providers*.
|
||||
The first provides an instance of the `Hero` class;
|
||||
the second specifies a literal string resource:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='use-value'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="use-value" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `Hero` provider token is a class which makes sense because the value is a `Hero`
|
||||
and the consumer of the injected hero would want the type information.
|
||||
@ -440,7 +521,9 @@ The value of a *value provider* must be defined *now*. We can't create the value
|
||||
Obviously the title string literal is immediately available.
|
||||
The `someHero` variable in this example was set earlier in the file:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='some-hero'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="some-hero">
|
||||
|
||||
</code-example>
|
||||
|
||||
The other providers create their values *lazily* when they're needed for injection.
|
||||
|
||||
@ -457,7 +540,9 @@ or fake the behavior of the real class in a test case.
|
||||
|
||||
We see two examples in the `HeroOfTheMonthComponent`:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='use-class'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="use-class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The first provider is the *de-sugared*, expanded form of the most typical case in which the
|
||||
class to be created (`HeroService`) is also the provider's injection token.
|
||||
@ -466,10 +551,19 @@ We wrote it in this long form to de-mystify the preferred short form.
|
||||
The second provider substitutes the `DateLoggerService` for the `LoggerService`.
|
||||
The `LoggerService` is already registered at the `AppComponent` level.
|
||||
When _this component_ requests the `LoggerService`, it receives the `DateLoggerService` instead.
|
||||
This component and its tree of child components receive the `DateLoggerService` instance.
|
||||
Components outside the tree continue to receive the original `LoggerService` instance.The `DateLoggerService` inherits from `LoggerService`; it appends the current date/time to each message:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='date-logger-service'}
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This component and its tree of child components receive the `DateLoggerService` instance.
|
||||
Components outside the tree continue to receive the original `LoggerService` instance.
|
||||
|
||||
~~~
|
||||
|
||||
The `DateLoggerService` inherits from `LoggerService`; it appends the current date/time to each message:
|
||||
|
||||
<code-example path="cb-dependency-injection/src/app/date-logger.service.ts" region="date-logger-service" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -481,7 +575,9 @@ The `useExisting` provider maps one token to another.
|
||||
In effect, the first token is an ***alias*** for the service associated with second token,
|
||||
creating ***two ways to access the same service object***.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='use-existing'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="use-existing">
|
||||
|
||||
</code-example>
|
||||
|
||||
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
|
||||
We're aliasing for that very purpose here.
|
||||
@ -489,7 +585,9 @@ Imagine that the `LoggerService` had a large API (it's actually only three metho
|
||||
We want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](guide/cb-dependency-injection#class-interface):
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='minimal-logger'}
|
||||
<code-example path="cb-dependency-injection/src/app/date-logger.service.ts" region="minimal-logger" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The constructor's `logger` parameter is typed as `MinimalLogger` so only its two members are visible in TypeScript:
|
||||
<figure class='image-display'>
|
||||
@ -512,7 +610,9 @@ The following image, which displays the logging date, confirms the point:
|
||||
The `useFactory` provider creates a dependency object by calling a factory function
|
||||
as seen in this example.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='use-factory'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="use-factory">
|
||||
|
||||
</code-example>
|
||||
|
||||
Use this technique to ***create a dependency object***
|
||||
with a factory function whose inputs are some ***combination of injected services and local state***.
|
||||
@ -528,7 +628,9 @@ The `runnersUpFactory` itself isn't the provider factory function.
|
||||
The true provider factory function is the function that `runnersUpFactory` returns.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/runners-up.ts' region='factory-synopsis'}
|
||||
<code-example path="cb-dependency-injection/src/app/runners-up.ts" region="factory-synopsis" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
That returned function takes a winning `Hero` and a `HeroService` as arguments.
|
||||
|
||||
@ -540,12 +642,19 @@ to provide these factory function dependencies.
|
||||
After some undisclosed work, the function returns the string of names
|
||||
and Angular injects it into the `runnersUp` parameter of the `HeroOfTheMonthComponent`.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The function retrieves candidate heroes from the `HeroService`,
|
||||
takes `2` of them to be the runners-up, and returns their concatenated names.
|
||||
Look at the <live-example name="cb-dependency-injection"></live-example>
|
||||
for the full source code.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a tokens}
|
||||
|
||||
## Provider token alternatives: the *class-interface* and *OpaqueToken*
|
||||
@ -562,11 +671,15 @@ That's the subject of our next section.
|
||||
In the previous *Hero of the Month* example, we used the `MinimalLogger` class
|
||||
as the token for a provider of a `LoggerService`.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='use-existing'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="use-existing">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `MinimalLogger` is an abstract class.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='minimal-logger'}
|
||||
<code-example path="cb-dependency-injection/src/app/date-logger.service.ts" region="minimal-logger" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We usually inherit from an abstract class.
|
||||
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
|
||||
@ -574,7 +687,9 @@ Instead, we use it like an interface.
|
||||
|
||||
Look again at the declaration for `DateLoggerService`
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='date-logger-service-signature'}
|
||||
<code-example path="cb-dependency-injection/src/app/date-logger.service.ts" region="date-logger-service-signature" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
|
||||
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
|
||||
@ -587,6 +702,9 @@ A ***class-interface*** should define *only* the members that its consumers are
|
||||
Such a narrowing interface helps decouple the concrete class from its consumers.
|
||||
The `MinimalLogger` defines just two of the `LoggerClass` members.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
#### Why *MinimalLogger* is a class and not an interface
|
||||
We can't use an interface as a provider token because
|
||||
interfaces are not JavaScript objects.
|
||||
@ -601,11 +719,17 @@ Using a class as an interface gives us the characteristics of an interface in a
|
||||
The minimize memory cost, the class should have *no implementation*.
|
||||
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='minimal-logger-transpiled'}
|
||||
<code-example path="cb-dependency-injection/src/app/date-logger.service.ts" region="minimal-logger-transpiled" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
It never grows larger no matter how many members we add *as long as they are typed but not implemented*.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a opaque-token}
|
||||
### OpaqueToken
|
||||
|
||||
@ -621,11 +745,15 @@ The `OpaqueToken` has these characteristics.
|
||||
We encountered them twice in the *Hero of the Month* example,
|
||||
in the *title* value provider and in the *runnersUp* factory provider.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='provide-opaque-token'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="provide-opaque-token" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We created the `TITLE` token like this:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='opaque-token'}
|
||||
<code-example path="cb-dependency-injection/src/app/hero-of-the-month.component.ts" region="opaque-token" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -649,14 +777,23 @@ It demands its own instance of the `HeroService` to get heroes
|
||||
and displays them in the order they arrive from the database.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/sorted-heroes.component.ts' region='heroes-base'}
|
||||
<code-example path="cb-dependency-injection/src/app/sorted-heroes.component.ts" region="heroes-base">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
We strongly prefer simple constructors. They should do little more than initialize variables.
|
||||
This rule makes the component safe to construct under test without fear that it will do something dramatic like talk to the server.
|
||||
That's why we call the `HeroService` from within the `ngOnInit` rather than the constructor.
|
||||
|
||||
We explain the mysterious `afterGetHeroes` below.Users want to see the heroes in alphabetical order.
|
||||
We explain the mysterious `afterGetHeroes` below.
|
||||
|
||||
~~~
|
||||
|
||||
Users want to see the heroes in alphabetical order.
|
||||
Rather than modify the original component, we sub-class it and create a
|
||||
`SortedHeroesComponent` that sorts the heroes before presenting them.
|
||||
The `SortedHeroesComponent` lets the base class fetch the heroes.
|
||||
@ -667,7 +804,9 @@ We must provide the `HeroService` again for *this* component,
|
||||
then pass it down to the base class inside the constructor.
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/sorted-heroes.component.ts' region='sorted-heroes'}
|
||||
<code-example path="cb-dependency-injection/src/app/sorted-heroes.component.ts" region="sorted-heroes">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now take note of the `afterGetHeroes` method.
|
||||
Our first instinct was to create an `ngOnInit` method in `SortedHeroesComponent` and do the sorting there.
|
||||
@ -711,12 +850,16 @@ In the following example, the parent `AlexComponent` has several children includ
|
||||
{@a alex}
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-1'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
*Cathy* reports whether or not she has access to *Alex*
|
||||
after injecting an `AlexComponent` into her constructor:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='cathy'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="cathy" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We added the [@Optional](guide/cb-dependency-injection#optional) qualifier for safety but
|
||||
the <live-example name="cb-dependency-injection"></live-example>
|
||||
@ -736,19 +879,30 @@ The app probably defines more than a dozen financial instrument components.
|
||||
If we're lucky, they all implement the same base class
|
||||
whose API our `NewsComponent` understands.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Looking for components that implement an interface would be better.
|
||||
That's not possible because TypeScript interfaces disappear from the transpiled JavaScript
|
||||
which doesn't support interfaces. There's no artifact we could look for.We're not claiming this is good design.
|
||||
which doesn't support interfaces. There's no artifact we could look for.
|
||||
|
||||
~~~
|
||||
|
||||
We're not claiming this is good design.
|
||||
We are asking *can a component inject its parent via the parent's base class*?
|
||||
|
||||
The sample's `CraigComponent` explores this question. [Looking back](guide/cb-dependency-injection#alex)
|
||||
we see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-class-signature'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-class-signature" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='craig'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="craig" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Unfortunately, this does not work.
|
||||
The <live-example name="cb-dependency-injection"></live-example>
|
||||
@ -772,14 +926,18 @@ and add that provider to the `providers` array of the `@Component` metadata for
|
||||
{@a alex-providers}
|
||||
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Parent](guide/cb-dependency-injection#parent-token) is the provider's *class-interface* token.
|
||||
The [*forwardRef*](guide/cb-dependency-injection#forwardref) breaks the circular reference we just created by having the `AlexComponent` refer to itself.
|
||||
|
||||
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter, the same way we've done it before:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='carol-class'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="carol-class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's *Alex* and family in action:
|
||||
<figure class='image-display'>
|
||||
@ -800,25 +958,28 @@ That means he must both *inject* the `Parent` *class-interface* to get *Alice* a
|
||||
|
||||
Here's *Barry*:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='barry'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="barry" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
*Barry*'s `providers` array looks just like [*Alex*'s](guide/cb-dependency-injection#alex-providers).
|
||||
If we're going to keep writing [*alias providers*](guide/cb-dependency-injection#useexisting) like this we should create a [helper function](guide/cb-dependency-injection#provideparent).
|
||||
|
||||
For now, focus on *Barry*'s constructor:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="Barry's constructor">
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='barry-ctor'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="Barry's constructor" path="cb-dependency-injection/src/app/parent-finder.component.ts" region="barry-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="Carol's constructor">
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='carol-ctor'}
|
||||
</md-tab>
|
||||
<code-pane title="Carol's constructor" path="cb-dependency-injection/src/app/parent-finder.component.ts" region="carol-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
It's identical to *Carol*'s constructor except for the additional `@SkipSelf` decorator.
|
||||
|
||||
@ -845,7 +1006,9 @@ We [learned earlier](guide/cb-dependency-injection#class-interface) that a *clas
|
||||
|
||||
Our example defines a `Parent` *class-interface* .
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='parent'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="parent" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `Parent` *class-interface* defines a `name` property with a type declaration but *no implementation*.,
|
||||
The `name` property is the only member of a parent component that a child component can call.
|
||||
@ -853,45 +1016,66 @@ Such a narrowing interface helps decouple the child component class from its par
|
||||
|
||||
A component that could serve as a parent *should* implement the *class-interface* as the `AliceComponent` does:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alice-class-signature'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alice-class-signature" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Doing so adds clarity to the code. But it's not technically necessary.
|
||||
Although the `AlexComponent` has a `name` property (as required by its `Base` class)
|
||||
its class signature doesn't mention `Parent`:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-class-signature'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-class-signature" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `AlexComponent` *should* implement `Parent` as a matter of proper style.
|
||||
It doesn't in this example *only* to demonstrate that the code will compile and run without the interface
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a provideparent}
|
||||
### A *provideParent* helper function
|
||||
|
||||
Writing variations of the same parent *alias provider* gets old quickly,
|
||||
especially this awful mouthful with a [*forwardRef*](guide/cb-dependency-injection#forwardref):
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We can extract that logic into a helper function like this:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='provide-the-parent'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="provide-the-parent" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now we can add a simpler, more meaningful parent provider to our components:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alice-providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alice-providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We can do better. The current version of the helper function can only alias the `Parent` *class-interface*.
|
||||
Our application might have a variety of parent types, each with its own *class-interface* token.
|
||||
|
||||
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent *class-interface*.
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='provide-parent'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="provide-parent" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
And here's how we could use it with a different parent type:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='beth-providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="beth-providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -917,5 +1101,7 @@ appear *above* the class definition.
|
||||
|
||||
We break the circularity with `forwardRef`:
|
||||
|
||||
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-providers'}
|
||||
<code-example path="cb-dependency-injection/src/app/parent-finder.component.ts" region="alex-providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -7,22 +7,31 @@ A collection of recipes for common Angular application scenarios.
|
||||
@description
|
||||
The *Cookbook* offers answers to common implementation questions.
|
||||
|
||||
Each cookbook chapter is a collection of recipes focused on a particular Angular feature or application challenge
|
||||
Each cookbook page is a collection of recipes focused on a particular Angular feature or application challenge
|
||||
such as data binding, cross-component interaction, and communicating with a remote server via HTTP.
|
||||
|
||||
The cookbook is just getting started. Many more recipes are on the way.
|
||||
Each cookbook chapter links to a live sample with every recipe included.
|
||||
|
||||
Recipes are deliberately brief and code-centric.
|
||||
Each recipe links to a chapter of the Developer Guide or the API Guide
|
||||
where you can learn more about the purpose, context, and design choices behind the code snippets.
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The cookbook is just getting started. Many more recipes are on the way.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Each cookbook links to a live sample with every recipe included.
|
||||
|
||||
Recipes are deliberately brief and code-centric.
|
||||
Each recipe links to a relevant page of the [Developer Guide](guide) or the
|
||||
[API Reference](api) where you can learn more about
|
||||
the purpose, context, and design choices behind the code snippets.
|
||||
|
||||
## Feedback
|
||||
|
||||
The cookbook is a perpetual *work-in-progress*.
|
||||
We welcome feedback! Leave a comment by clicking the icon in upper right corner of the banner.
|
||||
|
||||
Post *documentation* issues and pull requests on the
|
||||
[angular.io](https://github.com/angular/angular.io) github repository.
|
||||
Post *documentation* requests and comments as
|
||||
<a href="https://github.com/angular/angular.io/issues" target="_blank" title="Documentation issues on github">
|
||||
<i>issues</i> on the angular.io</a> github repository.
|
||||
Fixes (small ones) are greatly appreciated as
|
||||
<a href="https://github.com/angular/angular.io/pulls" target="_blank" title="Documentation PRs on github">
|
||||
<i>pull requests</i></a>.
|
||||
|
||||
Post issues with *Angular itself* to the [angular](https://github.com/angular/angular) github repository.
|
@ -5,9 +5,19 @@ Change Log
|
||||
An annotated history of recent documentation improvements.
|
||||
|
||||
@description
|
||||
|
||||
The Angular documentation is a living document with continuous improvements.
|
||||
This log calls attention to recent significant changes.
|
||||
|
||||
## Updated to Angular 4.0. Documentation for Angular 2.x can be found at [v2.angular.io](https://v2.angular.io).
|
||||
|
||||
## All mention of moduleId removed. "Component relative paths" cookbook deleted (2017-03-13)
|
||||
We added a new SystemJS plugin (systemjs-angular-loader.js) to our recommended SystemJS configuration.
|
||||
This plugin dynamically converts "component-relative" paths in templateUrl and styleUrls to "absolute paths" for you.
|
||||
|
||||
We strongly encourage you to only write component-relative paths.
|
||||
That is the only form of URL discussed in these docs. You no longer need to write @Component({ moduleId: module.id }), nor should you.
|
||||
|
||||
## NEW: Downloadable examples for each guide (2017-02-28)
|
||||
Now you can download the sample code for any guide and run it locally.
|
||||
Look for the new download links next to the "live example" links.
|
||||
|
@ -1,3 +1,395 @@
|
||||
@title
|
||||
Cheat Sheet
|
||||
|
||||
@description
|
||||
// SCSS from
|
||||
// resources/css/module/_cheatsheet.scss
|
||||
// resources/css/layout/_grids.scss: grid-fluid
|
||||
// resources/css/layout/_layout.scss: docs-content, l-content-small
|
||||
<div class="l-content-small grid-fluid docs-content cheatsheet">
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Bootstrapping</th>
|
||||
<th><p><code>import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>platformBrowserDynamic().bootstrapModule</b>(AppModule);</code></td>
|
||||
<td><p>Bootstraps the app, using the root component from the specified <code>NgModule</code>. </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>NgModules</th>
|
||||
<th><p><code>import { NgModule } from '@angular/core';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes that belong to this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||
<td><p>List of modules to import into this module. Everything from the imported modules
|
||||
is available to <code>declarations</code> of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Template syntax</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
|
||||
<code><div [title]="'Hello ' + ponyName"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||
<td><p>Sets up two-way data binding. Equivalent to: <code><my-cmp [title]="name" (titleChange)="name=$event"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
|
||||
<code><template [myUnless]="myExpression"><p>...</p></template></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||
<td><p>An <code><svg></code> root element is detected as an SVG element automatically, without the prefix.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Built-in directives</th>
|
||||
<th><p><code>import { CommonModule } from '@angular/common';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</template><br> <template <b>ngSwitchCase</b>="case2LiteralString">...</template><br> <template <b>ngSwitchDefault</b>>...</template><br></div></code></td>
|
||||
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngClass]</b>="{active: isActive, disabled: isDisabled}"></code></td>
|
||||
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Forms</th>
|
||||
<th><p><code>import { FormsModule } from '@angular/forms';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Class decorators</th>
|
||||
<th><p><code>import { Directive, ... } from '@angular/core';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||
<td><p>Declares that a class is a component and provides metadata about the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Directive configuration</th>
|
||||
<th><p><code>@Directive({ property1: value1, ... })</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
|
||||
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
|
||||
<p>Does not support parent-child relationship selectors.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers for this directive and its children.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Component configuration</th>
|
||||
<th><p>
|
||||
<code>@Component</code> extends <code>@Directive</code>,
|
||||
so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>moduleId:</b> module.id</code></td>
|
||||
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers scoped to this component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||
<td><p>Inline template or external template URL of the component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||
<td><p>List of inline CSS styles or external stylesheet URLs for styling the component’s view.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Class field decorators for directives and components</th>
|
||||
<th><p><code>import { Input, ... } from '@angular/core';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||
<td><p>Declares an input property that you can update via property binding (example:
|
||||
<code><my-cmp [myProperty]="someExpression"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code><my-cmp (myEvent)="doSomething()"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Directive and component change detection and lifecycle hooks</th>
|
||||
<th><p>(implemented as class methods)
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||
<td><p>Called after every change to input properties and before processing content or child views.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's or directive's content.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's view. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||
<td><p>Called once, before the instance is destroyed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Dependency injection configuration</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Routing and navigation</th>
|
||||
<th><p><code>import { Routes, RouterModule, ... } from '@angular/router';</code>
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||
<td><p>Marks the location to load the component of the active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
</div>
|
@ -7,39 +7,36 @@ Share information between different directives and components.
|
||||
@description
|
||||
<a id="top"></a>This cookbook contains recipes for common component communication scenarios
|
||||
in which two or more components share information.
|
||||
<a id="toc"></a>## Table of contents
|
||||
<a id="toc"></a># Contents
|
||||
|
||||
[Pass data from parent to child with input binding](guide/component-communication#parent-to-child)
|
||||
- [Pass data from parent to child with input binding](guide/component-communication#parent-to-child)
|
||||
- [Intercept input property changes with a setter](guide/component-communication#parent-to-child-setter)
|
||||
- [Intercept input property changes with `ngOnChanges()`](guide/component-communication#parent-to-child-on-changes)
|
||||
- [Parent calls an `@ViewChild()`](guide/component-communication#parent-to-view-child)
|
||||
- [Parent and children communicate via a service](guide/component-communication#bidirectional-service)
|
||||
|
||||
[Intercept input property changes with a setter](guide/component-communication#parent-to-child-setter)
|
||||
|
||||
[Intercept input property changes with *ngOnChanges*](guide/component-communication#parent-to-child-on-changes)
|
||||
|
||||
[Parent listens for child event](guide/component-communication#child-to-parent)
|
||||
|
||||
[Parent interacts with child via a *local variable*](guide/component-communication#parent-to-child-local-var)
|
||||
|
||||
[Parent calls a *ViewChild*](guide/component-communication#parent-to-view-child)
|
||||
|
||||
[Parent and children communicate via a service](guide/component-communication#bidirectional-service)
|
||||
**See the <live-example name="cb-component-communication"></live-example>**.
|
||||
|
||||
<a id="parent-to-child"></a>## Pass data from parent to child with input binding
|
||||
|
||||
`HeroChildComponent` has two ***input properties***,
|
||||
`HeroChildComponent` has two ***input properties***,
|
||||
typically adorned with [@Input decorations](guide/template-syntax).
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/hero-child.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/hero-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The second `@Input` aliases the child component property name `masterName` as `'master'`.
|
||||
|
||||
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater,
|
||||
binding its `master` string property to the child's `master` alias
|
||||
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater,
|
||||
binding its `master` string property to the child's `master` alias,
|
||||
and each iteration's `hero` instance to the child's `hero` property.
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/hero-parent.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/hero-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The running application displays three heroes:
|
||||
|
||||
@ -52,7 +49,9 @@ The running application displays three heroes:
|
||||
E2E test that all children were instantiated and displayed as expected:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='parent-to-child'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
@ -60,16 +59,20 @@ E2E test that all children were instantiated and displayed as expected:
|
||||
|
||||
Use an input property setter to intercept and act upon a value from the parent.
|
||||
|
||||
The setter of the `name` input property in the child `NameChildComponent`
|
||||
trims the whitespace from a name and replaces an empty value with default text.
|
||||
The setter of the `name` input property in the child `NameChildComponent`
|
||||
trims the whitespace from a name and replaces an empty value with default text.
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/name-child.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/name-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/name-parent.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/name-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -81,24 +84,37 @@ Here's the `NameParentComponent` demonstrating name variations including a name
|
||||
E2E tests of input property setter with empty and non-empty names:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='parent-to-child-setter'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-setter">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="parent-to-child-on-changes"></a>## Intercept input property changes with *ngOnChanges*
|
||||
<a id="parent-to-child-on-changes"></a>## Intercept input property changes with *ngOnChanges()*
|
||||
|
||||
Detect and act upon changes to input property values with the `ngOnChanges` method of the `OnChanges` lifecycle hook interface.
|
||||
May prefer this approach to the property setter when watching multiple, interacting input properties.
|
||||
Detect and act upon changes to input property values with the `ngOnChanges()` method of the `OnChanges` lifecycle hook interface.
|
||||
|
||||
Learn about `ngOnChanges` in the [LifeCycle Hooks](guide/lifecycle-hooks) chapter.This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You may prefer this approach to the property setter when watching multiple, interacting input properties.
|
||||
|
||||
Learn about `ngOnChanges()` in the [LifeCycle Hooks](guide/lifecycle-hooks) chapter.
|
||||
|
||||
~~~
|
||||
|
||||
This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/version-child.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/version-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them.
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/version-parent.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/version-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the output of a button-pushing sequence:
|
||||
|
||||
@ -108,35 +124,41 @@ Here's the output of a button-pushing sequence:
|
||||
|
||||
### Test it
|
||||
|
||||
Test that ***both*** input properties are set initially and that button clicks trigger
|
||||
Test that ***both*** input properties are set initially and that button clicks trigger
|
||||
the expected `ngOnChanges` calls and values:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='parent-to-child-onchanges'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-onchanges">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="child-to-parent"></a>## Parent listens for child event
|
||||
|
||||
The child component exposes an `EventEmitter` property with which it `emits` events when something happens.
|
||||
The child component exposes an `EventEmitter` property with which it `emits` events when something happens.
|
||||
The parent binds to that event property and reacts to those events.
|
||||
|
||||
The child's `EventEmitter` property is an ***output property***,
|
||||
The child's `EventEmitter` property is an ***output property***,
|
||||
typically adorned with an [@Output decoration](guide/template-syntax)
|
||||
as seen in this `VoterComponent`:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/voter.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/voter.component.ts">
|
||||
|
||||
Clicking a button triggers emission of a `true` or `false` (the boolean *payload*).
|
||||
</code-example>
|
||||
|
||||
The parent `VoteTakerComponent` binds an event handler (`onVoted`) that responds to the child event
|
||||
payload (`$event`) and updates a counter.
|
||||
Clicking a button triggers emission of a `true` or `false`, the boolean *payload*.
|
||||
|
||||
The parent `VoteTakerComponent` binds an event handler called `onVoted()` that responds to the child event
|
||||
payload `$event` and updates a counter.
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/votetaker.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/votetaker.component.ts">
|
||||
|
||||
The framework passes the event argument — represented by `$event` — to the handler method,
|
||||
</code-example>
|
||||
|
||||
The framework passes the event argument—represented by `$event`—to the handler method,
|
||||
and the method processes it:
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -148,39 +170,45 @@ and the method processes it:
|
||||
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='child-to-parent'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="child-to-parent">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
## Parent interacts with child via *local variable*
|
||||
|
||||
A parent component cannot use data binding to read child properties
|
||||
or invoke child methods. We can do both
|
||||
or invoke child methods. You can do both
|
||||
by creating a template reference variable for the child element
|
||||
and then reference that variable *within the parent template*
|
||||
as seen in the following example.
|
||||
|
||||
<a id="countdown-timer-example"></a>
|
||||
We have a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket.
|
||||
The following is a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket.
|
||||
It has `start` and `stop` methods that control the clock and it displays a
|
||||
countdown status message in its own template.
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/countdown-timer.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/countdown-timer.component.ts">
|
||||
|
||||
Let's see the `CountdownLocalVarParentComponent` that hosts the timer component.
|
||||
</code-example>
|
||||
|
||||
The `CountdownLocalVarParentComponent` that hosts the timer component is as follows:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/countdown-parent.component.ts' region='lv'}
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="lv">
|
||||
|
||||
The parent component cannot data bind to the child's
|
||||
</code-example>
|
||||
|
||||
The parent component cannot data bind to the child's
|
||||
`start` and `stop` methods nor to its `seconds` property.
|
||||
|
||||
We can place a local variable (`#timer`) on the tag (`<countdown-timer>`) representing the child component.
|
||||
That gives us a reference to the child component itself and the ability to access
|
||||
You can place a local variable, `#timer`, on the tag `<countdown-timer>` representing the child component.
|
||||
That gives you a reference to the child component and the ability to access
|
||||
*any of its properties or methods* from within the parent template.
|
||||
|
||||
In this example, we wire parent buttons to the child's `start` and `stop` and
|
||||
use interpolation to display the child's `seconds` property.
|
||||
This example wires parent buttons to the child's `start` and `stop` and
|
||||
uses interpolation to display the child's `seconds` property.
|
||||
|
||||
Here we see the parent and child working together.
|
||||
|
||||
@ -198,53 +226,65 @@ match the seconds displayed in the child's status message.
|
||||
Test also that clicking the *Stop* button pauses the countdown timer:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='countdown-timer-tests'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="countdown-timer-tests">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="parent-to-view-child"></a>## Parent calls a *ViewChild*
|
||||
<a id="parent-to-view-child"></a>## Parent calls an _@ViewChild()_
|
||||
|
||||
The *local variable* approach is simple and easy. But it is limited because
|
||||
The *local variable* approach is simple and easy. But it is limited because
|
||||
the parent-child wiring must be done entirely within the parent template.
|
||||
The parent component *itself* has no access to the child.
|
||||
|
||||
We can't use the *local variable* technique if an instance of the parent component *class*
|
||||
You can't use the *local variable* technique if an instance of the parent component *class*
|
||||
must read or write child component values or must call child component methods.
|
||||
|
||||
When the parent component *class* requires that kind of access,
|
||||
we ***inject*** the child component into the parent as a *ViewChild*.
|
||||
When the parent component *class* requires that kind of access,
|
||||
***inject*** the child component into the parent as a *ViewChild*.
|
||||
|
||||
We'll illustrate this technique with the same [Countdown Timer](guide/component-communication#countdown-timer-example) example.
|
||||
We won't change its appearance or behavior.
|
||||
The following example illustrates this technique with the same
|
||||
[Countdown Timer](guide/component-communication#countdown-timer-example) example.
|
||||
Neither its appearance nor its behavior will change.
|
||||
The child [CountdownTimerComponent](guide/component-communication#countdown-timer-example) is the same as well.
|
||||
We are switching from the *local variable* to the *ViewChild* technique
|
||||
solely for the purpose of demonstration.Here is the parent, `CountdownViewChildParentComponent`:
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/countdown-parent.component.ts' region='vc'}
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The switch from the *local variable* to the *ViewChild* technique
|
||||
is solely for the purpose of demonstration.
|
||||
|
||||
~~~
|
||||
|
||||
Here is the parent, `CountdownViewChildParentComponent`:
|
||||
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="vc">
|
||||
|
||||
</code-example>
|
||||
|
||||
It takes a bit more work to get the child view into the parent component *class*.
|
||||
|
||||
We import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook.
|
||||
|
||||
We inject the child `CountdownTimerComponent` into the private `timerComponent` property
|
||||
First, you have to import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook.
|
||||
|
||||
Next, inject the child `CountdownTimerComponent` into the private `timerComponent` property
|
||||
via the `@ViewChild` property decoration.
|
||||
|
||||
The `#timer` local variable is gone from the component metadata.
|
||||
Instead we bind the buttons to the parent component's own `start` and `stop` methods and
|
||||
The `#timer` local variable is gone from the component metadata.
|
||||
Instead, bind the buttons to the parent component's own `start` and `stop` methods and
|
||||
present the ticking seconds in an interpolation around the parent component's `seconds` method.
|
||||
|
||||
These methods access the injected timer component directly.
|
||||
|
||||
The `ngAfterViewInit` lifecycle hook is an important wrinkle.
|
||||
The `ngAfterViewInit()` lifecycle hook is an important wrinkle.
|
||||
The timer component isn't available until *after* Angular displays the parent view.
|
||||
So we display `0` seconds initially.
|
||||
So it displays `0` seconds initially.
|
||||
|
||||
Then Angular calls the `ngAfterViewInit` lifecycle hook at which time it is *too late*
|
||||
to update the parent view's display of the countdown seconds.
|
||||
Angular's unidirectional data flow rule prevents us from updating the parent view's
|
||||
in the same cycle. We have to *wait one turn* before we can display the seconds.
|
||||
Angular's unidirectional data flow rule prevents updating the parent view's
|
||||
in the same cycle. The app has to *wait one turn* before it can display the seconds.
|
||||
|
||||
We use `setTimeout` to wait one tick and then revise the `seconds` method so
|
||||
Use `setTimeout()` to wait one tick and then revise the `seconds()` method so
|
||||
that it takes future values from the timer component.
|
||||
|
||||
### Test it
|
||||
@ -255,34 +295,47 @@ Use [the same countdown timer tests](guide/component-communication#countdown-tes
|
||||
A parent component and its children share a service whose interface enables bi-directional communication
|
||||
*within the family*.
|
||||
|
||||
The scope of the service instance is the parent component and its children.
|
||||
The scope of the service instance is the parent component and its children.
|
||||
Components outside this component subtree have no access to the service or their communications.
|
||||
|
||||
This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children.
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/mission.service.ts'}
|
||||
<code-example path="cb-component-communication/src/app/mission.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `MissionControlComponent` both provides the instance of the service that it shares with its children
|
||||
(through the `providers` metadata array) and injects that instance into itself through its constructor:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/missioncontrol.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/missioncontrol.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `AstronautComponent` also injects the service in its constructor.
|
||||
Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/ts/src/app/astronaut.component.ts'}
|
||||
<code-example path="cb-component-communication/src/app/astronaut.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Notice that we capture the `subscription` and unsubscribe when the `AstronautComponent` is destroyed.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Notice that this example captures the `subscription` and `unsubscribe()` when the `AstronautComponent` is destroyed.
|
||||
This is a memory-leak guard step. There is no actual risk in this app because the
|
||||
lifetime of a `AstronautComponent` is the same as the lifetime of the app itself.
|
||||
That *would not* always be true in a more complex application.
|
||||
|
||||
We do not add this guard to the `MissionControlComponent` because, as the parent,
|
||||
it controls the lifetime of the `MissionService`.The *History* log demonstrates that messages travel in both directions between
|
||||
You don't add this guard to the `MissionControlComponent` because, as the parent,
|
||||
it controls the lifetime of the `MissionService`.
|
||||
|
||||
~~~
|
||||
|
||||
The *History* log demonstrates that messages travel in both directions between
|
||||
the parent `MissionControlComponent` and the `AstronautComponent` children,
|
||||
facilitated by the service:
|
||||
|
||||
@ -293,9 +346,11 @@ facilitated by the service:
|
||||
### Test it
|
||||
|
||||
Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children
|
||||
and verify that the *History* meets expectations:
|
||||
and verify that the history meets expectations:
|
||||
|
||||
|
||||
{@example 'cb-component-communication/e2e-spec.ts' region='bidirectional-service'}
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="bidirectional-service">
|
||||
|
||||
</code-example>
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
@ -1,194 +0,0 @@
|
||||
@title
|
||||
Component-relative Paths
|
||||
|
||||
@intro
|
||||
Use relative URLs for component templates and styles.
|
||||
|
||||
@description
|
||||
## Write *Component-Relative* URLs to component templates and style files
|
||||
|
||||
Our components often refer to external template and style files.
|
||||
We identify those files with a URL in the `templateUrl` and `styleUrls` properties of the `@Component` metadata
|
||||
as seen here:
|
||||
|
||||
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='absolute-config'}
|
||||
|
||||
By default, we *must* specify the full path back to the application root.
|
||||
We call this an ***absolute path*** because it is *absolute* with respect to the application root.
|
||||
|
||||
There are two problems with an *absolute path*:
|
||||
|
||||
1. We have to remember the full path back to the application root.
|
||||
|
||||
1. We have to update the URL when we move the component around in the application files structure.
|
||||
|
||||
It would be much easier to write and maintain our application components if we could specify template and style locations
|
||||
*relative* to their component class file.
|
||||
|
||||
*We can!*
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
We can if we build our application as `commonjs` modules and load those modules
|
||||
with a suitable package loader such as `systemjs` or `webpack`.
|
||||
Learn why [below](guide/component-relative-paths#why-default).
|
||||
|
||||
The Angular CLI uses these technologies and defaults to the
|
||||
*component-relative path* approach described here.
|
||||
CLI users can skip this chapter or read on to understand
|
||||
how it works.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## _Component-Relative_ Paths
|
||||
|
||||
Our goal is to specify template and style URLs *relative* to their component class files,
|
||||
hence the term ***component-relative path***.
|
||||
|
||||
The key to success is following a convention that puts related component files in well-known locations.
|
||||
|
||||
We recommend keeping component template and component-specific style files as *siblings* of their
|
||||
companion component class files.
|
||||
Here we see the three files for `SomeComponent` sitting next to each other in the `app` folder.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
some.component.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
some.component.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
some.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
We'll have more files and folders — and greater folder depth — as our application grows.
|
||||
We'll be fine as long as the component files travel together as the inseparable siblings they are.
|
||||
|
||||
### Set the *moduleId*
|
||||
|
||||
Having adopted this file structure convention, we can specify locations of the template and style files
|
||||
relative to the component class file simply by setting the `moduleId` property of the `@Component` metadata like this
|
||||
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='module-id'}
|
||||
|
||||
We strip the `src/app/` base path from the `templateUrl` and `styleUrls` and replace it with `./`.
|
||||
The result looks like this:
|
||||
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='relative-config'}
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
Webpack users may prefer [an alternative approach](guide/component-relative-paths#webpack).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Source
|
||||
|
||||
**We can see the <live-example name="cb-component-relative-paths"></live-example>**
|
||||
and download the source code from there
|
||||
or simply read the pertinent source here.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/some.component.ts">
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/some.component.html">
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.html'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/some.component.css">
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.css'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'cb-component-relative-paths/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
|
||||
|
||||
|
||||
{@a why-default}
|
||||
|
||||
## Appendix: why *component-relative* is not the default
|
||||
|
||||
A *component-relative* path is obviously superior to an *absolute* path.
|
||||
Why did Angular default to the *absolute* path?
|
||||
Why do *we* have to set the `moduleId`? Why can't Angular set it?
|
||||
|
||||
First, let's look at what happens if we use a relative path and omit the `moduleId`.
|
||||
|
||||
`EXCEPTION: Failed to load some.component.html`
|
||||
|
||||
Angular can't find the file so it throws an error.
|
||||
|
||||
Why can't Angular calculate the template and style URLs from the component file's location?
|
||||
|
||||
Because the location of the component can't be determined without the developer's help.
|
||||
Angular apps can be loaded in many ways: from individual files, from SystemJS packages, or
|
||||
from CommonJS packages, to name a few.
|
||||
We might generate modules in any of several formats.
|
||||
We might not be writing modular code at all!
|
||||
|
||||
With this diversity of packaging and module load strategies,
|
||||
it's not possible for Angular to know with certainty where these files reside at runtime.
|
||||
|
||||
The only location Angular can be sure of is the URL of the `index.html` home page, the application root.
|
||||
So by default it resolves template and style paths relative to the URL of `index.html`.
|
||||
That's why we previously wrote our file URLs with an `app/` base path prefix.
|
||||
|
||||
But *if* we follow the recommended guidelines and we write modules in `commonjs` format
|
||||
and we use a module loader that *plays nice*,
|
||||
*then* we — the developers of the application —
|
||||
know that the semi-global `module.id` variable is available and contains
|
||||
the absolute URL of the component class module file.
|
||||
|
||||
That knowledge enables us to tell Angular where the *component* file is
|
||||
by setting the `moduleId`:
|
||||
|
||||
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='module-id'}
|
||||
|
||||
|
||||
|
||||
{@a webpack}
|
||||
|
||||
## Webpack: load templates and styles
|
||||
Webpack developers have an alternative to `moduleId`.
|
||||
|
||||
They can load templates and styles at runtime by adding `./` at the beginning of the `template` and `styles` / `styleUrls`
|
||||
properties that reference *component-relative URLS.
|
||||
|
||||
|
||||
{@example 'webpack/ts/src/app/app.component.ts'}
|
||||
|
||||
|
||||
Webpack will do a `require` behind the scenes to load the templates and styles. Read more [here](guide/webpack).
|
||||
|
||||
See the [Introduction to Webpack](guide/webpack).
|
@ -5,6 +5,7 @@ Component Styles
|
||||
Learn how to apply CSS styles to components.
|
||||
|
||||
@description
|
||||
|
||||
Angular applications are styled with standard CSS. That means you can apply
|
||||
everything you know about CSS stylesheets, selectors, rules, and media queries
|
||||
directly to Angular applications.
|
||||
@ -36,7 +37,9 @@ The `styles` property takes #{_an} #{_array} of strings that contain CSS code.
|
||||
Usually you give it one string, as in the following example:
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-app.component.ts'}
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The selectors you put into a component's styles apply only within the template
|
||||
of that component. The `h1` selector in the preceding example applies only to the `<h1>` tag
|
||||
@ -70,7 +73,9 @@ Use the `:host` pseudo-class selector to target styles in the element that *host
|
||||
targeting elements *inside* the component's template).
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.css' region='host'}
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="host" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `:host` selector is the only way to target the host element. You can't reach
|
||||
the host element from inside the component with other selectors because it's not part of the
|
||||
@ -82,7 +87,9 @@ including another selector inside parentheses after `:host`.
|
||||
The next example targets the host element again, but only when it also has the `active` CSS class.
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.css' region='hostfunction'}
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### :host-context
|
||||
|
||||
@ -98,7 +105,9 @@ The following example applies a `background-color` style to all `<h2>` elements
|
||||
if some ancestor element has the CSS class `theme-light`.
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.css' region='hostcontext'}
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### /deep/
|
||||
|
||||
@ -111,7 +120,9 @@ children and content children of the component.
|
||||
The following example targets all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM.
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.css' region='deep'}
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
|
||||
|
||||
@ -144,7 +155,9 @@ You can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
||||
Each string in the #{_array} (usually just one string) defines the CSS.
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-app.component.ts'}
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Style URLs in metadata
|
||||
|
||||
@ -152,7 +165,42 @@ You can load styles from external CSS files by adding a `styleUrls` attribute
|
||||
into a component's `@Component` #{_decorator}:
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.ts' region='styleurls'}
|
||||
<code-example path="component-styles/src/app/hero-details.component.ts" region="styleurls">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
The URL is relative to the *application root*, which is usually the
|
||||
location of the `index.html` web page that hosts the application.
|
||||
The style file URL is *not* relative to the component file.
|
||||
That's why the example URL begins `src/app/`.
|
||||
To specify a URL relative to the component file, see [Appendix 2](guide/component-styles#relative-urls).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
||||
to load styles from external files at build time. You could write:
|
||||
|
||||
`styles: [require('my.component.css')]`
|
||||
|
||||
Set the `styles` property, not the `styleUrls` property. The module
|
||||
bundler loads the CSS strings, not Angular.
|
||||
Angular sees the CSS strings only after the bundler loads them.
|
||||
To Angular, it's as if you wrote the `styles` array by hand.
|
||||
For information on loading CSS in this manner, refer to the module bundler's documentation.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Template inline styles
|
||||
|
||||
@ -160,7 +208,9 @@ You can embed styles directly into the HTML template by putting them
|
||||
inside `<style>` tags.
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-controls.component.ts' region='inlinestyles'}
|
||||
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Template link tags
|
||||
|
||||
@ -170,7 +220,9 @@ As with `styleUrls`, the link tag's `href` URL is relative to the
|
||||
application root, not the component file.
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-team.component.ts' region='stylelink'}
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink">
|
||||
|
||||
</code-example>
|
||||
|
||||
### CSS @imports
|
||||
|
||||
@ -178,8 +230,12 @@ You can also import CSS files into the CSS files using the standard CSS `@import
|
||||
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
||||
on the [MDN](https://developer.mozilla.org) site.
|
||||
|
||||
In this case, the URL is relative to the CSS file into which you're importing.
|
||||
|
||||
{@example 'component-styles/ts/src/app/hero-details.component.css' region='import'}
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="import">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -210,7 +266,9 @@ Choose from the following modes:
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
|
||||
{@example 'component-styles/ts/src/app/quest-summary.component.ts' region='encapsulation.native'}
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`Native` view encapsulation only works on browsers that have native support
|
||||
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
||||
@ -283,3 +341,11 @@ It's common practice to split a component's code, HTML, and CSS into three separ
|
||||
You 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.
|
||||
|
||||
You can use a relative URL by prefixing your filenames with `./`:
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -108,12 +108,16 @@ Load the few files you need from the web instead.
|
||||
with versions that load from the web. It might look like this.
|
||||
|
||||
|
||||
{@example 'deployment/ts/src/index.html' region='node-module-scripts'}
|
||||
<code-example path="deployment/src/index.html" region="node-module-scripts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
(2) Replace the `systemjs.config.js` script with a script that
|
||||
loads `systemjs.config.server.js`.
|
||||
|
||||
{@example 'deployment/ts/src/index.html' region='systemjs-config'}
|
||||
<code-example path="deployment/src/index.html" region="systemjs-config" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
(3) Add `systemjs.config.server.js` (shown in the code sample below) to the `src/` folder.
|
||||
This alternative version configures _SystemJS_ to load _UMD_ versions of Angular
|
||||
@ -125,7 +129,9 @@ you make to `systemjs.config.js`.
|
||||
Notice the `paths` key:
|
||||
|
||||
|
||||
{@example 'deployment/ts/src/systemjs.config.server.js' region='paths'}
|
||||
<code-example path="deployment/src/systemjs.config.server.js" region="paths" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In the standard SystemJS config, the `npm` path points to the `node_modules/`.
|
||||
In this server config, it points to
|
||||
@ -143,44 +149,45 @@ Then change the config's `'npm'` path to point to that folder.
|
||||
|
||||
The following trivial router sample app shows these changes.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="index.html">
|
||||
{@example 'deployment/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="index.html" path="deployment/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="systemjs.config.server.js">
|
||||
{@example 'deployment/ts/src/systemjs.config.server.js'}
|
||||
</md-tab>
|
||||
<code-pane title="systemjs.config.server.js" path="deployment/src/systemjs.config.server.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="main.ts">
|
||||
{@example 'deployment/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="main.ts" path="deployment/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/app.module.ts">
|
||||
{@example 'deployment/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/app.module.ts" path="deployment/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/app.component.ts">
|
||||
{@example 'deployment/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/app.component.ts" path="deployment/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/crisis-list.component.ts">
|
||||
{@example 'deployment/ts/src/app/crisis-list.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/crisis-list.component.ts" path="deployment/src/app/crisis-list.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app/hero-list.component.ts">
|
||||
{@example 'deployment/ts/src/app/hero-list.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app/hero-list.component.ts" path="deployment/src/app/hero-list.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Practice with this sample before attempting these techniques on your application.
|
||||
|
||||
@ -338,7 +345,14 @@ For example, given the `<base href="/my/app/">`, the browser resolves a URL such
|
||||
into a server request for `my/app/some/place/foo.jpg`.
|
||||
During navigation, the Angular router uses the _base href_ as the base path to component, template, and module files.
|
||||
|
||||
See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let) alternative.In development, you typically start the server in the folder that holds `index.html`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let) alternative.
|
||||
|
||||
~~~
|
||||
|
||||
In development, you typically start the server in the folder that holds `index.html`.
|
||||
That's the root folder and you'd add `<base href="/">` near the top of `index.html` because `/` is the root of the app.
|
||||
|
||||
But on the shared or production server, you might serve the app from a subfolder.
|
||||
@ -364,7 +378,9 @@ Switching to production mode can make it run faster by disabling development spe
|
||||
To enable [production mode](api/core/index/enableProdMode-function) when running remotely, add the following code to the `main.ts`.
|
||||
|
||||
|
||||
{@example 'deployment/ts/src/main.ts' region='enableProdMode'}
|
||||
<code-example path="deployment/src/main.ts" region="enableProdMode" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@ Displaying Data
|
||||
Property binding helps show app data in the UI.
|
||||
|
||||
@description
|
||||
|
||||
You can display data by binding controls in an HTML template to properties of an Angular component.
|
||||
|
||||
In this page, you'll create a component with a list of heroes.
|
||||
@ -23,9 +24,16 @@ The final UI looks like this:
|
||||
* [Showing !{_an} !{_array} property with NgFor](guide/displaying-data#ngFor).
|
||||
* [Conditional display with NgIf](guide/displaying-data#ngIf).
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The <live-example></live-example> demonstrates all of the syntax and code
|
||||
snippets described in this page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Showing component properties with interpolation
|
||||
The easiest way to display a component property
|
||||
is to bind the property name through interpolation.
|
||||
@ -40,23 +48,44 @@ changing the template and the body of the component.
|
||||
When you're done, it should look like this:
|
||||
|
||||
|
||||
{@example 'displaying-data/ts/src/app/app.component.1.ts'}
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
You added two properties to the formerly empty component: `title` and `myHero`.
|
||||
|
||||
The revised template displays the two component properties using double curly brace
|
||||
interpolation:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
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}
|
||||
|
||||
More precisely, the redisplay occurs after some kind of asynchronous event related to
|
||||
the view, such as a keystroke, a timer completion, or a response to an HTTP request.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for you. How?
|
||||
|
||||
The CSS `selector` in the `@Component` !{_decorator} specifies an element named `<my-app>`.
|
||||
That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/index.html" linenums="false" title="src/index.html (body)" region="body">
|
||||
|
||||
</code-example>
|
||||
|
||||
When you 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.
|
||||
@ -83,13 +112,31 @@ In either style, the template data bindings have the same access to the componen
|
||||
## Showing !{_an} !{_array} property with ***ngFor**
|
||||
|
||||
To display a list of heroes, begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now use the Angular `ngFor` directive in the template to display
|
||||
each item in the `heroes` list.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
This UI uses the HTML unordered list with `<ul>` and `<li>` tags. The `*ngFor`
|
||||
in the `<li>` element is the Angular "repeater" directive.
|
||||
It marks that `<li>` element (and its children) as the "repeater template":
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (li)" region="li">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
|
||||
@ -107,8 +154,15 @@ Angular duplicates the `<li>` for each item in the list, setting the `hero` vari
|
||||
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}
|
||||
|
||||
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
|
||||
repeat items for any [iterable](guide/!{_iterableUrl}) object.Now the heroes appear in an unordered list.
|
||||
repeat items for any [iterable](guide/!{_iterableUrl}) object.
|
||||
|
||||
~~~
|
||||
|
||||
Now the heroes appear in an unordered list.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor"> </img>
|
||||
@ -128,13 +182,48 @@ of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `H
|
||||
|
||||
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
You've defined a class with a constructor and two properties: `id` and `name`.
|
||||
|
||||
It might not look like the class has properties, but it does.
|
||||
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
|
||||
|
||||
Consider the first parameter:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (id)" region="id">
|
||||
|
||||
</code-example>
|
||||
|
||||
That brief syntax does a lot:
|
||||
* Declares a constructor parameter and its type.
|
||||
* Declares a public property of the same name.
|
||||
* Initializes that property with the corresponding argument when creating an instance of the class.
|
||||
|
||||
## Using the Hero class
|
||||
|
||||
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ !{_array}
|
||||
of `Hero` objects:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (heroes)" region="heroes">
|
||||
|
||||
</code-example>
|
||||
|
||||
Next, update the template.
|
||||
At the moment it displays the hero's `id` and `name`.
|
||||
Fix that to display only the hero's `name` property.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
The display looks the same, but the code is clearer.
|
||||
|
||||
## Conditional display with NgIf
|
||||
@ -147,6 +236,12 @@ The Angular `ngIf` directive inserts or removes an element based on a !{_boolean
|
||||
To see it in action, add the following paragraph at the bottom of the template:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (message)" region="message">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
|
||||
@ -184,3 +279,30 @@ Now you know how to use:
|
||||
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||
|
||||
Here's the final code:
|
||||
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="displaying-data/src/app/app.component.ts" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="src/app/hero.ts" path="displaying-data/src/app/hero.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="src/app/app.module.ts" path="displaying-data/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="main.ts" path="displaying-data/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
@ -37,7 +37,9 @@ Before components can be added we have to define an anchor point to mark where c
|
||||
The ad banner uses a helper directive called `AdDirective` to mark valid insertion points in the template.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad.directive.ts'}
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad.directive.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`AdDirective` injects `ViewContainerRef` to gain access to the view container of the element that will become the host of the dynamically added component.
|
||||
|
||||
@ -47,41 +49,44 @@ The next step is to implement the ad banner. Most of the implementation is in `A
|
||||
|
||||
We start by adding a `template` element with the `AdDirective` directive applied.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="ad-banner.component.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="ad-banner.component.ts" path="cb-dynamic-component-loader/src/app/ad-banner.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ad.service.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad.service.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="ad.service.ts" path="cb-dynamic-component-loader/src/app/ad.service.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ad-item.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad-item.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="ad-item.ts" path="cb-dynamic-component-loader/src/app/ad-item.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.module.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app.module.ts" path="cb-dynamic-component-loader/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.component">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app.component" path="cb-dynamic-component-loader/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The `template` element decorated with the `ad-host` directive marks where dynamically loaded components will be added.
|
||||
|
||||
Using a `template` element is recommended since it doesn't render any additional output.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts' region='ad-host'}
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Resolving Components
|
||||
|
||||
@ -102,7 +107,9 @@ Generally the compiler will generate a component factory for any component refer
|
||||
With dynamically loaded components there are no selector references in the templates since components are loaded at runtime. In order to ensure that the compiler will still generate a factory, dynamically loaded components have to be added to their `NgModule`'s `entryComponents` array.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/app.module.ts' region='entry-components'}
|
||||
<code-example path="cb-dynamic-component-loader/src/app/app.module.ts" region="entry-components" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Components are added to the template by calling `createComponent` on the `ViewContainerRef` reference.
|
||||
|
||||
@ -112,24 +119,25 @@ In the Ad banner, all components implement a common `AdComponent` interface to s
|
||||
|
||||
Two sample components and the `AdComponent` interface are shown below:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="hero-job-ad.component.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-job-ad.component.ts" path="cb-dynamic-component-loader/src/app/hero-job-ad.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero-profile.component.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="hero-profile.component.ts" path="cb-dynamic-component-loader/src/app/hero-profile.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ad.component.ts">
|
||||
{@example 'cb-dynamic-component-loader/ts/src/app/ad.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="ad.component.ts" path="cb-dynamic-component-loader/src/app/ad.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The final ad banner looks like this:
|
||||
<figure class='image-display'>
|
||||
|
@ -42,19 +42,20 @@ Reactive Forms belongs to a different `NgModule` called `ReactiveFormsModule`, s
|
||||
|
||||
We bootstrap our `AppModule` in main.ts.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="app.module.ts">
|
||||
{@example 'cb-dynamic-form/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app.module.ts" path="cb-dynamic-form/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="main.ts">
|
||||
{@example 'cb-dynamic-form/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="main.ts" path="cb-dynamic-form/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
<a id="object-model"></a>## Question Model
|
||||
@ -66,7 +67,9 @@ The "question" is the most fundamental object in the model.
|
||||
We have created `QuestionBase` as the most fundamental question class.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/question-base.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/question-base.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
|
||||
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
|
||||
@ -74,53 +77,61 @@ The idea is that the form will be bound to specific question types and render th
|
||||
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/question-textbox.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/question-textbox.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`DropdownQuestion` presents a list of choices in a select box.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/question-dropdown.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/question-dropdown.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`.
|
||||
In a nutshell, the form group consumes the metadata from the question model and allows us to specify default values and validation rules.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/question-control.service.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/question-control.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="form-component"></a>## Question form components
|
||||
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
|
||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="dynamic-form.component.html">
|
||||
{@example 'cb-dynamic-form/ts/src/app/dynamic-form.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="dynamic-form.component.html" path="cb-dynamic-form/src/app/dynamic-form.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="dynamic-form.component.ts">
|
||||
{@example 'cb-dynamic-form/ts/src/app/dynamic-form.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="dynamic-form.component.ts" path="cb-dynamic-form/src/app/dynamic-form.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
It presents a list of questions, each question bound to a `<df-question>` component element.
|
||||
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
|
||||
the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="dynamic-form-question.component.html">
|
||||
{@example 'cb-dynamic-form/ts/src/app/dynamic-form-question.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="dynamic-form-question.component.html" path="cb-dynamic-form/src/app/dynamic-form-question.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="dynamic-form-question.component.ts">
|
||||
{@example 'cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="dynamic-form-question.component.ts" path="cb-dynamic-form/src/app/dynamic-form-question.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Notice this component can present any type of question in our model.
|
||||
We only have two types of questions at this point but we can imagine many more.
|
||||
@ -139,12 +150,16 @@ underlying control objects, populated from the question model with display and v
|
||||
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/question.service.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/question.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Finally, we display an instance of the form in the `AppComponent` shell.
|
||||
|
||||
|
||||
{@example 'cb-dynamic-form/ts/src/app/app.component.ts'}
|
||||
<code-example path="cb-dynamic-form/src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="dynamic-template"></a>## Dynamic Template
|
||||
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
|
||||
|
@ -8,29 +8,42 @@ Validate user's form entries.
|
||||
|
||||
|
||||
{@a top}
|
||||
We can improve overall data quality by validating user input for accuracy and completeness.
|
||||
Improve overall data quality by validating user input for accuracy and completeness.
|
||||
|
||||
In this cookbook we show how to validate user input in the UI and display useful validation messages
|
||||
This cookbook shows how to validate user input in the UI and display useful validation messages
|
||||
using first the template-driven forms and then the reactive forms approach.
|
||||
Learn more about these choices in the [Forms chapter.](guide/forms)
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read more about these choices in the [Forms](guide/forms)
|
||||
and the [Reactive Forms](guide/reactive-forms) guides.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a toc}
|
||||
## Table of Contents
|
||||
## Contents
|
||||
|
||||
[Simple Template-Driven Forms](guide/form-validation#template1)
|
||||
|
||||
[Template-Driven Forms with validation messages in code](guide/form-validation#template2)
|
||||
|
||||
[Reactive Forms with validation in code](guide/form-validation#reactive)
|
||||
|
||||
[Custom validation](guide/form-validation#custom-validation)
|
||||
|
||||
[Testing](guide/form-validation#testing)
|
||||
* [Simple template-driven forms](guide/form-validation#template1)
|
||||
* [Template-driven forms with validation messages in code](guide/form-validation#template2)
|
||||
- [Component Class](guide/form-validation#component-class)
|
||||
- [The benefits of messages in code](guide/form-validation#improvement)
|
||||
- [`FormModule` and template-driven forms](guide/form-validation#formmodule)
|
||||
* [Reactive forms with validation in code](guide/form-validation#reactive)
|
||||
- [Switch to the `ReactiveFormsModule`](guide/form-validation#reactive-forms-module)
|
||||
- [Component template](guide/form-validation#reactive-component-template)
|
||||
- [Component class](guide/form-validation#reactive-component-class)
|
||||
- [`FormBuilder` declaration](guide/form-validation#formbuilder)
|
||||
- [Committing hero value changes](guide/form-validation#committing-changes)
|
||||
* [Custom validation](guide/form-validation#custom-validation)
|
||||
- [Custom validation directive](guide/form-validation#custom-validation-directive)
|
||||
* [Testing considerations](guide/form-validation#testing)
|
||||
|
||||
|
||||
{@a live-example}
|
||||
**Try the live example to see and download the full cookbook source code**
|
||||
**Try the live example to see and download the full cookbook source code.**
|
||||
<live-example name="cb-form-validation" embedded=true img="cookbooks/form-validation/plunker.png">
|
||||
|
||||
</live-example>
|
||||
@ -39,14 +52,14 @@ Learn more about these choices in the [Forms chapter.](guide/forms)
|
||||
|
||||
|
||||
{@a template1}
|
||||
## Simple Template-Driven Forms
|
||||
## Simple template-driven forms
|
||||
|
||||
In the template-driven approach, you arrange
|
||||
[form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template.
|
||||
|
||||
You add Angular form directives (mostly directives beginning `ng...`) to help
|
||||
Angular construct a corresponding internal control model that implements form functionality.
|
||||
We say that the control model is _implicit_ in the template.
|
||||
In template-drive forms, the control model is _implicit_ in the template.
|
||||
|
||||
To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)
|
||||
to the elements. Angular interprets those as well, adding validator functions to the control model.
|
||||
@ -54,139 +67,167 @@ to the elements. Angular interprets those as well, adding validator functions to
|
||||
Angular exposes information about the state of the controls including
|
||||
whether the user has "touched" the control or made changes and if the control values are valid.
|
||||
|
||||
In the first template validation example,
|
||||
we add more HTML to read that control state and update the display appropriately.
|
||||
Here's an excerpt from the template html for a single input box control bound to the hero name:
|
||||
In this first template validation example,
|
||||
notice the HTML that reads the control state and updates the display appropriately.
|
||||
Here's an excerpt from the template HTML for a single input control bound to the hero name:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html' region='name-with-error-msg'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Note the following:
|
||||
- The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
|
||||
|
||||
- We set the `name` attribute of the input box to `"name"` so Angular can track this input element and associate it
|
||||
- The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
|
||||
with an Angular form control called `name` in its internal control model.
|
||||
|
||||
- We use the `[(ngModel)]` directive to two-way data bind the input box to the `hero.name` property.
|
||||
- The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
|
||||
|
||||
- We set a template variable (`#name`) to the value `"ngModel"` (always `ngModel`).
|
||||
This gives us a reference to the Angular `NgModel` directive
|
||||
associated with this control that we can use _in the template_
|
||||
- The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
|
||||
This gives you a reference to the Angular `NgModel` directive
|
||||
associated with this control that you can use _in the template_
|
||||
to check for control states such as `valid` and `dirty`.
|
||||
|
||||
- The `*ngIf` on `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
|
||||
- The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
|
||||
the control is either `dirty` or `touched`.
|
||||
|
||||
- Each nested `<div>` can present a custom message for one of the possible validation errors.
|
||||
We've prepared messages for `required`, `minlength`, and `maxlength`.
|
||||
There are messages for `required`, `minlength`, and `maxlength`.
|
||||
|
||||
The full template repeats this kind of layout for each data entry control on the form.
|
||||
|
||||
{@a why-check}
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
#### Why check _dirty_ and _touched_?
|
||||
|
||||
We shouldn't show errors for a new hero before the user has had a chance to edit the value.
|
||||
The app shouldn't show errors for a new hero before the user has had a chance to edit the value.
|
||||
The checks for `dirty` and `touched` prevent premature display of errors.
|
||||
|
||||
Learn about `dirty` and `touched` in the [Forms](guide/forms) chapter.The component class manages the hero model used in the data binding
|
||||
Learn about `dirty` and `touched` in the [Forms](guide/forms) guide.
|
||||
|
||||
~~~
|
||||
|
||||
The component class manages the hero model used in the data binding
|
||||
as well as other code to support the view.
|
||||
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.ts' region='class'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.ts" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
Use this template-driven validation technique when working with static forms with simple, standard validation rules.
|
||||
|
||||
Here are the complete files for the first version of `HeroFormTemplateCompononent` in the template-driven approach:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="template/hero-form-template1.component.html">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="template/hero-form-template1.component.html" path="cb-form-validation/src/app/template/hero-form-template1.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="template/hero-form-template1.component.ts">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="template/hero-form-template1.component.ts" path="cb-form-validation/src/app/template/hero-form-template1.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
|
||||
{@a template2}
|
||||
## Template-Driven Forms with validation messages in code
|
||||
## Template-driven forms with validation messages in code
|
||||
|
||||
While the layout is straightforward,
|
||||
there are obvious shortcomings with the way we handle validation messages:
|
||||
there are obvious shortcomings with the way it's handling validation messages:
|
||||
|
||||
* It takes a lot of HTML to represent all possible error conditions.
|
||||
This gets out of hand when there are many controls and many validation rules.
|
||||
|
||||
* We're not fond of so much JavaScript logic in HTML.
|
||||
* There's a lot of JavaScript logic in the HTML.
|
||||
|
||||
* The messages are static strings, hard-coded into the template.
|
||||
We often require dynamic messages that we should shape in code.
|
||||
It's easier to maintain _dynamic_ messages in the component class.
|
||||
|
||||
We can move the logic and the messages into the component with a few changes to
|
||||
In this example, you can move the logic and the messages into the component with a few changes to
|
||||
the template and component.
|
||||
|
||||
Here's the hero name again, excerpted from the revised template ("Template 2"), next to the original version:
|
||||
<md-tab-group>
|
||||
Here's the hero name again, excerpted from the revised template
|
||||
(Template 2), next to the original version:
|
||||
|
||||
<md-tab label="hero-form-template2.component.html (name #2)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.html' region='name-with-error-msg'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-form-template2.component.html (name #2)" path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-with-error-msg">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero-form-template1.component.html (name #1)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html' region='name-with-error-msg'}
|
||||
</md-tab>
|
||||
<code-pane title="hero-form-template1.component.html (name #1)" path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The `<input>` element HTML is almost the same. There are noteworthy differences:
|
||||
- The hard-code error message `<divs>` are gone.
|
||||
|
||||
- There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
|
||||
It invalidates the control if the user enters "bob" anywhere in the name ([try it](guide/form-validation#live-example)).
|
||||
We discuss [custom validation directives](guide/form-validation#custom-validation) later in this cookbook.
|
||||
It invalidates the control if the user enters "bob" in the name `<input>`([try it](guide/form-validation#live-example)).
|
||||
See the [custom validation](guide/form-validation#custom-validation) section later in this cookbook for more information
|
||||
on custom validation directives.
|
||||
|
||||
- The `#name` template variable is gone because we no longer refer to the Angular control for this element.
|
||||
- The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
|
||||
|
||||
- Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
|
||||
- Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
|
||||
|
||||
#### Component class
|
||||
The original component code stays the same.
|
||||
We _added_ new code to acquire the Angular form control and compose error messages.
|
||||
|
||||
{@a component-class}
|
||||
### Component class
|
||||
The original component code for Template 1 stayed the same; however,
|
||||
Template 2 requires some changes in the component. This section covers the code
|
||||
necessary in Template 2's component class to acquire the Angular
|
||||
form control and compose error messages.
|
||||
|
||||
The first step is to acquire the form control that Angular created from the template by querying for it.
|
||||
|
||||
Look back at the top of the component template where we set the
|
||||
Look back at the top of the component template at the
|
||||
`#heroForm` template variable in the `<form>` element:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html' region='form-tag'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="form-tag" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `heroForm` variable is a reference to the control model that Angular derived from the template.
|
||||
We tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
|
||||
Tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='view-child'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="view-child" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Some observations:
|
||||
|
||||
- Angular `@ViewChild` queries for a template variable when you pass it
|
||||
the name of that variable as a string (`'heroForm'` in this case).
|
||||
|
||||
- The `heroForm` object changes several times during the life of the component, most notably when we add a new hero.
|
||||
We'll have to re-inspect it periodically.
|
||||
- The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
|
||||
Periodically inspecting it reveals these changes.
|
||||
|
||||
- Angular calls the `ngAfterViewChecked` [lifecycle hook method](guide/lifecycle-hooks)
|
||||
when anything changes in the view.
|
||||
That's the right time to see if there's a new `heroForm` object.
|
||||
|
||||
- When there _is_ a new `heroForm` model, we subscribe to its `valueChanged` _Observable_ property.
|
||||
The `onValueChanged` handler looks for validation errors after every user keystroke.
|
||||
- When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
|
||||
The `onValueChanged` handler looks for validation errors after every keystroke.
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='handler'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="handler" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `onValueChanged` handler interprets user data entry.
|
||||
The `data` object passed into the handler contains the current element values.
|
||||
@ -196,26 +237,33 @@ The `formErrors` is a dictionary of the hero fields that have validation rules a
|
||||
Only two hero properties have validation rules, `name` and `power`.
|
||||
The messages are empty strings when the hero data are valid.
|
||||
|
||||
For each field, the handler
|
||||
- clears the prior error message if any
|
||||
- acquires the field's corresponding Angular form control
|
||||
- if such a control exists _and_ its been changed ("dirty") _and_ its invalid ...
|
||||
- the handler composes a consolidated error message for all of the control's errors.
|
||||
For each field, the `onValueChanged` handler does the following:
|
||||
- Clears the prior error message, if any.
|
||||
- Acquires the field's corresponding Angular form control.
|
||||
- If such a control exists _and_ it's been changed ("dirty")
|
||||
_and_ it's invalid, the handler composes a consolidated error message for all of the control's errors.
|
||||
|
||||
We'll need some error messages of course, a set for each validated property, one message per validation rule:
|
||||
Next, the component needs some error messages of course—a set for each validated property with
|
||||
one message per validation rule:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='messages'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="messages" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
|
||||
|
||||
### Is this an improvement?
|
||||
|
||||
{@a improvement}
|
||||
### The benefits of messages in code
|
||||
|
||||
Clearly the template got substantially smaller while the component code got substantially larger.
|
||||
It's not easy to see the benefit when there are just three fields and only two of them have validation rules.
|
||||
|
||||
Consider what happens as we increase the number of validated fields and rules.
|
||||
Consider what happens as the number of validated
|
||||
fields and rules increases.
|
||||
In general, HTML is harder to read and maintain than code.
|
||||
The initial template was already large and threatening to get rapidly worse as we add more validation message `<divs>`.
|
||||
The initial template was already large and threatening to get rapidly worse
|
||||
with the addition of more validation message `<div>` elements.
|
||||
|
||||
After moving the validation messaging to the component,
|
||||
the template grows more slowly and proportionally.
|
||||
@ -225,32 +273,44 @@ and one line per validation message.
|
||||
|
||||
Both trends are manageable.
|
||||
|
||||
Now that the messages are in code, we have more flexibility. We can compose messages more intelligently.
|
||||
We can refactor the messages out of the component, perhaps to a service class that retrieves them from the server.
|
||||
Now that the messages are in code, you have more flexibility and can compose messages more efficiently.
|
||||
You can refactor the messages out of the component, perhaps to a service class that retrieves them from the server.
|
||||
In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.
|
||||
|
||||
|
||||
{@a formmodule}
|
||||
### _FormModule_ and template-driven forms
|
||||
|
||||
Angular has two different forms modules — `FormsModule` and `ReactiveFormsModule` —
|
||||
that correspond with the two approaches to form development.
|
||||
Both modules come from the same `@angular/forms` library package.
|
||||
Angular has two different forms modules—`FormsModule` and
|
||||
`ReactiveFormsModule`—that correspond with the
|
||||
two approaches to form development. Both modules come
|
||||
from the same `@angular/forms` library package.
|
||||
|
||||
We've been reviewing the "Template-driven" approach which requires the `FormsModule`
|
||||
Here's how we imported it in the `HeroFormTemplateModule`.
|
||||
You've been reviewing the "Template-driven" approach which requires the `FormsModule`.
|
||||
Here's how you imported it in the `HeroFormTemplateModule`.
|
||||
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template.module.ts'}
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
We haven't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This guide hasn't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
|
||||
form template in this cookbook.
|
||||
|
||||
They're not germane to the validation story. Look at the [live example](guide/form-validation#live-example) if you're interested.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
{@a reactive}
|
||||
## Reactive Forms
|
||||
## Reactive forms with validation in code
|
||||
|
||||
In the template-driven approach, you markup the template with form elements, validation attributes,
|
||||
and `ng...` directives from the Angular `FormsModule`.
|
||||
@ -263,101 +323,140 @@ At runtime, Angular binds the template elements to your control model based on y
|
||||
|
||||
This approach requires a bit more effort. *You have to write the control model and manage it*.
|
||||
|
||||
In return, you can
|
||||
* add, change, and remove validation functions on the fly
|
||||
* manipulate the control model dynamically from within the component
|
||||
* [test](guide/form-validation#testing) validation and control logic with isolated unit tests.
|
||||
This allows you to do the following:
|
||||
* Add, change, and remove validation functions on the fly.
|
||||
* Manipulate the control model dynamically from within the component.
|
||||
* [Test](guide/form-validation#testing) validation and control logic with isolated unit tests.
|
||||
|
||||
The third cookbook sample re-writes the hero form in _reactive forms_ style.
|
||||
The following cookbook sample re-writes the hero form in _reactive forms_ style.
|
||||
|
||||
|
||||
{@a reactive-forms-module}
|
||||
### Switch to the _ReactiveFormsModule_
|
||||
The reactive forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`.
|
||||
The application module for the "Reactive Forms" feature in this sample looks like this:
|
||||
The application module for the reactive forms feature in this sample looks like this:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts'}
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.module.ts" linenums="false">
|
||||
|
||||
The "Reactive Forms" feature module and component are in the `src/app/reactive` folder.
|
||||
Let's focus on the `HeroFormReactiveComponent` there, starting with its template.
|
||||
</code-example>
|
||||
|
||||
The reactive forms feature module and component are in the `src/app/reactive` folder.
|
||||
Focus on the `HeroFormReactiveComponent` there, starting with its template.
|
||||
|
||||
|
||||
{@a reactive-component-template}
|
||||
### Component template
|
||||
|
||||
We begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
|
||||
Begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
|
||||
to the `heroForm` property in the component class.
|
||||
The `heroForm` is the control model that the component class builds and maintains.
|
||||
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html' region='form-tag'}
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="form-tag" linenums="false">
|
||||
|
||||
Then we modify the template HTML elements to match the _reactive forms_ style.
|
||||
</code-example>
|
||||
|
||||
Next, modify the template HTML elements to match the _reactive forms_ style.
|
||||
Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="hero-form-reactive.component.html (name #3)">
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html' region='name-with-error-msg'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-form-reactive.component.html (name #3)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero-form-template1.component.html (name #2)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.html' region='name-with-error-msg'}
|
||||
</md-tab>
|
||||
<code-pane title="hero-form-template1.component.html (name #2)" path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-with-error-msg">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Key changes:
|
||||
- the validation attributes are gone (except `required`) because we'll be validating in code.
|
||||
Key changes are:
|
||||
- The validation attributes are gone (except `required`) because
|
||||
validating happens in code.
|
||||
|
||||
- `required` remains, not for validation purposes (we'll cover that in the code),
|
||||
- `required` remains, not for validation purposes (that's in the code),
|
||||
but rather for css styling and accessibility.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
A future version of reactive forms will add the `required` HTML validation attribute to the DOM element
|
||||
(and perhaps the `aria-required` attribute) when the control has the `required` validator function.
|
||||
|
||||
Until then, apply the `required` attribute _and_ add the `Validator.required` function
|
||||
to the control model, as we'll do below.
|
||||
- the `formControlName` replaces the `name` attribute; it serves the same
|
||||
purpose of correlating the input box with the Angular form control.
|
||||
to the control model, as you'll see below.
|
||||
|
||||
- the two-way `[(ngModel)]` binding is gone.
|
||||
|
||||
~~~
|
||||
|
||||
- The `formControlName` replaces the `name` attribute; it serves the same
|
||||
purpose of correlating the input with the Angular form control.
|
||||
|
||||
- The two-way `[(ngModel)]` binding is gone.
|
||||
The reactive approach does not use data binding to move data into and out of the form controls.
|
||||
We do that in code.
|
||||
That's all in code.
|
||||
|
||||
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.### Component class
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a reactive-component-class}
|
||||
### Component class
|
||||
|
||||
The component class is now responsible for defining and managing the form control model.
|
||||
|
||||
Angular no longer derives the control model from the template so we can no longer query for it.
|
||||
We create the Angular form control model explicitly with the help of the `FormBuilder`.
|
||||
Angular no longer derives the control model from the template so you can no longer query for it.
|
||||
You can create the Angular form control model explicitly with
|
||||
the help of the `FormBuilder` class.
|
||||
|
||||
Here's the section of code devoted to that process, paired with the template-driven code it replaces:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="reactive/hero-form-reactive.component.ts (FormBuilder)">
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts' region='form-builder'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="reactive/hero-form-reactive.component.ts (FormBuilder)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="form-builder">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="template/hero-form-template2.component.ts (ViewChild)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='view-child'}
|
||||
</md-tab>
|
||||
<code-pane title="template/hero-form-template2.component.ts (ViewChild)" path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="view-child">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
- we inject the `FormBuilder` in a constructor.
|
||||
- Inject `FormBuilder` in a constructor.
|
||||
|
||||
- we call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks)
|
||||
because that's when we'll have the hero data. We'll call it again in the `addHero` method.
|
||||
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.- the `buildForm` method uses the `FormBuilder` (`fb`) to declare the form control model.
|
||||
- Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks)
|
||||
because that's when you'll have the hero data. Call it again in the `addHero` method.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
|
||||
|
||||
~~~
|
||||
|
||||
- The `buildForm` method uses the `FormBuilder`, `fb`, to declare the form control model.
|
||||
Then it attaches the same `onValueChanged` handler (there's a one line difference)
|
||||
to the form's `valueChanged` event and calls it immediately
|
||||
to the form's `valueChanges` event and calls it immediately
|
||||
to set error messages for the new control model.
|
||||
|
||||
|
||||
{@a formbuilder}
|
||||
#### _FormBuilder_ declaration
|
||||
The `FormBuilder` declaration object specifies the three controls of the sample's hero form.
|
||||
|
||||
Each control spec is a control name with an array value.
|
||||
The first array element is the current value of the corresponding hero field.
|
||||
The (optional) second value is a validator function or an array of validator functions.
|
||||
The optional second value is a validator function or an array of validator functions.
|
||||
|
||||
Most of the validator functions are stock validators provided by Angular as static methods of the `Validators` class.
|
||||
Angular has stock validators that correspond to the standard HTML validation attributes.
|
||||
@ -365,7 +464,17 @@ Angular has stock validators that correspond to the standard HTML validation att
|
||||
The `forbiddenNames` validator on the `"name"` control is a custom validator,
|
||||
discussed in a separate [section below](guide/form-validation#custom-validation).
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reactive-forms) section of Reactive Forms guide.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a committing-changes}
|
||||
#### Committing hero value changes
|
||||
|
||||
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
|
||||
@ -373,57 +482,78 @@ Reactive forms do not use data binding to update data model properties.
|
||||
The developer decides _when and how_ to update the data model from control values.
|
||||
|
||||
This sample updates the model twice:
|
||||
1. when the user submits the form
|
||||
1. when the user chooses to add a new hero
|
||||
1. When the user submits the form.
|
||||
1. When the user adds a new hero.
|
||||
|
||||
The `onSubmit` method simply replaces the `hero` object with the combined values of the form:
|
||||
The `onSubmit()` method simply replaces the `hero` object with the combined values of the form:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts' region='on-submit'}
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="on-submit" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
This example is "lucky" in that the `heroForm.value` properties _just happen_ to
|
||||
correspond _exactly_ to the hero data object properties.The `addHero` method discards pending changes and creates a brand new `hero` model object.
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts' region='add-hero'}
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Then it calls `buildForm` again which replaces the previous `heroForm` control model with a new one.
|
||||
This example is lucky in that the `heroForm.value` properties _just happen_ to
|
||||
correspond _exactly_ to the hero data object properties.
|
||||
|
||||
~~~
|
||||
|
||||
The `addHero()` method discards pending changes and creates a brand new `hero` model object.
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="add-hero" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Then it calls `buildForm()` again which replaces the previous `heroForm` control model with a new one.
|
||||
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
|
||||
|
||||
Here's the complete reactive component file, compared to the two template-driven component files.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="reactive/hero-form-reactive.component.ts (#3)">
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="reactive/hero-form-reactive.component.ts (#3)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="template/hero-form-template2.component.ts (#2)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="template/hero-form-template2.component.ts (#2)" path="cb-form-validation/src/app/template/hero-form-template2.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="template/hero-form-template1.component.ts (#1)">
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="template/hero-form-template1.component.ts (#1)" path="cb-form-validation/src/app/template/hero-form-template1.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
Run the [live example](guide/form-validation#live-example) to see how the reactive form behaves
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Run the [live example](guide/form-validation#live-example) to see how the reactive form behaves,
|
||||
and to compare all of the files in this cookbook sample.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
{@a custom-validation}
|
||||
## Custom validation
|
||||
This cookbook sample has a custom `forbiddenNamevalidator` function that's applied to both the
|
||||
This cookbook sample has a custom `forbiddenNamevalidator()` function that's applied to both the
|
||||
template-driven and the reactive form controls. It's in the `src/app/shared` folder
|
||||
and declared in the `SharedModule`.
|
||||
|
||||
Here's the `forbiddenNamevalidator` function itself:
|
||||
Here's the `forbiddenNamevalidator()` function:
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts' region='custom-validator'}
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
|
||||
and returns a validator function.
|
||||
@ -432,43 +562,80 @@ In this sample, the forbidden name is "bob";
|
||||
the validator rejects any hero name containing "bob".
|
||||
Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
|
||||
|
||||
The `forbiddenNamevalidator` factory returns the configured validator function.
|
||||
The `forbiddenNameValidator` factory returns the configured validator function.
|
||||
That function takes an Angular control object and returns _either_
|
||||
null if the control value is valid _or_ a validation error object.
|
||||
The validation error object typically has a property whose name is the validation key ('forbiddenName')
|
||||
and whose value is an arbitrary dictionary of values that we could insert into an error message (`{name}`).
|
||||
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
|
||||
and whose value is an arbitrary dictionary of values that you could insert into an error message (`{name}`).
|
||||
|
||||
Learn more about validator functions in a _forthcoming_ chapter on custom form validation.#### Custom validation directive
|
||||
In the reactive forms component we added a configured `forbiddenNamevalidator`
|
||||
to the bottom of the `'name'` control's validator function list.
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts' region='name-validators'}
|
||||
{@a custom-validation-directive}
|
||||
### Custom validation directive
|
||||
In the reactive forms component, the `'name'` control's validator function list
|
||||
has a `forbiddenNameValidator` at the bottom.
|
||||
|
||||
In the template-driven component template, we add the selector (`forbiddenName`) of a custom _attribute directive_ to the name's input box
|
||||
and configured it to reject "bob".
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="name-validators" linenums="false">
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.html' region='name-input'}
|
||||
</code-example>
|
||||
|
||||
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNamevalidator`.
|
||||
In the _template-driven_ example, the `<input>` has the selector (`forbiddenName`)
|
||||
of a custom _attribute directive_, which rejects "bob".
|
||||
|
||||
Angular forms recognizes the directive's role in the validation process because the directive registers itself
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-input" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`.
|
||||
|
||||
Angular `forms` recognizes the directive's role in the validation process because the directive registers itself
|
||||
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives.
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts' region='directive-providers'}
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" linenums="false">
|
||||
|
||||
The rest of the directive is unremarkable and we present it here without further comment.
|
||||
</code-example>
|
||||
|
||||
{@example 'cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts' region='directive'}
|
||||
Here is the rest of the directive to help you get an idea of how it all comes together:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
See the [Attribute Directives](guide/attribute-directives) chapter.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
If you are familiar with Angular validations, you may have noticed
|
||||
that the custom validation directive is instantiated with `useExisting`
|
||||
rather than `useClass`. The registered validator must be _this instance_ of
|
||||
the `ForbiddenValidatorDirective`—the instance in the form with
|
||||
its `forbiddenName` property bound to “bob". If you were to replace
|
||||
`useExisting` with `useClass`, then you’d be registering a new class instance, one that
|
||||
doesn’t have a `forbiddenName`.
|
||||
|
||||
To see this in action, run the example and then type “bob” in the name of Hero Form 2.
|
||||
Notice that you get a validation error. Now change from `useExisting` to `useClass` and try again.
|
||||
This time, when you type “bob”, there's no "bob" error message.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
For more information on attaching behavior to elements,
|
||||
see [Attribute Directives](guide/attribute-directives).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
{@a testing}
|
||||
## Testing Considerations
|
||||
|
||||
We can write _isolated unit tests_ of validation and control logic in _Reactive Forms_.
|
||||
You can write _isolated unit tests_ of validation and control logic in _Reactive Forms_.
|
||||
|
||||
_Isolated unit tests_ probe the component class directly, independent of its
|
||||
interactions with its template, the DOM, other dependencies, or Angular itself.
|
||||
@ -476,11 +643,12 @@ interactions with its template, the DOM, other dependencies, or Angular itself.
|
||||
Such tests have minimal setup, are quick to write, and easy to maintain.
|
||||
They do not require the `Angular TestBed` or asynchronous testing practices.
|
||||
|
||||
That's not possible with _Template-driven_ forms.
|
||||
That's not possible with _template-driven_ forms.
|
||||
The template-driven approach relies on Angular to produce the control model and
|
||||
to derive validation rules from the HTML validation attributes.
|
||||
You must use the `Angular TestBed` to create component test instances,
|
||||
write asynchronous tests, and interact with the DOM.
|
||||
|
||||
While not difficult, this takes more time, work and skill —
|
||||
factors that tend to diminish test code coverage and quality.
|
||||
While not difficult, this takes more time, work and
|
||||
skill—factors that tend to diminish test code
|
||||
coverage and quality.
|
@ -32,8 +32,15 @@ You can run the <live-example></live-example> in Plunker and download the code f
|
||||
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
|
||||
the form-specific directives and techniques described in this page.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You can also use a reactive (or model-driven) approach to build forms.
|
||||
However, this page focuses on template-driven forms.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
You can build almost any form with an Angular template—login forms, contact forms, and pretty much any business form.
|
||||
You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
|
||||
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
|
||||
@ -60,7 +67,14 @@ If you delete the hero name, the form displays a validation error in an attentio
|
||||
|
||||
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You can customize the colors and location of the "required" bar with standard CSS.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
You'll build this form in small steps:
|
||||
|
||||
1. Create the `Hero` model class.
|
||||
@ -89,7 +103,9 @@ and one optional field (`alterEgo`).
|
||||
In the `!{_appDir}` directory, create the following file with the given content:
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/hero.ts'}
|
||||
<code-example path="forms/src/app/hero.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
It's an anemic model with few requirements and no behavior. Perfect for the demo.
|
||||
|
||||
@ -100,6 +116,12 @@ The `alterEgo` is optional, so the constructor lets you omit it; note the questi
|
||||
|
||||
You can create a new hero like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (SkyDog)" region="SkyDog">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
## Create a form component
|
||||
|
||||
An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||
@ -107,6 +129,12 @@ to handle data and user interactions programmatically.
|
||||
Begin with the class because it states, in brief, what the hero editor can do.
|
||||
|
||||
Create the following file with the given content:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (v1)" region="v1">
|
||||
|
||||
</code-example>
|
||||
|
||||
There’s nothing special about this component, nothing form-specific,
|
||||
nothing to distinguish it from any component you've written before.
|
||||
|
||||
@ -114,7 +142,6 @@ Understanding this component requires only the Angular concepts covered in previ
|
||||
|
||||
- The code imports the Angular core library and the `Hero` model you just created.
|
||||
- The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `<hero-form>` tag.
|
||||
- The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`.
|
||||
- The `templateUrl` property points to a separate file for the template HTML.
|
||||
- You defined dummy data for `model` and `powers`, as befits a demo.
|
||||
Down the road, you can inject a data service to get and save real data
|
||||
@ -148,8 +175,13 @@ Because template-driven forms are in their own module, you need to add the `Form
|
||||
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
|
||||
{@example 'forms/ts/src/app/app.module.ts'}
|
||||
<code-example path="forms/src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
There are three changes:
|
||||
|
||||
@ -162,6 +194,10 @@ access to all of the template-driven forms features, including `ngModel`.
|
||||
the `HeroFormComponent` component visible throughout this module.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
If a component, directive, or pipe belongs to a module in the `imports` array, _don't_ re-declare it in the `declarations` array.
|
||||
@ -178,20 +214,31 @@ If you wrote it and it should belong to this module, _do_ declare it in th
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/app.component.ts'}
|
||||
<code-example path="forms/src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
There are only two changes.
|
||||
The `template` is simply the new element tag identified by the component's `selector` property.
|
||||
This displays the hero form when the application component is loaded.
|
||||
You've also dropped the `name` field from the class body.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Create an initial HTML form template
|
||||
|
||||
Create the template file with the following contents:
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/hero-form.component.html' region='start'}
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="start">
|
||||
|
||||
</code-example>
|
||||
|
||||
The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and
|
||||
opening them up for user input in input boxes.
|
||||
@ -223,6 +270,12 @@ the styles of any external library. Angular apps can use any CSS library or none
|
||||
|
||||
To add the stylesheet, open `index.html` and add the following link to the `<head>`:
|
||||
|
||||
|
||||
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (bootstrap)" region="bootstrap">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
## Add powers with _*ngFor_
|
||||
|
||||
The hero must choose one superpower from a fixed list of agency-approved powers.
|
||||
@ -233,6 +286,12 @@ form and bind the options to the `powers` list using `ngFor`,
|
||||
a technique seen previously in the [Displaying Data](guide/displaying-data) page.
|
||||
|
||||
Add the following HTML *immediately below* the *Alter Ego* group:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (powers)" region="powers">
|
||||
|
||||
</code-example>
|
||||
|
||||
This code repeats the `<option>` tag for each power in the list of powers.
|
||||
The `pow` template input variable is a different power in each iteration;
|
||||
you display its name using the interpolation syntax.
|
||||
@ -259,9 +318,22 @@ makes binding the form to the model easy.
|
||||
|
||||
Find the `<input>` tag for *Name* and update it like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You added a diagnostic interpolation after the input tag
|
||||
so you can see what you're doing.
|
||||
You left yourself a note to throw it away when you're done.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
|
||||
If you ran the app now and started typing in the *Name* input box,
|
||||
@ -276,18 +348,32 @@ At some point it might look like this:
|
||||
The diagnostic is evidence that values really are flowing from the input box to the model and
|
||||
back again.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
That's *two-way data binding*.
|
||||
For more information, see
|
||||
[Two-way binding with NgModel](guide/template-syntax) on the
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Notice that you also added a `name` attribute to the `<input>` tag and set it to "name",
|
||||
which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.
|
||||
Defining a `name` attribute is a requirement when using `[(ngModel)]` in combination with a form.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Internally, Angular creates `FormControl` instances and
|
||||
registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
|
||||
Each `FormControl` is registered under the name you assigned to the `name` attribute.
|
||||
Read more in [The NgForm directive](guide/forms#ngForm), later in this page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
|
||||
You'll ditch the input box binding message
|
||||
and add a new binding (at the top) to the component's `diagnostic` property.
|
||||
@ -295,9 +381,22 @@ Then you can confirm that two-way data binding works *for the entire hero model*
|
||||
|
||||
After revision, the core of the form should look like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModel-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
- Each input element has an `id` property that is used by the `label` element's `for` attribute
|
||||
to match the label to its input control.
|
||||
- Each input element has a `name` property that is required by Angular forms to register the control with the form.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
If you run the app now and change every hero model property, the form might display like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -403,6 +502,12 @@ You can leverage those class names to change the appearance of the control.
|
||||
|
||||
Temporarily add a [template reference variable](guide/template-syntax) named `spy`
|
||||
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now run the app and look at the _Name_ input box.
|
||||
Follow these steps *precisely*:
|
||||
|
||||
@ -442,9 +547,17 @@ You achieve this effect by adding these class definitions to a new `forms.css` f
|
||||
that you add to the project as a sibling to `index.html`:
|
||||
|
||||
|
||||
{@example 'forms/ts/src/forms.css'}
|
||||
<code-example path="forms/src/forms.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
Update the `<head>` of `index.html` to include this style sheet:
|
||||
|
||||
|
||||
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (styles)" region="styles">
|
||||
|
||||
</code-example>
|
||||
|
||||
## Show and hide validation error messages
|
||||
|
||||
You can improve the form. The _Name_ input box is required and clearing it turns the bar red.
|
||||
@ -462,15 +575,34 @@ To achieve this effect, extend the `<input>` tag with the following:
|
||||
- The "*is required*" message in a nearby `<div>`, which you'll display only if the control is invalid.
|
||||
|
||||
Here's an example of an error message added to the _name_ input box:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="name-with-error-msg">
|
||||
|
||||
</code-example>
|
||||
|
||||
You need a template reference variable to access the input box's Angular control from within the template.
|
||||
Here you created a variable called `name` and gave it the value "ngModel".
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](api/core/index/Directive-decorator) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
You control visibility of the name error message by binding properties of the `name`
|
||||
control to the message `<div>` element's `hidden` property.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
|
||||
|
||||
</code-example>
|
||||
|
||||
In this example, you hide the message when the control is valid or pristine;
|
||||
"pristine" means the user hasn't changed the value since it was displayed in this form.
|
||||
|
||||
@ -494,11 +626,15 @@ Now you'll add a new hero in this form.
|
||||
Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method.
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/hero-form.component.html' region='new-hero-button-no-reset'}
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-no-reset">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/hero-form.component.ts' region='new-hero'}
|
||||
<code-example path="forms/src/app/hero-form.component.ts" region="new-hero" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Run the application again, click the *New Hero* button, and the form clears.
|
||||
The *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties.
|
||||
@ -518,7 +654,9 @@ You have to clear all of the flags imperatively, which you can do
|
||||
by calling the form's `reset()` method after calling the `newHero()` method.
|
||||
|
||||
|
||||
{@example 'forms/ts/src/app/hero-form.component.html' region='new-hero-button-form-reset'}
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-form-reset">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now clicking "New Hero" resets both the form and its control flags.
|
||||
|
||||
@ -532,11 +670,20 @@ trigger a form submit because of its type (`type="submit"`).
|
||||
A "form submit" is useless at the moment.
|
||||
To make it useful, bind the form's `ngSubmit` event property
|
||||
to the hero form component's `onSubmit()` method:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="forms/ts/src/app/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
|
||||
</code-example>
|
||||
|
||||
You added something extra at the end. You defined a
|
||||
template reference variable, `#heroForm`, and initialized it with the value "ngForm".
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### The _NgForm_ directive
|
||||
|
||||
What `NgForm` directive?
|
||||
@ -549,9 +696,19 @@ It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
You'll bind the form's overall validity via
|
||||
the `heroForm` variable to the button's `disabled` property
|
||||
using an event binding. Here's the code:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (submit-button)" region="submit-button">
|
||||
|
||||
</code-example>
|
||||
|
||||
If you run the application now, you find that the button is enabled—although
|
||||
it doesn't do anything useful yet.
|
||||
|
||||
@ -571,24 +728,49 @@ For you, it was as simple as this:
|
||||
|
||||
Submitting the form isn't terribly dramatic at the moment.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
An unsurprising observation for a demo. To be honest,
|
||||
jazzing it up won't teach you anything new about forms.
|
||||
But this is an opportunity to exercise some of your newly won
|
||||
binding skills.
|
||||
If you aren't interested, skip to this page's conclusion.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
For a more strikingly visual effect,
|
||||
hide the data entry area and display something else.
|
||||
|
||||
Wrap the form in a `<div>` and bind
|
||||
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="edit-div">
|
||||
|
||||
</code-example>
|
||||
|
||||
The main form is visible from the start because the
|
||||
`submitted` property is false until you submit the form,
|
||||
as this fragment from the `HeroFormComponent` shows:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (submitted)" region="submitted">
|
||||
|
||||
</code-example>
|
||||
|
||||
When you click the *Submit* button, the `submitted` flag becomes true and the form disappears
|
||||
as planned.
|
||||
|
||||
Now the app needs to show something else while the form is in the submitted state.
|
||||
Add the following HTML below the `<div>` wrapper you just wrote:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="submitted">
|
||||
|
||||
</code-example>
|
||||
|
||||
There's the hero again, displayed read-only with interpolation bindings.
|
||||
This `<div>` appears only while the component is in the submitted state.
|
||||
|
||||
@ -685,47 +867,48 @@ The final project folder structure should look like this:
|
||||
|
||||
Here’s the code for the final version of the application:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="hero-form.component.ts">
|
||||
{@example 'forms/ts/src/app/hero-form.component.ts' region='final'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-form.component.ts" path="forms/src/app/hero-form.component.ts" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero-form.component.html">
|
||||
{@example 'forms/ts/src/app/hero-form.component.html' region='final'}
|
||||
</md-tab>
|
||||
<code-pane title="hero-form.component.html" path="forms/src/app/hero-form.component.html" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero.ts">
|
||||
{@example 'forms/ts/src/app/hero.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="hero.ts" path="forms/src/app/hero.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.module.ts">
|
||||
{@example 'forms/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app.module.ts" path="forms/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.component.ts">
|
||||
{@example 'forms/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app.component.ts" path="forms/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="main.ts">
|
||||
{@example 'forms/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="main.ts" path="forms/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="index.html">
|
||||
{@example 'forms/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-pane title="index.html" path="forms/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="forms.css">
|
||||
{@example 'forms/ts/src/forms.css'}
|
||||
</md-tab>
|
||||
<code-pane title="forms.css" path="forms/src/forms.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
@ -4,3 +4,5 @@ Glossary
|
||||
@intro
|
||||
Brief definitions of the most important words in the Angular vocabulary.
|
||||
|
||||
@description
|
||||
|
||||
|
@ -5,6 +5,7 @@ Hierarchical Dependency Injectors
|
||||
Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree.
|
||||
|
||||
@description
|
||||
|
||||
You learned the basics of Angular Dependency injection in the
|
||||
[Dependency Injection](guide/dependency-injection) guide.
|
||||
|
||||
@ -26,10 +27,17 @@ 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}
|
||||
|
||||
The component's injector may be a _proxy_ for an ancestor injector higher in the component tree.
|
||||
That's an implementation detail that improves efficiency.
|
||||
You won't notice the difference and
|
||||
your mental model should be that every component has its own injector.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Consider this guide's variation on the Tour of Heroes application.
|
||||
At the top is the `AppComponent` which has some sub-components.
|
||||
One of them is the `HeroesListComponent`.
|
||||
@ -49,9 +57,16 @@ If that injector can't satisfy the request, it passes it along to *its* parent i
|
||||
The requests keep bubbling up until Angular finds an injector that can handle the request or runs out of ancestor injectors.
|
||||
If it runs out of ancestors, Angular throws an error.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You can cap the bubbling. An intermediate component can declare that it is the "host" component.
|
||||
The hunt for providers will climb no higher than the injector for that host component.
|
||||
This is a topic for another day.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Re-providing a service at different levels
|
||||
|
||||
You can re-register a provider for a particular dependency token at multiple levels of the injector tree.
|
||||
@ -83,6 +98,12 @@ If you later modified the `VillainsService`, you could break something in a hero
|
||||
That's not supposed to happen but providing the service in the root `AppModule` creates that risk.
|
||||
|
||||
Instead, provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/villains-list.component.ts" linenums="false" title="src/app/villains-list.component.ts (metadata)" region="metadata">
|
||||
|
||||
</code-example>
|
||||
|
||||
By providing `VillainsService` in the `VillainsListComponent` metadata and nowhere else,
|
||||
the service becomes available only in the `VillainsListComponent` and its sub-component tree.
|
||||
It's still a singleton, but it's a singleton that exist solely in the _villain_ domain.
|
||||
@ -102,6 +123,7 @@ To open a hero's tax return, the preparer clicks on a hero name, which opens a c
|
||||
Each selected hero tax return opens in its own component and multiple returns can be open at the same time.
|
||||
|
||||
Each tax return component has the following characteristics:
|
||||
|
||||
* Is its own tax return editing session.
|
||||
* Can change a tax return without affecting a return in another component.
|
||||
* Has the ability to save the changes to its tax return or cancel them.
|
||||
@ -120,12 +142,16 @@ It caches a single `HeroTaxReturn`, tracks changes to that return, and can save
|
||||
It also delegates to the application-wide singleton `HeroService`, which it gets by injection.
|
||||
|
||||
|
||||
{@example 'hierarchical-dependency-injection/ts/src/app/hero-tax-return.service.ts'}
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here is the `HeroTaxReturnComponent` that makes use of it.
|
||||
|
||||
|
||||
{@example 'hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts'}
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The _tax-return-to-edit_ arrives via the input property which is implemented with getters and setters.
|
||||
The setter initializes the component's own instance of the `HeroTaxReturnService` with the incoming return.
|
||||
@ -138,13 +164,26 @@ Each component would overwrite the tax return that belonged to another hero.
|
||||
What a mess!
|
||||
|
||||
Look closely at the metadata for the `HeroTaxReturnComponent`. Notice the `providers` property.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" linenums="false" title="src/app/hero-tax-return.component.ts (providers)" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `HeroTaxReturnComponent` has its own provider of the `HeroTaxReturnService`.
|
||||
Recall that every component _instance_ has its own injector.
|
||||
Providing the service at the component level ensures that _every_ instance of the component gets its own, private instance of the service.
|
||||
No tax return overwriting. No mess.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation.
|
||||
You can review it and download it from the <live-example></live-example>.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Scenario: specialized providers
|
||||
|
||||
Another reason to re-provide a service is to substitute a _more specialized_ implementation of that service,
|
||||
@ -176,5 +215,11 @@ its injector produces an instance of `Car` resolved by injector (C) with an `Eng
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The code for this _cars_ scenario is in the `car.components.ts` and `car.services.ts` files of the sample
|
||||
which you can review and download from the <live-example></live-example>.
|
||||
which you can review and download from the <live-example></live-example>.
|
||||
|
||||
~~~
|
||||
|
||||
|
@ -40,8 +40,15 @@ This page describes the _i18n_ tools available to assist translation of componen
|
||||
into multiple languages.
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Practitioners of _internationalization_ refer to a translatable text as a "_message_".
|
||||
This page uses the words "_text_" and "_message_" interchangably and in the combination, "_text message_".
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The _i18n_ template translation process has four phases:
|
||||
|
||||
1. Mark static text messages in your component templates for translation.
|
||||
@ -78,12 +85,16 @@ After translation, the compiler removes it.
|
||||
In the accompanying sample, an `<h1>` tag displays a simple English language greeting
|
||||
that you translate into Spanish:
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='greeting'}
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="greeting" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Add the `i18n` attribute to the tag to mark it for translation.
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-attribute'}
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Help the translator with a _description_ and _intent_
|
||||
|
||||
@ -92,7 +103,9 @@ need a description of the message.
|
||||
Assign a description to the i18n attribute:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-attribute-desc'}
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute-desc" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In order to deliver a correct translation, the translator may need to
|
||||
know your _intent_—the true _meaning_ of the text
|
||||
@ -101,7 +114,9 @@ In front of the description, add some contextual meaning to the assigned string,
|
||||
separating it from the description with the `|` character (`<meaning>|<description>`):
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-attribute-meaning'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-attribute-meaning" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
While all appearances of a message with the _same_ meaning have the _same_ translation,
|
||||
a message with *a variety of possible meanings* could have different translations.
|
||||
@ -119,12 +134,16 @@ Here are two techniques to try.
|
||||
(1) Wrap the text in an `<ng-container>` element. The `<ng-container>` is never renderered:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-ng-container'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-ng-container" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
(2) Wrap the text in a pair of HTML comments:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-with-comment'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-with-comment" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -134,7 +153,9 @@ Here are two techniques to try.
|
||||
You've added an image to your template. You care about accessibility too so you add a `title` attribute:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-title'}
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-title" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `title` attribute needs to be translated.
|
||||
Angular i18n support has more translation attributes in the form,`i18n-x`, where `x` is the
|
||||
@ -143,7 +164,9 @@ name of the attribute to translate.
|
||||
To translate the `title` on the `img` tag from the previous example, write:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-title-translate'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-title-translate" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can also assign a meaning and a description with the `i18n-x="<meaning>|<description>"` syntax.
|
||||
|
||||
@ -161,7 +184,9 @@ Other languages might express the _cardinality_ differently.
|
||||
Here's how you could mark up the component template to display the phrase appropriate to the number of wolves:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-plural'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-plural" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
* The first parameter is the key. It is bound to the component property (`wolves`)
|
||||
that determines the number of wolves.
|
||||
@ -185,6 +210,9 @@ You could keep this up for three, four, and every other number of wolves.
|
||||
Or you could specify the **`other`** category as a catch-all for any unmatched cardinality
|
||||
and write something like: `other {a wolf pack}`.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This syntax conforms to the
|
||||
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU Message Format</a>
|
||||
that derives from the
|
||||
@ -193,6 +221,10 @@ which specifies the
|
||||
<a href="http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules" target="_blank" title="Pluralization Rules">pluralization rules</a>.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a select}
|
||||
## Select among alternative texts
|
||||
The application displays different text depending upon whether the hero is male or female.
|
||||
@ -208,7 +240,9 @@ property, which outputs either an "m" or an "f".
|
||||
The message maps those values to the appropriate translation:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-select'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -235,7 +269,14 @@ Open a terminal window at the root of the application project and enter the `ng-
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Windows users may have to quote the command like this: `"./node_modules/.bin/ng-xi18n"`
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
By default, the tool generates a translation file named **`messages.xlf`** in the
|
||||
<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>.
|
||||
|
||||
@ -312,8 +353,15 @@ for the project structure to reflect your entire internationalization effort.
|
||||
|
||||
One approach is to dedicate a folder to localization and store related assets
|
||||
(for example, internationalization files) there.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Localization and internationalization are
|
||||
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.This cookbook follows that suggestion. It has a `locale` folder under the `src/`.
|
||||
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.
|
||||
|
||||
~~~
|
||||
|
||||
This cookbook follows that suggestion. It has a `locale` folder under the `src/`.
|
||||
Assets within the folder carry a filename extension that matches a language-culture code from a
|
||||
<a href="https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx" target="_blank">well-known codeset</a>.
|
||||
|
||||
@ -330,14 +378,18 @@ This sample file is easy to translate without a special editor or knowledge of S
|
||||
Open `messages.es.xlf` and find the first `<trans-unit>` section:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-hello'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
|
||||
|
||||
Using the _source_, _description_, and _meaning_ elements to guide your translation,
|
||||
replace the `<target/>` tag with the Spanish greeting:
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-hello'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -354,7 +406,9 @@ See the **[translation file maintenance discussion](guide/i18n#maintenance)**.
|
||||
Translate the other text nodes the same way:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-other-nodes'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-other-nodes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -373,13 +427,17 @@ In this example, you know the translation unit for the `select` must be just bel
|
||||
To translate a `plural`, translate its ICU format match values:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-plural'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-plural" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Translate _select_
|
||||
The `select` behaves a little differently. Here again is the ICU format message in the component template:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-select'}
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The extraction tool broke that into _two_ translation units.
|
||||
|
||||
@ -388,17 +446,23 @@ In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `
|
||||
Translate the text and leave the placeholder where it is.
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translate-select-1'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The second translation unit, immediately below the first one, contains the `select` message. Translate that.
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translate-select-2'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-2" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here they are together, after translation:
|
||||
|
||||
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-select'}
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-select" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<div class='l-main-content'>
|
||||
@ -416,34 +480,35 @@ time to incorporate that translation into the application.
|
||||
|
||||
When the previous steps finish, the sample app _and_ its translation file are as follows:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.component.html">
|
||||
{@example 'cb-i18n/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.html" path="cb-i18n/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'cb-i18n/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.ts" path="cb-i18n/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts">
|
||||
{@example 'cb-i18n/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts" path="cb-i18n/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/main.ts">
|
||||
{@example 'cb-i18n/ts/src/main.1.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/main.ts" path="cb-i18n/src/main.1.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/locale/messages.es.xlf">
|
||||
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/locale/messages.es.xlf" path="cb-i18n/src/locale/messages.es.xlf.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -483,7 +548,9 @@ Translation with the JIT compiler is a dynamic process of:
|
||||
|
||||
Open `index.html` and revise the launch script as follows:
|
||||
|
||||
{@example 'cb-i18n/ts/src/index.html' region='i18n'}
|
||||
<code-example path="cb-i18n/src/index.html" region="i18n" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In this sample, the user's language is hardcoded as a global `document.locale` variable
|
||||
in the `index.html`.
|
||||
@ -500,7 +567,9 @@ You'll need it to import the language translation file.
|
||||
SystemJS doesn't ship with a raw text plugin but it's easy to add.
|
||||
Create the following `systemjs-text-plugin.js` in the `src/` folder:
|
||||
|
||||
{@example 'cb-i18n/ts/src/systemjs-text-plugin.js'}
|
||||
<code-example path="cb-i18n/src/systemjs-text-plugin.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Create translation providers
|
||||
|
||||
@ -515,7 +584,9 @@ The `getTranslationProviders` function in the following `src/app/i18n-providers.
|
||||
creates those providers based on the user's _locale_
|
||||
and the corresponding translation file:
|
||||
|
||||
{@example 'cb-i18n/ts/src/app/i18n-providers.ts'}
|
||||
<code-example path="cb-i18n/src/app/i18n-providers.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
1. It gets the locale from the global `document.locale` variable that was set in `index.html`.
|
||||
|
||||
@ -542,7 +613,9 @@ You'll create an _options_ object with the translation providers from `getTransl
|
||||
and pass it to `bootstrapModule`.
|
||||
Open the `src/main.ts` and modify the bootstrap code as follows:
|
||||
|
||||
{@example 'cb-i18n/ts/src/main.ts'}
|
||||
<code-example path="cb-i18n/src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice that it waits for the `getTranslationProviders` promise to resolve before
|
||||
bootstrapping the app.
|
||||
@ -592,6 +665,9 @@ For this sample, the Spanish language command would be
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Windows users may have to quote the command:
|
||||
<code-example language="sh" class="code-shell">
|
||||
"./node_modules/.bin/ngc" --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
|
||||
@ -600,6 +676,10 @@ Windows users may have to quote the command:
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a maintenance}
|
||||
## Translation file maintenance and _id_ changes
|
||||
|
||||
|
@ -5,6 +5,7 @@ Documentation Overview
|
||||
How to read and use this documentation.
|
||||
|
||||
@description
|
||||
|
||||
This page describes the Angular documentation at a high level.
|
||||
If you're new to Angular, you may want to visit "[Learning Angular](guide/learning-angular)" first.
|
||||
|
||||
@ -13,6 +14,7 @@ If you're new to Angular, you may want to visit "[Learning Angular](guide/learni
|
||||
The documentation is divided into major thematic sections, each
|
||||
a collection of pages devoted to that theme.
|
||||
|
||||
|
||||
<table width="100%">
|
||||
|
||||
<col width="15%">
|
||||
|
@ -6,6 +6,7 @@ A suggested path through the documentation for Angular newcomers.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/intro/people.png" width="200px" height="152px" alt="Us" align="left" style="margin-left:-40px;margin-right:10px"> </img>
|
||||
</figure>
|
||||
@ -40,7 +41,13 @@ from small, single-purpose parts.
|
||||
|
||||
After reading the above sections, feel free to skip around among the other pages on this site.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Next Step
|
||||
|
||||
Try the [tutorial](tutorial "Tour of Heroes") if you're ready to start coding or
|
||||
visit the [Architecture](guide/architecture) page if you prefer to learn the basic concepts first.
|
||||
visit the [Architecture](guide/architecture) page if you prefer to learn the basic concepts first.
|
||||
|
||||
~~~
|
||||
|
||||
|
@ -6,11 +6,12 @@ Angular calls lifecycle hook methods on directives and components as it creates,
|
||||
|
||||
@description
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px"> </img>
|
||||
</figure>
|
||||
|
||||
A component has a lifecycle managed by Angular itself.
|
||||
A component has a lifecycle managed by Angular.
|
||||
|
||||
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.
|
||||
@ -24,17 +25,19 @@ A directive has the same set of lifecycle hooks, minus the hooks that are specif
|
||||
|
||||
{@a hooks-overview}
|
||||
|
||||
## Component lifecycle hooks
|
||||
## Component lifecycle hooks overview
|
||||
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.
|
||||
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`
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit()`
|
||||
that Angular calls shortly after creating the component:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/peek-a-boo.component.ts' region='ngOnInit'}
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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*.
|
||||
@ -75,7 +78,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngOnChanges
|
||||
<code>ngOnChanges()</code>
|
||||
</td>
|
||||
|
||||
|
||||
@ -83,7 +86,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
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.
|
||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||
|
||||
</td>
|
||||
|
||||
@ -94,7 +97,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngOnInit
|
||||
<code>ngOnInit()</code>
|
||||
</td>
|
||||
|
||||
|
||||
@ -102,7 +105,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
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`.
|
||||
Called _once_, after the _first_ `ngOnChanges()`.
|
||||
|
||||
</td>
|
||||
|
||||
@ -113,14 +116,14 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngDoCheck
|
||||
<code>ngDoCheck()</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
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`.
|
||||
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
||||
|
||||
</td>
|
||||
|
||||
@ -131,14 +134,14 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngAfterContentInit
|
||||
<code>ngAfterContentInit()</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Respond after Angular projects external content into the component's view.
|
||||
|
||||
Called _once_ after the first `NgDoCheck`.
|
||||
Called _once_ after the first `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
@ -151,14 +154,14 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngAfterContentChecked
|
||||
<code>ngAfterContentChecked()</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Respond after Angular checks the content projected into the component.
|
||||
|
||||
Called after the `ngAfterContentInit` and every subsequent `NgDoCheck`.
|
||||
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
@ -171,14 +174,14 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngAfterViewInit
|
||||
<code>ngAfterViewInit()</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Respond after Angular initializes the component's views and child views.
|
||||
|
||||
Called _once_ after the first `ngAfterContentChecked`.
|
||||
Called _once_ after the first `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
@ -191,14 +194,14 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngAfterViewChecked
|
||||
<code>ngAfterViewChecked()</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Respond after Angular checks the component's views and child views.
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked`.
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
@ -211,13 +214,13 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
<tr style=top>
|
||||
|
||||
<td>
|
||||
ngOnDestroy
|
||||
<code>ngOnDestroy</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Cleanup just before Angular destroys the directive/component.
|
||||
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||||
Unsubscribe Observables and detach event handlers to avoid memory leaks.
|
||||
|
||||
Called _just before_ Angular destroys the directive/component.
|
||||
|
||||
@ -233,19 +236,20 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
|
||||
{@a other-lifecycle-hooks}
|
||||
|
||||
## Other lifecycle hooks
|
||||
## Other Angular lifecycle hooks
|
||||
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
||||
|
||||
3rd party libraries might implement their hooks as well in order to give developers more
|
||||
control over how these libraries are used.
|
||||
|
||||
## Lifecycle exercises
|
||||
## Lifecycle examples
|
||||
|
||||
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
|
||||
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:
|
||||
@ -321,7 +325,7 @@ Here's a brief description of each exercise:
|
||||
|
||||
|
||||
<td>
|
||||
See how Angular calls the `ngOnChanges` hook with a `changes` object
|
||||
See how Angular calls the `ngOnChanges()` hook with a `changes` object
|
||||
every time one of the component input properties changes.
|
||||
Shows how to interpret the `changes` object.
|
||||
</td>
|
||||
@ -338,7 +342,7 @@ Here's a brief description of each exercise:
|
||||
|
||||
|
||||
<td>
|
||||
Implements an `ngDoCheck` method with custom change detection.
|
||||
Implements an `ngDoCheck()` method with custom change detection.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
</td>
|
||||
|
||||
@ -403,7 +407,7 @@ Here's a brief description of each exercise:
|
||||
|
||||
</table>
|
||||
|
||||
The remainder of this chapter discusses selected exercises in further detail.
|
||||
The remainder of this page discusses selected exercises in further detail.
|
||||
|
||||
|
||||
{@a peek-a-boo}
|
||||
@ -423,10 +427,17 @@ 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}
|
||||
|
||||
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.Had the user clicked the *Update Hero* button, the log would show another `OnChanges` and two more triplets of
|
||||
The log confirms that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
|
||||
~~~
|
||||
|
||||
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!
|
||||
Clearly these three hooks fire *often*. Keep the logic in these hooks as lean as possible!
|
||||
|
||||
The next examples focus on hook details.
|
||||
|
||||
@ -440,26 +451,37 @@ Go undercover with these two spy hooks to discover when an element is initialize
|
||||
This is the perfect infiltration job for a directive.
|
||||
The heroes will never know they're being watched.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit` and `ngOnDestroy` hooks
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit()` and `ngOnDestroy()` hooks
|
||||
that log messages to the parent via an injected `LoggerService`.
|
||||
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/spy.directive.ts' region='spy-directive'}
|
||||
<code-example path="lifecycle-hooks/src/app/spy.directive.ts" region="spy-directive" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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>`
|
||||
Here it is attached to the repeated hero `<div>`:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/spy.component.html' region='template'}
|
||||
<code-example path="lifecycle-hooks/src/app/spy.component.html" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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:
|
||||
@ -468,72 +490,98 @@ with an entry in the *Hook Log* as seen here:
|
||||
<img src='assets/images/devguide/lifecycle-hooks/spy-directive.gif' alt="Spy Directive"> </img>
|
||||
</figure>
|
||||
|
||||
Adding a hero results in a new hero `<div>`. The spy's `ngOnInit` logs that event.
|
||||
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 spy's `ngOnDestroy()` method reports its last moments.
|
||||
|
||||
The `ngOnInit` and `ngOnDestroy` methods have more vital roles to play in real applications.
|
||||
The `ngOnInit()` and `ngOnDestroy()` methods have more vital roles to play in real applications.
|
||||
|
||||
### OnInit
|
||||
{@a oninit}
|
||||
### _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
|
||||
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}
|
||||
|
||||
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.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
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) and [HTTP](guide/server-communication) chapter
|
||||
show how.
|
||||
An `ngOnInit()` is a good place for a component to fetch its initial data. The
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4) and [HTTP Client](guide/server-communication)
|
||||
guides 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.
|
||||
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.You can count on Angular to call the `ngOnInit` method _soon_ after creating the component.
|
||||
They'll have been set when `ngOnInit()` runs.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
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.
|
||||
|
||||
~~~
|
||||
|
||||
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.
|
||||
{@a ondestroy}
|
||||
### _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.
|
||||
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.
|
||||
|
||||
## OnChanges
|
||||
|
||||
Angular calls its `ngOnChanges` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
|
||||
{@a onchanges}
|
||||
## _OnChanges()_
|
||||
|
||||
Angular calls its `ngOnChanges()` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
This example monitors the `OnChanges` hook.
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/on-changes.component.ts' region='ng-on-changes'}
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="ng-on-changes" linenums="false">
|
||||
|
||||
The `ngOnChanges` method takes an object that maps each changed property name to a
|
||||
</code-example>
|
||||
|
||||
The `ngOnChanges()` method takes an object that maps each changed property name to a
|
||||
[SimpleChange](api/core/index/SimpleChange-class) 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`.
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/on-changes.component.ts' region='inputs'}
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="inputs" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The host `OnChangesParentComponent` binds to them like this:
|
||||
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/on-changes-parent.component.html' region='on-changes'}
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes-parent.component.html" region="on-changes">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the sample in action as the user makes changes.
|
||||
|
||||
@ -550,13 +598,25 @@ 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!
|
||||
|
||||
## DoCheck
|
||||
|
||||
|
||||
{@a docheck}
|
||||
## _DoCheck()_
|
||||
Use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
Use this method to detect a change that Angular overlooked.The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck` hook:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/do-check.component.ts' region='ng-do-check'}
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This code inspects certain _values-of-interest_, capturing and comparing their current state against previous values.
|
||||
Use this method to detect a change that Angular overlooked.
|
||||
|
||||
~~~
|
||||
|
||||
The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck()` hook:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/do-check.component.ts" region="ng-do-check" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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:
|
||||
|
||||
@ -564,73 +624,99 @@ so you can see how often `DoCheck` is called. The results are illuminating:
|
||||
<img src='assets/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck"> </img>
|
||||
</figure>
|
||||
|
||||
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.
|
||||
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.
|
||||
Mere mousing into another `<input>` 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.
|
||||
Clearly our implementation must be very lightweight or the user experience suffers.
|
||||
|
||||
|
||||
|
||||
{@a afterview}
|
||||
## AfterView
|
||||
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
||||
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:
|
||||
Here's a child view that displays a hero's name in an `<input>`:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='child-view'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="child-view" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `AfterViewComponent` displays this child view *within its template*:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='template'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="template" linenums="false">
|
||||
|
||||
The following hooks take action based on changing values *within the child view*
|
||||
</code-example>
|
||||
|
||||
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).
|
||||
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='hooks'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="hooks" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<div id='wait-a-tick'>
|
||||
|
||||
</div>
|
||||
|
||||
{@a wait-a-tick}
|
||||
### Abide by the unidirectional data flow rule
|
||||
The `doSomething` method updates the screen when the hero name exceeds 10 characters.
|
||||
The `doSomething()` method updates the screen when the hero name exceeds 10 characters.
|
||||
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='do-something'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="do-something" linenums="false">
|
||||
|
||||
Why does the `doSomething` method wait a tick before updating `comment`?
|
||||
</code-example>
|
||||
|
||||
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!).Here's *AfterView* in action
|
||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||
The `LoggerService.tick_then()` postpones the log update
|
||||
for one turn of the browser's JavaScript cycle and that's just long enough.
|
||||
Here's *AfterView* in action:
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView"> </img>
|
||||
</figure>
|
||||
|
||||
Notice that Angular frequently calls `AfterViewChecked`, often when there are no changes of interest.
|
||||
Notice that Angular frequently calls `AfterViewChecked()`, often when there are no changes of interest.
|
||||
Write lean hook methods to avoid performance problems.
|
||||
|
||||
|
||||
|
||||
{@a aftercontent}
|
||||
## AfterContent
|
||||
The *AfterContent* sample explores the `AfterContentInit` and `AfterContentChecked` hooks that Angular calls
|
||||
The *AfterContent* sample explores the `AfterContentInit()` and `AfterContentChecked()` hooks that Angular calls
|
||||
*after* Angular projects external content into the component.
|
||||
|
||||
|
||||
{@a content-projection}
|
||||
### 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}
|
||||
|
||||
AngularJS developers know this technique as *transclusion*.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Consider this variation on the [previous _AfterView_](guide/lifecycle-hooks#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.
|
||||
the `AfterContentComponent`'s parent. Here's the parent's template:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='parent-template'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="parent-template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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
|
||||
@ -638,7 +724,9 @@ into the component*.
|
||||
|
||||
Now look at the component's template:
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='template'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
It tells Angular where to insert that content.
|
||||
@ -648,10 +736,21 @@ In this case, the projected content is the `<my-child>` from the parent.
|
||||
</figure>
|
||||
|
||||
|
||||
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.### AfterContent hooks
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The telltale signs of *content projection* are twofold:
|
||||
- HTML between component element tags.
|
||||
- The presence of `<ng-content>` tags in the component's template.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a aftercontent-hooks}
|
||||
### AfterContent hooks
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks.
|
||||
The key difference is in the child component
|
||||
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.
|
||||
@ -659,19 +758,21 @@ 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
|
||||
The following *AfterContent* hooks take action based on changing values in a *content child*,
|
||||
which can only be reached by querying for them via the property decorated with
|
||||
[@ContentChild](api/core/index/ContentChild-decorator).
|
||||
|
||||
|
||||
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='hooks'}
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="hooks" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a no-unidirectional-flow-worries}
|
||||
### No unidirectional flow worries with _AfterContent..._
|
||||
### No unidirectional flow worries with _AfterContent_
|
||||
|
||||
This component's `doSomething` method update's the component's data-bound `comment` property immediately.
|
||||
This component's `doSomething()` method update's the component's data-bound `comment` property immediately.
|
||||
There's no [need to wait](guide/lifecycle-hooks#wait-a-tick).
|
||||
|
||||
Recall that Angular calls both *AfterContent* hooks before calling either of the *AfterView* hooks.
|
||||
|
@ -1,5 +1,6 @@
|
||||
@description
|
||||
|
||||
|
||||
<style>
|
||||
h4 {font-size: 17px !important; text-transform: none !important;}
|
||||
.syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
|
||||
|
1624
aio/content/guide/ngmodule-faq.md
Normal file
1624
aio/content/guide/ngmodule-faq.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ NgModules
|
||||
Define application modules with @NgModule.
|
||||
|
||||
@description
|
||||
|
||||
**NgModules** help organize an application into cohesive blocks of functionality.
|
||||
<!-- CF: "app" and "application" are used interchangeably throughout this page.
|
||||
I'm not sure what's appropriate, so I left them as is for now. -->
|
||||
@ -121,7 +122,9 @@ By convention, the *root module* class is called `AppModule` and it exists in a
|
||||
|
||||
The `AppModule` from the QuickStart seed on the [Setup](guide/setup) page is as minimal as possible:
|
||||
|
||||
{@example 'setup/ts/src/app/app.module.ts'}
|
||||
<code-example path="setup/src/app/app.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `@NgModule` decorator defines the metadata for the module.
|
||||
This page takes an intuitive approach to understanding the metadata and fills in details as it progresses.
|
||||
@ -137,7 +140,9 @@ the _root component_, the top of the app's rather bare component tree.
|
||||
|
||||
The example `AppComponent` simply displays a data-bound title:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.component.0.ts'}
|
||||
<code-example path="ngmodule/src/app/app.component.0.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Lastly, the `@NgModule.bootstrap` property identifies this `AppComponent` as the _bootstrap component_.
|
||||
When Angular launches the app, it places the HTML rendering of `AppComponent` in the DOM,
|
||||
@ -157,7 +162,9 @@ In the first, _dynamic_ option, the [Angular compiler](cookbook/ngmodule-faq)
|
||||
compiles the application in the browser and then launches the app.
|
||||
|
||||
|
||||
{@example 'ngmodule/ts/src/main.ts'}
|
||||
<code-example path="ngmodule/src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The samples in this page demonstrate the dynamic bootstrapping approach.
|
||||
|
||||
@ -177,7 +184,9 @@ The syntax for bootstrapping the pre-compiled `AppModuleNgFactory` is similar to
|
||||
the dynamic version that bootstraps the `AppModule` class.
|
||||
|
||||
|
||||
{@example 'ngmodule/ts/src/main-static.ts'}
|
||||
<code-example path="ngmodule/src/main-static.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Because the entire application was pre-compiled,
|
||||
Angular doesn't ship the Angular compiler to the browser and doesn't compile in the browser.
|
||||
@ -209,39 +218,53 @@ As the app evolves,
|
||||
the first addition is a `HighlightDirective`, an [attribute directive](guide/attribute-directives)
|
||||
that sets the background color of the attached element.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/highlight.directive.ts'}
|
||||
<code-example path="ngmodule/src/app/highlight.directive.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Update the `AppComponent` template to attach the directive to the title:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.component.1.ts' region='template'}
|
||||
<code-example path="ngmodule/src/app/app.component.1.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
If you ran the app now, Angular wouldn't recognize the `highlight` attribute and would ignore it.
|
||||
You must declare the directive in `AppModule`.
|
||||
|
||||
Import the `HighlightDirective` class and add it to the module's `declarations` like this:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='directive'}
|
||||
<code-example path="ngmodule/src/app/app.module.1.ts" region="directive" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Add a component
|
||||
|
||||
Refactor the title into its own `TitleComponent`.
|
||||
The component's template binds to the component's `title` and `subtitle` properties like this:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/title.component.html' region='v1'}
|
||||
<code-example path="ngmodule/src/app/title.component.html" region="v1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@example 'ngmodule/ts/src/app/title.component.ts' region='v1'}
|
||||
<code-example path="ngmodule/src/app/title.component.ts" region="v1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Rewrite the `AppComponent` to display the new `TitleComponent` in the `<app-title>` element,
|
||||
using an input binding to set the `subtitle`.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.component.1.ts'}
|
||||
<code-example path="ngmodule/src/app/app.component.1.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular won't recognize the `<app-title>` tag until you declare it in `AppModule`.
|
||||
Import the `TitleComponent` class and add it to the module's `declarations`:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='component'}
|
||||
<code-example path="ngmodule/src/app/app.module.1.ts" region="component" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -264,22 +287,30 @@ accessible through a user service.
|
||||
This sample application has a dummy implementation of such a `UserService`.
|
||||
|
||||
|
||||
{@example 'ngmodule/ts/src/app/user.service.ts'}
|
||||
<code-example path="ngmodule/src/app/user.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The sample application should display a welcome message to the logged-in user just below the application title.
|
||||
Update the `TitleComponent` template to show the welcome message below the application title.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/title.component.html'}
|
||||
<code-example path="ngmodule/src/app/title.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Update the `TitleComponent` class with a constructor that injects the `UserService`
|
||||
and sets the component's `user` property from the service.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/title.component.ts'}
|
||||
<code-example path="ngmodule/src/app/title.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You've defined and used the service. Now to _provide_ it for all components to use,
|
||||
add it to a `providers` property in the `AppModule` metadata:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='providers'}
|
||||
<code-example path="ngmodule/src/app/app.module.1.ts" region="providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -290,7 +321,9 @@ add it to a `providers` property in the `AppModule` metadata:
|
||||
In the revised `TitleComponent`, an `*ngIf` directive guards the message.
|
||||
There is no message if there is no user.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/title.component.html' region='ngIf'}
|
||||
<code-example path="ngmodule/src/app/title.component.html" region="ngIf" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Although `AppModule` doesn't declare `NgIf`, the application still compiles and runs.
|
||||
How can that be? The Angular compiler should either ignore or complain about unrecognized HTML.
|
||||
@ -298,16 +331,25 @@ How can that be? The Angular compiler should either ignore or complain about unr
|
||||
Angular does recognize `NgIf` because you imported it earlier.
|
||||
The initial version of `AppModule` imports `BrowserModule`.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.0.ts' region='imports'}
|
||||
<code-example path="ngmodule/src/app/app.module.0.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Importing `BrowserModule` made all of its public components, directives, and pipes visible
|
||||
to the component templates in `AppModule`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
More accurately, `NgIf` is declared in `CommonModule` from `@angular/common`.
|
||||
|
||||
`CommonModule` contributes many of the common directives that applications need, including `ngIf` and `ngFor`.
|
||||
|
||||
`BrowserModule` imports `CommonModule` and [re-exports](cookbook/ngmodule-faq) it.
|
||||
The net effect is that an importer of `BrowserModule` gets `CommonModule` directives automatically.Many familiar Angular directives don't belong to `CommonModule`.
|
||||
The net effect is that an importer of `BrowserModule` gets `CommonModule` directives automatically.
|
||||
|
||||
~~~
|
||||
|
||||
Many familiar Angular directives don't belong to `CommonModule`.
|
||||
For example, `NgModel` and `RouterLink` belong to Angular's `FormsModule` and `RouterModule` respectively.
|
||||
You must import those modules before you can use their directives.
|
||||
|
||||
@ -321,6 +363,9 @@ a form component that imports form support from the Angular `FormsModule`.
|
||||
The `ContactComponent` presents a "contact editor,"
|
||||
implemented with Angular forms in the [template-driven form](guide/forms) style.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Angular form styles
|
||||
|
||||
You can write Angular form components in
|
||||
@ -332,10 +377,16 @@ The following sample imports the `FormsModule` from `@angular/forms` because
|
||||
the `ContactComponent` is written in _template-driven_ style.
|
||||
Modules with components written in the _reactive_ style
|
||||
import the `ReactiveFormsModule`.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The `ContactComponent` selector matches an element named `<app-contact>`.
|
||||
Add an element with that name to the `AppComponent` template, just below the `<app-title>`:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.component.1b.ts' region='template'}
|
||||
<code-example path="ngmodule/src/app/app.component.1b.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Form components are often complex. The `ContactComponent` has its own `ContactService`
|
||||
and [custom pipe](guide/pipes) (called `Awesome`),
|
||||
@ -343,39 +394,40 @@ and an alternative version of the `HighlightDirective`.
|
||||
|
||||
To make it manageable, place all contact-related material in an `src/app/contact` folder
|
||||
and break the component into three constituent HTML, TypeScript, and css files:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/contact/contact.component.html">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.component.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/contact/contact.component.html" path="ngmodule/src/app/contact/contact.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/contact.component.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.component.3.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/contact.component.ts" path="ngmodule/src/app/contact/contact.component.3.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/contact.component.css">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.component.css'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/contact.component.css" path="ngmodule/src/app/contact/contact.component.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/contact.service.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.service.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/contact.service.ts" path="ngmodule/src/app/contact/contact.service.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/awesome.pipe.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/awesome.pipe.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/awesome.pipe.ts" path="ngmodule/src/app/contact/awesome.pipe.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/highlight.directive.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/highlight.directive.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/highlight.directive.ts" path="ngmodule/src/app/contact/highlight.directive.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
In the middle of the component template,
|
||||
notice the two-way data binding `[(ngModel)]`.
|
||||
@ -393,7 +445,9 @@ form features such as validation aren't yet available.
|
||||
|
||||
Add the `FormsModule` to the `AppModule` metadata's `imports` list.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='imports'}
|
||||
<code-example path="ngmodule/src/app/app.module.1.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now `[(ngModel)]` binding will work and the user input will be validated by Angular forms,
|
||||
once you declare the new component, pipe, and directive.
|
||||
@ -420,21 +474,32 @@ Components, directives, and pipes belong to _one module only_.
|
||||
The application won't compile until you declare the contact component, directive, and pipe.
|
||||
Update the `declarations` in the `AppModule` accordingly:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='declarations'}
|
||||
<code-example path="ngmodule/src/app/app.module.1.ts" region="declarations" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a import-name-conflict}
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
There are two directives with the same name, both called `HighlightDirective`.
|
||||
|
||||
To work around this, create an alias for the contact version using the `as` JavaScript import keyword.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1b.ts' region='import-alias'}
|
||||
<code-example path="ngmodule/src/app/app.module.1b.ts" region="import-alias" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This solves the immediate issue of referencing both directive _types_ in the same file but
|
||||
leaves another issue unresolved.
|
||||
You'll learn more about that issue later in this page, in [Resolve directive conflicts](guide/ngmodule#resolve-conflicts).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Provide the _ContactService_
|
||||
The `ContactComponent` displays contacts retrieved by the `ContactService`,
|
||||
which Angular injects into its constructor.
|
||||
@ -446,13 +511,18 @@ You want to share this service with other contact-related components that you'll
|
||||
|
||||
In this app, add `ContactService` to the `AppModule` metadata's `providers` list:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.1b.ts' region='providers'}
|
||||
<code-example path="ngmodule/src/app/app.module.1b.ts" region="providers" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now you can inject `ContactService` (like `UserService`) into any component in the application.
|
||||
|
||||
|
||||
{@a application-scoped-providers}
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Application-scoped providers
|
||||
The `ContactService` provider is _application_-scoped because Angular
|
||||
registers a module's `providers` with the application's *root injector*.
|
||||
@ -475,6 +545,10 @@ Now you can inject `ContactService` (like `UserService`) into any component in t
|
||||
|
||||
Read more in the [How do I restrict service scope to a module?](cookbook/ngmodule-faq) section
|
||||
of the [NgModule FAQs](cookbook/ngmodule-faq) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Run the app
|
||||
Everything is in place to run the application with its contact editor.
|
||||
|
||||
@ -553,19 +627,20 @@ An issue arose [earlier](guide/ngmodule#import-name-conflict) when you declared
|
||||
you already had a `HighlightDirective` class at the application level.
|
||||
|
||||
The selectors of the two directives both highlight the attached element with a different color.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/highlight.directive.ts">
|
||||
{@example 'ngmodule/ts/src/app/highlight.directive.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/highlight.directive.ts" path="ngmodule/src/app/highlight.directive.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/highlight.directive.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/highlight.directive.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/highlight.directive.ts" path="ngmodule/src/app/contact/highlight.directive.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Both directives are declared in this module so both directives are active.
|
||||
|
||||
@ -574,6 +649,9 @@ the directive that's declared later wins because its DOM changes overwrite the f
|
||||
In this case, the contact's `HighlightDirective` makes the application title text blue
|
||||
when it should stay gold.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The issue is that two different classes are trying to do the same thing.
|
||||
|
||||
It's OK to import the same directive class multiple times.
|
||||
@ -582,6 +660,10 @@ Angular removes duplicate classes and only registers one of them.
|
||||
But from Angular's perspective, two different classes, defined in different files, that have the same name
|
||||
are not duplicates. Angular keeps both directives and
|
||||
they take turns modifying the same HTML element.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
At least the app still compiles.
|
||||
If you define two different component classes with the same selector specifying the same element tag,
|
||||
the compiler reports an error. It can't insert two components in the same DOM location.
|
||||
@ -654,7 +736,9 @@ It's easy to refactor the contact material into a contact feature module.
|
||||
|
||||
Here's the new `ContactModule`:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.module.2.ts'}
|
||||
<code-example path="ngmodule/src/app/contact/contact.module.2.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
You copy from `AppModule` the contact-related import statements and `@NgModule` properties
|
||||
that concern the contact, and paste them into `ContactModule`.
|
||||
@ -693,19 +777,20 @@ Leave only the classes required at the application root level.
|
||||
Then import the `ContactModule` so the app can continue to display the exported `ContactComponent`.
|
||||
|
||||
Here's the refactored version of the `AppModule` along with the previous version.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.module.ts (v2)">
|
||||
{@example 'ngmodule/ts/src/app/app.module.2.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.module.ts (v2)" path="ngmodule/src/app/app.module.2.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts (v1)">
|
||||
{@example 'ngmodule/ts/src/app/app.module.1b.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts (v1)" path="ngmodule/src/app/app.module.1b.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### ImprovementsThere's a lot to like in the revised `AppModule`.
|
||||
* It does not change as the _Contact_ domain grows.
|
||||
@ -730,8 +815,15 @@ The Heroic Staffing Agency sample app has evolved.
|
||||
It has two more modules, one for managing the heroes on staff and another for matching crises to the heroes.
|
||||
Both modules are in the early stages of development.
|
||||
Their specifics aren't important to the story and this page doesn't discuss every line of code.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Examine and download the complete source for this version from
|
||||
the <live-example plnkr="pre-shared.3" img="devguide/ngmodule/v3-plunker.png">live example.</live-example>Some facets of the current application merit discussion are as follows:
|
||||
the <live-example plnkr="pre-shared.3" img="devguide/ngmodule/v3-plunker.png">live example.</live-example>
|
||||
|
||||
~~~
|
||||
|
||||
Some facets of the current application merit discussion are as follows:
|
||||
|
||||
* The app has three feature modules: Contact, Hero, and Crisis.
|
||||
* The Angular router helps users navigate among these modules.
|
||||
@ -743,19 +835,30 @@ the <live-example plnkr="pre-shared.3" img="devguide/ngmodule/v3-plunker.png">li
|
||||
The new `AppComponent` template has
|
||||
a title, three links, and a `<router-outlet>`.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.component.3.ts' region='template'}
|
||||
<code-example path="ngmodule/src/app/app.component.3.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `<app-contact>` element is gone; you're routing to the _Contact_ page now.
|
||||
|
||||
The `AppModule` has changed modestly:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.3.ts'}
|
||||
<code-example path="ngmodule/src/app/app.module.3.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Some file names bear a `.3` extension that indicates
|
||||
a difference with prior or future versions.
|
||||
The significant differences will be explained in due course.
|
||||
<!-- CF: Can you be more specific here? Are the differences explained later in this page or in another page? -->
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The module still imports `ContactModule` so that its routes and components are mounted when the app starts.
|
||||
|
||||
The module does _not_ import `HeroModule` or `CrisisModule`.
|
||||
@ -767,7 +870,9 @@ that handles the app's routing concerns.
|
||||
|
||||
### App routing
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app-routing.module.ts'}
|
||||
<code-example path="ngmodule/src/app/app-routing.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The router is the subject of the [Routing & Navigation](guide/router) page, so this section skips many of the details and
|
||||
concentrates on the intersection of NgModules and routing.
|
||||
@ -784,18 +889,29 @@ You'll get to that file in a moment.
|
||||
|
||||
The remaining two routes use lazy loading syntax to tell the router where to find the modules:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app-routing.module.ts' region='lazy-routes'}
|
||||
<code-example path="ngmodule/src/app/app-routing.module.ts" region="lazy-routes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
A lazy-loaded module location is a _string_, not a _type_.
|
||||
In this app, the string identifies both the module _file_ and the module _class_,
|
||||
the latter separated from the former by a `#`.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### RouterModule.forRoot
|
||||
|
||||
The `forRoot` static class method of the `RouterModule` with the provided configuration and
|
||||
added to the `imports` array provides the routing concerns for the module.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app-routing.module.ts' region='forRoot'}
|
||||
<code-example path="ngmodule/src/app/app-routing.module.ts" region="forRoot" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The returned `AppRoutingModule` class is a `Routing Module` containing both the `RouterModule` directives
|
||||
and the dependency-injection providers that produce a configured `Router`.
|
||||
@ -812,13 +928,17 @@ Never call `RouterModule.forRoot` in a feature-routing module.
|
||||
Back in the root `AppModule`, add the `AppRoutingModule` to its `imports` list,
|
||||
and the app is ready to navigate.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.3.ts' region='imports'}
|
||||
<code-example path="ngmodule/src/app/app.module.3.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Routing to a feature module
|
||||
The `src/app/contact` folder holds a new file, `contact-routing.module.ts`.
|
||||
It defines the `contact` route mentioned earlier and provides a `ContactRoutingModule` as follows:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/contact/contact-routing.module.ts' region='routing'}
|
||||
<code-example path="ngmodule/src/app/contact/contact-routing.module.ts" region="routing" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This time you pass the route list to the `forChild` method of the `RouterModule`.
|
||||
The route list is only responsible for providing additional routes and is intended for feature modules.
|
||||
@ -846,19 +966,20 @@ that has both shared [declarables](cookbook/ngmodule-faq) and services.
|
||||
~~~
|
||||
|
||||
`ContactModule` has changed in two small but important ways.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/contact/contact.module.3.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.module.3.ts' region='class'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/contact/contact.module.3.ts" path="ngmodule/src/app/contact/contact.module.3.ts" region="class">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/contact.module.2.ts">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.module.2.ts' region='class'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/contact.module.2.ts" path="ngmodule/src/app/contact/contact.module.2.ts" region="class">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
* It imports the `ContactRoutingModule` object from `contact-routing.module.ts`.
|
||||
* It no longer exports `ContactComponent`.
|
||||
@ -935,7 +1056,9 @@ In the next section, [Shared modules](guide/ngmodule#shared-module "Shared modul
|
||||
|
||||
The `HeroModule` is a feature module like any other.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/hero/hero.module.3.ts' region='class'}
|
||||
<code-example path="ngmodule/src/app/hero/hero.module.3.ts" region="class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
It imports the `FormsModule` because the `HeroDetailComponent` template binds with `[(ngModel)]`.
|
||||
It imports the `HeroRoutingModule` from `hero-routing.module.ts` just as `ContactModule` and `CrisisModule` do.
|
||||
@ -964,7 +1087,9 @@ and share them with the modules that need them.
|
||||
|
||||
Here is the `SharedModule`:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/shared/shared.module.ts'}
|
||||
<code-example path="ngmodule/src/app/shared/shared.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Note the following:
|
||||
* It imports the `CommonModule` because its component needs common directives.
|
||||
@ -1041,11 +1166,20 @@ Perform the following steps:
|
||||
|
||||
Most of this work is familiar. The interesting part is the `CoreModule`.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='v4'}
|
||||
<code-example path="ngmodule/src/app/core/core.module.ts" region="v4">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You're importing some extra symbols from the Angular core library that you're not using yet.
|
||||
They'll become relevant later in this page.The `@NgModule` metadata should be familiar.
|
||||
They'll become relevant later in this page.
|
||||
|
||||
~~~
|
||||
|
||||
The `@NgModule` metadata should be familiar.
|
||||
You declare the `TitleComponent` because this module owns it and you export it
|
||||
because `AppComponent` (which is in `AppModule`) displays the title in its template.
|
||||
`TitleComponent` needs the Angular `NgIf` directive that you import from `CommonModule`.
|
||||
@ -1054,6 +1188,9 @@ because `AppComponent` (which is in `AppModule`) displays the title in its templ
|
||||
making a singleton instance of the `UserService` available to any component that needs it,
|
||||
whether that component is eagerly or lazily loaded.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Why bother?
|
||||
This scenario is clearly contrived.
|
||||
The app is too small to worry about a single service file and a tiny, one-time component.
|
||||
@ -1079,25 +1216,30 @@ Their _providers_ aren't shared.
|
||||
We recommend collecting such single-use classes and hiding their details inside a `CoreModule`.
|
||||
A simplified root `AppModule` imports `CoreModule` in its capacity as orchestrator of the application as a whole.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Cleanup
|
||||
Having refactored to a `CoreModule` and a `SharedModule`, it's time to clean up the other modules.
|
||||
|
||||
### A trimmer _AppModule_
|
||||
|
||||
Here is the updated `AppModule` paired with version 3 for comparison:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.module.ts (v4)">
|
||||
{@example 'ngmodule/ts/src/app/app.module.ts' region='v4'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.module.ts (v4)" path="ngmodule/src/app/app.module.ts" region="v4">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts (v3)">
|
||||
{@example 'ngmodule/ts/src/app/app.module.3.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts (v3)" path="ngmodule/src/app/app.module.3.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
`AppModule` now has the following qualities:
|
||||
* A little smaller because many `src/app/root` classes have moved to other modules.
|
||||
@ -1107,19 +1249,20 @@ Here is the updated `AppModule` paired with version 3 for comparison:
|
||||
|
||||
### A trimmer _ContactModule_
|
||||
Here is the new `ContactModule` paired with the prior version:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/contact/contact.module.ts (v4)">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.module.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/contact/contact.module.ts (v4)" path="ngmodule/src/app/contact/contact.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/contact/contact.module.ts (v3)">
|
||||
{@example 'ngmodule/ts/src/app/contact/contact.module.3.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/contact/contact.module.ts (v3)" path="ngmodule/src/app/contact/contact.module.3.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Notice the following:
|
||||
* The `AwesomePipe` and `HighlightDirective` are gone.
|
||||
@ -1146,22 +1289,35 @@ a simple object with the following properties:
|
||||
* `providers`: the configured providers
|
||||
|
||||
The root `AppModule` imports the `CoreModule` and adds the `providers` to the `AppModule` providers.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
More precisely, Angular accumulates all imported providers before appending the items listed in `@NgModule.providers`.
|
||||
This sequence ensures that whatever you add explicitly to the `AppModule` providers takes precedence
|
||||
over the providers of imported modules.Add a `CoreModule.forRoot` method that configures the core `UserService`.
|
||||
over the providers of imported modules.
|
||||
|
||||
~~~
|
||||
|
||||
Add a `CoreModule.forRoot` method that configures the core `UserService`.
|
||||
|
||||
You've extended the core `UserService` with an optional, injected `UserServiceConfig`.
|
||||
If a `UserServiceConfig` exists, the `UserService` sets the user name from that config.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/core/user.service.ts' region='ctor'}
|
||||
<code-example path="ngmodule/src/app/core/user.service.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's `CoreModule.forRoot` that takes a `UserServiceConfig` object:
|
||||
|
||||
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='for-root'}
|
||||
<code-example path="ngmodule/src/app/core/core.module.ts" region="for-root" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Lastly, call it within the `imports` list of the `AppModule`.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/app.module.ts' region='import-for-root'}
|
||||
<code-example path="ngmodule/src/app/app.module.ts" region="import-for-root" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The app displays "Miss Marple" as the user instead of the default "Sherlock Holmes".
|
||||
|
||||
@ -1196,7 +1352,9 @@ It looks like it is supposed to go to a specific question/section within the pag
|
||||
You could hope that no developer makes that mistake.
|
||||
Or you can guard against it and fail fast by adding the following `CoreModule` constructor.
|
||||
|
||||
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='ctor'}
|
||||
<code-example path="ngmodule/src/app/core/core.module.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The constructor tells Angular to inject the `CoreModule` into itself.
|
||||
That seems dangerously circular.
|
||||
|
@ -7,6 +7,9 @@ Recommended npm packages, and how to specify package dependencies.
|
||||
@description
|
||||
Angular applications and Angular itself depend upon features and functionality provided by a variety of third-party packages.
|
||||
These packages are maintained and installed with the Node Package Manager (<a href="https://docs.npmjs.com/" target="_blank">npm</a>).
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Node.js and npm are essential to Angular development.
|
||||
|
||||
<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">
|
||||
@ -19,6 +22,10 @@ Older versions produce errors.
|
||||
Consider using [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.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
During [Setup](guide/setup), a <a href="https://docs.npmjs.com/files/package.json" target="_blank">package.json</a>
|
||||
file is installed with a comprehensive starter set of
|
||||
packages as specified in the `dependencies` and `devDependencies` sections.
|
||||
@ -26,7 +33,14 @@ packages as specified in the `dependencies` and `devDependencies` sections.
|
||||
You can use other packages but the packages in _this particular set_ work well together and include
|
||||
everything you need to build and run the sample applications in this series.
|
||||
|
||||
Note: A cookbook or guide page may require an additional library such as *jQuery*.You'll install more than you need for the QuickStart guide.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Note: A cookbook or guide page may require an additional library such as *jQuery*.
|
||||
|
||||
~~~
|
||||
|
||||
You'll install more than you need for the QuickStart guide.
|
||||
No worries!
|
||||
You only serve to the client those packages that the application actually requests.
|
||||
|
||||
@ -102,7 +116,14 @@ Install these polyfills using the npm packages that Angular lists in the *peerDe
|
||||
|
||||
You must list these packages in the `dependencies` section of your own `package.json`.
|
||||
|
||||
For background on this requirement, see [Why peerDependencies?](guide/npm-packages#why-peer-dependencies).***core-js***: Patches the global context (window) with essential features of ES2015 (ES6).
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
For background on this requirement, see [Why peerDependencies?](guide/npm-packages#why-peer-dependencies).
|
||||
|
||||
~~~
|
||||
|
||||
***core-js***: Patches the global context (window) with essential features of ES2015 (ES6).
|
||||
You may substitute an alternative polyfill that provides the same core APIs.
|
||||
When these APIs are implemented by the major browsers, this dependency will become unnecessary.
|
||||
|
||||
@ -205,9 +226,15 @@ They leave you in control of package and version resolution.
|
||||
|
||||
It is your responsibility to list all *peer dependency* packages **among your own *devDependencies***.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
#### The future of *peerDependencies*
|
||||
|
||||
The Angular polyfill dependencies are hard requirements. Currently, there is no way to make them optional.
|
||||
|
||||
However, there is an npm feature request for "optional peerDependencies," which would allow you to model this relationship better.
|
||||
When this feature request is implemented, Angular will switch from *peerDependencies* to *optionalPeerDependencies* for all polyfills.
|
||||
When this feature request is implemented, Angular will switch from *peerDependencies* to *optionalPeerDependencies* for all polyfills.
|
||||
|
||||
~~~
|
||||
|
||||
|
575
aio/content/guide/pipes.md
Normal file
575
aio/content/guide/pipes.md
Normal file
@ -0,0 +1,575 @@
|
||||
@title
|
||||
Pipes
|
||||
|
||||
@intro
|
||||
Pipes transform displayed values within a template.
|
||||
|
||||
@description
|
||||
|
||||
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, you could push their raw `toString` values directly to the view,
|
||||
but that rarely makes for a good user experience.
|
||||
For example, in most use cases, users prefer to see a date in a simple format like
|
||||
<samp>April 15, 1988</samp> rather than the 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 editing. You may notice that you
|
||||
desire many of the same transformations repeatedly, both within and across many applications.
|
||||
You can almost think of them as styles.
|
||||
In fact, you might like to apply them in your HTML templates as you do styles.
|
||||
|
||||
Introducing Angular pipes, a way to write display-value transformations that you can declare in your HTML.
|
||||
|
||||
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||
|
||||
## Using pipes
|
||||
|
||||
A pipe takes in data as input and transforms it to a desired output.
|
||||
In this page, you'll use pipes to transform a component's birthday property into
|
||||
a human-friendly date.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday1.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Focus on the component's template.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="hero-birthday-template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Inside the interpolation expression, you flow the component's `birthday` value through the
|
||||
[pipe operator](guide/template-syntax) ( | ) to the [Date pipe](api/common/index/DatePipe-pipe)
|
||||
function on the right. All pipes work this way.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `Date` and `Currency` pipes need the *ECMAScript Internationalization API*.
|
||||
Safari and other older browsers don't support it. You 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>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Built-in pipes
|
||||
Angular comes with a stock of pipes such as
|
||||
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
||||
They are all available for use in any template.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read more about these and many other built-in pipes in the [pipes topics](api/#!?query=pipe) of the
|
||||
[API Reference](api); filter for entries that include the word "pipe".
|
||||
|
||||
Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in the [Appendix](guide/pipes#no-filter-pipe) of this page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## Parameterizing a pipe
|
||||
|
||||
A pipe can accept any number of optional parameters to fine-tune its output.
|
||||
To add parameters to a pipe, follow the pipe name with a colon ( : ) and then the parameter value
|
||||
(such as `currency:'EUR'`). If the pipe accepts multiple parameters, separate the values with colons (such as `slice:1:5`)
|
||||
|
||||
Modify the birthday template to give the date pipe a format parameter.
|
||||
After formatting the hero's April 15th birthday, it renders as **<samp>04/15/88</samp>**:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="format-birthday" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The parameter value can be any valid template expression,
|
||||
(see the [Template expressions](guide/template-syntax) section of the
|
||||
[Template Syntax](guide/template-syntax) page)
|
||||
such as a string literal or a component property.
|
||||
In other words, you can control the format through a binding the same way you control the birthday value through a binding.
|
||||
|
||||
Write a second component that *binds* the pipe's format parameter
|
||||
to the component's `format` property. Here's the template for that component:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You 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'`).
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
As you click the button, the displayed date alternates between
|
||||
"**<samp>04/15/1988</samp>**" and
|
||||
"**<samp>Friday, April 15, 1988</samp>**".
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read more about the `DatePipe` format options in the [Date Pipe](api/common/index/DatePipe-pipe)
|
||||
API Reference page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
## Chaining pipes
|
||||
|
||||
You can chain pipes together in potentially useful combinations.
|
||||
In the following example, to display the birthday in uppercase,
|
||||
the birthday is chained to the `DatePipe` and on to the `UpperCasePipe`.
|
||||
The birthday displays as **<samp>APR 15, 1988</samp>**.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-birthday" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-parameter-birthday" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
## Custom pipes
|
||||
|
||||
You can write your own custom pipes.
|
||||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/exponential-strength.pipe.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This pipe definition reveals the following 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.
|
||||
Your pipe has one such parameter: the `exponent`.
|
||||
* To tell Angular that this is a pipe, you apply the
|
||||
`@Pipe` #{_decorator}, which you import from the core Angular library.
|
||||
* The `@Pipe` #{_decorator} allows you to define the
|
||||
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
|
||||
Your pipe's name is `exponentialStrength`.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### The *PipeTransform* interface
|
||||
|
||||
The `transform` method is essential to a pipe.
|
||||
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
||||
Technically, it's optional; Angular looks for and executes the `transform` method regardless.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Now you need a component to demonstrate the pipe.
|
||||
|
||||
<code-example path="pipes/src/app/power-booster.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/pipes/power-booster.png' alt="Power Booster"> </img>
|
||||
</figure>
|
||||
|
||||
Note the following:
|
||||
|
||||
* You use your custom pipe the same way you use built-in pipes.
|
||||
* You must include your pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
||||
|
||||
|
||||
~~~ {.callout.is-helpful}
|
||||
|
||||
|
||||
<header>
|
||||
Remember the
|
||||
</header>
|
||||
|
||||
You must manually register custom pipes.
|
||||
If you don't, Angular reports an error.
|
||||
In the previous example, you didn't list the `DatePipe` because all
|
||||
Angular built-in pipes are pre-registered.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
To probe the behavior in the <live-example></live-example>,
|
||||
change the value and optional exponent in the template.
|
||||
|
||||
## Power Boost Calculator
|
||||
|
||||
It's not much fun updating the template to test the custom pipe.
|
||||
Upgrade the example to a "Power Boost Calculator" that combines
|
||||
your pipe and two-way data binding with `ngModel`.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/power-boost-calculator.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
{@a change-detection}
|
||||
## Pipes and change detection
|
||||
|
||||
Angular looks for changes to data-bound values through a *change detection* process that runs after every DOM 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 you use a pipe.
|
||||
|
||||
### No pipe
|
||||
|
||||
In the next example, the component uses the default, aggressive change detection strategy to monitor and update
|
||||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The companion component class provides heroes, adds heroes into the #{_array}, and can reset the #{_array}.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="v1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can add heroes and Angular updates the display when you do.
|
||||
If you click the `reset` button, Angular replaces `heroes` with a new #{_array} of the original heroes and updates the display.
|
||||
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
|
||||
|
||||
### Flying-heroes pipe
|
||||
|
||||
Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-flying-heroes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the `FlyingHeroesPipe` implementation, which follows the pattern for custom pipes described earlier.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pure" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice the odd behavior in the <live-example></live-example>:
|
||||
when you add flying heroes, none of them are displayed under "Heroes who fly."
|
||||
|
||||
Although you're not getting the behavior you want, Angular isn't broken.
|
||||
It's just using a different change-detection algorithm that ignores changes to the list or any of its items.
|
||||
|
||||
Notice how a hero is added:
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="push" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You add the hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
||||
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
||||
|
||||
To fix that, create an #{_array} with the new hero appended and assign that to `heroes`.
|
||||
This time Angular detects that the #{_array} reference has changed.
|
||||
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero.
|
||||
|
||||
If you *mutate* the #{_array}, no pipe is invoked and the display isn't updated;
|
||||
if you *replace* the #{_array}, the pipe executes and the display is updated.
|
||||
The Flying Heroes application extends the
|
||||
code with checkbox switches and additional displays to help you experience these effects.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes"> </img>
|
||||
</figure>
|
||||
|
||||
Replacing the #{_array} is an efficient way to signal Angular to update the display.
|
||||
When do you replace the #{_array}? When the data change.
|
||||
That's an easy rule to follow in *this* example
|
||||
where the only way to change the data is by adding a hero.
|
||||
|
||||
More often, you 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 the component design to accommodate a pipe.
|
||||
Strive to keep the component class independent of the HTML.
|
||||
The component should be unaware of pipes.
|
||||
|
||||
For filtering flying heroes, consider an *impure pipe*.
|
||||
|
||||
## Pure and impure pipes
|
||||
|
||||
There are two categories of pipes: *pure* and *impure*.
|
||||
Pipes are pure by default. Every pipe you've seen so far has been pure.
|
||||
You make a pipe impure by setting its pure flag to false. You could make the `FlyingHeroesPipe`
|
||||
impure like this:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pipe-decorator" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Before doing that, understand the difference between pure and impure, starting with a pure pipe.
|
||||
|
||||
### Pure pipes
|
||||
|
||||
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`).
|
||||
|
||||
Angular ignores changes within (composite) objects.
|
||||
It won't call a pure pipe if you change an input month, add to an input #{_array}, or update an input object property.
|
||||
|
||||
This may seem restrictive but it's 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, a pure pipe is preferable when you can live with the change detection strategy.
|
||||
When you can't, you *can* use the impure pipe.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Or you 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 that's discussed later in this page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Impure pipes
|
||||
|
||||
Angular executes an *impure pipe* during every component change detection cycle.
|
||||
An impure pipe is called often, as often as every keystroke or mouse-move.
|
||||
|
||||
With that concern in mind, 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 the `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
||||
The complete implementation is as follows:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="FlyingHeroesImpurePipe" path="pipes/src/app/flying-heroes.pipe.ts" region="impure">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="FlyingHeroesPipe" path="pipes/src/app/flying-heroes.pipe.ts" region="pure">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</code-tabs>
|
||||
|
||||
You 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.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" linenums="false" title="src/app/flying-heroes.pipe.ts (filter)" region="filter">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes" linenums="false" title="src/app/flying-heroes (_region)" region="_region">
|
||||
|
||||
</code-example>
|
||||
|
||||
The only substantive change is the pipe in the template.
|
||||
You can confirm in the <live-example></live-example> that the _flying heroes_
|
||||
display updates as you add heroes, even when you mutate the `heroes` #{_array}.
|
||||
|
||||
<h3 id='async-pipe'>
|
||||
The impure <i> AsyncPipe </i>
|
||||
</h3>
|
||||
|
||||
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 values.
|
||||
|
||||
The `AsyncPipe` is also stateful.
|
||||
The pipe maintains a subscription to the input `#{_Observable}` and
|
||||
keeps delivering values from that `#{_Observable}` as they arrive.
|
||||
|
||||
This next example binds an `#{_Observable}` of message strings
|
||||
(`message#{_dollar}`) to a view with the `async` pipe.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-async-message.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The Async pipe saves boilerplate in the component code.
|
||||
The component doesn't have to subscribe to the async data source,
|
||||
extract the resolved values and expose them for binding,
|
||||
and have to unsubscribe when it's destroyed
|
||||
(a potent source of memory leaks).
|
||||
|
||||
### An impure caching pipe
|
||||
|
||||
Write one more impure pipe, a pipe that makes an HTTP request.
|
||||
|
||||
Remember that impure pipes are called every few milliseconds.
|
||||
If you're not careful, this pipe will punish the server with requests.
|
||||
|
||||
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
|
||||
The code<span if-docs="ts"> uses the [Angular http](guide/server-communication) client to retrieve data</span>:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/fetch-json.pipe.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now demonstrate it in a harness component whose template defines two bindings to this pipe,
|
||||
both requesting the heroes from the `heroes.json` file.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-list.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The component renders as the following:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/pipes/hero-list.png' alt="Hero List"> </img>
|
||||
</figure>
|
||||
|
||||
A breakpoint on the pipe's request for data shows the following:
|
||||
* 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*
|
||||
|
||||
In the previous code sample, the second `fetch` pipe binding 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
|
||||
</header>
|
||||
|
||||
The [JsonPipe](api/common/index/JsonPipe-pipe)
|
||||
provides an easy way to diagnosis a mysteriously failing data binding or
|
||||
inspect an object for future binding.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a pure-pipe-pure-fn}
|
||||
### 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 discussed earlier in this page are implemented with pure functions.
|
||||
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
||||
So are the `ExponentialStrengthPipe` and `FlyingHeroesPipe`.
|
||||
A few steps back, you reviewed the `FlyingHeroesImpurePipe`—an impure pipe with a pure function.
|
||||
|
||||
But always implement a *pure pipe* with a *pure function*.
|
||||
Otherwise, you'll see many console errors regarding expressions that changed after they were checked.
|
||||
|
||||
## Next steps
|
||||
|
||||
Pipes are a great way to encapsulate and share common display-value
|
||||
transformations. Use them like styles, dropping them
|
||||
into your template's expressions to enrich the appeal and usability
|
||||
of your views.
|
||||
|
||||
Explore Angular's inventory of built-in pipes in the [API Reference](api/#!?query=pipe).
|
||||
Try writing a custom pipe and perhaps contributing it to the community.
|
||||
|
||||
|
||||
{@a no-filter-pipe}
|
||||
|
||||
## Appendix: No *FilterPipe* or *OrderByPipe*
|
||||
|
||||
Angular doesn't provide pipes for filtering or sorting lists.
|
||||
Developers familiar with AngularJS know these as `filter` and `orderBy`.
|
||||
There are no equivalents in Angular.
|
||||
|
||||
This isn't an oversight. Angular doesn't offer such pipes because
|
||||
they perform poorly and prevent aggressive minification.
|
||||
Both `filter` and `orderBy` require parameters that reference object properties.
|
||||
Earlier in this page, you learned that such pipes must be [impure](guide/pipes#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.
|
||||
`filter` and `orderBy` have often been abused in AngularJS apps, leading to complaints that Angular itself is slow.
|
||||
That charge is fair in the indirect sense that AngularJS 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.
|
||||
The list might be sorted by hero `name` and `planet` of origin properties in the following way:
|
||||
<code-example language="html">
|
||||
<!-- NOT REAL CODE! -->
|
||||
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
|
||||
</code-example>
|
||||
|
||||
You identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
||||
(such as `hero['name']`).
|
||||
Unfortunately, aggressive minification manipulates the `Hero` property names so that `Hero.name` and `Hero.planet`
|
||||
become something like `Hero.a` and `Hero.b`. Clearly `hero['name']` doesn't work.
|
||||
|
||||
While some may not care to minify this aggressively,
|
||||
the Angular product shouldn't prevent anyone from minifying aggressively.
|
||||
Therefore, the Angular team decided that everything Angular provides will minify safely.
|
||||
|
||||
The Angular team and many experienced Angular developers strongly recommend moving
|
||||
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 don't apply to you, you can always create your own such pipes
|
||||
(similar to the [FlyingHeroesPipe](guide/pipes#impure-flying-heroes)) or find them in the community.
|
@ -159,7 +159,9 @@ You'll need a `hero` class and some hero data.
|
||||
Create a new `data-model.ts` file in the `app` directory and copy the content below into it.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/data-model.ts'}
|
||||
<code-example path="reactive-forms/src/app/data-model.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The file exports two classes and two constants. The `Address`
|
||||
and `Hero` classes define the application _data model_.
|
||||
@ -173,16 +175,16 @@ Make a new file called
|
||||
`hero-detail.component.ts` in the `app` directory and import these symbols:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-1.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now enter the `@Component` decorator that specifies the `HeroDetailComponent` metadata:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts' region='metadata'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="metadata" linenums="false">
|
||||
|
||||
The `moduleId: module.id` lets you use
|
||||
[component-relative paths](cookbook/component-relative-paths) in file URLs
|
||||
such as when specifying the `templateUrl`.
|
||||
</code-example>
|
||||
|
||||
Next, create an exported `HeroDetailComponent` class with a `FormControl`.
|
||||
`FormControl` is a directive that allows you to create and manage
|
||||
@ -190,7 +192,9 @@ a `FormControl` instance directly.
|
||||
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-1.component.ts' region='v1'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.ts" region="v1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here you are creating a `FormControl` called `name`.
|
||||
It will be bound in the template to an HTML `input` box for the hero name.
|
||||
@ -201,10 +205,17 @@ the initial data value, an array of validators, and an array of async validators
|
||||
This simple control doesn't have data or validators.
|
||||
In real apps, most form controls have both.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This guide touches only briefly on `Validators`. For an in-depth look at them,
|
||||
read the [Form Validation](cookbook/form-validation) cookbook.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
{@a create-template}
|
||||
## Create the template
|
||||
@ -212,19 +223,28 @@ read the [Form Validation](cookbook/form-validation) cookbook.
|
||||
Now create the component's template, `src/app/hero-detail.component.html`, with the following markup.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-1.component.html' region='simple-control'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.html" region="simple-control" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
To let Angular know that this is the input that you want to
|
||||
associate to the `name` `FormControl` in the class,
|
||||
you need `[formControl]="name"` in the template on the `<input>`.
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Disregard the `form-control` _CSS_ class. It belongs to the
|
||||
<a href="http://getbootstrap.com/" target="_blank" title="Bootstrap CSS">Bootstrap CSS library</a>,
|
||||
not Angular.
|
||||
It _styles_ the form but in no way impacts the logic of the form.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a import}
|
||||
## Import the _ReactiveFormsModule_
|
||||
|
||||
@ -239,7 +259,9 @@ the `ReactiveFormsModule` and the `HeroDetailComponent`.
|
||||
1. Add `HeroDetailComponent` to the declarations array.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/app.module.ts' region='v1'}
|
||||
<code-example path="reactive-forms/src/app/app.module.ts" region="v1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -248,7 +270,9 @@ the `ReactiveFormsModule` and the `HeroDetailComponent`.
|
||||
## Display the _HeroDetailComponent_
|
||||
Revise the `AppComponent` template so it displays the `HeroDetailComponent`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/app.component.1.ts'}
|
||||
<code-example path="reactive-forms/src/app/app.component.1.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -280,7 +304,9 @@ You used bootstrap CSS classes in the template HTML of both the `AppComponent` a
|
||||
Add the `bootstrap` _CSS stylesheet_ to the head of `index.html`:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/index.html' region='bootstrap'}
|
||||
<code-example path="reactive-forms/src/index.html" region="bootstrap" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now that everything is wired up, the browser should display something like this:
|
||||
|
||||
@ -298,18 +324,24 @@ This is simple to do. To add a `FormGroup`, add it to the imports section
|
||||
of `hero-detail.component.ts`:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-2.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In the class, wrap the `FormControl` in a `FormGroup` called `heroForm` as follows:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-2.component.ts' region='v2'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.ts" region="v2" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now that you've made changes in the class, they need to be reflected in the
|
||||
template. Update `hero-detail.component.html` by replacing it with the following.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-2.component.html' region='basic-form'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.html" region="basic-form" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice that now the single input is in a `form` element. The `novalidate`
|
||||
attribute in the `<form>` element prevents the browser
|
||||
@ -333,12 +365,19 @@ in the class. This syntax tells Angular to look for the parent
|
||||
`FormGroup`, in this case `heroForm`, and then _inside_ that group
|
||||
to look for a `FormControl` called `name`.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Disregard the `form-group` _CSS_ class. It belongs to the
|
||||
<a href="http://getbootstrap.com/" target="_blank" title="Bootstrap CSS">Bootstrap CSS library</a>,
|
||||
not Angular.
|
||||
Like the `form-control` class, it _styles_ the form
|
||||
but in no way impacts its logic.
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The form looks great. But does it work?
|
||||
When the user enters a name, where does the value go?
|
||||
|
||||
@ -351,7 +390,9 @@ To see the form model, add the following line after the
|
||||
closing `form` tag in the `hero-detail.component.html`:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3.component.html' region='form-value-json'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.html" region="form-value-json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `heroForm.value` returns the _form model_.
|
||||
Piping it through the `JsonPipe` renders the model as JSON in the browser:
|
||||
@ -380,7 +421,9 @@ clutter by handling details of control creation for you.
|
||||
|
||||
To use `FormBuilder`, you need to import it into `hero-detail.component.ts`:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3a.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3a.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Use it now to refactor the `HeroDetailComponent` into something that's a little easier to read and write,
|
||||
by following this plan:
|
||||
@ -392,7 +435,9 @@ by following this plan:
|
||||
|
||||
The revised `HeroDetailComponent` looks like this:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3a.component.ts' region='v3a'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3a.component.ts" region="v3a" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`FormBuilder.group` is a factory method that creates a `FormGroup`.
|
||||
`FormBuilder.group` takes an object whose keys and values are `FormControl` names and their definitions.
|
||||
@ -409,7 +454,9 @@ demonstrates the simplicity of using `Validators.required` in reactive forms.
|
||||
|
||||
First, import the `Validators` symbol.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
To make the `name` `FormControl` required, replace the `name`
|
||||
property in the `FormGroup` with an array.
|
||||
@ -417,14 +464,25 @@ The first item is the initial value for `name`;
|
||||
the second is the required validator, `Validators.required`.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3.component.ts' region='required'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.ts" region="required" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Reactive validators are simple, composable functions.
|
||||
Configuring validation is harder in template-driven forms where you must wrap validators in a directive. Update the diagnostic message at the bottom of the template to display the form's validity status.
|
||||
Configuring validation is harder in template-driven forms where you must wrap validators in a directive.
|
||||
|
||||
~~~
|
||||
|
||||
Update the diagnostic message at the bottom of the template to display the form's validity status.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-3.component.html' region='form-value-json'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.html" region="form-value-json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The browser displays the following:
|
||||
|
||||
@ -448,18 +506,24 @@ A hero has an address, a super power and sometimes a sidekick too.
|
||||
The address has a state property. The user will select a state with a `<select>` box and you'll populate
|
||||
the `<option>` elements with states. So import `states` from `data-model.ts`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-4.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Declare the `states` property and add some address `FormControls` to the `heroForm` as follows.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-4.component.ts' region='v4'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.ts" region="v4" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Then add corresponding markup in `hero-detail.component.html`
|
||||
within the `form` element.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-4.component.html'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -508,7 +572,9 @@ Let that be the parent `FormGroup`.
|
||||
Use `FormBuilder` again to create a child `FormGroup` that encapsulates the address controls;
|
||||
assign the result to a new `address` property of the parent `FormGroup`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-5.component.ts' region='v5'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.ts" region="v5" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You’ve changed the structure of the form controls in the component class;
|
||||
you must make corresponding adjustments to the component template.
|
||||
@ -521,7 +587,9 @@ To make this change visually obvious, slip in an `<h4>` header near the top with
|
||||
The new _address_ HTML looks like this:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-5.component.html' region='add-group'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="add-group" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
After these changes, the JSON output in the browser shows the revised _form model_
|
||||
with the nested address `FormGroup`:
|
||||
@ -546,12 +614,16 @@ page by adding the following to the template,
|
||||
immediately after the `{{form.value | json}}` interpolation as follows:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-5.component.html' region='inspect-value'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="inspect-value" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
To get the state of a `FormControl` that’s inside a `FormGroup`, use dot notation to path to the control.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-5.component.html' region='inspect-child-control'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="inspect-child-control" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can use this technique to display _any_ property of a `FormControl`
|
||||
such as one of the following:
|
||||
@ -692,12 +764,16 @@ In this `HeroDetailComponent`, the two models are quite close.
|
||||
|
||||
Recall the definition of `Hero` in `data-model.ts`:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/data-model.ts' region='model-classes'}
|
||||
<code-example path="reactive-forms/src/app/data-model.ts" region="model-classes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here, again, is the component's `FormGroup` definition.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-6.component.ts' region='hero-form-model'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="hero-form-model" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
There are two significant differences between these models:
|
||||
|
||||
@ -710,11 +786,15 @@ Nonetheless, the two models are pretty close in shape and you'll see in a moment
|
||||
to the _form model_ with the `patchValue` and `setValue` methods.
|
||||
Take a moment to refactor the _address_ `FormGroup` definition for brevity and clarity as follows:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='address-form-group'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="address-form-group" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Also be sure to update the import from `data-model` so you can reference the `Hero` and `Address` classes:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='import-address'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="import-address" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -730,7 +810,9 @@ With **`setValue`**, you assign _every_ form control value _at once_
|
||||
by passing in a data object whose properties exactly match the _form model_ behind the `FormGroup`.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='set-value'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="set-value" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `setValue` method checks the data object thoroughly before assigning any form control values.
|
||||
|
||||
@ -748,7 +830,9 @@ because its shape is similar to the component's `FormGroup` structure.
|
||||
You can only show the hero's first address and you must account for the possibility that the `hero` has no addresses at all.
|
||||
This explains the conditional setting of the `address` property in the data object argument:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='set-value-address'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="set-value-address" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### _patchValue_
|
||||
With **`patchValue`**, you can assign values to specific controls in a `FormGroup`
|
||||
@ -756,7 +840,9 @@ by supplying an object of key/value pairs for just the controls of interest.
|
||||
|
||||
This example sets only the form's `name` control.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-6.component.ts' region='patch-value'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="patch-value" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
With **`patchValue`** you have more flexibility to cope with wildly divergent data and form models.
|
||||
But unlike `setValue`, `patchValue` cannot check for missing control
|
||||
@ -773,7 +859,9 @@ When the user clicks on a hero, the list component passes the selected hero into
|
||||
by binding to its `hero` input property.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-list.component.1.html'}
|
||||
<code-example path="reactive-forms/src/app/hero-list.component.1.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In this approach, the value of `hero` in the `HeroDetailComponent` changes
|
||||
every time the user selects a new hero.
|
||||
@ -781,19 +869,25 @@ You should call _setValue_ in the [ngOnChanges](guide/lifecyle-hooks)
|
||||
hook, which Angular calls whenever the input `hero` property changes
|
||||
as the following steps demonstrate.
|
||||
|
||||
First, import the `ngOnChanges` and `import` symbol in `hero-detail.component.ts`.
|
||||
First, import the `OnChanges` and `Input` symbols in `hero-detail.component.ts`.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-6.component.ts' region='import-input'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="import-input" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Add the `hero` input property.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-6.component.ts' region='hero'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="hero" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Add the `ngOnChanges` method to the class as follows:
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='ngOnChanges-1'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="ngOnChanges-1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### _reset_ the form flags
|
||||
|
||||
@ -802,13 +896,17 @@ control values from the previous hero are cleared and
|
||||
status flags are restored to the _pristine_ state.
|
||||
You could call `reset` at the top of `ngOnChanges` like this.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='reset'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="reset" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `reset` method has an optional `state` value so you can reset the flags _and_ the control values at the same.
|
||||
Internally, `reset` passes the argument to `setValue`.
|
||||
A little refactoring and `ngOnChanges` becomes this:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='ngOnChanges'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="ngOnChanges" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -864,7 +962,9 @@ An Angular `FormArray` can display an array of _address_ `FormGroups`.
|
||||
|
||||
To get access to the `FormArray` class, import it into `hero-detail.component.ts`:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='imports'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
To _work_ with a `FormArray` you do the following:
|
||||
1. Define the items (`FormControls` or `FormGroups`) in the array.
|
||||
@ -877,7 +977,9 @@ let the user add or modify addresses (removing addresses is your homework).
|
||||
You’ll need to redefine the form model in the `HeroDetailComponent` constructor,
|
||||
which currently only displays the first hero address in an _address_ `FormGroup`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-7.component.ts' region='address-form-group'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="address-form-group" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### From _address_ to _secret lairs_
|
||||
|
||||
@ -885,7 +987,9 @@ From the user's point of view, heroes don't have _addresses_.
|
||||
_Addresses_ are for mere mortals. Heroes have _secret lairs_!
|
||||
Replace the _address_ `FormGroup` definition with a _secretLairs_ `FormArray` definition:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='secretLairs-form-array'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="secretLairs-form-array" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -913,7 +1017,9 @@ the parent `HeroListComponent` sets the `HeroListComponent.hero` input property
|
||||
The following `setAddresses` method replaces the _secretLairs_ `FormArray` with a new `FormArray`,
|
||||
initialized by an array of hero address `FormGroups`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='set-addresses'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="set-addresses" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice that you replace the previous `FormArray` with the **`FormGroup.setControl` method**, not with `setValue`.
|
||||
You're replacing a _control_, not the _value_ of a control.
|
||||
@ -926,7 +1032,9 @@ The `HeroDetailComponent` should be able to display, add, and remove items from
|
||||
Use the `FormGroup.get` method to acquire a reference to that `FormArray`.
|
||||
Wrap the expression in a `secretLairs` convenience property for clarity and re-use.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='get-secret-lairs'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="get-secret-lairs" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Display the _FormArray_
|
||||
|
||||
@ -950,22 +1058,30 @@ You'll re-use that index to compose a unique label for each address.
|
||||
|
||||
Here's the skeleton for the _secret lairs_ section of the HTML template:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.html' region='form-array-skeleton'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="form-array-skeleton" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's the complete template for the _secret lairs_ section:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.html' region='form-array'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="form-array">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Add a new lair to the _FormArray_
|
||||
|
||||
Add an `addLair` method that gets the _secretLairs_ `FormArray` and appends a new _address_ `FormGroup` to it.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='add-lair'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="add-lair" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Place a button on the form so the user can add a new _secret lair_ and wire it to the component's `addLair` method.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.html' region='add-lair'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="add-lair" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -1015,16 +1131,22 @@ You don't need to know much about RxJS `Observable` to monitor form control valu
|
||||
|
||||
Add the following method to log changes to the value of the _name_ `FormControl`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts' region='log-name-change'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="log-name-change" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Call it in the constructor, after creating the form.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail-8.component.ts' region='ctor'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="ctor" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `logNameChange` method pushes name-change values into a `nameChangeLog` array.
|
||||
Display that array at the bottom of the component template with this `*ngFor` binding:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.html' region='name-change-log'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.html" region="name-change-log" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Return to the browser, select a hero (e.g, "Magneta"), and start typing in the _name_ input box.
|
||||
You should see a new name in the log after each keystroke.
|
||||
@ -1054,15 +1176,22 @@ In this sample application, when the user submits the form,
|
||||
the `HeroDetailComponent` will pass an instance of the hero _data model_
|
||||
to a save method on the injected `HeroService`.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts' region='on-submit'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="on-submit" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This original `hero` had the pre-save values. The user's changes are still in the _form model_.
|
||||
So you create a new `hero` from a combination of original hero values (the `hero.id`)
|
||||
and deep copies of the changed form model values, using the `prepareSaveHero` helper.
|
||||
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts' region='prepare-save-hero'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="prepare-save-hero" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
**Address deep copy**
|
||||
|
||||
@ -1072,17 +1201,25 @@ as the lairs in the `formModel.secretLairs`.
|
||||
A user's subsequent changes to a lair street would mutate an address street in the `saveHero`.
|
||||
|
||||
The `prepareSaveHero` method makes copies of the form model's `secretLairs` objects so that can't happen.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Revert (cancel changes)
|
||||
The user cancels changes and reverts the form to the original state by pressing the _Revert_ button.
|
||||
|
||||
Reverting is easy. Simply re-execute the `ngOnChanges` method that built the _form model_ from the original, unchanged `hero` _data model_.
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts' region='revert'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="revert" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Buttons
|
||||
Add the "Save" and "Revert" buttons near the top of the component's template:
|
||||
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.html' region='buttons'}
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.html" region="buttons" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The buttons are disabled until the user "dirties" the form by changing a value in any of its form controls (`heroForm.dirty`).
|
||||
|
||||
@ -1111,49 +1248,50 @@ This page covered:
|
||||
{@a source-code}
|
||||
The key files of the final version are as follows:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'reactive-forms/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="reactive-forms/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts">
|
||||
{@example 'reactive-forms/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts" path="reactive-forms/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero-detail.component.ts">
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/hero-detail.component.ts" path="reactive-forms/src/app/hero-detail.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero-detail.component.html">
|
||||
{@example 'reactive-forms/ts/src/app/hero-detail.component.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/hero-detail.component.html" path="reactive-forms/src/app/hero-detail.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero-list.component.html">
|
||||
{@example 'reactive-forms/ts/src/app/hero-list.component.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/hero-list.component.html" path="reactive-forms/src/app/hero-list.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero-list.component.ts">
|
||||
{@example 'reactive-forms/ts/src/app/hero-list.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/hero-list.component.ts" path="reactive-forms/src/app/hero-list.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/data-model.ts">
|
||||
{@example 'reactive-forms/ts/src/app/data-model.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/data-model.ts" path="reactive-forms/src/app/data-model.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero.service.ts">
|
||||
{@example 'reactive-forms/ts/src/app/hero.service.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/hero.service.ts" path="reactive-forms/src/app/hero.service.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
You can download the complete source for all steps in this guide
|
||||
from the <live-example title="Reactive Forms Demo in Plunker">Reactive Forms Demo</live-example> live example.
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ Security
|
||||
Developing for content security in Angular applications.
|
||||
|
||||
@description
|
||||
|
||||
This page describes Angular's built-in
|
||||
protections against common web-application vulnerabilities and attacks such as cross-site
|
||||
scripting attacks. It doesn't cover application-level security, such as authentication (_Who is
|
||||
@ -93,7 +94,9 @@ The following template binds the value of `htmlSnippet`, once by interpolating i
|
||||
content, and once by binding it to the `innerHTML` property of an element:
|
||||
|
||||
|
||||
{@example 'security/ts/src/app/inner-html-binding.component.html'}
|
||||
<code-example path="security/src/app/inner-html-binding.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
Interpolated content is always escaped—the HTML isn't interpreted and the browser displays
|
||||
angle brackets in the element's text content.
|
||||
@ -101,6 +104,20 @@ angle brackets in the element's text content.
|
||||
For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`. But binding
|
||||
a value that an attacker might control into `innerHTML` normally causes an XSS
|
||||
vulnerability. For example, code contained in a `<script>` tag is executed:
|
||||
|
||||
|
||||
<code-example path="security/src/app/inner-html-binding.component.ts" linenums="false" title="src/app/inner-html-binding.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
|
||||
tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/security/binding-inner-html.png' alt='A screenshot showing interpolated and bound HTML values'> </img>
|
||||
</figure>
|
||||
|
||||
### Avoid direct use of the DOM APIs
|
||||
|
||||
The built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
|
||||
@ -136,6 +153,163 @@ the server. Don't generate Angular templates on the server side using a templati
|
||||
carries a high risk of introducing template-injection vulnerabilities.
|
||||
|
||||
|
||||
|
||||
<h2 id='bypass-security-apis'>
|
||||
Trusting safe values
|
||||
</h2>
|
||||
|
||||
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
|
||||
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
|
||||
situations, you can tell Angular that you inspected a value, checked how it was generated, and made
|
||||
sure it will always be secure. But *be careful*. If you trust a value that might be malicious, you
|
||||
are introducing a security vulnerability into your application. If in doubt, find a professional
|
||||
security reviewer.
|
||||
|
||||
To mark a value as trusted, inject `DomSanitizer` and call one of the
|
||||
following methods:
|
||||
|
||||
* `bypassSecurityTrustHtml`
|
||||
* `bypassSecurityTrustScript`
|
||||
* `bypassSecurityTrustStyle`
|
||||
* `bypassSecurityTrustUrl`
|
||||
* `bypassSecurityTrustResourceUrl`
|
||||
|
||||
Remember, whether a value is safe depends on context, so choose the right context for
|
||||
your intended use of the value. Imagine that the following template needs to bind a URL to a
|
||||
`javascript:alert(...)` call:
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.html" linenums="false" title="src/app/bypass-security.component.html (URL)" region="URL">
|
||||
|
||||
</code-example>
|
||||
|
||||
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
|
||||
in development mode, logs this action to the console. To prevent
|
||||
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.ts" linenums="false" title="src/app/bypass-security.component.ts (trust-url)" region="trust-url">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/security/bypass-security-component.png' alt='A screenshot showing an alert box created from a trusted URL'> </img>
|
||||
</figure>
|
||||
|
||||
If you need to convert user input into a trusted value, use a
|
||||
controller method. The following template allows users to enter a YouTube video ID and load the
|
||||
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
||||
context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users
|
||||
could execute. So call a method on the controller to construct a trusted video URL, which causes
|
||||
Angular to allow binding into `<iframe src>`:
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.html" linenums="false" title="src/app/bypass-security.component.html (iframe)" region="iframe">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.ts" linenums="false" title="src/app/bypass-security.component.ts (trust-video-url)" region="trust-video-url">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
<h2 id='http'>
|
||||
HTTP-level vulnerabilities
|
||||
</h2>
|
||||
|
||||
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
|
||||
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
|
||||
on the server side, but Angular provides helpers to make integration on the client side easier.
|
||||
|
||||
<h3 id='xsrf'>
|
||||
Cross-site request forgery
|
||||
</h3>
|
||||
|
||||
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
||||
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
|
||||
to the application's web server (such as `example-bank.com`).
|
||||
|
||||
Assume the user is logged into the application at `example-bank.com`.
|
||||
The user opens an email and clicks a link to `evil.com`, which opens in a new tab.
|
||||
|
||||
The `evil.com` page immediately sends a malicious request to `example-bank.com`.
|
||||
Perhaps it's a request to transfer money from the user's account to the attacker's account.
|
||||
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
|
||||
|
||||
If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate
|
||||
request from the application and the forged request from `evil.com`.
|
||||
|
||||
To prevent this, the application must ensure that a user request originates from the real
|
||||
application, not from a different site.
|
||||
The server and client must cooperate to thwart this attack.
|
||||
|
||||
In a common anti-XSRF technique, the application server sends a randomly
|
||||
generated authentication token in a cookie.
|
||||
The client code reads the cookie and adds a custom request header with the token in all subsequent requests.
|
||||
The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
|
||||
|
||||
This technique is effective because all browsers implement the _same origin policy_. Only code from the website
|
||||
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
|
||||
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
|
||||
|
||||
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
|
||||
The default `CookieXSRFStrategy` is turned on automatically.
|
||||
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
|
||||
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
|
||||
|
||||
The server must do its part by setting the
|
||||
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
|
||||
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
|
||||
|
||||
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
|
||||
cryptographically secure random number generator, and expire in a day or two.
|
||||
|
||||
Your server may use a different cookie or header name for this purpose.
|
||||
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
|
||||
<code-example language="typescript">
|
||||
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
|
||||
</code-example>
|
||||
|
||||
Or you can implement and provide an entirely custom `XSRFStrategy`:
|
||||
|
||||
<code-example language="typescript">
|
||||
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
|
||||
|
||||
</code-example>
|
||||
|
||||
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
||||
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29" target="_blank">Cross-Site Request Forgery (CSRF)</a> and
|
||||
<a href="https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet" target="_blank">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>.
|
||||
The Stanford University paper
|
||||
<a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail.
|
||||
|
||||
See also Dave Smith's easy-to-understand
|
||||
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
|
||||
|
||||
<h3 id='xssi'>
|
||||
Cross-site script inclusion (XSSI)
|
||||
</h3>
|
||||
|
||||
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
|
||||
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
|
||||
object constructors, and then including an API URL using a `<script>` tag.
|
||||
|
||||
This attack is only successful if the returned JSON is executable as JavaScript. Servers can
|
||||
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
|
||||
well-known string `")]}',\n"`.
|
||||
|
||||
Angular's `Http` library recognizes this convention and automatically strips the string
|
||||
`")]}',\n"` from all responses before further parsing.
|
||||
|
||||
For more information, see the XSSI section of this [Google web security blog
|
||||
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
|
||||
|
||||
|
||||
<h2 id='code-review'>
|
||||
Auditing Angular applications
|
||||
</h2>
|
||||
|
@ -5,9 +5,17 @@ HTTP Client
|
||||
Use an HTTP Client to talk to a remote server.
|
||||
|
||||
@description
|
||||
|
||||
[HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The [`WebSocket`](https://tools.ietf.org/html/rfc6455) protocol is another important communication technology;
|
||||
it isn't covered in this page.Modern browsers support two HTTP-based APIs:
|
||||
it isn't covered in this page.
|
||||
|
||||
~~~
|
||||
|
||||
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).
|
||||
@ -60,9 +68,16 @@ A <live-example>live example</live-example> illustrates these topics.
|
||||
# Demos
|
||||
|
||||
This page describes server communication with the help of the following demos:
|
||||
|
||||
- [The Tour of Heroes *HTTP* client demo](guide/server-communication#http-client).
|
||||
- [Fall back to !{_Promise}s](guide/server-communication#promises).
|
||||
- [Cross-Origin Requests: Wikipedia example](guide/server-communication#cors).
|
||||
- [More fun with Observables](guide/server-communication#more-observables).
|
||||
The root `AppComponent` orchestrates these demos:
|
||||
|
||||
{@example 'server-communication/ts/src/app/app.component.ts'}
|
||||
<code-example path="server-communication/src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
# Providing HTTP services
|
||||
@ -73,22 +88,42 @@ The !{_Angular_Http} client communicates with the server using a familiar HTTP r
|
||||
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read about providers in the [Dependency Injection](guide/dependency-injection) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Register providers by importing other NgModules to the root NgModule in `app.module.ts`.
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/app.module.1.ts'}
|
||||
<code-example path="server-communication/src/app/app.module.1.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Begin by importing the necessary members.
|
||||
The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
|
||||
|
||||
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `HttpModule` is necessary for making HTTP calls.
|
||||
Though the `JsonpModule` isn't necessary for plain HTTP,
|
||||
there is a JSONP demo later in this page.
|
||||
Loading its module now saves time.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
## The Tour of Heroes HTTP client demo
|
||||
|
||||
The first demo is a mini-version of the [tutorial](tutorial)'s "Tour of Heroes" (ToH) application.
|
||||
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
|
||||
The app uses the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
|
||||
The app uses the !{_Angular_Http} client to communicate via **XMLHttpRequest (XHR)**.
|
||||
|
||||
It works like this:
|
||||
<figure class='image-display'>
|
||||
@ -97,14 +132,16 @@ It works like this:
|
||||
|
||||
This demo has a single component, the `HeroListComponent`. Here's its template:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero-list.component.html'}
|
||||
<code-example path="server-communication/src/app/toh/hero-list.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
It presents the list of heroes with an `ngFor`.
|
||||
Below the list is an input box and an *Add Hero* button where you can enter the names of new heroes
|
||||
and add them to the database.
|
||||
A [template reference variable](guide/template-syntax), `newHeroName`, accesses the
|
||||
value of the input box in the `(click)` event binding.
|
||||
When the user clicks the button, that value passes to the component's `addHero` method and then
|
||||
When the user clicks the button, that value is passed to the component's `addHero` method and then
|
||||
the event binding clears it to make it ready for a new hero name.
|
||||
|
||||
Below the button is an area for an error message.
|
||||
@ -117,7 +154,9 @@ Below the button is an area for an error message.
|
||||
### The *HeroListComponent* class
|
||||
Here's the component class:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='component'}
|
||||
<code-example path="server-communication/src/app/toh/hero-list.component.ts" region="component">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular [injects](guide/dependency-injection) a `HeroService` into the constructor
|
||||
and the component calls that service to fetch and save data.
|
||||
@ -132,9 +171,23 @@ Although _at runtime_ the component requests heroes immediately after creation,
|
||||
you **don't** call the service's `get` method in the component's constructor.
|
||||
Instead, call it inside the `ngOnInit` [lifecycle hook](guide/lifecycle-hooks)
|
||||
and rely on Angular to call `ngOnInit` when it instantiates this component.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
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.With a basic understanding of the component, you're ready to look inside the `HeroService`.
|
||||
(especially calling a remote server) is handled in a separate method.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
|
||||
|
||||
Think of an `Observable` as a stream of events published by some source.
|
||||
To listen for events in this stream, ***subscribe*** to the `Observable`.
|
||||
These subscriptions 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).
|
||||
With a basic understanding of the component, you're ready to look inside the `HeroService`.
|
||||
|
||||
|
||||
{@a HeroService}
|
||||
@ -144,28 +197,45 @@ Components are easier to test and debug when their constructors are simple, and
|
||||
In many of the previous samples the app faked the interaction with the server by
|
||||
returning mock heroes in a service like this one:
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.ts' region='just-get-heroes'}
|
||||
<code-example path="toh-4/src/app/hero.service.ts" region="just-get-heroes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='v1'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="v1">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice that the !{_Angular_Http} client service is
|
||||
[injected](guide/dependency-injection) into the `HeroService` constructor.
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='ctor'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="ctor">
|
||||
|
||||
</code-example>
|
||||
|
||||
Look closely at how to call `!{_priv}http.get`:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='http-get'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="http-get" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You pass the resource URL to `get` and it calls the server which returns heroes.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The server returns heroes once you've set up the [in-memory web api](guide/server-communication#in-mem-web-api)
|
||||
described in the appendix below.
|
||||
Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='endpoint-json'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="endpoint-json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
@ -174,7 +244,9 @@ Alternatively, you can temporarily target a JSON file by changing the endpoint U
|
||||
## Process the response object
|
||||
Remember that the `getHeroes()` method used an `!{_priv}extractData()` helper method to map the `!{_priv}http.get` response object to heroes:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='extract-data'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="extract-data" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `response` object doesn't hold the data in a form the app can use directly.
|
||||
You must parse the response data into a JSON object.
|
||||
@ -182,6 +254,25 @@ You must parse the response data into a JSON object.
|
||||
|
||||
{@a parse-to-json}
|
||||
### Parse to JSON
|
||||
The response data are in JSON string form.
|
||||
The app must parse that string into JavaScript objects by calling `response.json()`.
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
This is not Angular's own design.
|
||||
The Angular HTTP client follows the Fetch 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}
|
||||
|
||||
Don't expect the decoded JSON to be the heroes !{_array} directly.
|
||||
This server always wraps JSON results in an object with a `data`
|
||||
property. You have to unwrap it to get the heroes.
|
||||
@ -189,6 +280,10 @@ 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}
|
||||
|
||||
Make no assumptions about the server API.
|
||||
@ -202,7 +297,7 @@ Not all servers return an object with a `data` property.
|
||||
{@a no-return-response-object}
|
||||
### Do not return the response object
|
||||
The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't
|
||||
be a best practice.
|
||||
follow best practices.
|
||||
The point of a data service is to hide the server interaction details from consumers.
|
||||
The component that calls the `HeroService` only wants heroes and is kept separate
|
||||
from getting them, the code dealing with where they come from, and the response object.
|
||||
@ -219,8 +314,14 @@ but only if it says something that the user can understand and act upon.
|
||||
This simple app conveys that idea, albeit imperfectly, in the way it handles a `getHeroes` error.
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='error-handling'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="error-handling" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
The `catch()` operator passes the error object from `http` to the `handleError()` method.
|
||||
The `handleError` method transforms the error into a developer-friendly message,
|
||||
logs it to the console, and returns the message in a new, failed Observable via `Observable.throw`.
|
||||
|
||||
|
||||
{@a subscribe}
|
||||
@ -233,12 +334,26 @@ This simple app conveys that idea, albeit imperfectly, in the way it handles a `
|
||||
</h3>
|
||||
|
||||
|
||||
Back in the `HeroListComponent`, in `!{_priv}heroService.getHeroes()`,
|
||||
the `subscribe` function has a second function parameter to handle the error message.
|
||||
It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template.
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='getHeroes'}
|
||||
|
||||
<code-example path="server-communication/src/app/toh/hero-list.component.ts" region="getHeroes" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value. Afterward, remember to restore it.
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
<a id="create"></a>
|
||||
<a id="update"></a>
|
||||
<a id="post"></a>
|
||||
## Send data to the server
|
||||
@ -246,11 +361,13 @@ Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value
|
||||
So far you've seen how to retrieve data from a remote location using an HTTP service.
|
||||
Now you'll add the ability to create new heroes and save them in the backend.
|
||||
|
||||
You'll write a method for the `HeroListComponent` to call, an `addHero()` method, that takes
|
||||
You'll write a method for the `HeroListComponent` to call, a `create()` method, that takes
|
||||
just the name of a new hero and returns an `Observable` of `Hero`. It begins like this:
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='addhero-sig'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="create-sig" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
To implement it, you must know the server's API for creating heroes.
|
||||
|
||||
@ -269,10 +386,12 @@ The server generates the `id` and returns 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 you know how the API works, implement `addHero()` as follows:
|
||||
Now that you know how the API works, implement `create()` as follows:
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='addhero'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" linenums="false" title="src/app/toh/hero.service.ts (create)" region="create">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -288,8 +407,12 @@ In the `headers` object, the `Content-Type` specifies that the body represents J
|
||||
As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](guide/server-communication#extract-data)
|
||||
from the response.
|
||||
|
||||
Back in the `HeroListComponent`, its `addHero()` method subscribes to the Observable returned by the service's `create()` method.
|
||||
When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user.
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='addHero'}
|
||||
<code-example path="server-communication/src/app/toh/hero-list.component.ts" region="addHero" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
<h2 id='cors'>
|
||||
@ -297,19 +420,33 @@ from the response.
|
||||
</h2>
|
||||
|
||||
You just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
|
||||
This is the most common approach for server communication, but it doesn't work in all scenarios.
|
||||
This is the most common approach to server communication, but it doesn't work in all scenarios.
|
||||
|
||||
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
|
||||
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}
|
||||
|
||||
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, enable them in the [request headers](guide/server-communication#headers).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
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}
|
||||
|
||||
This [Stack Overflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a search-wikipedia}
|
||||
### Search Wikipedia
|
||||
|
||||
@ -321,6 +458,198 @@ types in a text box:
|
||||
</figure>
|
||||
|
||||
|
||||
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter.
|
||||
The Angular `Jsonp` service both extends the `!{_Http}` service for JSONP and restricts you to `GET` requests.
|
||||
All other HTTP methods throw an error because `JSONP` is a read-only facility.
|
||||
|
||||
As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
||||
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wikipedia.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The constructor expects Angular to inject its `Jsonp` service, which
|
||||
is available because `JsonpModule` is in the root `@NgModule` `imports` array
|
||||
in `app.module.ts`.
|
||||
<a id="query-parameters"></a>### 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}
|
||||
|
||||
The `JSONP` technique requires that you 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.
|
||||
|
||||
~~~
|
||||
|
||||
If you're looking for articles with the word "Angular", you could construct the query string by hand and call `jsonp` like this:
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wikipedia.service.1.ts" region="query-string" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
In more parameterized examples you could build the query string with the Angular `URLSearchParams` helper:
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wikipedia.service.ts" region="search-parameters" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This time you call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object.
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wikipedia.service.ts" region="call-jsonp" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`Jsonp` flattens the `params` object into the same query string you saw earlier, sending the request
|
||||
to the server.
|
||||
<a id="wikicomponent"></a>### The WikiComponent
|
||||
|
||||
Now that you have a service that can query the Wikipedia API,
|
||||
turn your attention to the component (template and class) that takes user input and displays search results.
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wiki.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The template presents an `<input>` element *search box* to gather search terms from the user,
|
||||
and calls a `search(term)` method after each `keyup` event.
|
||||
|
||||
The component's `search(term)` method delegates to the `WikipediaService`, which returns an
|
||||
Observable !{_array} of string results (`Observable<string[]>`).
|
||||
Instead of subscribing to the Observable inside the component, as in the `HeroListComponent`,
|
||||
the app forwards the Observable result to the template (via `items`) where the `async` pipe
|
||||
in the `ngFor` handles the subscription. Read more about [async pipes](guide/pipes)
|
||||
in the [Pipes](guide/pipes) page.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The [async pipe](guide/pipes) is a good choice in read-only components
|
||||
where the component has no need to interact with the data.
|
||||
|
||||
`HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a wasteful-app}
|
||||
## A wasteful app
|
||||
|
||||
The 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
|
||||
Presently, the code calls the server after every keystroke.
|
||||
It should only make requests when the user *stops typing*.
|
||||
Here's how it will work after refactoring:
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250"> </img>
|
||||
</figure>
|
||||
|
||||
### 2. Search when the search term changes
|
||||
|
||||
Suppose a 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 arrives first? It's unpredictable.
|
||||
When there are multiple requests in-flight, the app should present the responses
|
||||
in the original request order.
|
||||
In this example, the app must always display the results for the *http* search
|
||||
no matter which response arrives first.
|
||||
|
||||
<a id="more-observables"></a>
|
||||
## More fun with Observables
|
||||
|
||||
You could make changes to the `WikipediaService`, but for a better
|
||||
user experience, create a copy of the `WikiComponent` instead and make it smarter,
|
||||
with the help of some nifty Observable operators.
|
||||
|
||||
Here's the `WikiSmartComponent`, shown next to the original `WikiComponent`:
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/wiki/wiki-smart.component.ts" path="server-communication/src/app/wiki/wiki-smart.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="src/app/wiki/wiki.component.ts" path="server-communication/src/app/wiki/wiki.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</code-tabs>
|
||||
|
||||
While the templates are virtually identical,
|
||||
there's a lot more RxJS in the "smart" version,
|
||||
starting with `debounceTime`, `distinctUntilChanged`, and `switchMap` operators,
|
||||
imported as [described above](guide/server-communication#rxjs-library).
|
||||
|
||||
|
||||
{@a create-stream}
|
||||
### Create a stream of search terms
|
||||
|
||||
The `WikiComponent` passes a new search term directly to the `WikipediaService` after every keystroke.
|
||||
|
||||
The `WikiSmartComponent` class turns the user's keystrokes into an Observable _stream of search terms_
|
||||
with the help of a `Subject`, which you import from RxJS:
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wiki-smart.component.ts" region="import-subject" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The component creates a `searchTermStream` as a `Subject` of type `string`.
|
||||
The `search()` method adds each new search box value to that stream via the subject's `next()` method.
|
||||
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wiki-smart.component.ts" region="subject" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a listen-for-search-terms}
|
||||
### Listen for search terms
|
||||
|
||||
The `WikiSmartComponent` listens to the *stream of search terms* and
|
||||
processes that stream _before_ calling the service.
|
||||
|
||||
<code-example path="server-communication/src/app/wiki/wiki-smart.component.ts" region="observable-operators" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
* <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md" target="_blank" title="debounce operator"><i>debounceTime</i></a>
|
||||
waits for the user to stop typing for at least 300 milliseconds.
|
||||
|
||||
* <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md" target="_blank" title="distinctUntilChanged operator"><i>distinctUntilChanged</i></a>
|
||||
ensures that the service is called only when the new search term is different from the previous search term.
|
||||
|
||||
* The <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md" target="_blank" title="switchMap operator"><i>switchMap</i></a>
|
||||
calls the `WikipediaService` with a fresh, debounced search term and coordinates the stream(s) of service response.
|
||||
|
||||
The role of `switchMap` is particularly important.
|
||||
The `WikipediaService` returns a separate Observable of string arrays (`Observable<string[]>`) for each search request.
|
||||
The user could issue multiple requests before a slow server has had time to reply,
|
||||
which means a backlog of response Observables could arrive at the client, at any moment, in any order.
|
||||
|
||||
The `switchMap` returns its own Observable that _combines_ all `WikipediaService` response Observables,
|
||||
re-arranges them in their original request order,
|
||||
and delivers to subscribers only the most recent search results.
|
||||
|
||||
|
||||
{@a xsrf}
|
||||
|
||||
@ -357,26 +686,39 @@ This sample creates a class that sets the default `Content-Type` header to JSON.
|
||||
It exports a constant with the necessary `RequestOptions` provider to simplify registration in `AppModule`.
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/default-request-options.service.ts'}
|
||||
<code-example path="server-communication/src/app/default-request-options.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Then it registers the provider in the root `AppModule`.
|
||||
|
||||
{@example 'server-communication/ts/src/app/app.module.ts' region='provide-default-request-options'}
|
||||
<code-example path="server-communication/src/app/app.module.ts" region="provide-default-request-options" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Remember to include this provider during setup when unit testing the app's HTTP services.After this change, the `header` option setting in `HeroService.addHero()` is no longer necessary,
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Remember to include this provider during setup when unit testing the app's HTTP services.
|
||||
|
||||
~~~
|
||||
|
||||
After this change, the `header` option setting in `HeroService.create()` is no longer necessary,
|
||||
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='addhero'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" linenums="false" title="src/app/toh/hero.service.ts (create)" region="create">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can confirm that `DefaultRequestOptions` is working by examing HTTP requests in the browser developer tools' network tab.
|
||||
If you're short-circuiting the server call with something like the [_in-memory web api_](guide/server-communication#in-mem-web-api),
|
||||
try commenting-out the `addHero` header option,
|
||||
try commenting-out the `create` header option,
|
||||
set a breakpoint on the POST call, and step through the request processing
|
||||
to verify the header is there.
|
||||
|
||||
Individual requests options, like this one, take precedence over the default `RequestOptions`.
|
||||
It might be wise to keep the `addHero` request header setting for extra safety.
|
||||
It might be wise to keep the `create` request header setting for extra safety.
|
||||
|
||||
|
||||
{@a in-mem-web-api}
|
||||
@ -384,17 +726,29 @@ It might be wise to keep the `addHero` request header setting for extra safety.
|
||||
## Appendix: Tour of Heroes _in-memory web api_
|
||||
|
||||
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
You 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.You'd set the endpoint to the JSON file like this:
|
||||
posed by top-level JSON arrays.
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='endpoint-json'}
|
||||
~~~
|
||||
|
||||
You'd set the endpoint to the JSON file like this:
|
||||
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="endpoint-json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The *get heroes* scenario would work,
|
||||
but since the app can't save changes to a JSON file, it needs a web API server.
|
||||
Because there isn't a real server for this demo,
|
||||
it substitutes the Angular _in-memory web api_ simulator for the actual XHR backend service.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The in-memory web api is not part of Angular _proper_.
|
||||
It's an optional service in its own
|
||||
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API"><i>angular-in-memory-web-api</i></a>
|
||||
@ -403,21 +757,60 @@ library installed with npm (see `package.json`).
|
||||
See the
|
||||
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md" target="_blank" title='In-memory Web API "README.md"'><i>README file</i></a>
|
||||
for configuration options, default behaviors, and limitations.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
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 for this sample, based on the JSON data:
|
||||
|
||||
{@example 'server-communication/ts/src/app/hero-data.ts'}
|
||||
<code-example path="server-communication/src/app/hero-data.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Ensure that the `HeroService` endpoint refers to the web API:
|
||||
|
||||
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='endpoint'}
|
||||
<code-example path="server-communication/src/app/toh/hero.service.ts" region="endpoint" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Finally, redirect client HTTP requests to the in-memory web API by
|
||||
adding the `InMemoryWebApiModule` to the `AppModule.imports` list.
|
||||
At the same time, call its `forRoot()` configuration method with the `HeroData` class.
|
||||
|
||||
<code-example path="server-communication/src/app/app.module.ts" region="in-mem-web-api" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### How it works
|
||||
|
||||
Angular's `http` service delegates the client/server communication tasks
|
||||
to a helper service called the `XHRBackend`.
|
||||
|
||||
Using standard Angular provider registration techniques, the `InMemoryWebApiModule`
|
||||
replaces the default `XHRBackend` service with its own in-memory alternative.
|
||||
At the same time, the `forRoot` method initializes the in-memory web API with the *seed data* from the mock hero dataset.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
|
||||
while setting the metadata for the root `AppModule`. Don't call it again.
|
||||
|
||||
~~~
|
||||
|
||||
Here is the final, revised version of <span ngio-ex>src/app/app.module.ts</span>, demonstrating these steps.
|
||||
|
||||
|
||||
<code-example path="server-communication/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that
|
||||
|
@ -45,10 +45,17 @@ The HTML `<title>` is in the document `<head>`, outside the body, making it inac
|
||||
|
||||
We could grab the browser `document` object and set the title manually.
|
||||
That's dirty and undermines our chances of running the app outside of a browser someday.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Running your app outside a browser means that you can take advantage of server-side
|
||||
pre-rendering for near-instant first app render times and for SEO. It means you could run from
|
||||
inside a Web Worker to improve your app's responsiveness by using multiple threads. And it
|
||||
means that you could run your app inside Electron.js or Windows Universal to deliver it to the desktop.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
## Use the *Title* service
|
||||
Fortunately, Angular bridges the gap by providing a `Title` service as part of the *Browser platform*.
|
||||
The [Title](api/platform-browser/index/Title-class) service is a simple class that provides an API
|
||||
@ -60,7 +67,9 @@ for getting and setting the current HTML document title:
|
||||
Let's inject the `Title` service into the root `AppComponent` and expose a bindable `setTitle` method that calls it:
|
||||
|
||||
|
||||
{@example 'cb-set-document-title/ts/src/app/app.component.ts' region='class'}
|
||||
<code-example path="cb-set-document-title/src/app/app.component.ts" region="class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
We bind that method to three anchor tags and, voilà!
|
||||
<figure class='image-display'>
|
||||
@ -69,24 +78,25 @@ We bind that method to three anchor tags and, voilà!
|
||||
|
||||
Here's the complete solution
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/main.ts">
|
||||
{@example 'cb-set-document-title/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/main.ts" path="cb-set-document-title/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts">
|
||||
{@example 'cb-set-document-title/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts" path="cb-set-document-title/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'cb-set-document-title/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.ts" path="cb-set-document-title/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
## Why we provide the *Title* service in *bootstrap*
|
||||
|
387
aio/content/guide/setup-systemjs-anatomy.md
Normal file
387
aio/content/guide/setup-systemjs-anatomy.md
Normal file
@ -0,0 +1,387 @@
|
||||
@title
|
||||
Setup Anatomy
|
||||
|
||||
@intro
|
||||
Inside the local development environment for SystemJS.
|
||||
|
||||
@description
|
||||
|
||||
The documentation [setup](guide/setup) procedures install a _lot_ of files.
|
||||
Most of them can be safely ignored.
|
||||
|
||||
Application files _inside the_ **`src/`** and **`e2e/`** folders matter most to developers.
|
||||
|
||||
Files _outside_ those folders condition the development environment.
|
||||
They rarely change and you may never view or modify them.
|
||||
If you do, this page can help you understand their purpose.
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
</style>
|
||||
|
||||
|
||||
<table width="100%">
|
||||
|
||||
<col width="10%">
|
||||
|
||||
</col>
|
||||
|
||||
|
||||
<col width="90%">
|
||||
|
||||
</col>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<th>
|
||||
File
|
||||
</th>
|
||||
|
||||
|
||||
<th>
|
||||
Purpose
|
||||
</th>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>src/app/</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Angular application files go here.
|
||||
|
||||
Ships with the "Hello Angular" sample's
|
||||
`AppComponent`, `AppModule`, a component unit test (`app.component.spec.ts`), and
|
||||
the bootstrap file, `main.ts`.
|
||||
|
||||
Try the <live-example name="setup">sample application</live-example>
|
||||
and the <live-example name="setup" plnkr="quickstart-specs">unit test</live-example>
|
||||
as _live examples_.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>e2e/</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
_End-to-end_ (e2e) tests of the application,
|
||||
written in Jasmine and run by the
|
||||
<a href="http://www.protractortest.org/" target="_blank" title="Protractor: end-to-end testing for Angular">protractor</a>
|
||||
e2e test runner.
|
||||
|
||||
Initialized with an e2e test for the "Hello Angular" sample.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>node_modules/</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The _npm_ packages installed with the `npm install` command.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code> .editorconfig<br>
|
||||
.git/<br>
|
||||
.gitignore<br>
|
||||
.travis.yml </code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Tooling configuration files and folders.
|
||||
Ignore them until you have a compelling reason to do otherwise.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>CHANGELOG.md</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The history of changes to the _QuickStart_ repository.
|
||||
Delete or ignore.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>favicon.ico</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The application icon that appears in the browser tab.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>index.html</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The application host page.
|
||||
It loads a few essential scripts in a prescribed order.
|
||||
Then it boots the application, placing the root `AppComponent`
|
||||
in the custom `<my-app>` body tag.
|
||||
|
||||
The same `index.html` satisfies all documentation application samples.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>karma.conf.js</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Configuration for the <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma unit test runner">karma</a>
|
||||
test runner described in the [Testing](guide/testing) guide.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>karma-test-shim.js</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Script to run <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma unit test runner">karma</a>
|
||||
with SystemJS as described in the [Testing](guide/testing) guide.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>non-essential-files.txt</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
A list of files that you can delete if you want to purge your setup of the
|
||||
original QuickStart Seed testing and git maintainence artifacts.
|
||||
See instructions in the optional
|
||||
[_Deleting non-essential files_](guide/setup) section.
|
||||
*Do this only in the beginning to avoid accidentally deleting your own tests and git setup!*
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>LICENSE</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The open source MIT license to use this setup code in your application.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>package.json</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Identifies `npm `package dependencies for the project.
|
||||
|
||||
Contains command scripts for running the application,
|
||||
running tests, and more. Enter `npm run` for a listing.
|
||||
<a href="https://github.com/angular/quickstart/blob/master/README.md#npm-scripts"
|
||||
target="_blank" title="npm scripts for Angular documentation samples">Read more</a> about them.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>protractor.config.js</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Configuration for the
|
||||
<a href="http://www.protractortest.org/" target="_blank" title="Protractor: end-to-end testing for Angular">protractor</a>
|
||||
_end-to-end_ (e2e) test runner.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>README.md</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Instruction for using this git repository in your project.
|
||||
Worth reading before deleting.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>styles.css</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Global styles for the application. Initialized with an `<h1>` style for the QuickStart demo.
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>systemjs<br>.config.js</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Tells the **SystemJS** module loader where to find modules
|
||||
referenced in JavaScript `import` statements. For example:
|
||||
<code-example language="ts">
|
||||
import { Component } from '@angular/core;
|
||||
</code-example>
|
||||
|
||||
Don't touch this file unless you are fully versed in SystemJS configuration.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>systemjs<br>.config.extras.js</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Optional extra SystemJS configuration.
|
||||
A way to add SystemJS mappings, such as for appliation _barrels_,
|
||||
without changing the original `system.config.js`.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>tsconfig.json</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
Tells the TypeScript compiler how to transpile TypeScript source files
|
||||
into JavaScript files that run in all modern browsers.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<code>tslint.json</code>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
The `npm` installed TypeScript linter inspects your TypeScript code
|
||||
and complains when you violate one of its rules.
|
||||
|
||||
This file defines linting rules favored by the
|
||||
[Angular style guide](guide/style-guide) and by the authors of the documentation.
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
|
@ -7,6 +7,7 @@ Install the Angular QuickStart seed for faster, more efficient development on yo
|
||||
@description
|
||||
|
||||
|
||||
|
||||
{@a develop-locally}
|
||||
## Setup a local development environment
|
||||
|
||||
@ -111,6 +112,67 @@ Open a terminal window in the project folder and enter the following commands fo
|
||||
{@a seed}
|
||||
|
||||
## What's in the QuickStart seed?
|
||||
|
||||
The **QuickStart seed** contains the same application as the QuickStart playground.
|
||||
But its 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](guide/setup-systemjs-anatomy).
|
||||
|
||||
|
||||
|
||||
{@a app-files}
|
||||
Focus on the following three TypeScript (`.ts`) files in the **`/src`** folder.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="setup/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="src/app/app.module.ts" path="setup/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane title="src/main.ts" path="setup/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</code-tabs>
|
||||
|
||||
All guides and cookbooks have _at least these core files_.
|
||||
Each file has a distinct purpose and evolves independently as the application grows.
|
||||
|
||||
@ -213,11 +275,32 @@ The following are all in `src/`
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Next Step
|
||||
|
||||
If you're new to Angular, we recommend staying on the [learning path](guide/learning-angular).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
<br></br><br></br>
|
||||
|
||||
{@a install-prerequisites}
|
||||
|
||||
## Appendix: !{_prereq}
|
||||
## Appendix: !{_prereq}
|
||||
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.
|
||||
|
@ -6,6 +6,7 @@ Angular has a powerful template engine that lets us easily manipulate the DOM st
|
||||
|
||||
@description
|
||||
|
||||
|
||||
<style>
|
||||
h4 {font-size: 17px !important; text-transform: none !important;}
|
||||
.syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
|
||||
@ -46,6 +47,12 @@ The directive then does whatever it's supposed to do with that host element and
|
||||
|
||||
Structural directives are easy to recognize.
|
||||
An asterisk (*) precedes the directive attribute name as in this example.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif)" region="ngif">
|
||||
|
||||
</code-example>
|
||||
|
||||
No brackets. No parentheses. Just `*ngIf` set to a string.
|
||||
|
||||
You'll learn in this guide that the [asterisk (*) is a convenience notation](guide/structural-directives#asterisk)
|
||||
@ -59,6 +66,12 @@ Three of the common, built-in structural directives—[NgIf](guide/template-
|
||||
[NgFor](guide/template-syntax), and [NgSwitch...](guide/template-syntax)—are
|
||||
described in the [_Template Syntax_](guide/template-syntax) guide and seen in samples throughout the Angular documentation.
|
||||
Here's an example of them in a template:
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (built-in)" region="built-in">
|
||||
|
||||
</code-example>
|
||||
|
||||
This guide won't repeat how to _use_ them. But it does explain _how they work_
|
||||
and how to [write your own](guide/structural-directives#unless) structural directive.
|
||||
|
||||
@ -85,6 +98,9 @@ you apply the directive to an element in the HTML template.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
There are two other kinds of Angular directives, described extensively elsewhere:
|
||||
(1) components and (2) attribute directives.
|
||||
|
||||
@ -100,12 +116,22 @@ You can apply many _attribute_ directives to one host element.
|
||||
You can [only apply one](guide/structural-directives#one-per-element) _structural_ directive to a host element.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a ngIf}
|
||||
|
||||
## NgIf case study
|
||||
|
||||
`NgIf` is the simplest structural directive and the easiest to understand.
|
||||
It takes a boolean expression and makes an entire chunk of the DOM appear or disappear.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-true)" region="ngif-true">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `ngIf` directive doesn't hide elements with CSS. It adds and removes them physically from the DOM.
|
||||
Confirm that fact using browser developer tools to inspect the DOM.
|
||||
|
||||
@ -124,6 +150,12 @@ The component and DOM nodes can be garbage-collected and free up memory.
|
||||
### Why *remove* rather than *hide*?
|
||||
|
||||
A directive could hide the unwanted paragraph instead by setting its `display` style to `none`.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (display-none)" region="display-none">
|
||||
|
||||
</code-example>
|
||||
|
||||
While invisible, the element remains in the DOM.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -162,10 +194,28 @@ Surely you noticed the asterisk (*) prefix to the directive name
|
||||
and wondered why it is necessary and what it does.
|
||||
|
||||
Here is `*ngIf` displaying the hero's name if `hero` exists.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (asterisk)" region="asterisk">
|
||||
|
||||
</code-example>
|
||||
|
||||
The asterisk is "syntactic sugar" for something a bit more complicated.
|
||||
Internally, Angular desugars it in two stages.
|
||||
First, it translates the `*ngIf="..."` into a template _attribute_, `template="ngIf ..."`, like this.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-template-attr)" region="ngif-template-attr">
|
||||
|
||||
</code-example>
|
||||
|
||||
Then it translates the template _attribute_ into a template _element_, wrapped around the host element, like this.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-template)" region="ngif-template">
|
||||
|
||||
</code-example>
|
||||
|
||||
* The `*ngIf` directive moved to the `<template>` element where it became a property binding,`[ngIf]`.
|
||||
* The rest of the `<div>`, including its class attribute, moved inside the `<template>` element.
|
||||
|
||||
@ -190,6 +240,12 @@ Angular transforms the `*ngFor` in similar fashion from asterisk (*) syntax thro
|
||||
template _attribute_ to template _element_.
|
||||
|
||||
Here's a full-featured application of `NgFor`, written all three ways:
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (inside-ngfor)" region="inside-ngfor">
|
||||
|
||||
</code-example>
|
||||
|
||||
This is manifestly more complicated than `ngIf` and rightly so.
|
||||
The `NgFor` directive has more features, both required and optional, than the `NgIf` shown in this guide.
|
||||
At minimum `NgFor` needs a looping variable (`let hero`) and a list (`heroes`).
|
||||
@ -293,6 +349,12 @@ One or both elements can be an [`ng-container`](guide/structural-directives#ngco
|
||||
The Angular _NgSwitch_ is actually a set of cooperating directives: `NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault`.
|
||||
|
||||
Here's an example.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngswitch)" region="ngswitch">
|
||||
|
||||
</code-example>
|
||||
|
||||
The switch value assigned to `NgSwitch` (`hero.emotion`) determines which
|
||||
(if any) of the switch cases are displayed.
|
||||
|
||||
@ -305,14 +367,33 @@ You attach them to elements using the asterisk (*) prefix notation.
|
||||
An `NgSwitchCase` displays its host element when its value matches the switch value.
|
||||
The `NgSwitchDefault` displays its host element when no sibling `NgSwitchCase` matches the switch value.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The element to which you apply a directive is its _host_ element.
|
||||
The `<happy-hero>` is the host element for the happy `*ngSwitchCase`.
|
||||
The `<unknown-hero>` is the host element for the `*ngSwitchDefault`.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
As with other structural directives, the `NgSwitchCase` and `NgSwitchDefault`
|
||||
can be desugared into the template _attribute_ form.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngswitch-template-attr)" region="ngswitch-template-attr">
|
||||
|
||||
</code-example>
|
||||
|
||||
That, in turn, can be desugared into the `<template>` element form.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngswitch-template)" region="ngswitch-template">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a prefer-asterisk}
|
||||
## Prefer the asterisk (*) syntax.
|
||||
|
||||
@ -337,6 +418,12 @@ In fact, before rendering the view, Angular _replaces_ the `<template>` and its
|
||||
If there is no structural directive and you merely wrap some elements in a `<template>`,
|
||||
those elements disappear.
|
||||
That's the fate of the middle "Hip!" in the phrase "Hip! Hip! Hooray!".
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (template-tag)" region="template-tag">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular erases the middle "Hip!", leaving the cheer a bit less enthusiastic.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -356,8 +443,20 @@ as you'll see when you [write your own structural directive](guide/structural-di
|
||||
|
||||
There's often a _root_ element that can and should host the structural directive.
|
||||
The list element (`<li>`) is a typical host element of an `NgFor` repeater.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngfor-li)" region="ngfor-li">
|
||||
|
||||
</code-example>
|
||||
|
||||
When there isn't a host element, you can usually wrap the content in a native HTML container element,
|
||||
such as a `<div>`, and attach the directive to that wrapper.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif)" region="ngif">
|
||||
|
||||
</code-example>
|
||||
|
||||
Introducing another container element—typically a `<span>` or `<div>`—to
|
||||
group the elements under a single _root_ is usually harmless.
|
||||
_Usually_ ... but not _always_.
|
||||
@ -365,7 +464,19 @@ _Usually_ ... but not _always_.
|
||||
The grouping element may break the template appearance because CSS styles
|
||||
neither expect nor accommodate the new layout.
|
||||
For example, suppose you have the following paragraph layout.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-span)" region="ngif-span">
|
||||
|
||||
</code-example>
|
||||
|
||||
You also have a CSS style rule that happens to apply to a `<span>` within a `<p>`aragraph.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.css" linenums="false" title="src/app/app.component.css (p-span)" region="p-span">
|
||||
|
||||
</code-example>
|
||||
|
||||
The constructed paragraph renders strangely.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -379,6 +490,12 @@ For example, the `<select>` element requires `<option>` children.
|
||||
You can't wrap the _options_ in a conditional `<div>` or a `<span>`.
|
||||
|
||||
When you try this,
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (select-span)" region="select-span">
|
||||
|
||||
</code-example>
|
||||
|
||||
the drop down is empty.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -393,6 +510,12 @@ The Angular `<ng-container>` is a grouping element that doesn't interfere with s
|
||||
because Angular _doesn't put it in the DOM_.
|
||||
|
||||
Here's the conditional paragraph again, this time using `<ng-container>`.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-ngcontainer)" region="ngif-ngcontainer">
|
||||
|
||||
</code-example>
|
||||
|
||||
It renders properly.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -400,6 +523,12 @@ It renders properly.
|
||||
</figure>
|
||||
|
||||
Now conditionally exclude a _select_ `<option>` with `<ng-container>`.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (select-ngcontainer)" region="select-ngcontainer">
|
||||
|
||||
</code-example>
|
||||
|
||||
The drop down works properly.
|
||||
|
||||
<figure class='image-display'>
|
||||
@ -432,6 +561,12 @@ In this section, you write an `UnlessDirective` structural directive
|
||||
that does the opposite of `NgIf`.
|
||||
`NgIf` displays the template content when the condition is `true`.
|
||||
`UnlessDirective` displays the content when the condition is ***false***.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (myUnless-1)" region="myUnless-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
Creating a directive is similar to creating a component.
|
||||
|
||||
* Import the `Directive` decorator (instead of the `Component` decorator).
|
||||
@ -443,6 +578,12 @@ Creating a directive is similar to creating a component.
|
||||
* Set the CSS *attribute selector* that identifies the directive when applied to an element in a template.
|
||||
|
||||
Here's how you might begin:
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/unless.directive.ts" linenums="false" title="src/app/unless.directive.ts (skeleton)" region="skeleton">
|
||||
|
||||
</code-example>
|
||||
|
||||
The directive's _selector_ is typically the directive's **attribute name** in square brackets, `[myUnless]`.
|
||||
The brackets define a CSS
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors" target="_blank" title="MDN: Attribute selectors">attribute selector</a>.
|
||||
@ -468,12 +609,31 @@ and access the _view container_ through a
|
||||
[`ViewContainerRef`](api/core/index/ViewContainerRef-class).
|
||||
|
||||
You inject both in the directive constructor as private variables of the class.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/unless.directive.ts" linenums="false" title="src/app/unless.directive.ts (ctor)" region="ctor">
|
||||
|
||||
</code-example>
|
||||
|
||||
### The _myUnless_ property
|
||||
|
||||
The directive consumer expects to bind a true/false condition to `[myUnless]`.
|
||||
That means the directive needs a `myUnless` property, decorated with `@Input`
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read about `@Input` in the [_Template Syntax_](guide/template-syntax) guide.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/unless.directive.ts" linenums="false" title="src/app/unless.directive.ts (set)" region="set">
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular sets the `myUnless` property whenever the value of the condition changes.
|
||||
Because the `myUnless` property does work, it needs a setter.
|
||||
|
||||
@ -486,9 +646,21 @@ clear the container which also destroys the view.
|
||||
Nobody reads the `myUnless` property so it doesn't need a getter.
|
||||
|
||||
The completed directive code looks like this:
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/unless.directive.ts" linenums="false" title="src/app/unless.directive.ts (excerpt)" region="no-docs">
|
||||
|
||||
</code-example>
|
||||
|
||||
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
|
||||
|
||||
Then create some HTML to try it.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (myUnless)" region="myUnless">
|
||||
|
||||
</code-example>
|
||||
|
||||
When the `condition` is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.
|
||||
When the `condition` is truthy, the top (A) paragraph is removed and the bottom (B) paragraph appears.
|
||||
|
||||
@ -506,44 +678,45 @@ You can both try and download the source code for this guide in the <live-exampl
|
||||
|
||||
Here is the source from the `src/app/` folder.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="app.component.ts">
|
||||
{@example 'structural-directives/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app.component.ts" path="structural-directives/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.component.html">
|
||||
{@example 'structural-directives/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
<code-pane title="app.component.html" path="structural-directives/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.component.css">
|
||||
{@example 'structural-directives/ts/src/app/app.component.css'}
|
||||
</md-tab>
|
||||
<code-pane title="app.component.css" path="structural-directives/src/app/app.component.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="app.module.ts">
|
||||
{@example 'structural-directives/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="app.module.ts" path="structural-directives/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero.ts">
|
||||
{@example 'structural-directives/ts/src/app/hero.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="hero.ts" path="structural-directives/src/app/hero.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="hero-switch.components.ts">
|
||||
{@example 'structural-directives/ts/src/app/hero-switch.components.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="hero-switch.components.ts" path="structural-directives/src/app/hero-switch.components.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="unless.directive.ts">
|
||||
{@example 'structural-directives/ts/src/app/unless.directive.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="unless.directive.ts" path="structural-directives/src/app/unless.directive.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
You learned
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3555
aio/content/guide/testing.md
Normal file
3555
aio/content/guide/testing.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -85,29 +85,30 @@ In _ES5_, you access the Angular entities of the [the Angular packages](glossary
|
||||
through the global `ng` object.
|
||||
Anything you can import from `@angular` is a nested member of this `ng` object:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/app.module.ts' region='ng2import'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/app.module.ts" region="ng2import">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/app.module.es6' region='ng2import'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/app.module.es6' region='ng2import'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/app.module.es6" region="ng2import">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/app.module.js' region='ng2import'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/app.module.js" region="ng2import">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Exporting Application Code
|
||||
|
||||
@ -136,29 +137,30 @@ to limit unintentional leaking of private symbols into the global scope.
|
||||
|
||||
Here is a `HeroComponent` as it might be defined and "exported" in each of the four language variants.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero.component.ts' region='appexport'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero.component.ts" region="appexport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='appexport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='appexport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero.component.es6" region="appexport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='appexport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero.component.js" region="appexport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Importing Application Code
|
||||
|
||||
@ -166,29 +168,30 @@ In _TypeScript_ and _ES6_ apps, you `import` things that have been exported from
|
||||
|
||||
In _ES5_ you use the shared namespace object to access "exported" entities from other files.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/app.module.ts' region='appimport'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/app.module.ts" region="appimport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/app.module.es6' region='appimport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/app.module.es6' region='appimport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/app.module.es6" region="appimport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/app.module.js' region='appimport'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/app.module.js" region="appimport">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -231,29 +234,30 @@ In _ES6-without-decorators_, properties of classes must be assigned inside the c
|
||||
_ES5_ JavaScript has no classes.
|
||||
Use the constructor function pattern instead, adding methods to the prototype.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero.component.ts' region='class'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero.component.ts" region="class">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='class'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='class'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero.component.es6" region="class">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='constructorproto'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero.component.js" region="constructorproto">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Metadata
|
||||
|
||||
@ -270,71 +274,66 @@ In _ES5_, you also provide an `annotations` array but you attach it to the _cons
|
||||
|
||||
See these variations side-by-side:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero.component.ts' region='metadata'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero.component.ts" region="metadata">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='metadata'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='metadata'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero.component.es6" region="metadata">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='metadata'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero.component.js" region="metadata">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
***External Template file***
|
||||
|
||||
A large component template is often kept in a separate template file.
|
||||
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-title.component.html'}
|
||||
<code-example path="cb-ts-to-js/ts/src/app/hero-title.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The component (`HeroTitleComponent` in this case) then references the template file in its metadata `templateUrl` property:
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-title.component.ts' region='templateUrl'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6' region='templateUrl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-title.component.es6' region='templateUrl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-title.component.es6" region="templateUrl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js' region='templateUrl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-title.component.js" region="templateUrl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
Note that both the _TypeScript_ and _ES6_ `templateUrl` properties identify the location of the template file _relative to the component module_.
|
||||
All three metadata configurations specify the `moduleId` property
|
||||
so that Angular can calculate the proper module address.
|
||||
|
||||
The _ES5_ approach shown here does not support modules and therefore there is no way to calculate a _module-relative URL_.
|
||||
The `templateUrl` for the _ES5_ code must specify the _path from the project root_ and
|
||||
omits the irrelevant `moduleId` property.
|
||||
|
||||
With the right tooling, the `moduleId` may not be needed in the other JavaScript dialects either.
|
||||
But it's safest to provide it anyway.
|
||||
|
||||
|
||||
{@a dsl}
|
||||
@ -354,19 +353,20 @@ Then chain a call to the `Class` method which takes an object defining the class
|
||||
Here is an example of the `HeroComponent`, re-written with the DSL,
|
||||
next to the original _ES5_ version for comparison:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -391,7 +391,9 @@ _TypeScript_ and _ES6_ support with getters and setters.
|
||||
Here's an example of a read-only _TypeScript_ property with a getter
|
||||
that prepares a toggle-button label for the next clicked state:
|
||||
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-queries.component.ts' region='defined-property'}
|
||||
<code-example path="cb-ts-to-js/ts/src/app/hero-queries.component.ts" region="defined-property" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
This _TypeScript_ "getter" property is transpiled to an _ES5_
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
|
||||
@ -400,7 +402,9 @@ The _ES5 DSL_ does not support _defined properties_ directly
|
||||
but you can still create them by extracting the "class" prototype and
|
||||
adding the _defined property_ in raw JavaScript like this:
|
||||
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-queries.component.js' region='defined-property'}
|
||||
<code-example path="cb-ts-to-js/js/src/app/hero-queries.component.js" region="defined-property" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### DSL for other classes
|
||||
There are similar DSLs for other decorated classes.
|
||||
@ -439,34 +443,35 @@ _TypeScript_ interfaces exist for developer convenience and are not used by Angu
|
||||
They have no physical manifestation in the generated JavaScript code.
|
||||
Just implement the methods and ignore interfaces when translating code samples from _TypeScript_ to JavaScript.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-lifecycle.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-lifecycle.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-lifecycle.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-lifecycle.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -489,34 +494,35 @@ there's nothing fundamentally new about adding another property.
|
||||
But note that what would have been _separate_ `@Input` and `@Output` property decorators for each class property are
|
||||
combined in the metadata `inputs` and `outputs` _arrays_.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/confirm.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/confirm.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/confirm.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/confirm.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/confirm.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/confirm.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/confirm.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/confirm.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
In the previous example, one of the public-facing binding names (`cancelMsg`)
|
||||
differs from the corresponding class property name (`notOkMsg`).
|
||||
@ -560,34 +566,35 @@ an array whose first parameters are the injectable constructor functions and who
|
||||
last parameter is the class constructor itself.
|
||||
This format should be familiar to AngularJS developers.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-di.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-di.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-di.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-di.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-di.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-di.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-di.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-di.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Injection with the @Inject decorator
|
||||
|
||||
@ -607,34 +614,35 @@ Each item constains a new instance of `Inject`:
|
||||
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
|
||||
array as before. Create a new instance of `ng.core.Inject(token)` for each parameter.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-di-inject.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-di-inject.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-di-inject.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-di-inject.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-di-inject.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-di-inject.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Additional Injection Decorators
|
||||
|
||||
@ -656,42 +664,50 @@ For example, you'd write `new Optional()` in _plain ES6_ and `new ng.core.Option
|
||||
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
|
||||
array as before. Use a nested array to define a parameter's complete injection specification.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-title.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-title.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-title.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-title.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-title.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-title.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
In the example above, there is no provider for the `'titlePrefix'` token.
|
||||
Without `Optional`, Angular would raise an error.
|
||||
With `Optional`, Angular sets the constructor parameter to `null`
|
||||
and the component displays the title without a prefix.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a host-binding}
|
||||
|
||||
## Host Binding
|
||||
@ -716,34 +732,35 @@ The `host` value is an object whose properties are host property and listener b
|
||||
or `(event)` for host listeners.
|
||||
* Each value identifies the corresponding component property or method.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-host.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-host.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-host.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-host.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-host.component.js'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript" path="cb-ts-to-js/js/src/app/hero-host.component.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-host.component.js' region='dsl'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-host.component.js" region="dsl">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
### Host Metadata
|
||||
Some developers prefer to specify host properties and listeners
|
||||
@ -753,19 +770,20 @@ They'd _rather_ do it the way you _must_ do it _ES5_ and _plain ES6_.
|
||||
The following re-implementation of the `HeroComponent` reminds us that _any property metadata decorator_
|
||||
can be expressed as component or directive metadata in both _TypeScript_ and _ES6-with-decorators_.
|
||||
These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-host-meta.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-host-meta.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
@ -775,10 +793,17 @@ These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
|
||||
|
||||
Several _property_ decorators query a component's nested view and content components.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
_View_ children are associated with element tags that appear _within_ the component's template.
|
||||
|
||||
_Content_ children are associated with elements that appear _between_ the component's element tags;
|
||||
they are projected into an `<ng-content>` slot in the component's template. The [`@ViewChild`](api/core/index/ViewChild-decorator) and
|
||||
they are projected into an `<ng-content>` slot in the component's template.
|
||||
|
||||
~~~
|
||||
|
||||
The [`@ViewChild`](api/core/index/ViewChild-decorator) and
|
||||
[`@ViewChildren`](api/core/index/ViewChildren-decorator) property decorators
|
||||
allow a component to query instances of other components that are used in
|
||||
its view.
|
||||
@ -789,29 +814,30 @@ The `queries` property value is a hash map.
|
||||
* each _key_ is the name of a component property that will hold the view child or children.
|
||||
* each _value_ is a new instance of either `ViewChild` or `ViewChildren`.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-queries.component.ts' region='view'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-queries.component.ts" region="view">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6' region='view'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-queries.component.es6' region='view'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-queries.component.es6" region="view">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-queries.component.js' region='view'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-queries.component.js" region="view">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The [`@ContentChild`](api/core/index/ContentChild-decorator) and
|
||||
[`@ContentChildren`](api/core/index/ContentChildren-decorator) property decorators
|
||||
@ -821,29 +847,30 @@ into its view from elsewhere.
|
||||
They can be added in the same way as [`@ViewChild`](api/core/index/ViewChild-decorator) and
|
||||
[`@ViewChildren`](api/core/index/ViewChildren-decorator).
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="TypeScript">
|
||||
{@example 'cb-ts-to-js/ts/src/app/hero-queries.component.ts' region='content'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-queries.component.ts" region="content">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript with decorators">
|
||||
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6' region='content'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES6 JavaScript">
|
||||
{@example 'cb-ts-to-js/js-es6/src/app/hero-queries.component.es6' region='content'}
|
||||
</md-tab>
|
||||
<code-pane title="ES6 JavaScript" path="cb-ts-to-js/js-es6/src/app/hero-queries.component.es6" region="content">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="ES5 JavaScript with DSL">
|
||||
{@example 'cb-ts-to-js/js/src/app/hero-queries.component.js' region='content'}
|
||||
</md-tab>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="cb-ts-to-js/js/src/app/hero-queries.component.js" region="content">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
|
119
aio/content/guide/typescript-configuration.md
Normal file
119
aio/content/guide/typescript-configuration.md
Normal file
@ -0,0 +1,119 @@
|
||||
@title
|
||||
TypeScript Configuration
|
||||
|
||||
@intro
|
||||
TypeScript configuration for Angular developers.
|
||||
|
||||
@description
|
||||
TypeScript is a primary language for Angular application development.
|
||||
It is a superset of JavaScript with design-time support for type safety and tooling.
|
||||
|
||||
Browsers can't execute TypeScript directly. Typescript must be "transpiled" into JavaScript using the *tsc* compiler,
|
||||
which requires some configuration.
|
||||
|
||||
This page covers some aspects of TypeScript configuration and the TypeScript environment
|
||||
that are important to Angular developers, including details about the following files:
|
||||
|
||||
* [tsconfig.json](guide/typescript-configuration#tsconfig)—TypeScript compiler configuration.
|
||||
* [typings](guide/typescript-configuration#typings)—TypesScript declaration files.
|
||||
|
||||
|
||||
{@a tsconfig}
|
||||
|
||||
## *tsconfig.json*
|
||||
Typically, you add a TypeScript configuration file called `tsconfig.json` to your project to
|
||||
guide the compiler as it generates JavaScript files.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
For details about `tsconfig.json`, see the official
|
||||
[TypeScript wiki](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
||||
|
||||
~~~
|
||||
|
||||
The [Setup](guide/setup) guide uses the following `tsconfig.json`:This file contains options and flags that are essential for Angular applications.
|
||||
|
||||
<a id="noImplicitAny"></a>
|
||||
### *noImplicitAny* and *suppressImplicitAnyIndexErrors*
|
||||
|
||||
TypeScript developers disagree about whether the `noImplicitAny` flag should be `true` or `false`.
|
||||
There is no correct answer and you can change the flag later.
|
||||
But your choice now can make a difference in larger projects, so it merits discussion.
|
||||
|
||||
When the `noImplicitAny` flag is `false` (the default), and if
|
||||
the compiler cannot infer the variable type based on how it's used,
|
||||
the compiler silently defaults the type to `any`. That's what is meant by *implicit `any`*.
|
||||
|
||||
The documentation setup sets the `noImplicitAny` flag to `true`.
|
||||
When the `noImplicitAny` flag is `true` and the TypeScript compiler cannot infer
|
||||
the type, it still generates the JavaScript files, but it also **reports an error**.
|
||||
Many seasoned developers prefer this stricter setting because type checking catches more
|
||||
unintentional errors at compile time.
|
||||
|
||||
You can set a variable's type to `any` even when the `noImplicitAny` flag is `true`.
|
||||
|
||||
When the `noImplicitAny` flag is `true`, you may get *implicit index errors* as well.
|
||||
Most developers feel that *this particular error* is more annoying than helpful.
|
||||
You can suppress them with the following additional flag:
|
||||
<code-example format=".">
|
||||
"suppressImplicitAnyIndexErrors":true
|
||||
|
||||
</code-example>
|
||||
|
||||
The documentation setup sets this flag to `true` as well.
|
||||
|
||||
|
||||
{@a typings}
|
||||
|
||||
## TypeScript Typings
|
||||
Many JavaScript libraries, such as jQuery, the Jasmine testing library, and Angular,
|
||||
extend the JavaScript environment with features and syntax
|
||||
that the TypeScript compiler doesn't recognize natively.
|
||||
When the compiler doesn't recognize something, it throws an error.
|
||||
|
||||
Use [TypeScript type definition files](https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html)—`d.ts files`—to tell the compiler about the libraries you load.
|
||||
|
||||
TypeScript-aware editors leverage these same definition files to display type information about library features.
|
||||
|
||||
Many libraries include definition files in their npm packages where both the TypeScript compiler and editors
|
||||
can find them. Angular is one such library.
|
||||
The `node_modules/@angular/core/` folder of any Angular application contains several `d.ts` files that describe parts of Angular.
|
||||
|
||||
**You need do nothing to get *typings* files for library packages that include `d.ts` files.
|
||||
Angular packages include them already.**
|
||||
|
||||
### lib.d.ts
|
||||
|
||||
TypeScript includes a special declaration file called `lib.d.ts`. This file contains the ambient declarations for various common JavaScript constructs present in JavaScript runtimes and the DOM.
|
||||
|
||||
Based on the `--target`, TypeScript adds _additional_ ambient declarations
|
||||
like `Promise` if the target is `es6`.
|
||||
|
||||
Since the QuickStart is targeting `es5`, you can override the
|
||||
list of declaration files to be included:
|
||||
|
||||
<code-example format=".">
|
||||
"lib": ["es2015", "dom"]
|
||||
|
||||
</code-example>
|
||||
|
||||
Thanks to that, you have all the `es6` typings even when targeting `es5`.
|
||||
|
||||
### Installable typings files
|
||||
Many libraries—jQuery, Jasmine, and Lodash among them—do *not* include `d.ts` files in their npm packages.
|
||||
Fortunately, either their authors or community contributors have created separate `d.ts` files for these libraries and
|
||||
published them in well-known locations.
|
||||
|
||||
You can install these typings via `npm` using the
|
||||
[`@types/*` scoped package](http://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html)
|
||||
and Typescript, starting at 2.0, automatically recognizes them.
|
||||
|
||||
For instance, to install typings for `jasmine` you could do `npm install @types/jasmine --save-dev`.
|
||||
QuickStart identifies two *typings*, or `d.ts`, files:
|
||||
|
||||
* [jasmine](http://jasmine.github.io/) typings for the Jasmine test framework.
|
||||
|
||||
* [node](https://www.npmjs.com/package/@types/node) for code that references objects in the *nodejs* environment;
|
||||
you can view an example in the [webpack](guide/webpack) page.
|
||||
|
||||
QuickStart doesn't require these typings but many of the samples do.
|
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,9 @@ To bind to a DOM event, surround the DOM event name in parentheses and assign a
|
||||
|
||||
The following example shows an event binding that implements a click handler:
|
||||
|
||||
{@example 'user-input/ts/src/app/click-me.component.ts' region='click-me-button'}
|
||||
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-button" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="click"></a>The `(click)` to the left of the equals sign identifies the button's click event as the **target of the binding**.
|
||||
The text in quotes to the right of the equals sign
|
||||
@ -36,7 +38,9 @@ usually the Angular component controlling the template.
|
||||
The example above shows a single line of HTML, but that HTML belongs to a larger component:
|
||||
|
||||
|
||||
{@example 'user-input/ts/src/app/click-me.component.ts' region='click-me-component'}
|
||||
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-component" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
When the user clicks the button, Angular calls the `onClickMe` method from `ClickMeComponent`.
|
||||
|
||||
@ -46,12 +50,16 @@ This section shows how to bind to the `keyup` event of an input box to get the u
|
||||
|
||||
The following code listens to the `keyup` event and passes the entire event payload (`$event`) to the component event handler.
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-1-template'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-template" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
When a user presses and releases a key, the `keyup` event occurs, and Angular provides a corresponding
|
||||
DOM event object in the `$event` variable which this code passes as a parameter to the component's `onKey()` method.
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-1-class-no-type'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-class-no-type" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The properties of an `$event` object vary depending on the type of DOM event. For example,
|
||||
a mouse event includes different information than a input box editing event.
|
||||
@ -78,6 +86,9 @@ Here's what the UI displays:
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Alternatively, you could accumulate the individual keys themselves by substituting `event.key`
|
||||
for `event.target.value` in which case the same user input would produce:
|
||||
<code-example>
|
||||
@ -87,6 +98,10 @@ for `event.target.value` in which case the same user input would produce:
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a keyup1}
|
||||
### Type the _$event_
|
||||
|
||||
@ -97,7 +112,9 @@ that could reveal properties of the event object and prevent silly mistakes.
|
||||
|
||||
The following example rewrites the method with types:
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-1-class'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-class" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `$event` is now a specific `KeyboardEvent`.
|
||||
Not all elements have a `value` property so it casts `target` to an input element.
|
||||
@ -121,7 +138,9 @@ To declare a template reference variable, precede an identifier with a hash (or
|
||||
The following example uses a template reference variable
|
||||
to implement a keystroke loopback in a simple template.
|
||||
|
||||
{@example 'user-input/ts/src/app/loop-back.component.ts' region='loop-back-component'}
|
||||
<code-example path="user-input/src/app/loop-back.component.ts" region="loop-back-component" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The template reference variable named `box`, declared on the `<input>` element,
|
||||
refers to the `<input>` element itself.
|
||||
@ -138,6 +157,9 @@ Type something in the input box, and watch the display update with each keystrok
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
**This won't work at all unless you bind to an event**.
|
||||
|
||||
Angular updates the bindings (and therefore the screen)
|
||||
@ -145,11 +167,17 @@ only if the app does something in response to asynchronous events, such as keyst
|
||||
This example code binds the `keyup` event
|
||||
to the number 0, the shortest template statement possible.
|
||||
While the statement does nothing useful,
|
||||
it satisfies Angular's requirement so that Angular will update the screen.It's easier to get to the input box with the template reference
|
||||
it satisfies Angular's requirement so that Angular will update the screen.
|
||||
|
||||
~~~
|
||||
|
||||
It's easier to get to the input box with the template reference
|
||||
variable than to go through the `$event` object. Here's a rewrite of the previous
|
||||
`keyup` example that uses a template reference variable to get the user's input.
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-2'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-2" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
A nice aspect of this approach is that the component gets clean data values from the view.
|
||||
It no longer requires knowledge of the `$event` and its structure.
|
||||
@ -162,7 +190,9 @@ One way to reduce the noise would be to examine every `$event.keyCode` and take
|
||||
There's an easier way: bind to Angular's `keyup.enter` pseudo-event.
|
||||
Then Angular calls the event handler only when the user presses _Enter_.
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-3'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-3" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's how it works.
|
||||
<figure class='image-display'>
|
||||
@ -180,7 +210,9 @@ The component's `value` property is updated only when the user presses _Enter_.
|
||||
To fix this issue, listen to both the _Enter_ key and the _blur_ event.
|
||||
|
||||
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts' region='key-up-component-4'}
|
||||
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-4" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
## Put it all together
|
||||
@ -199,7 +231,9 @@ clicking **Add**.
|
||||
Below is the "Little Tour of Heroes" component.
|
||||
|
||||
|
||||
{@example 'user-input/ts/src/app/little-tour.component.ts' region='little-tour'}
|
||||
<code-example path="user-input/src/app/little-tour.component.ts" region="little-tour" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Observations
|
||||
|
||||
@ -219,29 +253,30 @@ clears the input box after a new hero is added to the list.
|
||||
## Source code
|
||||
|
||||
Following is all the code discussed in this page.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="click-me.component.ts">
|
||||
{@example 'user-input/ts/src/app/click-me.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="click-me.component.ts" path="user-input/src/app/click-me.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="keyup.components.ts">
|
||||
{@example 'user-input/ts/src/app/keyup.components.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="keyup.components.ts" path="user-input/src/app/keyup.components.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="loop-back.component.ts">
|
||||
{@example 'user-input/ts/src/app/loop-back.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="loop-back.component.ts" path="user-input/src/app/loop-back.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="little-tour.component.ts">
|
||||
{@example 'user-input/ts/src/app/little-tour.component.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="little-tour.component.ts" path="user-input/src/app/little-tour.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
## Summary
|
||||
|
@ -9,16 +9,30 @@ Use Visual Studio 2015 with the QuickStart files.
|
||||
|
||||
This cookbook describes the steps required to set up and use the
|
||||
Angular QuickStart files in **Visual Studio 2015 within an ASP.NET 4.x project**.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
There is no *live example* for this cookbook because it describes Visual Studio, not the application.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
<a id="asp-net-4"></a>## ASP.NET 4.x Project
|
||||
|
||||
This cookbook explains how to set up the QuickStart files with an **ASP.NET 4.x project** in
|
||||
Visual Studio 2015.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
If you prefer a `File | New Project` experience and are using **ASP.NET Core**,
|
||||
then consider the _experimental_
|
||||
<a href="http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/" target="_blank">ASP.NET Core + Angular template for Visual Studio 2015</a>.
|
||||
Note that the resulting code does not map to the docs. Adjust accordingly.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The steps are as follows:
|
||||
|
||||
- [Prerequisite](guide/visual-studio-2015#prereq1): Install Node.js
|
||||
@ -38,11 +52,18 @@ The steps are as follows:
|
||||
|
||||
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
||||
if they are not already on your machine.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
**Verify that you are running node version `4.6.x` or greater, and npm `3.x.x` or greater**
|
||||
by running `node -v` and `npm -v` in a terminal/console window.
|
||||
Older versions produce errors.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
<h2 id='prereq2'>
|
||||
Prerequisite: Visual Studio 2015 Update 3
|
||||
</h2>
|
||||
@ -109,10 +130,17 @@ Create the ASP.NET 4.x project in the usual way as follows:
|
||||
* Select the `ASP.NET Web Application` template, give the project a name, and click OK.
|
||||
* Select the desired ASP.NET 4.5.2 template and click OK.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
In this cookbook we'll select the `Empty` template with no added folders,
|
||||
no authentication and no hosting. Pick the template and options appropriate for your project.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
<h2 id='copy'>
|
||||
Step 3: Copy the QuickStart files into the ASP.NET project folder
|
||||
</h2>
|
||||
@ -154,7 +182,14 @@ First, ensure that `index.html` is set as the start page.
|
||||
Right-click `index.html` in Solution Explorer and select option `Set As Start Page`.
|
||||
|
||||
Build and launch the app with debugger by clicking the **Run** button or press `F5`.
|
||||
It's faster to run without the debugger by pressing `Ctrl-F5`.The default browser opens and displays the QuickStart sample application.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
It's faster to run without the debugger by pressing `Ctrl-F5`.
|
||||
|
||||
~~~
|
||||
|
||||
The default browser opens and displays the QuickStart sample application.
|
||||
|
||||
Try editing any of the project files. *Save* and refresh the browser to
|
||||
see the changes.
|
||||
|
@ -13,44 +13,50 @@ Create Angular applications with a Webpack based tooling.
|
||||
</style>
|
||||
|
||||
[**Webpack**](https://webpack.github.io/) is a popular module bundler,
|
||||
a tool for bundling application source code in convenient _chunks_
|
||||
a tool for bundling application source code in convenient _chunks_
|
||||
and for loading that code from a server into a browser.
|
||||
|
||||
It's an excellent alternative to the *SystemJS* approach used elsewhere in the documentation.
|
||||
This guide offers a taste of Webpack and explains how to use it with Angular applications.
|
||||
|
||||
<a id="top"></a>
|
||||
## Table of contents
|
||||
|
||||
[What is Webpack?](guide/webpack#what-is-webpack)
|
||||
# Contents
|
||||
|
||||
* [What is Webpack?](guide/webpack#what-is-webpack)
|
||||
* [Entries and outputs](guide/webpack#entries-outputs)
|
||||
* [Multiple bundles](guide/webpack#multiple-bundles)
|
||||
* [Loaders](guide/webpack#loaders)
|
||||
* [Plugins](guide/webpack#plugins)
|
||||
|
||||
[Configuring Webpack](guide/webpack#configure-webpack)
|
||||
|
||||
* [Configuring Webpack](guide/webpack#configure-webpack)
|
||||
* [Polyfills](guide/webpack#polyfills)
|
||||
* [Common configuration](guide/webpack#common-configuration)
|
||||
* [Inside `webpack.common.js`](guide/webpack#inside-webpack-commonjs)
|
||||
- [entry](guide/webpack#common-entries)
|
||||
- [resolve extension-less imports](guide/webpack#common-resolves)
|
||||
- [`module.rules`](guide/webpack#common-rules)
|
||||
- [Plugins](guide/webpack#plugins)
|
||||
- [`CommonsChunkPlugin`](guide/webpack#commons-chunk-plugin)
|
||||
- [`HtmlWebpackPlugin`](guide/webpack#html-webpack-plugin)
|
||||
* [Environment specific configuration](guide/webpack#environment-configuration)
|
||||
* [Development configuration](guide/webpack#development-configuration)
|
||||
* [Production configuration](guide/webpack#production-configuration)
|
||||
* [Test configuration](guide/webpack#test-configuration)
|
||||
|
||||
[Trying it out](guide/webpack#try)
|
||||
|
||||
[Conclusions](guide/webpack#conclusions)
|
||||
* [Trying it out](guide/webpack#try)
|
||||
* [Highlights](guide/webpack#highlights)
|
||||
* [Conclusion](guide/webpack#conclusion)
|
||||
|
||||
You can also <a href="/resources/zips/webpack/webpack.zip">download the final result.</a>
|
||||
|
||||
<a id="what-is-webpack"></a>## What is Webpack?
|
||||
|
||||
Webpack is a powerful module bundler.
|
||||
A _bundle_ is a JavaScript file that incorporate _assets_ that *belong* together and
|
||||
Webpack is a powerful module bundler.
|
||||
A _bundle_ is a JavaScript file that incorporates _assets_ that *belong* together and
|
||||
should be served to the client in a response to a single file request.
|
||||
A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file.
|
||||
|
||||
Webpack roams over your application source code,
|
||||
looking for `import` statements, building a dependency graph, and emitting one (or more) _bundles_.
|
||||
With plugins and rules, Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.
|
||||
Webpack roams over your application source code,
|
||||
looking for `import` statements, building a dependency graph, and emitting one or more _bundles_.
|
||||
With plugins and rules, Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.
|
||||
|
||||
You determine what Webpack does and how it does it with a JavaScript configuration file, `webpack.config.js`.
|
||||
|
||||
@ -59,47 +65,85 @@ You determine what Webpack does and how it does it with a JavaScript configurati
|
||||
|
||||
### Entries and outputs
|
||||
|
||||
You supply Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries.
|
||||
The one entry point file in this example is the application's root file, `src/app.ts`:
|
||||
You supply Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries.
|
||||
The one entry point file in this example is the application's root file, `src/main.ts`:
|
||||
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='one-entry'}
|
||||
<code-example path="webpack/config/webpack.common.js" region="one-entry" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Webpack inspects that file and traverses its `import` dependencies recursively.
|
||||
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='app-example'}
|
||||
<code-example path="webpack/src/app/app.component.ts" region="component" linenums="false">
|
||||
|
||||
It sees that you're importing *@angular/core* so it adds that to its dependency list for (potential) inclusion in the bundle.
|
||||
It opens the *@angular/core* file and follows _its_ network of `import` statements until it has built the complete dependency graph from `app.ts` down.
|
||||
</code-example>
|
||||
|
||||
It sees that you're importing `@angular/core` so it adds that to its dependency list for potential inclusion in the bundle.
|
||||
It opens the `@angular/core` file and follows _its_ network of `import` statements until it has built the complete dependency graph from `main.ts` down.
|
||||
|
||||
Then it **outputs** these files to the `app.js` _bundle file_ designated in configuration:
|
||||
|
||||
<div class='code-example'>
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='one-output'}
|
||||
<code-example name="webpack.config.js (single output)" language="javascript">
|
||||
output: {
|
||||
filename: 'app.js'
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
This `app.js` output bundle is a single JavaScript file that contains the application source and its dependencies.
|
||||
You'll load it later with a `<script>` tag in the `index.html`.
|
||||
|
||||
</div>
|
||||
|
||||
This `app.js` output bundle is a single JavaScript file that contains the application source and its dependencies.
|
||||
You'll load it later with a `<script>` tag in the `index.html`.
|
||||
|
||||
|
||||
{@a multiple-bundles}
|
||||
#### Multiple bundles
|
||||
You probably don't want one giant bundle of everything.
|
||||
It's preferable to separate the volatile application app code from comparatively stable vendor code modules.
|
||||
|
||||
Change the configuration so that it has two entry points, `app.ts` and `vendor.ts`:
|
||||
Change the configuration so that it has two entry points, `main.ts` and `vendor.ts`:
|
||||
|
||||
<div class='code-example'>
|
||||
|
||||
<code-example language="javascript">
|
||||
entry: {
|
||||
app: 'src/app.ts',
|
||||
vendor: 'src/vendor.ts'
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: '[name].js'
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='two-entries'}
|
||||
</div>
|
||||
|
||||
Webpack constructs two separate dependency graphs
|
||||
and emits *two* bundle files, one called `app.js` containing only the application code and
|
||||
and emits *two* bundle files, one called `app.js` containing only the application code and
|
||||
another called `vendor.js` with all the vendor dependencies.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `[name]` in the output name is a *placeholder* that a Webpack plugin replaces with the entry names,
|
||||
`app` and `vendor`. Plugins are [covered later](guide/webpack#commons-chunk-plugin) in the guide.
|
||||
To tell Webpack what belongs in the vendor bundle,
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
To tell Webpack what belongs in the vendor bundle,
|
||||
add a `vendor.ts` file that only imports the application's third-party modules:
|
||||
|
||||
{@example 'webpack/ts/src/vendor.ts'}
|
||||
<code-example path="webpack/src/vendor.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -107,28 +151,54 @@ add a `vendor.ts` file that only imports the application's third-party modules:
|
||||
|
||||
### Loaders
|
||||
|
||||
Webpack can bundle any kind of file: JavaScript, TypeScript, CSS, SASS, LESS, images, html, fonts, whatever.
|
||||
Webpack can bundle any kind of file: JavaScript, TypeScript, CSS, SASS, LESS, images, HTML, fonts, whatever.
|
||||
Webpack _itself_ only understands JavaScript files.
|
||||
Teach it to transform non-JavaScript file into their JavaScript equivalents with *loaders*.
|
||||
Teach it to transform non-JavaScript file into their JavaScript equivalents with *loaders*.
|
||||
Configure loaders for TypeScript and CSS as follows.
|
||||
|
||||
<div class='code-example'>
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='loaders'}
|
||||
|
||||
As Webpack encounters `import` statements like these ...
|
||||
<code-example language="javascript">
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'awesome-typescript-loader'
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
loaders: 'style-loader!css-loader'
|
||||
}
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='imports'}
|
||||
</div>
|
||||
|
||||
... it applies the `test` RegEx patterns. When a pattern matches the filename, Webpack processes the file with the associated loader.
|
||||
When Webpack encounters `import` statements like the following,
|
||||
it applies the `test` RegEx patterns.
|
||||
|
||||
<div class='code-example'>
|
||||
|
||||
<code-example language="typescript">
|
||||
import { AppComponent } from './app.component.ts';
|
||||
|
||||
import 'uiframework/dist/uiframework.css';
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
When a pattern matches the filename, Webpack processes the file with the associated loader.
|
||||
|
||||
The first `import` file matches the `.ts` pattern so Webpack processes it with the `awesome-typescript-loader`.
|
||||
The imported file doesn't match the second pattern so its loader is ignored.
|
||||
The imported file doesn't match the second pattern so its loader is ignored.
|
||||
|
||||
The second `import` matches the second `.css` pattern for which you have *two* loaders chained by the (!) character.
|
||||
Webpack applies chained loaders *right to left* so it applies
|
||||
the `css` loader first (to flatten CSS `@import` and `url(...)` statements) and
|
||||
then the `style` loader (to append the css inside *<style>* elements on the page).
|
||||
The second `import` matches the second `.css` pattern for which you have *two* loaders chained by the (!) character.
|
||||
Webpack applies chained loaders *right to left*. So it applies
|
||||
the `css` loader first to flatten CSS `@import` and `url(...)` statements.
|
||||
Then it applies the `style` loader to append the css inside `<style>` elements on the page.
|
||||
|
||||
|
||||
{@a plugins}
|
||||
@ -138,20 +208,29 @@ the `css` loader first (to flatten CSS `@import` and `url(...)` statements) and
|
||||
Webpack has a build pipeline with well-defined phases.
|
||||
Tap into that pipeline with plugins such as the `uglify` minification plugin:
|
||||
|
||||
<div class='code-example'>
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='plugins'}
|
||||
<code-example language="javascript">
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin()
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a configure-webpack}
|
||||
|
||||
## Configure Webpack
|
||||
## Configuring Webpack
|
||||
|
||||
After that brief orientation, you are ready to build your own Webpack configuration for Angular apps.
|
||||
After that brief orientation, you are ready to build your own Webpack configuration for Angular apps.
|
||||
|
||||
Begin by setting up the development environment.
|
||||
|
||||
Create a **new project folder**
|
||||
Create a new project folder.
|
||||
<code-example language="sh" class="code-shell">
|
||||
mkdir angular-webpack
|
||||
cd angular-webpack
|
||||
@ -160,43 +239,51 @@ Create a **new project folder**
|
||||
|
||||
Add these files:
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="package.json">
|
||||
{@example 'webpack/ts/package.webpack.json'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="package.json" path="webpack/package.webpack.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/tsconfig.json">
|
||||
{@example 'webpack/ts/src/tsconfig.1.json'}
|
||||
</md-tab>
|
||||
<code-pane title="src/tsconfig.json" path="webpack/src/tsconfig.1.json">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="webpack.config.js">
|
||||
{@example 'webpack/ts/webpack.config.js'}
|
||||
</md-tab>
|
||||
<code-pane title="webpack.config.js" path="webpack/webpack.config.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="karma.conf.js">
|
||||
{@example 'webpack/ts/karma.webpack.conf.js'}
|
||||
</md-tab>
|
||||
<code-pane title="karma.conf.js" path="webpack/karma.webpack.conf.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="config/helpers.js">
|
||||
{@example 'webpack/ts/config/helpers.js'}
|
||||
</md-tab>
|
||||
<code-pane title="config/helpers.js" path="webpack/config/helpers.js">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Many of these files should be familiar from other Angular documentation guides,
|
||||
especially the [_Typescript configuration_](guide/typescript-configuration) and
|
||||
[_npm packages_](guide/npm-packages) guides.
|
||||
especially the [Typescript configuration](guide/typescript-configuration) and
|
||||
[npm packages](guide/npm-packages) guides.
|
||||
|
||||
Webpack, the plugins, and the loaders are also installed as packages.
|
||||
Webpack, the plugins, and the loaders are also installed as packages.
|
||||
They are listed in the updated `packages.json`.
|
||||
Open a terminal window and (re)install the *npm* packages
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
Open a terminal window and install the npm packages.
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm install
|
||||
|
||||
@ -209,13 +296,15 @@ Open a terminal window and (re)install the *npm* packages
|
||||
### Polyfills
|
||||
|
||||
You'll need polyfills to run an Angular application in most browsers as explained
|
||||
in the [_Browser Support_](guide/browser-support) guide.
|
||||
in the [Browser Support](guide/browser-support) guide.
|
||||
|
||||
Polyfills should be bundled separately from the application and vendor bundles.
|
||||
Add a `polyfills.ts` like this one to the `src/` folder.
|
||||
|
||||
|
||||
{@example 'webpack/ts/src/polyfills.ts'}
|
||||
<code-example path="webpack/src/polyfills.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
@ -231,13 +320,13 @@ Load `zone.js` early within `polyfills.ts`, immediately after the other ES6 and
|
||||
|
||||
~~~
|
||||
|
||||
Because this bundle file will load first, `polyfills.ts` is also a good place to configure the browser environment
|
||||
Because this bundle file will load first, `polyfills.ts` is also a good place to configure the browser environment
|
||||
for production or development.
|
||||
|
||||
|
||||
{@a common-configuration}
|
||||
|
||||
### Common Configuration
|
||||
### Common configuration
|
||||
|
||||
Developers typically have separate configurations for development, production, and test environments.
|
||||
All three have a lot of configuration in common.
|
||||
@ -245,103 +334,153 @@ All three have a lot of configuration in common.
|
||||
Gather the common configuration in a file called `webpack.common.js`.
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.common.js'}
|
||||
<code-example path="webpack/config/webpack.common.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a inside-webpack-commonjs}
|
||||
### Inside _webpack.common.js_
|
||||
Webpack is a NodeJS-based tool that reads configuration from a JavaScript _commonjs_ module file.
|
||||
Webpack is a NodeJS-based tool that reads configuration from a JavaScript commonjs module file.
|
||||
|
||||
The configuration imports dependencies with `require` statements
|
||||
and exports several objects as properties of a `module.exports` object.
|
||||
|
||||
* [`entries`](guide/webpack#common-entries) - the entry-point files that define the bundles.
|
||||
* [`resolve`](guide/webpack#common-resolve) - how to resolve file names when they lack extensions.
|
||||
* [`module.rules`](guide/webpack#common-rules) - `module` is an object with `rules` for deciding how files are loaded.
|
||||
* [`plugins`](guide/webpack#common-plugins) - creates instances of the plugins.
|
||||
* [`entry`](guide/webpack#common-entries)—the entry-point files that define the bundles.
|
||||
* [`resolve`](guide/webpack#common-resolve)—how to resolve file names when they lack extensions.
|
||||
* [`module.rules`](guide/webpack#common-rules)— `module` is an object with `rules` for deciding how files are loaded.
|
||||
* [`plugins`](guide/webpack#common-plugins)—creates instances of the plugins.
|
||||
|
||||
|
||||
{@a common-entries}
|
||||
#### _entries_
|
||||
#### _entry_
|
||||
|
||||
The first export is the *entries* object, described above:
|
||||
The first export is the `entry` object:
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.common.js' region='entries'}
|
||||
<code-example path="webpack/config/webpack.common.js" region="entries" linenums="false">
|
||||
|
||||
This *entries* object defines the three bundles:
|
||||
</code-example>
|
||||
|
||||
* polyfills - the polyfills needed to run Angular applications in most modern browsers.
|
||||
* vendor - the third-party dependencies such as Angular, lodash, and bootstrap.css.
|
||||
* app - the application code.
|
||||
This `entry` object defines the three bundles:
|
||||
|
||||
* `polyfills`—the polyfills needed to run Angular applications in most modern browsers.
|
||||
* `vendor`—the third-party dependencies such as Angular, lodash, and bootstrap.css.
|
||||
* `app`—the application code.
|
||||
|
||||
|
||||
{@a common-resolve}
|
||||
#### _resolve_ extension-less imports
|
||||
|
||||
The app will `import` dozens if not hundreds of JavaScript and TypeScript files.
|
||||
The app will `import` dozens if not hundreds of JavaScript and TypeScript files.
|
||||
You could write `import` statements with explicit extensions like this example:
|
||||
|
||||
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='single-import'}
|
||||
<div class='code-example'>
|
||||
|
||||
<code-example language="typescript">
|
||||
import { AppComponent } from './app.component.ts';
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
But most `import` statements don't mention the extension at all.
|
||||
Tell Webpack to resolve extension-less file requests by looking for matching files with
|
||||
`.ts` extension or `.js` extension (for regular JavaScript files and pre-compiled TypeScript files).
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.common.js' region='resolve'}
|
||||
<code-example path="webpack/config/webpack.common.js" region="resolve" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
If Webpack should resolve extension-less files for styles and HTML,
|
||||
add `.css` and `.html` to the list.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a common-rules}
|
||||
#### _module.rules_
|
||||
Rules tell Webpack which loaders to use for each file (AKA _module_):
|
||||
Rules tell Webpack which loaders to use for each file, or module:
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.common.js' region='loaders'}
|
||||
<code-example path="webpack/config/webpack.common.js" region="loaders" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
* `awesome-typescript-loader`—a loader to transpile the Typescript code to ES5, guided by the `tsconfig.json` file.
|
||||
* `angular2-template-loader`—loads angular components' template and styles.
|
||||
* `html-loader`—for component templates.
|
||||
* images/fonts—Images and fonts are bundled as well.
|
||||
* CSS—the first pattern matches application-wide styles; the second handles
|
||||
component-scoped styles (the ones specified in a component's `styleUrls` metadata property).
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
* awesome-typescript-loader - a loader to transpile the Typescript code to ES5, guided by the `tsconfig.json` file
|
||||
* angular2-template-loader - loads angular components' template and styles
|
||||
* html - for component templates
|
||||
* images/fonts - Images and fonts are bundled as well.
|
||||
* css - The pattern matches application-wide styles; the second handles component-scoped styles (the ones specified in a component's `styleUrls` metadata property)
|
||||
The first pattern is for the application-wide styles. It excludes `.css` files within the `src/app` directory
|
||||
where the component-scoped styles sit. The `ExtractTextPlugin` (described below) applies the `style` and `css`
|
||||
loaders to these files.
|
||||
|
||||
The second pattern filters for component-scoped styles and loads them as strings via the `raw` loader —
|
||||
The second pattern filters for component-scoped styles and loads them as strings via the `raw-loader`,
|
||||
which is what Angular expects to do with styles specified in a `styleUrls` metadata property.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Multiple loaders can be chained using the array notation.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a common-plugins}
|
||||
#### _plugins_
|
||||
Finally, create instances of three plugins:
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.common.js' region='plugins'}
|
||||
<code-example path="webpack/config/webpack.common.js" region="plugins" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a commons-chunk-plugin}
|
||||
#### *CommonsChunkPlugin*
|
||||
|
||||
The `app.js` bundle should contain only application code. All vendor code belongs in the `vendor.js` bundle.
|
||||
The `app.js` bundle should contain only application code. All vendor code belongs in the `vendor.js` bundle.
|
||||
|
||||
Of course the application code `imports` vendor code.
|
||||
Webpack itself is not smart enough to keep the vendor code out of the `app.js` bundle.
|
||||
The `CommonsChunkPlugin` does that job.
|
||||
The `CommonsChunkPlugin` identifies the hierarchy among three _chunks_: `app` -> `vendor` -> `polyfills`.
|
||||
Of course the application code imports vendor code.
|
||||
On its own, Webpack is not smart enough to keep the vendor code out of the `app.js` bundle.
|
||||
The `CommonsChunkPlugin` does that job.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The `CommonsChunkPlugin` identifies the hierarchy among three _chunks_: `app` -> `vendor` -> `polyfills`.
|
||||
Where Webpack finds that `app` has shared dependencies with `vendor`, it removes them from `app`.
|
||||
It would remove `polyfills` from `vendor` if they shared dependencies (which they don't).
|
||||
It would remove `polyfills` from `vendor` if they shared dependencies, which they don't.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a html-webpack-plugin}
|
||||
#### *HtmlWebpackPlugin*
|
||||
#### _HtmlWebpackPlugin_
|
||||
|
||||
Webpack generates a number of js and css files.
|
||||
Webpack generates a number of js and CSS files.
|
||||
You _could_ insert them into the `index.html` _manually_. That would be tedious and error-prone.
|
||||
Webpack can inject those scripts and links for you with the `HtmlWebpackPlugin`.
|
||||
|
||||
@ -350,7 +489,7 @@ Webpack can inject those scripts and links for you with the `HtmlWebpackPlugin`.
|
||||
|
||||
### Environment-specific configuration
|
||||
|
||||
The `webpack.common.js` configuration file does most of the heavy lifting.
|
||||
The `webpack.common.js` configuration file does most of the heavy lifting.
|
||||
Create separate, environment-specific configuration files that build on `webpack.common`
|
||||
by merging into it the peculiarities particular to the target environments.
|
||||
|
||||
@ -359,27 +498,30 @@ These files tend to be short and simple.
|
||||
|
||||
{@a development-configuration}
|
||||
|
||||
### Development Configuration
|
||||
### Development configuration
|
||||
|
||||
Here is the `webpack.dev.js` development configuration file.
|
||||
Here is the `webpack.dev.js` development configuration file.
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.dev.js'}
|
||||
<code-example path="webpack/config/webpack.dev.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The development build relies on the Webpack development server, configured near the bottom of the file.
|
||||
|
||||
Although you tell Webpack to put output bundles in the `dist` folder,
|
||||
the dev server keeps all bundles in memory; it doesn't write them to disk.
|
||||
You won't find any files in the `dist` folder (at least not any generated from *this development build*).
|
||||
You won't find any files in the `dist` folder, at least not any generated from *this development build*.
|
||||
|
||||
|
||||
The `HtmlWebpackPlugin` (added in `webpack.common.js`) use the *publicPath* and the *filename* settings to generate
|
||||
appropriate <script> and <link> tags into the `index.html`.
|
||||
The `HtmlWebpackPlugin`, added in `webpack.common.js`, uses the `publicPath` and the `filename` settings to generate
|
||||
appropriate `<script>` and `<link>` tags into the `index.html`.
|
||||
|
||||
The CSS styles are buried inside the Javascript bundles by default. The `ExtractTextPlugin` extracts them into
|
||||
external `.css` files that the `HtmlWebpackPlugin` inscribes as <link> tags into the `index.html`.
|
||||
external `.css` files that the `HtmlWebpackPlugin` inscribes as `<link>` tags into the `index.html`.
|
||||
|
||||
Refer to the Webpack documentation for details on these and other configuration options in this file
|
||||
Refer to the [Webpack documentation](https://webpack.github.io/docs/) for details on these and
|
||||
other configuration options in this file.
|
||||
|
||||
Grab the app code at the end of this guide and try:
|
||||
|
||||
@ -392,12 +534,14 @@ Grab the app code at the end of this guide and try:
|
||||
|
||||
{@a production-configuration}
|
||||
|
||||
### Production Configuration
|
||||
### Production configuration
|
||||
|
||||
Configuration of a *production* build resembles *development* configuration ... with a few key changes.
|
||||
Configuration of a *production* build resembles *development* configuration with a few key changes.
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.prod.js'}
|
||||
<code-example path="webpack/config/webpack.prod.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You'll deploy the application and its dependencies to a real production server.
|
||||
You won't deploy the artifacts needed only in development.
|
||||
@ -405,20 +549,22 @@ You won't deploy the artifacts needed only in development.
|
||||
Put the production output bundle files in the `dist` folder.
|
||||
|
||||
Webpack generates file names with cache-busting hash.
|
||||
Thanks to the `HtmlWebpackPlugin`, you don't have to update the `index.html` file when the hashes changes.
|
||||
Thanks to the `HtmlWebpackPlugin`, you don't have to update the `index.html` file when the hash changes.
|
||||
|
||||
There are additional plugins:
|
||||
|
||||
* **NoEmitOnErrorsPlugin** - stops the build if there is an error.
|
||||
* **UglifyJsPlugin** - minifies the bundles.
|
||||
* **ExtractTextPlugin** - extracts embedded css as external files, adding cache-busting hash to the filename.
|
||||
* **DefinePlugin** - use to define environment variables that you can reference within the application.
|
||||
* **LoaderOptionsPlugins** - to override options of certain loaders.
|
||||
* *`NoEmitOnErrorsPlugin`—stops the build if there is an error.
|
||||
* *`UglifyJsPlugin`—minifies the bundles.
|
||||
* *`ExtractTextPlugin`—extracts embedded css as external files, adding cache-busting hash to the filename.
|
||||
* *`DefinePlugin`—use to define environment variables that you can reference within the application.
|
||||
* *`LoaderOptionsPlugins`—to override options of certain loaders.
|
||||
|
||||
Thanks to the *DefinePlugin* and the `ENV` variable defined at top, you can enable Angular production mode like this:
|
||||
Thanks to the `DefinePlugin` and the `ENV` variable defined at top, you can enable Angular production mode like this:
|
||||
|
||||
|
||||
{@example 'webpack/ts/src/main.ts' region='enable-prod'}
|
||||
<code-example path="webpack/src/main.ts" region="enable-prod" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Grab the app code at the end of this guide and try:
|
||||
|
||||
@ -431,9 +577,9 @@ Grab the app code at the end of this guide and try:
|
||||
|
||||
{@a test-configuration}
|
||||
|
||||
### Test Configuration
|
||||
### Test configuration
|
||||
|
||||
You don't need much configuration to run unit tests.
|
||||
You don't need much configuration to run unit tests.
|
||||
You don't need the loaders and plugins that you declared for your development and production builds.
|
||||
You probably don't need to load and process the application-wide styles files for unit tests and doing so would slow you down;
|
||||
you'll use the `null` loader for those CSS files.
|
||||
@ -442,25 +588,31 @@ You could merge the test configuration into the `webpack.common` configuration a
|
||||
But it might be simpler to start over with a completely fresh configuration.
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/webpack.test.js'}
|
||||
<code-example path="webpack/config/webpack.test.js" linenums="false">
|
||||
|
||||
Reconfigure karma to use webpack to run the tests:
|
||||
</code-example>
|
||||
|
||||
Reconfigure [Karma](https://karma-runner.github.io/1.0/index.html) to use Webpack to run the tests:
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/karma.conf.js'}
|
||||
<code-example path="webpack/config/karma.conf.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You don't precompile the TypeScript; Webpack transpiles the Typescript files on the fly, in memory, and feeds the emitted JS directly to Karma.
|
||||
There are no temporary files on disk.
|
||||
|
||||
The `karma-test-shim` tells Karma what files to pre-load and
|
||||
The `karma-test-shim` tells Karma what files to pre-load and
|
||||
primes the Angular test framework with test versions of the providers that every app expects to be pre-loaded.
|
||||
|
||||
|
||||
{@example 'webpack/ts/config/karma-test-shim.js'}
|
||||
<code-example path="webpack/config/karma-test-shim.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Notice that you do _not_ load the application code explicitly.
|
||||
You tell Webpack to find and load the test files (the files ending in `.spec.ts`).
|
||||
Each spec file imports all — and only — the application source code that it tests.
|
||||
Each spec file imports all—and only—the application source code that it tests.
|
||||
Webpack loads just _those_ specific application files and ignores the other files that you aren't testing.
|
||||
Grab the app code at the end of this guide and try:
|
||||
|
||||
@ -474,95 +626,99 @@ Grab the app code at the end of this guide and try:
|
||||
Here is the source code for a small application that bundles with the
|
||||
Webpack techniques covered in this guide.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/index.html">
|
||||
{@example 'webpack/ts/src/index.html'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/index.html" path="webpack/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/main.ts">
|
||||
{@example 'webpack/ts/src/main.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/main.ts" path="webpack/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/assets/css/styles.css">
|
||||
{@example 'webpack/ts/src/assets/css/styles.css'}
|
||||
</md-tab>
|
||||
<code-pane title="src/assets/css/styles.css" path="webpack/src/assets/css/styles.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'webpack/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="webpack/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.html">
|
||||
{@example 'webpack/ts/src/app/app.component.html'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.html" path="webpack/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.css">
|
||||
{@example 'webpack/ts/src/app/app.component.css'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.css" path="webpack/src/app/app.component.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.spec.ts">
|
||||
{@example 'webpack/ts/src/app/app.component.spec.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.component.spec.ts" path="webpack/src/app/app.component.spec.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts">
|
||||
{@example 'webpack/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/app/app.module.ts" path="webpack/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
The <code>app.component.html</code> displays this downloadable Angular logo
|
||||
<a href="https://raw.githubusercontent.com/angular/angular.io/master/publicassets/images/logos/angular2/angular.png" target="_blank">
|
||||
<img src="assets/images/logos/angular2/angular.png" height="40px" title="download Angular logo"></a>.
|
||||
Create folder `images` under the project's "assets" folder, then right-click and download the image to that folder.
|
||||
Create a folder called `images` under the project's `assets` folder, then right-click (Cmd+click on Mac)
|
||||
on the image and download it to that folder.
|
||||
|
||||
|
||||
{@a bundle-ts}
|
||||
Here again are the TypeScript entry-point files that define the `polyfills` and `vendor` bundles.
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/polyfills.ts">
|
||||
{@example 'webpack/ts/src/polyfills.ts'}
|
||||
</md-tab>
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/polyfills.ts" path="webpack/src/polyfills.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
<md-tab label="src/vendor.ts">
|
||||
{@example 'webpack/ts/src/vendor.ts'}
|
||||
</md-tab>
|
||||
<code-pane title="src/vendor.ts" path="webpack/src/vendor.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
</code-tabs>
|
||||
|
||||
<a id="highlights"></a>### Highlights:
|
||||
<a id="highlights"></a>### Highlights
|
||||
|
||||
* There are no <script> or <link> tags in the `index.html`.
|
||||
* There are no `<script>` or `<link>` tags in the `index.html`.
|
||||
The `HtmlWebpackPlugin` inserts them dynamically at runtime.
|
||||
|
||||
|
||||
* The `AppComponent` in `app.component.ts` imports the application-wide css with a simple `import` statement.
|
||||
|
||||
* The `AppComponent` itself has its own html template and css file. WebPack loads them with calls to `require()`.
|
||||
* The `AppComponent` itself has its own html template and css file. WebPack loads them with calls to `require()`.
|
||||
Webpack stashes those component-scoped files in the `app.js` bundle too.
|
||||
You don't see those calls in the source code;
|
||||
they're added behind the scenes by the `angular2-template-loader` plug-in.
|
||||
You don't see those calls in the source code;
|
||||
they're added behind the scenes by the `angular2-template-loader` plug-in.
|
||||
|
||||
* The `vendor.ts` consists of vendor dependency `import` statements that drive the `vendor.js` bundle.
|
||||
The application imports these modules too; they'd be duplicated in the `app.js` bundle
|
||||
if the `CommonsChunkPlugin` hadn't detected the overlap and removed them from `app.js`.
|
||||
<a id="conclusions"></a>## Conclusions
|
||||
<a id="conclusion"></a>## Conclusion
|
||||
|
||||
You've learned just enough Webpack to configurate development, test and production builds
|
||||
You've learned just enough Webpack to configurate development, test and production builds
|
||||
for a small Angular application.
|
||||
|
||||
_You could always do more_. Search the web for expert advice and expand your Webpack knowledge.
|
||||
|
Reference in New Issue
Block a user