docs(aio): migrate content docs from angular.io
This content was generated by a tool: https://github.com/petebacondarwin/aio-migrator
This commit is contained in:
parent
c3247c64a4
commit
3e34ba01bd
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,623 @@
|
||||||
|
@title
|
||||||
|
Ahead-of-Time Compilation
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Learn how to use Ahead-of-time compilation
|
||||||
|
|
||||||
|
@description
|
||||||
|
This cookbook describes how to radically improve performance by compiling _Ahead of Time_ (AOT)
|
||||||
|
during a build process.
|
||||||
|
|
||||||
|
|
||||||
|
{@a toc}
|
||||||
|
## Table of Contents
|
||||||
|
* [Overview](#overview)
|
||||||
|
* [_Ahead-of-Time_ vs _Just-in-Time_](#aot-jit)
|
||||||
|
* [Compile with AOT](#compile)
|
||||||
|
* [Bootstrap](#bootstrap)
|
||||||
|
* [Tree Shaking](#tree-shaking)
|
||||||
|
* [Load the bundle](#load)
|
||||||
|
* [Serve the app](#serve)
|
||||||
|
* [Workflow and convenience script](#workflow)
|
||||||
|
* [Source Code](#source-code)
|
||||||
|
* [Tour of Heroes](#toh)
|
||||||
|
|
||||||
|
|
||||||
|
{@a overview}
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An Angular application consist 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**.
|
||||||
|
This is the standard development approach shown throughout the documentation.
|
||||||
|
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.
|
||||||
|
The application is bigger because it includes the Angular compiler
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a aot-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.
|
||||||
|
|
||||||
|
### Why do AOT compilation?
|
||||||
|
|
||||||
|
*Faster rendering*
|
||||||
|
|
||||||
|
With AOT, the browser downloads a pre-compiled version of the application.
|
||||||
|
The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
|
||||||
|
|
||||||
|
*Fewer asynchronous requests*
|
||||||
|
|
||||||
|
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*
|
||||||
|
|
||||||
|
There's no need to download the Angular compiler if the app is already compiled.
|
||||||
|
The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.
|
||||||
|
|
||||||
|
|
||||||
|
*Detect template errors earlier*
|
||||||
|
|
||||||
|
The AOT compiler detects and reports template binding errors during the build step
|
||||||
|
before users can see them.
|
||||||
|
|
||||||
|
|
||||||
|
*Better security*
|
||||||
|
|
||||||
|
AOT compiles HTML templates and components into JavaScript files long before they are served to the client.
|
||||||
|
With no templates to read and no risky client-side HTML or JavaScript evaluation,
|
||||||
|
there are fewer opportunities for injection attacks.
|
||||||
|
|
||||||
|
|
||||||
|
{@a compile}
|
||||||
|
|
||||||
|
## Compile with AOT
|
||||||
|
|
||||||
|
### Prepare for offline compilation
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'cb-aot-compiler/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
Install a few new npm dependencies with the following command:
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
npm install @angular/compiler-cli @angular/platform-server --save
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
You will run the `ngc` compiler provided in the `@angular/compiler-cli` npm package
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-aot-compiler/ts/tsconfig-aot.json'}
|
||||||
|
|
||||||
|
The `compilerOptions` section is unchanged except for one property.
|
||||||
|
**Set the `module` to `es2015`**.
|
||||||
|
This is important as explained later in the [Tree Shaking](#tree-shaking) section.
|
||||||
|
|
||||||
|
What's really new is the `ngc` section at the bottom called `angularCompilerOptions`.
|
||||||
|
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***
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Initiate AOT compilation from the command line using the previously installed `ngc` compiler by executing:
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
node_modules/.bin/ngc -p tsconfig-aot.json
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
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`).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a bootstrap}
|
||||||
|
|
||||||
|
## Bootstrap
|
||||||
|
|
||||||
|
The AOT path changes application bootstrapping.
|
||||||
|
|
||||||
|
Instead of bootstrapping `AppModule`, you bootstrap the application with the generated module factory, `AppModuleNgFactory`.
|
||||||
|
|
||||||
|
Make a copy of `main.ts` and name it `main-jit.ts`.
|
||||||
|
This is the JIT version; set it aside as you may need it [later](#run-jit "Running with JIT").
|
||||||
|
|
||||||
|
Open `main.ts` and convert it to AOT compilation.
|
||||||
|
Switch from the `platformBrowserDynamic.bootstrap` used in JIT compilation to
|
||||||
|
`platformBrowser().bootstrapModuleFactory` and pass in the AOT-generated `AppModuleNgFactory`.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/main-jit.ts">
|
||||||
|
{@example 'cb-aot-compiler/ts/src/main-jit.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
Be sure to recompile with `ngc`!
|
||||||
|
|
||||||
|
|
||||||
|
{@a 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
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='.'>
|
||||||
|
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Next, create a configuration file (`rollup-config.js`)
|
||||||
|
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'}
|
||||||
|
|
||||||
|
It 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.
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-aot-compiler/ts/rollup-config.js' region='commonjs'}
|
||||||
|
|
||||||
|
*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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-aot-compiler/ts/rollup-config.js' region='uglify'}
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
### Run Rollup
|
||||||
|
Execute the Rollup process with this command:
|
||||||
|
<code-example format='.'>
|
||||||
|
node_modules/.bin/rollup -c rollup-config.js
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
Windows users should surround the `rollup` command in double quotes:
|
||||||
|
<code-example format='.'>
|
||||||
|
"node_modules/.bin/rollup" -c rollup-config.js
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a load}
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-aot-compiler/ts/src/index.html' region='bundle'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a serve}
|
||||||
|
|
||||||
|
## Serve the app
|
||||||
|
|
||||||
|
You'll need a web server to host the application.
|
||||||
|
Use the same _Lite Server_ employed elsewhere in the documentation:
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
npm run lite
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The server starts, launches a browser, and the app should appear.
|
||||||
|
|
||||||
|
|
||||||
|
{@a 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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'cb-aot-compiler/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/main.ts">
|
||||||
|
{@example 'cb-aot-compiler/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/index.html">
|
||||||
|
{@example 'cb-aot-compiler/ts/src/index.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="tsconfig-aot.json">
|
||||||
|
{@example 'cb-aot-compiler/ts/tsconfig-aot.json'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="rollup-config.js">
|
||||||
|
{@example 'cb-aot-compiler/ts/rollup-config.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a workflow}
|
||||||
|
|
||||||
|
## Workflow and convenience script
|
||||||
|
|
||||||
|
You'll rebuild the AOT version of the application every time you make a change.
|
||||||
|
Those _npm_ commands are long and difficult to remember.
|
||||||
|
|
||||||
|
Add the following _npm_ convenience script to the `package.json` so you can compile and rollup in one command.Open a terminal window and try it.
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
npm run build:aot
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a run-jit}
|
||||||
|
### And JIT too!
|
||||||
|
|
||||||
|
AOT compilation and rollup together take several seconds.
|
||||||
|
You may be able to develop iteratively a little faster with SystemJS and JIT.
|
||||||
|
The same source code can be built both ways. Here's one way to do that.
|
||||||
|
|
||||||
|
* Make a copy of `index.html` and call it `index-jit.html`.
|
||||||
|
* 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'}
|
||||||
|
|
||||||
|
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](#bootstrap)
|
||||||
|
Open a _different_ terminal window and enter.
|
||||||
|
<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).
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
Now you can develop JIT and AOT, side-by-side.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a toh}
|
||||||
|
|
||||||
|
## 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.html).
|
||||||
|
|
||||||
|
### JIT in development, AOT in production
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
***index.html***
|
||||||
|
|
||||||
|
The JIT and AOT apps require their own `index.html` files because they setup and launch so differently.
|
||||||
|
|
||||||
|
Here they are for comparison:
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="aot/index.html (AOT)">
|
||||||
|
{@example 'toh-6/ts/aot/index.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/index.html (JIT)">
|
||||||
|
{@example 'toh-6/ts/src/index.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
The JIT version relies on `SystemJS` to load individual modules.
|
||||||
|
Its scripts appear in its `index.html`.
|
||||||
|
|
||||||
|
The AOT version loads the entire application in a single script, `aot/dist/build.js`.
|
||||||
|
It does not need `SystemJS`, so that script is absent from its `index.html`
|
||||||
|
|
||||||
|
***main.ts***
|
||||||
|
|
||||||
|
JIT and AOT applications boot in much the same way but require different Angular libraries to do so.
|
||||||
|
The key differences, covered in the [Bootstrap](#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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="main.ts (JIT)">
|
||||||
|
{@example 'toh-6/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
***TypeScript configuration***
|
||||||
|
|
||||||
|
JIT-compiled applications transpile to `commonjs` modules.
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/tsconfig.json (JIT)">
|
||||||
|
{@example 'toh-6/ts/src/tsconfig.1.json'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-helpful}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
@Types and node modules
|
||||||
|
</header>
|
||||||
|
|
||||||
|
In the file structure of _this particular sample project_,
|
||||||
|
the `node_modules` folder happens to be two levels up from the project root.
|
||||||
|
Therefore, `"typeRoots"` must be set to `"../../node_modules/@types/"`.
|
||||||
|
|
||||||
|
In a more typical project, `node_modules` would be a sibling of `tsconfig-aot.json`
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### Running the application
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
The general audience instructions for running the AOT build of the Tour of Heroes app are not ready.
|
||||||
|
|
||||||
|
The following instructions presuppose that you have cloned the
|
||||||
|
<a href="https://github.com/angular/angular.io" target="_blank">angular.io</a>
|
||||||
|
github repository and prepared it for development as explained in the repo's README.md.
|
||||||
|
|
||||||
|
The _Tour of Heroes_ source code is in the `public/docs/_examples/toh-6/ts` folder.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="copy-dist-files.js">
|
||||||
|
{@example 'toh-6/ts/copy-dist-files.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="rollup-config.js">
|
||||||
|
{@example 'toh-6/ts/rollup-config.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="tsconfig-aot.json">
|
||||||
|
{@example 'toh-6/ts/tsconfig-aot.json'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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">
|
||||||
|
node copy-dist-files
|
||||||
|
</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:
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
npm run build:aot && npm run serve:aot
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
### Inspect the Bundle
|
||||||
|
|
||||||
|
It's fascinating to see what the generated JavaScript bundle looks like after Rollup.
|
||||||
|
The code is minified, so you won't learn much from inspecting the bundle directly.
|
||||||
|
But the <a href="https://github.com/danvk/source-map-explorer/blob/master/README.md" target="_blank">source-map-explorer</a>
|
||||||
|
tool can be quite revealing.
|
||||||
|
|
||||||
|
Install it:
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
npm install source-map-explorer --save-dev
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Run the following command to generate the map.
|
||||||
|
|
||||||
|
<code-example language="none" class="code-shell">
|
||||||
|
node_modules/.bin/source-map-explorer aot/dist/build.js
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
|
||||||
|
showing exactly which application and Angular modules and classes are included in the bundle.
|
||||||
|
|
||||||
|
Here's the map for _Tour of Heroes_.
|
||||||
|
<a href="/resources/images/cookbooks/aot-compiler/toh6-bundle.png" target="_blank" title="View larger image">
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/aot-compiler/toh6-bundle.png" alt="TOH-6-bundle"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
</a>
|
|
@ -0,0 +1,301 @@
|
||||||
|
@title
|
||||||
|
Component Interaction
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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
|
||||||
|
|
||||||
|
[Pass data from parent to child with input binding](#parent-to-child)
|
||||||
|
|
||||||
|
[Intercept input property changes with a setter](#parent-to-child-setter)
|
||||||
|
|
||||||
|
[Intercept input property changes with *ngOnChanges*](#parent-to-child-on-changes)
|
||||||
|
|
||||||
|
[Parent listens for child event](#child-to-parent)
|
||||||
|
|
||||||
|
[Parent interacts with child via a *local variable*](#parent-to-child-local-var)
|
||||||
|
|
||||||
|
[Parent calls a *ViewChild*](#parent-to-view-child)
|
||||||
|
|
||||||
|
[Parent and children communicate via a service](#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***,
|
||||||
|
typically adorned with [@Input decorations](../guide/template-syntax.html#inputs-outputs).
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/hero-child.component.ts'}
|
||||||
|
|
||||||
|
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
|
||||||
|
and each iteration's `hero` instance to the child's `hero` property.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/hero-parent.component.ts'}
|
||||||
|
|
||||||
|
The running application displays three heroes:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
E2E test that all children were instantiated and displayed as expected:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/e2e-spec.ts' region='parent-to-child'}
|
||||||
|
|
||||||
|
[Back to top](#top)
|
||||||
|
|
||||||
|
<a id="parent-to-child-setter"></a>## Intercept input property changes with a setter
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/name-child.component.ts'}
|
||||||
|
|
||||||
|
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/name-parent.component.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/setter.png" alt="Parent-to-child-setter"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
E2E tests of input property setter with empty and non-empty names:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/e2e-spec.ts' region='parent-to-child-setter'}
|
||||||
|
|
||||||
|
[Back to top](#top)
|
||||||
|
|
||||||
|
<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.
|
||||||
|
|
||||||
|
Learn about `ngOnChanges` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) 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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Here's the output of a button-pushing sequence:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
[Back to top](#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 parent binds to that event property and reacts to those events.
|
||||||
|
|
||||||
|
The child's `EventEmitter` property is an ***output property***,
|
||||||
|
typically adorned with an [@Output decoration](../guide/template-syntax.html#inputs-outputs)
|
||||||
|
as seen in this `VoterComponent`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/voter.component.ts'}
|
||||||
|
|
||||||
|
Clicking a button triggers emission of a `true` or `false` (the boolean *payload*).
|
||||||
|
|
||||||
|
The parent `VoteTakerComponent` binds an event handler (`onVoted`) that responds to the child event
|
||||||
|
payload (`$event`) and updates a counter.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/votetaker.component.ts'}
|
||||||
|
|
||||||
|
The framework passes the event argument — represented by `$event` — to the handler method,
|
||||||
|
and the method processes it:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/e2e-spec.ts' region='child-to-parent'}
|
||||||
|
|
||||||
|
[Back to top](#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
|
||||||
|
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.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Let's see the `CountdownLocalVarParentComponent` that hosts the timer component.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/ts/src/app/countdown-parent.component.ts' region='lv'}
|
||||||
|
|
||||||
|
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
|
||||||
|
*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.
|
||||||
|
|
||||||
|
Here we see the parent and child working together.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a countdown-tests}
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
Test that the seconds displayed in the parent template
|
||||||
|
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'}
|
||||||
|
|
||||||
|
[Back to top](#top)
|
||||||
|
|
||||||
|
<a id="parent-to-view-child"></a>## Parent calls a *ViewChild*
|
||||||
|
|
||||||
|
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*
|
||||||
|
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*.
|
||||||
|
|
||||||
|
We'll illustrate this technique with the same [Countdown Timer](#countdown-timer-example) example.
|
||||||
|
We won't change its appearance or behavior.
|
||||||
|
The child [CountdownTimerComponent](#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'}
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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 timer component isn't available until *after* Angular displays the parent view.
|
||||||
|
So we display `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.
|
||||||
|
|
||||||
|
We use `setTimeout` to wait one tick and then revise the `seconds` method so
|
||||||
|
that it takes future values from the timer component.
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
Use [the same countdown timer tests](#countdown-tests) as before.[Back to top](#top)
|
||||||
|
|
||||||
|
<a id="bidirectional-service"></a>## Parent and children communicate via a service
|
||||||
|
|
||||||
|
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.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
Notice that we capture 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
|
||||||
|
the parent `MissionControlComponent` and the `AstronautComponent` children,
|
||||||
|
facilitated by the service:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children
|
||||||
|
and verify that the *History* meets expectations:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-component-communication/e2e-spec.ts' region='bidirectional-service'}
|
||||||
|
|
||||||
|
[Back to top](#top)
|
|
@ -11,7 +11,8 @@ 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
|
We identify those files with a URL in the `templateUrl` and `styleUrls` properties of the `@Component` metadata
|
||||||
as seen here:
|
as seen here:
|
||||||
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.ts' -region='absolute-config' -linenums='false' }
|
|
||||||
|
{@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.
|
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.
|
We call this an ***absolute path*** because it is *absolute* with respect to the application root.
|
||||||
|
@ -20,13 +21,14 @@ 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 remember the full path back to the application root.
|
||||||
|
|
||||||
2. We have to update the URL when we move the component around in the application files structure.
|
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
|
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.
|
*relative* to their component class file.
|
||||||
|
|
||||||
*We can!*
|
*We can!*
|
||||||
|
|
||||||
|
|
||||||
~~~ {.alert.is-important}
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
We can if we build our application as `commonjs` modules and load those modules
|
We can if we build our application as `commonjs` modules and load those modules
|
||||||
|
@ -38,27 +40,44 @@ The Angular CLI uses these technologies and defaults to the
|
||||||
CLI users can skip this chapter or read on to understand
|
CLI users can skip this chapter or read on to understand
|
||||||
how it works.
|
how it works.
|
||||||
|
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|
||||||
## _Component-Relative_ Paths
|
## _Component-Relative_ Paths
|
||||||
|
|
||||||
Our goal is to specify template and style URLs *relative* to their component class files,
|
Our goal is to specify template and style URLs *relative* to their component class files,
|
||||||
hence the term ***component-relative path***.
|
hence the term ***component-relative path***.
|
||||||
|
|
||||||
The key to success is following a convention that puts related component files in well-known locations.
|
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
|
We recommend keeping component template and component-specific style files as *siblings* of their
|
||||||
companion component class files.
|
companion component class files.
|
||||||
Here we see the three files for `SomeComponent` sitting next to each other in the `app` folder.
|
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-file-tree>
|
|
||||||
<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-file>...</aio-file>
|
|
||||||
</aio-folder>
|
</aio-folder>
|
||||||
</aio-file-tree>
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
We'll have more files and folders — and greater folder depth — as our application grows.
|
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.
|
We'll be fine as long as the component files travel together as the inseparable siblings they are.
|
||||||
|
@ -68,16 +87,19 @@ We'll be fine as long as the component files travel together as the inseparable
|
||||||
Having adopted this file structure convention, we can specify locations of the template and style files
|
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
|
relative to the component class file simply by setting the `moduleId` property of the `@Component` metadata like this
|
||||||
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.ts' -region='module-id' -linenums='false'}
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='module-id'}
|
||||||
|
|
||||||
We strip the `app/` base path from the `templateUrl` and `styleUrls` and replace it with `./`.
|
We strip the `src/app/` base path from the `templateUrl` and `styleUrls` and replace it with `./`.
|
||||||
The result looks like this:
|
The result looks like this:
|
||||||
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.ts' -region='relative-config' -linenums='false'}
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='relative-config'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
~~~ {.alert.is-helpful}
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
Webpack users may prefer [an alternative approach](#webpack).
|
Webpack users may prefer [an alternative approach](#webpack).
|
||||||
|
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -87,21 +109,30 @@ The result looks like this:
|
||||||
**We can see the <live-example name="cb-component-relative-paths"></live-example>**
|
**We can see the <live-example name="cb-component-relative-paths"></live-example>**
|
||||||
and download the source code from there
|
and download the source code from there
|
||||||
or simply read the pertinent source here.
|
or simply read the pertinent source here.
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
<md-tab-group>
|
<md-tab label="src/app/some.component.ts">
|
||||||
<md-tab label="app/some.component.ts">
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts'}
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.ts'}
|
|
||||||
</md-tab>
|
</md-tab>
|
||||||
<md-tab label="app/some.component.html">
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.html'}
|
|
||||||
|
<md-tab label="src/app/some.component.html">
|
||||||
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.html'}
|
||||||
</md-tab>
|
</md-tab>
|
||||||
<md-tab label="app/some.component.css">
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.css'}
|
|
||||||
|
<md-tab label="src/app/some.component.css">
|
||||||
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.css'}
|
||||||
</md-tab>
|
</md-tab>
|
||||||
<md-tab label="app/app.component.ts">
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/app.component.ts'}
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'cb-component-relative-paths/ts/src/app/app.component.ts'}
|
||||||
</md-tab>
|
</md-tab>
|
||||||
<md-tab-group>
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a why-default}
|
{@a why-default}
|
||||||
|
@ -118,15 +149,15 @@ First, let's look at what happens if we use a relative path and omit the `module
|
||||||
|
|
||||||
Angular can't find the file so it throws an error.
|
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?
|
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.
|
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
|
Angular apps can be loaded in many ways: from individual files, from SystemJS packages, or
|
||||||
from CommonJS packages, to name a few.
|
from CommonJS packages, to name a few.
|
||||||
We might generate modules in any of several formats.
|
We might generate modules in any of several formats.
|
||||||
We might not be writing modular code at all!
|
We might not be writing modular code at all!
|
||||||
|
|
||||||
With this diversity of packaging and module load strategies,
|
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.
|
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.
|
The only location Angular can be sure of is the URL of the `index.html` home page, the application root.
|
||||||
|
@ -142,7 +173,8 @@ the absolute URL of the component class module file.
|
||||||
That knowledge enables us to tell Angular where the *component* file is
|
That knowledge enables us to tell Angular where the *component* file is
|
||||||
by setting the `moduleId`:
|
by setting the `moduleId`:
|
||||||
|
|
||||||
{@example 'cb-component-relative-paths/ts/app/some.component.ts' -region='module-id' -linenums='false'}
|
{@example 'cb-component-relative-paths/ts/src/app/some.component.ts' region='module-id'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a webpack}
|
{@a webpack}
|
||||||
|
@ -153,8 +185,10 @@ 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`
|
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.
|
properties that reference *component-relative URLS.
|
||||||
|
|
||||||
{@example 'webpack/ts/src/app/app.component.ts' --linenums='false'}
|
|
||||||
|
{@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.html#highlights).
|
Webpack will do a `require` behind the scenes to load the templates and styles. Read more [here](../guide/webpack.html#highlights).
|
||||||
|
|
||||||
See the [Introduction to Webpack](../guide/webpack.html).
|
See the [Introduction to Webpack](../guide/webpack.html).
|
|
@ -0,0 +1,921 @@
|
||||||
|
@title
|
||||||
|
Dependency Injection
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Techniques for Dependency Injection
|
||||||
|
|
||||||
|
@description
|
||||||
|
Dependency Injection is a powerful pattern for managing code dependencies.
|
||||||
|
In this cookbook we will explore many of the features of Dependency Injection (DI) in Angular.
|
||||||
|
<a id="toc"></a>## Table of contents
|
||||||
|
|
||||||
|
[Application-wide dependencies](#app-wide-dependencies)
|
||||||
|
|
||||||
|
[External module configuration](#external-module-configuration)
|
||||||
|
|
||||||
|
[*@Injectable* and nested service dependencies](#nested-dependencies)
|
||||||
|
|
||||||
|
[Limit service scope to a component subtree](#service-scope)
|
||||||
|
|
||||||
|
[Multiple service instances (sandboxing)](#multiple-service-instances)
|
||||||
|
|
||||||
|
[Qualify dependency lookup with *@Optional* and *@Host*](#qualify-dependency-lookup)
|
||||||
|
|
||||||
|
[Inject the component's DOM element](#component-element)
|
||||||
|
|
||||||
|
[Define dependencies with providers](#providers)
|
||||||
|
* [The *provide* object literal](#provide)
|
||||||
|
* [useValue - the *value provider*](#usevalue)
|
||||||
|
* [useClass - the *class provider*](#useclass)
|
||||||
|
* [useExisting - the *alias provider*](#useexisting)
|
||||||
|
* [useFactory - the *factory provider*](#usefactory)
|
||||||
|
|
||||||
|
[Provider token alternatives](#tokens)
|
||||||
|
* [class-interface](#class-interface)
|
||||||
|
* [OpaqueToken](#opaque-token)
|
||||||
|
|
||||||
|
[Inject into a derived class](#di-inheritance)
|
||||||
|
|
||||||
|
[Find a parent component by injection](#find-parent)
|
||||||
|
* [Find parent with a known component type](#known-parent)
|
||||||
|
* [Cannot find a parent by its base class](#base-parent)
|
||||||
|
* [Find a parent by its class-interface](#class-interface-parent)
|
||||||
|
* [Find a parent in a tree of parents (*@SkipSelf*)](#parent-tree)
|
||||||
|
* [A *provideParent* helper function](#provideparent)
|
||||||
|
|
||||||
|
[Break circularities with a forward class reference (*forwardRef*)](#forwardref)
|
||||||
|
**See the <live-example name="cb-dependency-injection"></live-example>**
|
||||||
|
of the code supporting this cookbook.
|
||||||
|
|
||||||
|
<a id="app-wide-dependencies"></a>## Application-wide dependencies
|
||||||
|
Register providers for dependencies used throughout the application in the root application component, `AppComponent`.
|
||||||
|
|
||||||
|
In the following example, we import and register several services
|
||||||
|
(the `LoggerService`, `UserContext`, and the `UserService`)
|
||||||
|
in the `@Component` metadata `providers` array.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/app.component.ts' region='import-services'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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](#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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/user-context.service.ts' region='ctor'}
|
||||||
|
|
||||||
|
<a id="external-module-configuration"></a>
|
||||||
|
## External module configuration
|
||||||
|
We often register providers in the `NgModule` rather than in the root application component.
|
||||||
|
|
||||||
|
We do this when (a) we expect the service to be injectable everywhere
|
||||||
|
or (b) we must configure another application global service _before it starts_.
|
||||||
|
|
||||||
|
We see an example of the second case here, where we configure the Component Router with a non-default
|
||||||
|
[location strategy](../guide/router.html#location-strategy) by listing its provider
|
||||||
|
in the `providers` list of the `AppModule`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/app.module.ts' region='providers'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a injectable}
|
||||||
|
|
||||||
|
|
||||||
|
{@a nested-dependencies}
|
||||||
|
|
||||||
|
## *@Injectable* and nested service dependencies
|
||||||
|
The consumer of an injected service does not know how to create that service.
|
||||||
|
It shouldn't care.
|
||||||
|
It's the dependency injection's job to create and cache that service.
|
||||||
|
|
||||||
|
Sometimes a service depends on other services ... which may depend on yet other services.
|
||||||
|
Resolving these nested dependencies in the correct order is also the framework's job.
|
||||||
|
At each step, the consumer of dependencies simply declares what it requires in its constructor and the framework takes over.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
When Angular creates an`AppComponent`, the dependency injection framework creates an instance of the `LoggerService` and
|
||||||
|
starts to create the `UserContextService`.
|
||||||
|
The `UserContextService` needs the `LoggerService`, which the framework already has, and the `UserService`, which it has yet to create.
|
||||||
|
The `UserService` has no dependencies so the dependency injection framework can just `new` one into existence.
|
||||||
|
|
||||||
|
The beauty of dependency injection is that the author of `AppComponent` didn't care about any of this.
|
||||||
|
The author simply declared what was needed in the constructor (`LoggerService` and `UserContextService`) and the framework did the rest.
|
||||||
|
|
||||||
|
Once all the dependencies are in place, the `AppComponent` displays the user information:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### *@Injectable()*
|
||||||
|
Notice the `@Injectable()`decorator on the `UserContextService` class.
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/user-context.service.ts' region='injectable'}
|
||||||
|
|
||||||
|
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
|
||||||
|
|
||||||
|
Technically, the `@Injectable()`decorator is only _required_ for a service class that has _its own dependencies_.
|
||||||
|
The `LoggerService` doesn't depend on anything. The logger would work if we omitted `@Injectable()`
|
||||||
|
and the generated code would be slightly smaller.
|
||||||
|
|
||||||
|
But the service would break the moment we gave it a dependency and we'd have to go back and
|
||||||
|
and add `@Injectable()` to fix it. We add `@Injectable()` from the start for the sake of consistency and to avoid future pain.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
Although we recommend applying `@Injectable` to all service classes, do not feel bound by it.
|
||||||
|
Some developers prefer to add it only where needed and that's a reasonable policy too.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
All injected service dependencies are singletons meaning that,
|
||||||
|
for a given dependency injector ("injector"), there is only one instance of service.
|
||||||
|
|
||||||
|
But an Angular application has multiple dependency injectors, arranged in a tree hierarchy that parallels the component tree.
|
||||||
|
So a particular service can be *provided* (and created) at any component level and multiple times
|
||||||
|
if provided in multiple components.
|
||||||
|
|
||||||
|
By default, a service dependency provided in one component is visible to all of its child components and
|
||||||
|
Angular injects the same service instance into all child components that ask for that service.
|
||||||
|
|
||||||
|
Accordingly, dependencies provided in the root `AppComponent` can be injected into *any* component *anywhere* in the application.
|
||||||
|
|
||||||
|
That isn't always desirable.
|
||||||
|
Sometimes we want to restrict service availability to a particular region of the application.
|
||||||
|
|
||||||
|
We can limit the scope of an injected service to a *branch* of the application hierarchy
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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}
|
||||||
|
|
||||||
|
### Take a break!
|
||||||
|
This much Dependency Injection knowledge may be all that many Angular developers
|
||||||
|
ever need to build their applications. It doesn't always have to be more complicated.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
<a id="multiple-service-instances"></a>
|
||||||
|
## Multiple service instances (sandboxing)
|
||||||
|
|
||||||
|
Sometimes we want multiple instances of a service at *the same level of the component hierarchy*.
|
||||||
|
|
||||||
|
A good example is a service that holds state for its companion component instance.
|
||||||
|
We need a separate instance of the service for each component.
|
||||||
|
Each service has its own work-state, isolated from the service-and-state of a different component.
|
||||||
|
We call this *sandboxing* because each service and component instance has its own sandbox to play in.
|
||||||
|
|
||||||
|
<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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The parent `HeroBiosComponent` binds a value to the `heroId`.
|
||||||
|
The `ngOnInit` pass that `id` to the service which fetches and caches the hero.
|
||||||
|
The getter for the `hero` property pulls the cached hero from the service.
|
||||||
|
And the template displays this data-bound property.
|
||||||
|
|
||||||
|
Find this example in <live-example name="cb-dependency-injection">live code</live-example>
|
||||||
|
and confirm that the three `HeroBioComponent` instances have their own cached hero data.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/hero-bios.png" alt="Bios"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a optional}
|
||||||
|
|
||||||
|
|
||||||
|
{@a qualify-dependency-lookup}
|
||||||
|
|
||||||
|
## Qualify dependency lookup with *@Optional* and *@Host*
|
||||||
|
We learned that dependencies can be registered at any level in the component hierarchy.
|
||||||
|
|
||||||
|
When a component requests a dependency, Angular starts with that component's injector and walks up the injector tree
|
||||||
|
until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.
|
||||||
|
|
||||||
|
We *want* this behavior most of the time.
|
||||||
|
But sometimes we need to limit the search and/or accommodate a missing dependency.
|
||||||
|
We can modify Angular's search behavior with the `@Host` and `@Optional` qualifying decorators,
|
||||||
|
used individually or together.
|
||||||
|
|
||||||
|
The `@Optional` decorator tells Angular to continue when it can't find the dependency.
|
||||||
|
Angular sets the injection parameter to `null` instead.
|
||||||
|
|
||||||
|
The `@Host` decorator stops the upward search at the *host component*.
|
||||||
|
|
||||||
|
The host component is typically the component requesting the dependency.
|
||||||
|
But when this component is projected into a *parent* component, that parent component becomes the host.
|
||||||
|
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](#hero-bios-component).
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='hero-bios-and-contacts'}
|
||||||
|
|
||||||
|
Focus on the template:
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='template'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
It looks like this, with the hero's telephone number from `HeroContactComponent` projected above the hero description:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png" alt="bio and contact"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Focus on the constructor parameters
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/hero-contact.component.ts' region='ctor-params'}
|
||||||
|
|
||||||
|
The `@Host()` function decorating the `heroCache` property ensures that
|
||||||
|
we get a reference to the cache service from the parent `HeroBioComponent`.
|
||||||
|
Angular throws if the parent lacks that service, even if a component higher in the component tree happens to have that service.
|
||||||
|
|
||||||
|
A second `@Host()` function decorates the `loggerService` property.
|
||||||
|
We know the only `LoggerService` instance in the app is provided at the `AppComponent` level.
|
||||||
|
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.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
If we comment out the `@Host()` decorator, Angular now walks up the injector ancestor tree
|
||||||
|
until it finds the logger at the `AppComponent` level. The logger logic kicks in and the hero display updates
|
||||||
|
with the gratuitous "!!!", indicating that the logger was found.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png" alt="Without @Host"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
On the other hand, if we restore the `@Host()` decorator and comment out `@Optional`,
|
||||||
|
the application fails for lack of the required logger at the host component level.
|
||||||
|
<br>
|
||||||
|
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
|
||||||
|
<a id="component-element"></a>## Inject the component's element
|
||||||
|
|
||||||
|
On occasion we might need to access a component's corresponding DOM element.
|
||||||
|
Although we strive to avoid it, many visual effects and 3rd party tools (such as jQuery)
|
||||||
|
require DOM access.
|
||||||
|
|
||||||
|
To illustrate, we've written a simplified version of the `HighlightDirective` from
|
||||||
|
the [Attribute Directives](../guide/attribute-directives.html) chapter.
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/highlight.directive.ts'}
|
||||||
|
|
||||||
|
The directive sets the background to a highlight color when the user mouses over the
|
||||||
|
DOM element to which it is applied.
|
||||||
|
|
||||||
|
Angular set the constructor's `el` parameter to the injected `ElementRef` which is
|
||||||
|
a wrapper around that DOM element.
|
||||||
|
Its `nativeElement` property exposes the DOM element for the directive to manipulate.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The following image shows the effect of mousing over the `<hero-bios-and-contacts>` tag.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/highlight.png" alt="Highlighted bios"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<a id="providers"></a>
|
||||||
|
## Define dependencies with providers
|
||||||
|
|
||||||
|
In this section we learn to write providers that deliver dependent services.
|
||||||
|
|
||||||
|
### Background
|
||||||
|
We get a service from a dependency injector by giving it a ***token***.
|
||||||
|
|
||||||
|
We usually let Angular handle this transaction for us by specifying a constructor parameter and its type.
|
||||||
|
The parameter type serves as the injector lookup *token*.
|
||||||
|
Angular passes this token to the injector and assigns the result to the parameter.
|
||||||
|
Here's a typical example:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/hero-bios.component.ts' region='ctor'}
|
||||||
|
|
||||||
|
Angular asks the injector for the service associated with the `LoggerService` and
|
||||||
|
and assigns the returned value to the `logger` parameter.
|
||||||
|
|
||||||
|
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*.
|
||||||
|
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](#optional).
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### 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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
We need other ways to deliver dependency values and that means we need other ways to specify a provider.
|
||||||
|
|
||||||
|
The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why we need them.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a provide}
|
||||||
|
#### The *provide* object literal
|
||||||
|
|
||||||
|
The `provide` object literal takes a *token* and a *definition object*.
|
||||||
|
The *token* is usually a class but [it doesn't have to be](#tokens).
|
||||||
|
|
||||||
|
The *definition* object has one main property, (e.g. `useValue`) that indicates how the provider
|
||||||
|
should create or return the provided value.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a usevalue}
|
||||||
|
#### useValue - the *value provider*
|
||||||
|
|
||||||
|
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object.
|
||||||
|
|
||||||
|
Use this technique to provide *runtime configuration constants* such as web-site base addresses and feature flags.
|
||||||
|
We often use a *value provider* in a unit test to replace a production service with a fake or mock.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The `TITLE` provider token is *not a class*.
|
||||||
|
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken).
|
||||||
|
We often use an `OpaqueToken` when the dependency is a simple value like a string, a number, or a function.
|
||||||
|
|
||||||
|
The value of a *value provider* must be defined *now*. We can't create the value later.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The other providers create their values *lazily* when they're needed for injection.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a useclass}
|
||||||
|
#### useClass - the *class provider*
|
||||||
|
|
||||||
|
The `useClass` provider creates and returns new instance of the specified class.
|
||||||
|
|
||||||
|
Use this technique to ***substitute an alternative implementation*** for a common or default class.
|
||||||
|
The alternative could implement a different strategy, extend the default class,
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a useexisting}
|
||||||
|
#### useExisting - the *alias provider*
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
|
||||||
|
We're aliasing for that very purpose here.
|
||||||
|
Imagine that the `LoggerService` had a large API (it's actually only three methods and a property).
|
||||||
|
We want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='minimal-logger'}
|
||||||
|
|
||||||
|
The constructor's `logger` parameter is typed as `MinimalLogger` so only its two members are visible in TypeScript:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService`
|
||||||
|
which happens to be the `DateLoggerService` thanks to the override provider registered previously via `useClass`.
|
||||||
|
The following image, which displays the logging date, confirms the point:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a usefactory}
|
||||||
|
#### useFactory - the *factory provider*
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Use this technique to ***create a dependency object***
|
||||||
|
with a factory function whose inputs are some ***combination of injected services and local state***.
|
||||||
|
|
||||||
|
The *dependency object* doesn't have to be a class instance. It could be anything.
|
||||||
|
In this example, the *dependency object* is a string of the names of the runners-up
|
||||||
|
to the "Hero of the Month" contest.
|
||||||
|
|
||||||
|
The local state is the number `2`, the number of runners-up this component should show.
|
||||||
|
We execute `runnersUpFactory` immediately with `2`.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
That returned function takes a winning `Hero` and a `HeroService` as arguments.
|
||||||
|
|
||||||
|
Angular supplies these arguments from injected values identified by
|
||||||
|
the two *tokens* in the `deps` array.
|
||||||
|
The two `deps` values are *tokens* that the injector uses
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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*
|
||||||
|
|
||||||
|
Angular dependency injection is easiest when the provider *token* is a class
|
||||||
|
that is also the type of the returned dependency object (what we usually call the *service*).
|
||||||
|
|
||||||
|
But the token doesn't have to be a class and even when it is a class,
|
||||||
|
it doesn't have to be the same type as the returned object.
|
||||||
|
That's the subject of our next section.
|
||||||
|
|
||||||
|
<a id="class-interface"></a>
|
||||||
|
### class-interface
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The `MinimalLogger` is an abstract class.
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/date-logger.service.ts' region='minimal-logger'}
|
||||||
|
|
||||||
|
We usually inherit from an abstract class.
|
||||||
|
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
|
||||||
|
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
|
||||||
|
|
||||||
|
We call a class used in this way a ***class-interface***.
|
||||||
|
The key benefit of a *class-interface* is that we can get the strong-typing of an interface
|
||||||
|
and we can ***use it as a provider token*** in the same manner as a normal class.
|
||||||
|
|
||||||
|
A ***class-interface*** should define *only* the members that its consumers are allowed to call.
|
||||||
|
Such a narrowing interface helps decouple the concrete class from its consumers.
|
||||||
|
The `MinimalLogger` defines just two of the `LoggerClass` members.
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
They exist only in the TypeScript design space.
|
||||||
|
They disappear after the code is transpiled to JavaScript.
|
||||||
|
|
||||||
|
A provider token must be a real JavaScript object of some kind:
|
||||||
|
a function, an object, a string ... a class.
|
||||||
|
|
||||||
|
Using a class as an interface gives us the characteristics of an interface in a JavaScript object.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
It never grows larger no matter how many members we add *as long as they are typed but not implemented*.
|
||||||
|
|
||||||
|
|
||||||
|
{@a opaque-token}
|
||||||
|
### OpaqueToken
|
||||||
|
|
||||||
|
Dependency objects can be simple values like dates, numbers and strings or
|
||||||
|
shapeless objects like arrays and functions.
|
||||||
|
|
||||||
|
Such objects don't have application interfaces and therefore aren't well represented by a class.
|
||||||
|
They're better represented by a token that is both unique and symbolic,
|
||||||
|
a JavaScript object that has a friendly name but won't conflict with
|
||||||
|
another token that happens to have the same name.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
We created the `TITLE` token like this:
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts' region='opaque-token'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a di-inheritance}
|
||||||
|
|
||||||
|
## Inject into a derived class
|
||||||
|
We must take care when writing a component that inherits from another component.
|
||||||
|
If the base component has injected dependencies,
|
||||||
|
we must re-provide and re-inject them in the derived class
|
||||||
|
and then pass them down to the base class through the constructor.
|
||||||
|
|
||||||
|
In this contrived example, `SortedHeroesComponent` inherits from `HeroesBaseComponent`
|
||||||
|
to display a *sorted* list of heroes.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/sorted-heroes.png" alt="Sorted Heroes"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The `HeroesBaseComponent` could stand on its own.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
(we said it was contrived).
|
||||||
|
|
||||||
|
Unfortunately, Angular cannot inject the `HeroService` directly into the base class.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Now take note of the `afterGetHeroes` method.
|
||||||
|
Our first instinct was to create an `ngOnInit` method in `SortedHeroesComponent` and do the sorting there.
|
||||||
|
But Angular calls the *derived* class's `ngOnInit` *before* calling the base class's `ngOnInit`
|
||||||
|
so we'd be sorting the heroes array *before they arrived*. That produces a nasty error.
|
||||||
|
|
||||||
|
Overriding the base class's `afterGetHeroes` method solves the problem
|
||||||
|
|
||||||
|
These complications argue for *avoiding component inheritance*.
|
||||||
|
|
||||||
|
|
||||||
|
{@a find-parent}
|
||||||
|
|
||||||
|
## Find a parent component by injection
|
||||||
|
|
||||||
|
Application components often need to share information.
|
||||||
|
We prefer the more loosely coupled techniques such as data binding and service sharing.
|
||||||
|
But sometimes it makes sense for one component to have a direct reference to another component
|
||||||
|
perhaps to access values or call methods on that component.
|
||||||
|
|
||||||
|
Obtaining a component reference is a bit tricky in Angular.
|
||||||
|
Although an Angular application is a tree of components,
|
||||||
|
there is no public API for inspecting and traversing that tree.
|
||||||
|
|
||||||
|
There is an API for acquiring a child reference
|
||||||
|
(checkout `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`).
|
||||||
|
|
||||||
|
There is no public API for acquiring a parent reference.
|
||||||
|
But because every component instance is added to an injector's container,
|
||||||
|
we can use Angular dependency injection to reach a parent component.
|
||||||
|
|
||||||
|
This section describes some techniques for doing that.
|
||||||
|
|
||||||
|
<a id="known-parent"></a>
|
||||||
|
### Find a parent component of known type
|
||||||
|
|
||||||
|
We use standard class injection to acquire a parent component whose type we know.
|
||||||
|
|
||||||
|
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
|
||||||
|
|
||||||
|
{@a alex}
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-1'}
|
||||||
|
|
||||||
|
*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'}
|
||||||
|
|
||||||
|
We added the [@Optional](#optional) qualifier for safety but
|
||||||
|
the <live-example name="cb-dependency-injection"></live-example>
|
||||||
|
confirms that the `alex` parameter is set.
|
||||||
|
|
||||||
|
<a id="base-parent"></a>
|
||||||
|
### Cannot find a parent by its base class
|
||||||
|
|
||||||
|
What if we do *not* know the concrete parent component class?
|
||||||
|
|
||||||
|
A re-usable component might be a child of multiple components.
|
||||||
|
Imagine a component for rendering breaking news about a financial instrument.
|
||||||
|
For sound (cough) business reasons, this news component makes frequent calls
|
||||||
|
directly into its parent instrument as changing market data stream by.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
We are asking *can a component inject its parent via the parent's base class*?
|
||||||
|
|
||||||
|
The sample's `CraigComponent` explores this question. [Looking back](#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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Unfortunately, this does not work.
|
||||||
|
The <live-example name="cb-dependency-injection"></live-example>
|
||||||
|
confirms that the `alex` parameter is null.
|
||||||
|
*We cannot inject a parent by its base class.*
|
||||||
|
|
||||||
|
<a id="class-interface-parent"></a>
|
||||||
|
### Find a parent by its class-interface
|
||||||
|
|
||||||
|
We can find a parent component with a [class-interface](#class-interface).
|
||||||
|
|
||||||
|
The parent must cooperate by providing an *alias* to itself in the name of a *class-interface* token.
|
||||||
|
|
||||||
|
Recall that Angular always adds a component instance to its own injector;
|
||||||
|
that's why we could inject *Alex* into *Cathy* [earlier](#known-parent).
|
||||||
|
|
||||||
|
We write an [*alias provider*](#useexisting) — a `provide` object literal with a `useExisting` definition —
|
||||||
|
that creates an *alternative* way to inject the same component instance
|
||||||
|
and add that provider to the `providers` array of the `@Component` metadata for the `AlexComponent`:
|
||||||
|
|
||||||
|
{@a alex-providers}
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-providers'}
|
||||||
|
|
||||||
|
[Parent](#parent-token) is the provider's *class-interface* token.
|
||||||
|
The [*forwardRef*](#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'}
|
||||||
|
|
||||||
|
Here's *Alex* and family in action:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/alex.png" alt="Alex in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a parent-tree}
|
||||||
|
### Find the parent in a tree of parents
|
||||||
|
|
||||||
|
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
|
||||||
|
Both *Alice* and *Barry* implement the `Parent` *class-interface*.
|
||||||
|
|
||||||
|
*Barry* is the problem. He needs to reach his parent, *Alice*, and also be a parent to *Carol*.
|
||||||
|
That means he must both *inject* the `Parent` *class-interface* to get *Alice* and
|
||||||
|
*provide* a `Parent` to satisfy *Carol*.
|
||||||
|
|
||||||
|
Here's *Barry*:
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='barry'}
|
||||||
|
|
||||||
|
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
|
||||||
|
If we're going to keep writing [*alias providers*](#useexisting) like this we should create a [helper function](#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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="Carol's constructor">
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='carol-ctor'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
It's identical to *Carol*'s constructor except for the additional `@SkipSelf` decorator.
|
||||||
|
|
||||||
|
`@SkipSelf` is essential for two reasons:
|
||||||
|
|
||||||
|
1. It tells the injector to start its search for a `Parent` dependency in a component *above* itself,
|
||||||
|
which *is* what parent means.
|
||||||
|
|
||||||
|
2. Angular throws a cyclic dependency error if we omit the `@SkipSelf` decorator.
|
||||||
|
|
||||||
|
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
|
||||||
|
|
||||||
|
Here's *Alice*, *Barry* and family in action:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dependency-injection/alice.png" alt="Alice in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a parent-token}
|
||||||
|
### The *Parent* class-interface
|
||||||
|
We [learned earlier](#class-interface) that a *class-interface* is an abstract class used as an interface rather than as a base class.
|
||||||
|
|
||||||
|
Our example defines a `Parent` *class-interface* .
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='parent'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
Such a narrowing interface helps decouple the child component class from its parent components.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
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*](#forwardref):
|
||||||
|
|
||||||
|
{@example 'cb-dependency-injection/ts/src/app/parent-finder.component.ts' region='alex-providers'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a forwardref}
|
||||||
|
|
||||||
|
## Break circularities with a forward class reference (*forwardRef*)
|
||||||
|
|
||||||
|
The order of class declaration matters in TypeScript.
|
||||||
|
We can't refer directly to a class until it's been defined.
|
||||||
|
|
||||||
|
This isn't usually a problem, especially if we adhere to the recommended *one class per file* rule.
|
||||||
|
But sometimes circular references are unavoidable.
|
||||||
|
We're in a bind when class 'A refers to class 'B' and 'B' refers to 'A'.
|
||||||
|
One of them has to be defined first.
|
||||||
|
|
||||||
|
The Angular `forwardRef` function creates an *indirect* reference that Angular can resolve later.
|
||||||
|
|
||||||
|
The *Parent Finder* sample is full of circular class references that are impossible to break.
|
||||||
|
We face this dilemma when a class makes *a reference to itself*
|
||||||
|
as does the `AlexComponent` in its `providers` array.
|
||||||
|
The `providers` array is a property of the `@Component` decorator function which must
|
||||||
|
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'}
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
@title
|
||||||
|
Dynamic Component Loader
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Load components dynamically
|
||||||
|
|
||||||
|
@description
|
||||||
|
Component templates are not always fixed. An application may need to load new components at runtime.
|
||||||
|
|
||||||
|
In this cookbook we show how to use `ComponentFactoryResolver` to add components dynamically.
|
||||||
|
|
||||||
|
<a id="toc"></a>## Table of contents
|
||||||
|
|
||||||
|
[Dynamic Component Loading](#dynamic-loading)
|
||||||
|
|
||||||
|
[Where to load the component](#where-to-load)
|
||||||
|
|
||||||
|
[Loading components](#loading-components)
|
||||||
|
|
||||||
|
<a id="dynamic-loading"></a>## Dynamic Component Loading
|
||||||
|
|
||||||
|
The following example shows how to build a dynamic ad banner.
|
||||||
|
|
||||||
|
The hero agency is planning an ad campaign with several different ads cycling through the banner.
|
||||||
|
|
||||||
|
New ad components are added frequently by several different teams. This makes it impractical to use a template with a static component structure.
|
||||||
|
|
||||||
|
Instead we need a way to load a new component without a fixed reference to the component in the ad banner's template.
|
||||||
|
|
||||||
|
Angular comes with its own API for loading components dynamically. In the following sections you will learn how to use it.
|
||||||
|
|
||||||
|
|
||||||
|
<a id="where-to-load"></a>## Where to load the component
|
||||||
|
|
||||||
|
Before components can be added we have to define an anchor point to mark where components can be inserted dynamically.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
`AdDirective` injects `ViewContainerRef` to gain access to the view container of the element that will become the host of the dynamically added component.
|
||||||
|
|
||||||
|
<a id="loading-components"></a>## Loading components
|
||||||
|
|
||||||
|
The next step is to implement the ad banner. Most of the implementation is in `AdBannerComponent`.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ad.service.ts">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/ad.service.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ad-item.ts">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/ad-item.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.module.ts">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.component">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### Resolving Components
|
||||||
|
|
||||||
|
`AdBanner` takes an array of `AdItem` objects as input. `AdItem` objects specify the type of component to load and any data to bind to the component.
|
||||||
|
|
||||||
|
The ad components making up the ad campaign are returned from `AdService`.
|
||||||
|
|
||||||
|
Passing an array of components to `AdBannerComponent` allows for a dynamic list of ads without static elements in the template.
|
||||||
|
|
||||||
|
`AdBannerComponent` cycles through the array of `AdItems` and loads the corresponding components on an interval. Every 3 seconds a new component is loaded.
|
||||||
|
|
||||||
|
`ComponentFactoryResolver` is used to resolve a `ComponentFactory` for each specific component. The component factory is need to create an instance of the component.
|
||||||
|
|
||||||
|
`ComponentFactories` are generated by the Angular compiler.
|
||||||
|
|
||||||
|
Generally the compiler will generate a component factory for any component referenced in a template.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Components are added to the template by calling `createComponent` on the `ViewContainerRef` reference.
|
||||||
|
|
||||||
|
`createComponent` returns a reference to the loaded component. The component reference can be used to pass input data or call methods to interact with the component.
|
||||||
|
|
||||||
|
In the Ad banner, all components implement a common `AdComponent` interface to standardize the api for passing data to the components.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="hero-profile.component.ts">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ad.component.ts">
|
||||||
|
{@example 'cb-dynamic-component-loader/ts/src/app/ad.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
The final ad banner looks like this:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads"> </img>
|
||||||
|
</figure>
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
@title
|
||||||
|
Dynamic Forms
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Render dynamic forms with FormGroup
|
||||||
|
|
||||||
|
@description
|
||||||
|
We can't always justify the cost and time to build handcrafted forms,
|
||||||
|
especially if we'll need a great number of them, they're similar to each other, and they change frequently
|
||||||
|
to meet rapidly changing business and regulatory requirements.
|
||||||
|
|
||||||
|
It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
|
||||||
|
|
||||||
|
In this cookbook we show how to use `formGroup` to dynamically render a simple form with different control types and validation.
|
||||||
|
It's a primitive start.
|
||||||
|
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
|
||||||
|
All such greatness has humble beginnings.
|
||||||
|
|
||||||
|
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
|
||||||
|
The agency is constantly tinkering with the application process.
|
||||||
|
We can create the forms on the fly *without changing our application code*.
|
||||||
|
<a id="toc"></a>## Table of contents
|
||||||
|
|
||||||
|
[Bootstrap](#bootstrap)
|
||||||
|
|
||||||
|
[Question Model](#object-model)
|
||||||
|
|
||||||
|
[Form Component](#form-component)
|
||||||
|
|
||||||
|
[Questionnaire Metadata](#questionnaire-metadata)
|
||||||
|
|
||||||
|
[Dynamic Template](#dynamic-template)
|
||||||
|
**See the <live-example name="cb-dynamic-form"></live-example>**.
|
||||||
|
|
||||||
|
<a id="bootstrap"></a>## Bootstrap
|
||||||
|
|
||||||
|
We start by creating an `NgModule` called `AppModule`.
|
||||||
|
|
||||||
|
In our example we will be using Reactive Forms.
|
||||||
|
|
||||||
|
Reactive Forms belongs to a different `NgModule` called `ReactiveFormsModule`, so in order to access any Reactive Forms directives, we have to import `ReactiveFormsModule` from the `@angular/forms` library.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="main.ts">
|
||||||
|
{@example 'cb-dynamic-form/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
<a id="object-model"></a>## Question Model
|
||||||
|
|
||||||
|
The next step is to define an object model that can describe all scenarios needed by the form functionality.
|
||||||
|
The hero application process involves a form with a lot of questions.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dynamic-form/ts/src/app/question-textbox.ts'}
|
||||||
|
|
||||||
|
`DropdownQuestion` presents a list of choices in a select box.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dynamic-form/ts/src/app/question-dropdown.ts'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="dynamic-form.component.ts">
|
||||||
|
{@example 'cb-dynamic-form/ts/src/app/dynamic-form.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="dynamic-form-question.component.ts">
|
||||||
|
{@example 'cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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.
|
||||||
|
The `ngSwitch` determines which type of question to display.
|
||||||
|
|
||||||
|
In both components we're relying on Angular's **formGroup** to connect the template HTML to the
|
||||||
|
underlying control objects, populated from the question model with display and validation rules.
|
||||||
|
|
||||||
|
`formControlName` and `formGroup` are directives defined in `ReactiveFormsModule`. Our templates can can access these directives directly since we imported `ReactiveFormsModule` from `AppModule`.
|
||||||
|
<a id="questionnaire-metadata"></a>## Questionnaire data`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
||||||
|
|
||||||
|
The set of questions we have defined for the job application is returned from the `QuestionService`.
|
||||||
|
In a real app we'd retrieve these questions from storage.
|
||||||
|
|
||||||
|
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Finally, we display an instance of the form in the `AppComponent` shell.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-dynamic-form/ts/src/app/app.component.ts'}
|
||||||
|
|
||||||
|
<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
|
||||||
|
outside the objects returned by `QuestionService`.
|
||||||
|
|
||||||
|
This is very important since it allows us to repurpose the components for any type of survey
|
||||||
|
as long as it's compatible with our *question* object model.
|
||||||
|
The key is the dynamic data binding of metadata used to render the form
|
||||||
|
without making any hardcoded assumptions about specific questions.
|
||||||
|
In addition to control metadata, we are also adding validation dynamically.
|
||||||
|
|
||||||
|
The *Save* button is disabled until the form is in a valid state.
|
||||||
|
When the form is valid, we can click *Save* and the app renders the current form values as JSON.
|
||||||
|
This proves that any user input is bound back to the data model.
|
||||||
|
Saving and retrieving the data is an exercise for another time.
|
||||||
|
The final form looks like this:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
[Back to top](#top)
|
|
@ -0,0 +1,486 @@
|
||||||
|
@title
|
||||||
|
Form Validation
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Validate user's form entries
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
|
||||||
|
{@a top}
|
||||||
|
We can 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
|
||||||
|
using first the template-driven forms and then the reactive forms approach.
|
||||||
|
Learn more about these choices in the [Forms chapter.](../guide/forms.html)
|
||||||
|
|
||||||
|
|
||||||
|
{@a toc}
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
[Simple Template-Driven Forms](#template1)
|
||||||
|
|
||||||
|
[Template-Driven Forms with validation messages in code](#template2)
|
||||||
|
|
||||||
|
[Reactive Forms with validation in code](#reactive)
|
||||||
|
|
||||||
|
[Custom validation](#custom-validation)
|
||||||
|
|
||||||
|
[Testing](#testing)
|
||||||
|
|
||||||
|
|
||||||
|
{@a live-example}
|
||||||
|
**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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a template1}
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html' region='name-with-error-msg'}
|
||||||
|
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
- 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_
|
||||||
|
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 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`.
|
||||||
|
|
||||||
|
The full template repeats this kind of layout for each data entry control on the form.
|
||||||
|
#### 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 checks for `dirty` and `touched` prevent premature display of errors.
|
||||||
|
|
||||||
|
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter.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'}
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="template/hero-form-template1.component.ts">
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a template2}
|
||||||
|
## Template-Driven Forms with validation messages in code
|
||||||
|
|
||||||
|
While the layout is straightforward,
|
||||||
|
there are obvious shortcomings with the way we handle 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.
|
||||||
|
|
||||||
|
* The messages are static strings, hard-coded into the template.
|
||||||
|
We often require dynamic messages that we should shape in code.
|
||||||
|
|
||||||
|
We 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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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](#live-example)).
|
||||||
|
We discuss [custom validation directives](#custom-validation) later in this cookbook.
|
||||||
|
|
||||||
|
- The `#name` template variable is gone because we no longer refer to the Angular control for this element.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
`#heroForm` template variable in the `<form>` element:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template1.component.html' region='form-tag'}
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='view-child'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
- Angular calls the `ngAfterViewChecked` [lifecycle hook method](../guide/lifecycle-hooks.html#afterview)
|
||||||
|
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.
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='handler'}
|
||||||
|
|
||||||
|
The `onValueChanged` handler interprets user data entry.
|
||||||
|
The `data` object passed into the handler contains the current element values.
|
||||||
|
The handler ignores them. Instead, it iterates over the fields of the component's `formErrors` object.
|
||||||
|
|
||||||
|
The `formErrors` is a dictionary of the hero fields that have validation rules and their current error messages.
|
||||||
|
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.
|
||||||
|
|
||||||
|
We'll need some error messages of course, a set for each validated property, one message per validation rule:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.ts' region='messages'}
|
||||||
|
|
||||||
|
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
|
||||||
|
|
||||||
|
### Is this an improvement?
|
||||||
|
|
||||||
|
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.
|
||||||
|
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>`.
|
||||||
|
|
||||||
|
After moving the validation messaging to the component,
|
||||||
|
the template grows more slowly and proportionally.
|
||||||
|
Each field has approximately the same number of lines no matter its number of validation rules.
|
||||||
|
The component also grows proportionally, at the rate of one line per validated field
|
||||||
|
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.
|
||||||
|
In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.
|
||||||
|
|
||||||
|
### _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.
|
||||||
|
|
||||||
|
We've been reviewing the "Template-driven" approach which requires the `FormsModule`
|
||||||
|
Here's how we imported it in the `HeroFormTemplateModule`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template.module.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
We haven'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](#live-example) if you're interested.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a reactive}
|
||||||
|
## Reactive Forms
|
||||||
|
|
||||||
|
In the template-driven approach, you markup the template with form elements, validation attributes,
|
||||||
|
and `ng...` directives from the Angular `FormsModule`.
|
||||||
|
At runtime, Angular interprets the template and derives its _form control model_.
|
||||||
|
|
||||||
|
**Reactive Forms** takes a different approach.
|
||||||
|
You create the form control model in code. You write the template with form elements
|
||||||
|
and`form...` directives from the Angular `ReactiveFormsModule`.
|
||||||
|
At runtime, Angular binds the template elements to your control model based on your instructions.
|
||||||
|
|
||||||
|
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](#testing) validation and control logic with isolated unit tests.
|
||||||
|
|
||||||
|
The third cookbook sample re-writes the hero form in _reactive forms_ style.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Component template
|
||||||
|
|
||||||
|
We 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'}
|
||||||
|
|
||||||
|
Then we 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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
- the validation attributes are gone (except `required`) because we'll be validating in code.
|
||||||
|
|
||||||
|
- `required` remains, not for validation purposes (we'll cover that in the code),
|
||||||
|
but rather for css styling and accessibility.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.### 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`.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
- we inject the `FormBuilder` in a constructor.
|
||||||
|
|
||||||
|
- we call a `buildForm` method in the `ngOnInit` [lifecycle hook method](../guide/lifecycle-hooks.html#hooks-overview)
|
||||||
|
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.
|
||||||
|
Then it attaches the same `onValueChanged` handler (there's a one line difference)
|
||||||
|
to the form's `valueChanged` event and calls it immediately
|
||||||
|
to set error messages for the new control model.
|
||||||
|
#### _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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The `forbiddenNames` validator on the `"name"` control is a custom validator,
|
||||||
|
discussed in a separate [section below](#custom-validation).
|
||||||
|
|
||||||
|
Learn more about `FormBuilder` in a _forthcoming_ chapter on reactive forms.
|
||||||
|
#### Committing hero value changes
|
||||||
|
|
||||||
|
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
|
||||||
|
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
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
Run the [live example](#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
|
||||||
|
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:
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts' region='custom-validator'}
|
||||||
|
|
||||||
|
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
|
||||||
|
and returns a validator function.
|
||||||
|
|
||||||
|
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.
|
||||||
|
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}`).
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/template/hero-form-template2.component.html' region='name-input'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The rest of the directive is unremarkable and we present it here without further comment.
|
||||||
|
|
||||||
|
{@example 'cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts' region='directive'}
|
||||||
|
|
||||||
|
|
||||||
|
See the [Attribute Directives](../guide/attribute-directives.html) chapter.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a testing}
|
||||||
|
## Testing Considerations
|
||||||
|
|
||||||
|
We 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.
|
|
@ -0,0 +1,620 @@
|
||||||
|
@title
|
||||||
|
Internationalization (i18n)
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Translate the app's template text into multiple languages.
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
|
||||||
|
{@a top}
|
||||||
|
Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Angular and i18n template translation](#angular-i18n)
|
||||||
|
* [Mark text with the _i18n_ attribute](#i18n-attribute)
|
||||||
|
* [Add _i18n-..._ translation attributes](#translate-attributes)
|
||||||
|
* [Handle singular and plural](#cardinality)
|
||||||
|
* [Select among alternative texts](#select)
|
||||||
|
* [Create a translation source file with the **_ng-xi18n_ extraction tool**](#ng-xi18n)
|
||||||
|
* [Translate text messages](#translate)
|
||||||
|
* [Merge the completed translation file into the app](#merge)
|
||||||
|
* [Merge with the JIT compiler](#jit)
|
||||||
|
* [Internationalization with the AOT compiler](#aot)
|
||||||
|
* [Translation file maintenance and _id_ changes](#maintenance)
|
||||||
|
**Try this** <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
|
||||||
|
of a JIT-compiled app, translated into Spanish.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a angular-i18n}
|
||||||
|
|
||||||
|
## Angular and _i18n_ template translation
|
||||||
|
|
||||||
|
Application internationalization is a challenging, many-faceted effort that
|
||||||
|
takes dedication and enduring commitment.
|
||||||
|
Angular's _i18n_ internationalization facilities can help.
|
||||||
|
|
||||||
|
This page describes the _i18n_ tools available to assist translation of component template text
|
||||||
|
into multiple languages.
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
|
||||||
|
|
||||||
|
1. A translator edits that file, translating the extracted text messages into the target language,
|
||||||
|
and returns the file to you.
|
||||||
|
|
||||||
|
1. The Angular compiler imports the completed translation files,
|
||||||
|
replaces the original messages with translated text, and generates a new version of the application
|
||||||
|
in the target language.
|
||||||
|
|
||||||
|
You need to build and deploy a separate version of the application for each supported language.
|
||||||
|
|
||||||
|
|
||||||
|
{@a i18n-attribute}
|
||||||
|
|
||||||
|
## Mark text with the _i18n_ attribute
|
||||||
|
|
||||||
|
The Angular `i18n` attribute is a marker for translatable content.
|
||||||
|
Place it on every element tag whose fixed text should be translated.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
`i18n` is not an Angular _directive_.
|
||||||
|
It's a custom _attribute_, recognized by Angular tools and compilers.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### Help the translator with a _description_ and _intent_
|
||||||
|
|
||||||
|
In order to translate it accurately, the translator may
|
||||||
|
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'}
|
||||||
|
|
||||||
|
In order to deliver a correct translation, the translator may need to
|
||||||
|
know your _intent_—the true _meaning_ of the text
|
||||||
|
within _this particular_ application context.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
|
||||||
|
to facilitiate contextually-specific translations.
|
||||||
|
|
||||||
|
### Translate text without creating an element
|
||||||
|
|
||||||
|
Suppose there is a stretch of text that you'd like to translate.
|
||||||
|
You could wrap it in a `<span>` tag but for some reason (CSS comes to mind)
|
||||||
|
you don't want to create a new DOM element merely to facilitate translation.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
(2) Wrap the text in a pair of HTML comments:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-with-comment'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a translate-attributes}
|
||||||
|
## Add _i18n-..._ translation attributes
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The `title` attribute needs to be translated.
|
||||||
|
Angular i18n support has more translation attributes in the form,`i18n-x`, where `x` is the
|
||||||
|
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'}
|
||||||
|
|
||||||
|
You can also assign a meaning and a description with the `i18n-x="<meaning>|<description>"` syntax.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a cardinality}
|
||||||
|
## Handle singular and plural
|
||||||
|
|
||||||
|
Different languages have different pluralization rules.
|
||||||
|
|
||||||
|
Suppose your application says something about a collection of wolves.
|
||||||
|
In English, depending upon the number of wolves, you could display "no wolves", "one wolf", "two wolves", or "a wolf pack".
|
||||||
|
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'}
|
||||||
|
|
||||||
|
* The first parameter is the key. It is bound to the component property (`wolves`)
|
||||||
|
that determines the number of wolves.
|
||||||
|
* The second parameter identifies this as a `plural` translation type.
|
||||||
|
* The third parameter defines a pluralization pattern consisting of pluralization
|
||||||
|
categories and their matching values.
|
||||||
|
|
||||||
|
Pluralization categories include:
|
||||||
|
* =0
|
||||||
|
* =1
|
||||||
|
* =5
|
||||||
|
* few
|
||||||
|
* other
|
||||||
|
|
||||||
|
Put the default _English_ translation in braces (`{}`) next to the pluralization category.
|
||||||
|
* When you're talking about one wolf, you could write `=1 {one wolf}`.
|
||||||
|
* For zero wolves, you could write `=0 {no wolves}`.
|
||||||
|
* For two wolves, you could write `=2 {two wolves}`.
|
||||||
|
|
||||||
|
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}`.
|
||||||
|
|
||||||
|
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
|
||||||
|
<a href="http://cldr.unicode.org/" target="_blank" title="CLDR">Common Locale Data Repository (CLDR),</a>
|
||||||
|
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.
|
||||||
|
These text alternatives require translation too.
|
||||||
|
|
||||||
|
You can handle this with a `select` translation.
|
||||||
|
A `select` also follows the
|
||||||
|
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU message syntax</a>.
|
||||||
|
You choose among alternative translation based on a string value instead of a number.
|
||||||
|
|
||||||
|
The following format message in the component template binds to the component's `gender`
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a ng-xi18n}
|
||||||
|
|
||||||
|
## Create a translation source file with the _ng-xi18n_ tool
|
||||||
|
|
||||||
|
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
|
||||||
|
into a translation source file in an industry standard format.
|
||||||
|
|
||||||
|
This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
|
||||||
|
If you haven't already installed the CLI and its `platform-server` peer dependency, do so now:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm install @angular/compiler-cli @angular/platform-server --save
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Open a terminal window at the root of the application project and enter the `ng-xi18n` command:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
./node_modules/.bin/ng-xi18n
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
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>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a other-formats}
|
||||||
|
### Other translation formats
|
||||||
|
|
||||||
|
You can generate a file named **`messages.xmb`** in the
|
||||||
|
<a href="http://cldr.unicode.org/development/development-process/design-proposals/xmb" target="_blank">XML Message Bundle (XMB)</a> format
|
||||||
|
by adding the `--i18nFormat=xmb` flag.
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
./node_modules/.bin/ng-xi18n --i18nFormat=xmb
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
This sample sticks with the _XLIFF_ format.
|
||||||
|
|
||||||
|
|
||||||
|
{@a ng-xi18n-options}
|
||||||
|
### Other options
|
||||||
|
You may have to specify additional options.
|
||||||
|
For example, if the `tsconfig.json` TypeScript configuration
|
||||||
|
file is located somewhere other than in the root folder,
|
||||||
|
you must identify the path to it with the `-p` option:
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
|
||||||
|
./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
|
||||||
|
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a npm-i18n-script}
|
||||||
|
### Add an _npm_ script for convenience
|
||||||
|
|
||||||
|
Consider adding a convenience shortcut to the `scripts` section of the `package.json`
|
||||||
|
to make the command easier to remember and run:
|
||||||
|
<code-example format='.' language='sh'>
|
||||||
|
"scripts": {
|
||||||
|
"i18n": "ng-xi18n",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Now you can issue command variations such as these:
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm run i18n
|
||||||
|
npm run i18n -- -p path/to/tsconfig.json
|
||||||
|
npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Note the `--` flag before the options.
|
||||||
|
It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a translate}
|
||||||
|
|
||||||
|
## Translate text messages
|
||||||
|
|
||||||
|
The `ng-xi18n` command generates a translation source file
|
||||||
|
in the project root folder named `messages.xlf`.
|
||||||
|
The next step is to translate the English language template
|
||||||
|
text into the specific language translation
|
||||||
|
files. The cookbook sample creates a Spanish translation file.
|
||||||
|
|
||||||
|
|
||||||
|
{@a localization-folder}
|
||||||
|
### Create a localization folder
|
||||||
|
|
||||||
|
You will probably translate into more than one other language so it's a good idea
|
||||||
|
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.
|
||||||
|
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/`.
|
||||||
|
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>.
|
||||||
|
|
||||||
|
Make a copy of the `messages.xlf` file, put it in the `locale` folder and
|
||||||
|
rename it `messages.es.xlf`for the Spanish language translation.
|
||||||
|
Do the same for each target language.
|
||||||
|
|
||||||
|
### Translate text nodes
|
||||||
|
In the real world, you send the `messages.es.xlf` file to a Spanish translator who fills in the translations
|
||||||
|
using one of the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">many XLIFF file editors</a>.
|
||||||
|
|
||||||
|
This sample file is easy to translate without a special editor or knowledge of Spanish.
|
||||||
|
Open `messages.es.xlf` and find the first `<trans-unit>` section:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-hello'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Note that the tool generates the `id`. **Don't touch it.**
|
||||||
|
Its value depends on the content of the message and its assigned meaning.
|
||||||
|
Change either factor and the `id` changes as well.
|
||||||
|
See the **[translation file maintenance discussion](#maintenance)**.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Translate the other text nodes the same way:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-other-nodes'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a translate-plural-select}
|
||||||
|
## Translate _plural_ and _select_
|
||||||
|
Translating _plural_ and _select_ messages is a little tricky.
|
||||||
|
|
||||||
|
The `<source>` tag is empty for `plural` and `select` translation
|
||||||
|
units, which makes them hard to correlate with the original template.
|
||||||
|
The `XLIFF` format doesn't yet support the ICU rules; it soon will.
|
||||||
|
However, the `XMB` format does support the ICU rules.
|
||||||
|
|
||||||
|
You'll just have to look for them in relation to other translation units that you recognize from elsewhere in the source template.
|
||||||
|
In this example, you know the translation unit for the `select` must be just below the translation unit for the logo.
|
||||||
|
### Translate _plural_
|
||||||
|
To translate a `plural`, translate its ICU format match values:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-plural'}
|
||||||
|
|
||||||
|
### 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'}
|
||||||
|
|
||||||
|
The extraction tool broke that into _two_ translation units.
|
||||||
|
|
||||||
|
The first unit contains the text that was _outside_ the `select`.
|
||||||
|
In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `select` message.
|
||||||
|
Translate the text and leave the placeholder where it is.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translate-select-1'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Here they are together, after translation:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-select'}
|
||||||
|
|
||||||
|
|
||||||
|
<div class='l-main-content'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The entire template translation is complete. It's
|
||||||
|
time to incorporate that translation into the application.
|
||||||
|
|
||||||
|
<div id='app-pre-translation'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### The app before translation
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'cb-i18n/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.module.ts">
|
||||||
|
{@example 'cb-i18n/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/main.ts">
|
||||||
|
{@example 'cb-i18n/ts/src/main.1.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/locale/messages.es.xlf">
|
||||||
|
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a merge}
|
||||||
|
|
||||||
|
## Merge the completed translation file into the app
|
||||||
|
|
||||||
|
To merge the translated text into component templates,
|
||||||
|
compile the application with the completed translation file.
|
||||||
|
The process is the same whether the file is in `.xlf` format or
|
||||||
|
in another format (`.xlif` and `.xtb`) that Angular understands.
|
||||||
|
|
||||||
|
You provide the Angular compiler with three new pieces of information:
|
||||||
|
* the translation file
|
||||||
|
* the translation file format
|
||||||
|
* the <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
|
||||||
|
(`es` or `en-US` for instance)
|
||||||
|
|
||||||
|
_How_ you provide this information depends upon whether you compile with
|
||||||
|
the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
|
||||||
|
|
||||||
|
* With [JIT](#jit), you provide the information at bootstrap time.
|
||||||
|
* With [AOT](#aot), you pass the information as `ngc` options.
|
||||||
|
|
||||||
|
|
||||||
|
{@a jit}
|
||||||
|
|
||||||
|
### Merge with the JIT compiler
|
||||||
|
|
||||||
|
The JIT compiler compiles the application in the browser as the application loads.
|
||||||
|
Translation with the JIT compiler is a dynamic process of:
|
||||||
|
|
||||||
|
1. Determining the language version for the current user.
|
||||||
|
2. Importing the appropriate language translation file as a string constant.
|
||||||
|
3. Creating corresponding translation providers to guide the JIT compiler.
|
||||||
|
4. Bootstrapping the application with those providers.
|
||||||
|
|
||||||
|
Open `index.html` and revise the launch script as follows:
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/index.html' region='i18n'}
|
||||||
|
|
||||||
|
In this sample, the user's language is hardcoded as a global `document.locale` variable
|
||||||
|
in the `index.html`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a text-plugin}
|
||||||
|
### SystemJS Text plugin
|
||||||
|
|
||||||
|
Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`.
|
||||||
|
With the help of a text plugin, SystemJS can read any file as raw text and
|
||||||
|
return the contents as a string.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### Create translation providers
|
||||||
|
|
||||||
|
Three providers tell the JIT compiler how to translate the template texts for a particular language
|
||||||
|
while compiling the application:
|
||||||
|
|
||||||
|
* `TRANSLATIONS` is a string containing the content of the translation file.
|
||||||
|
* `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlif` or `xtb`.
|
||||||
|
* `LOCALE_ID` is the locale of the target language.
|
||||||
|
|
||||||
|
The `getTranslationProviders` function in the following `src/app/i18n-providers.ts`
|
||||||
|
creates those providers based on the user's _locale_
|
||||||
|
and the corresponding translation file:
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/app/i18n-providers.ts'}
|
||||||
|
|
||||||
|
1. It gets the locale from the global `document.locale` variable that was set in `index.html`.
|
||||||
|
|
||||||
|
1. If there is no locale or the language is U.S. English (`en-US`), there is no need to translate.
|
||||||
|
The function returns an empty `noProviders` array as a `Promise`.
|
||||||
|
It must return a `Promise` because this function could read a translation file asynchronously from the server.
|
||||||
|
|
||||||
|
1. It creates a transaction filename from the locale according to the name and location convention
|
||||||
|
[described earlier](#localization-folder).
|
||||||
|
|
||||||
|
1. The `getTranslationsWithSystemJs` method reads the translation and returns the contents as a string.
|
||||||
|
Notice that it appends `!text` to the filename, telling SystemJS to use the [text plugin](#text-plugin).
|
||||||
|
|
||||||
|
1. The callback composes a providers array with the three translation providers.
|
||||||
|
|
||||||
|
1. Finally, `getTranslationProviders` returns the entire effort as a promise.
|
||||||
|
|
||||||
|
### Bootstrap the app with translation providers
|
||||||
|
|
||||||
|
The Angular `bootstrapModule` method has a second, _options_ parameter
|
||||||
|
that can influence the behavior of the compiler.
|
||||||
|
|
||||||
|
You'll create an _options_ object with the translation providers from `getTranslationProviders`
|
||||||
|
and pass it to `bootstrapModule`.
|
||||||
|
Open the `src/main.ts` and modify the bootstrap code as follows:
|
||||||
|
|
||||||
|
{@example 'cb-i18n/ts/src/main.ts'}
|
||||||
|
|
||||||
|
Notice that it waits for the `getTranslationProviders` promise to resolve before
|
||||||
|
bootstrapping the app.
|
||||||
|
|
||||||
|
The app is now _internationalized_ for English and Spanish and there is a clear path for adding
|
||||||
|
more languages.
|
||||||
|
|
||||||
|
|
||||||
|
{@a aot}
|
||||||
|
|
||||||
|
### _Internationalize_ with the AOT compiler
|
||||||
|
|
||||||
|
The JIT compiler translates the application into the target language
|
||||||
|
while compiling dynamically in the browser.
|
||||||
|
That's flexible but may not be fast enough for your users.
|
||||||
|
|
||||||
|
The AOT (_Ahead-of-Time_) compiler is part of a build process that
|
||||||
|
produces a small, fast, ready-to-run application package.
|
||||||
|
When you internationalize with the AOT compiler, you pre-build
|
||||||
|
a separate application package for each
|
||||||
|
language. Then in the host web page (`index.html`),
|
||||||
|
you determine which language the user needs
|
||||||
|
and serve the appropriate application package.
|
||||||
|
|
||||||
|
This cookbook doesn't cover how to build multiple application packages and
|
||||||
|
serve them according to the user's language preference.
|
||||||
|
It does explain the few steps necessary to tell the AOT compiler to apply a translations file.
|
||||||
|
|
||||||
|
Internationalization with the AOT compiler requires
|
||||||
|
some setup specifically for AOT compilation.
|
||||||
|
Start with the application project as shown
|
||||||
|
[just before merging the translation file](#app-pre-translation)
|
||||||
|
and refer to the [AOT cookbook](aot-compiler.html) to make it _AOT-ready_.
|
||||||
|
|
||||||
|
Next, issue an `ngc` compile command for each supported language (including English).
|
||||||
|
The result is a separate version of the application for each language.
|
||||||
|
|
||||||
|
Tell AOT how to translate by adding three options to the `ngc` command:
|
||||||
|
* `--i18nFile`: the path to the translation file
|
||||||
|
* `--locale`: the name of the locale
|
||||||
|
* `--i18nFormat`: the format of the localization file
|
||||||
|
|
||||||
|
For this sample, the Spanish language command would be
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a maintenance}
|
||||||
|
## Translation file maintenance and _id_ changes
|
||||||
|
|
||||||
|
As the application evolves, you will change the _i18n_ markup
|
||||||
|
and re-run the `ng-xi18n` extraction tool many times.
|
||||||
|
The _new_ markup that you add is not a problem;
|
||||||
|
but _most_ changes to _existing_ markup trigger
|
||||||
|
generation of _new_ `id`s for the affected translation units.
|
||||||
|
|
||||||
|
After an `id` changes, the translation files are no longer in-sync.
|
||||||
|
**All translated versions of the application will fail** during re-compilation.
|
||||||
|
The error messages identify the old `id`s that are no longer valid but
|
||||||
|
they don't tell you what the new `id`s should be.
|
||||||
|
|
||||||
|
**Commit all translation message files to source control**,
|
||||||
|
especially the English source `messages.xlf`.
|
||||||
|
The difference between the old and the new `messages.xlf` file
|
||||||
|
help you find and update `id` changes across your translation files.
|
|
@ -0,0 +1,28 @@
|
||||||
|
@title
|
||||||
|
Cookbook
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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 issues with *Angular itself* to the [angular](https://github.com/angular/angular) github repository.
|
|
@ -0,0 +1,103 @@
|
||||||
|
@title
|
||||||
|
Set the Document Title
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Setting the document or window title using the Title service.
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
|
||||||
|
{@a top}
|
||||||
|
Our app should be able to make the browser title bar say whatever we want it to say.
|
||||||
|
This cookbook explains how to do it.**See the <live-example name="cb-set-document-title"></live-example>**.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
To see the browser title bar change in the live example,
|
||||||
|
open it again in the Plunker editor by clicking the icon in the upper right,
|
||||||
|
then pop out the preview window by clicking the blue 'X' button in the upper right corner.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<img src='/resources/images/devguide/plunker-switch-to-editor-button.png' width="200px" height="70px" alt="pop out the window" align="right"> </img> <br> </br> <img src='/resources/images/devguide/plunker-separate-window-button.png' width="200px" height="47px" alt="pop out the window" align="right"> </img>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## The problem with *<title>*
|
||||||
|
|
||||||
|
The obvious approach is to bind a property of the component to the HTML `<title>` like this:
|
||||||
|
<code-example format=''>
|
||||||
|
<title>{{This_Does_Not_Work}}</title>
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Sorry but that won't work.
|
||||||
|
The root component of our application is an element contained within the `<body>` tag.
|
||||||
|
The HTML `<title>` is in the document `<head>`, outside the body, making it inaccessible to Angular data binding.
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.html) service is a simple class that provides an API
|
||||||
|
for getting and setting the current HTML document title:
|
||||||
|
|
||||||
|
* `getTitle() : string` — Gets the title of the current HTML document.
|
||||||
|
* `setTitle( newTitle : string )` — Sets the title of the current HTML document.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
We bind that method to three anchor tags and, voilà!
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/cookbooks/set-document-title/set-title-anim.gif" alt="Set title"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.module.ts">
|
||||||
|
{@example 'cb-set-document-title/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'cb-set-document-title/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
## Why we provide the *Title* service in *bootstrap*
|
||||||
|
|
||||||
|
We generally recommended providing application-wide services in the root application component, `AppComponent`.
|
||||||
|
|
||||||
|
Here we recommend registering the title service during bootstrapping,
|
||||||
|
a location we reserve for configuring the runtime Angular environment.
|
||||||
|
|
||||||
|
That's exactly what we're doing.
|
||||||
|
The `Title` service is part of the Angular *browser platform*.
|
||||||
|
If we bootstrap our application into a different platform,
|
||||||
|
we'll have to provide a different `Title` service that understands the concept of a "document title" for that specific platform.
|
||||||
|
Ideally the application itself neither knows nor cares about the runtime environment.[Back to top](#top)
|
|
@ -0,0 +1,868 @@
|
||||||
|
@title
|
||||||
|
TypeScript to JavaScript
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Convert Angular TypeScript examples into ES6 and ES5 JavaScript
|
||||||
|
|
||||||
|
@description
|
||||||
|
Anything you can do with Angular in _TypeScript_, you can also do
|
||||||
|
in JavaScript. Translating from one language to the other is mostly a
|
||||||
|
matter of changing the way you organize your code and access Angular APIs.
|
||||||
|
|
||||||
|
_TypeScript_ is a popular language option for Angular development.
|
||||||
|
Most code examples on the Internet as well as on this site are written in _TypeScript_.
|
||||||
|
This cookbook contains recipes for translating _TypeScript_
|
||||||
|
code examples to _ES6_ and to _ES5_ so that JavaScript developers
|
||||||
|
can read and write Angular apps in their preferred dialect.
|
||||||
|
|
||||||
|
|
||||||
|
{@a toc}
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
[_TypeScript_ to _ES6_ to _ES5_](#from-ts)<br>
|
||||||
|
[Modularity: imports and exports](#modularity)<br>
|
||||||
|
[Classes and Class Metadata](#class-metadata)<br>
|
||||||
|
[_ES5_ DSL](#dsl)<br>
|
||||||
|
[Interfaces](#interfaces)<br>
|
||||||
|
[Input and Output Metadata](#io-decorators)<br>
|
||||||
|
[Dependency Injection](#dependency-injection)<br>
|
||||||
|
[Host Binding](#host-binding)<br>
|
||||||
|
[View and Child Decorators](#view-child-decorators)<br>
|
||||||
|
[AOT compilation in _TypeScript_ Only](#aot)<br>
|
||||||
|
|
||||||
|
**Run and compare the live <live-example name="cb-ts-to-js">_TypeScript_</live-example> and <live-example name="cb-ts-to-js" lang="js">JavaScript</live-example>
|
||||||
|
code shown in this cookbook.**
|
||||||
|
|
||||||
|
|
||||||
|
{@a from-ts}
|
||||||
|
|
||||||
|
## _TypeScript_ to _ES6_ to _ES5_
|
||||||
|
|
||||||
|
_TypeScript_
|
||||||
|
<a href="https://www.typescriptlang.org" target="_blank" title='"TypeScript is a typed, superset of JavaScript"'>is a typed superset of _ES6 JavaScript_</a>.
|
||||||
|
_ES6 JavaScript_ is a superset of _ES5 JavaScript_. _ES5_ is the kind of JavaScript that runs natively in all modern browsers.
|
||||||
|
The transformation of _TypeScript_ code all the way down to _ES5_ code can be seen as "shedding" features.
|
||||||
|
|
||||||
|
The downgrade progression is
|
||||||
|
* _TypeScript_ to _ES6-with-decorators_
|
||||||
|
* _ES6-with-decorators_ to _ES6-without-decorators_ ("_plain ES6_")
|
||||||
|
* _ES6-without-decorators_ to _ES5_
|
||||||
|
|
||||||
|
When translating from _TypeScript_ to _ES6-with-decorators_, remove
|
||||||
|
[class property access modifiers](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers)
|
||||||
|
such as `public` and `private`.
|
||||||
|
Remove most of the
|
||||||
|
[type declarations](https://www.typescriptlang.org/docs/handbook/basic-types.html),
|
||||||
|
such as `:string` and `:boolean`
|
||||||
|
but **keep the constructor parameter types which are used for dependency injection**.
|
||||||
|
|
||||||
|
|
||||||
|
From _ES6-with-decorators_ to _plain ES6_, remove all
|
||||||
|
[decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
|
||||||
|
and the remaining types.
|
||||||
|
You must declare properties in the class constructor (`this.title = '...'`) rather than in the body of the class.
|
||||||
|
|
||||||
|
Finally, from _plain ES6_ to _ES5_, the main missing features are `import`
|
||||||
|
statements and `class` declarations.
|
||||||
|
|
||||||
|
For _plain ES6_ transpilation you can _start_ with a setup similar to the
|
||||||
|
[_TypeScript_ quickstart](https://github.com/angular/quickstart) and adjust the application code accordingly.
|
||||||
|
Transpile with [Babel](https://babeljs.io/) using the `es2015` preset.
|
||||||
|
To use decorators and annotations with Babel, install the
|
||||||
|
[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations) preset as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a modularity}
|
||||||
|
|
||||||
|
## Importing and Exporting
|
||||||
|
|
||||||
|
### Importing Angular Code
|
||||||
|
|
||||||
|
In both _TypeScript_ and _ES6_, you import Angular classes, functions, and other members with _ES6_ `import` statements.
|
||||||
|
|
||||||
|
In _ES5_, you access the Angular entities of the [the Angular packages](../glossary.html#scoped-package)
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/app.module.es6' region='ng2import'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/app.module.es6' region='ng2import'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/app.module.js' region='ng2import'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Exporting Application Code
|
||||||
|
|
||||||
|
Each file in a _TypeScript_ or _ES6_ Angular application constitutes an _ES6_ module.
|
||||||
|
When you want to make something available to other modules, you `export` it.
|
||||||
|
|
||||||
|
_ES5_ lacks native support for modules.
|
||||||
|
In an Angular _ES5_ application, you load each file manually by adding a `<script>` tag to `index.html`.
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
The order of `<script>` tags is often significant.
|
||||||
|
You must load a file that defines a public JavaScript entity before a file that references that entity.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The best practice in _ES5_ is to create a form of modularity that avoids polluting the global scope.
|
||||||
|
Add one application namespace object such as `app` to the global `document`.
|
||||||
|
Then each code file "exports" public entities by attaching them to that namespace object, e.g., `app.HeroComponent`.
|
||||||
|
You could factor a large application into several sub-namespaces
|
||||||
|
which leads to "exports" along the lines of `app.heroQueries.HeroComponent`.
|
||||||
|
|
||||||
|
Every _ES5_ file should wrap code in an
|
||||||
|
[Immediately Invoked Function Expression (IIFE)](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='appexport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='appexport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='appexport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Importing Application Code
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6_ apps, you `import` things that have been exported from other modules.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/app.module.es6' region='appimport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/app.module.es6' region='appimport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/app.module.js' region='appimport'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
Alternatively, you can use a module loader such as Webpack or
|
||||||
|
Browserify in an Angular JavaScript project. In such a project, you would
|
||||||
|
use _CommonJS_ modules and the `require` function to load Angular framework code.
|
||||||
|
Then use `module.exports` and `require` to export and import application code.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a class-metadata}
|
||||||
|
|
||||||
|
## Classes and Class Metadata
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
|
||||||
|
Most Angular _TypeScript_ and _ES6_ code is written as classes.
|
||||||
|
|
||||||
|
Properties and method parameters of _TypeScript_ classes may be marked with the access modifiers
|
||||||
|
`private`, `internal`, and `public`.
|
||||||
|
Remove these modifiers when translating to JavaScript.
|
||||||
|
|
||||||
|
Most type declarations (e.g, `:string` and `:boolean`) should be removed when translating to JavaScript.
|
||||||
|
When translating to _ES6-with-decorators_, ***do not remove types from constructor parameters!***
|
||||||
|
|
||||||
|
Look for types in _TypeScript_ property declarations.
|
||||||
|
In general it is better to initialize such properties with default values because
|
||||||
|
many browser JavaScript engines can generate more performant code.
|
||||||
|
When _TypeScript_ code follows this same advice, it can infer the property types
|
||||||
|
and there is nothing to remove during translation.
|
||||||
|
|
||||||
|
In _ES6-without-decorators_, properties of classes must be assigned inside the constructor.
|
||||||
|
|
||||||
|
_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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='class'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='class'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='constructorproto'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
When writing in _TypeScript_ or _ES6-with-decorators_,
|
||||||
|
provide configuration and metadata by adorning a class with one or more *decorators*.
|
||||||
|
For example, you supply metadata to a component class by preceding its definition with a
|
||||||
|
[`@Component`](../api/core/index/Component-decorator.html) decorator function whose
|
||||||
|
argument is an object literal with metadata properties.
|
||||||
|
|
||||||
|
In _plain ES6_, you provide metadata by attaching an `annotations` array to the _class_.
|
||||||
|
Each item in the array is a new instance of a metadata decorator created with a similar metadata object literal.
|
||||||
|
|
||||||
|
In _ES5_, you also provide an `annotations` array but you attach it to the _constructor function_ rather than to a class.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6' region='metadata'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero.component.es6' region='metadata'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero.component.js' region='metadata'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
***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'}
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-title.component.es6' region='templateUrl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js' region='templateUrl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
## _ES5_ DSL
|
||||||
|
|
||||||
|
This _ES5_ pattern of creating a constructor and annotating it with metadata is so common that Angular
|
||||||
|
provides a convenience API to make it a little more compact and locates the metadata above the constructor,
|
||||||
|
as you would if you wrote in _TypeScript_ or _ES6-with-decorators_.
|
||||||
|
|
||||||
|
This _API_ (_Application Programming Interface_) is commonly known as the _ES5 DSL_ (_Domain Specific Language_).
|
||||||
|
|
||||||
|
Set an application namespace property (e.g., `app.HeroDslComponent`) to the result of an `ng.core.Component` function call.
|
||||||
|
Pass the same metadata object to `ng.core.Component` as you did before.
|
||||||
|
Then chain a call to the `Class` method which takes an object defining the class constructor and instance methods.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-helpful}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
Name the constructor
|
||||||
|
</header>
|
||||||
|
|
||||||
|
A **named** constructor displays clearly in the console log
|
||||||
|
if the component throws a runtime error.
|
||||||
|
An **unnamed** constructor displays as an anonymous function (e.g., `class0`)
|
||||||
|
which is impossible to find in the source code.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Properties with getters and setters
|
||||||
|
|
||||||
|
_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'}
|
||||||
|
|
||||||
|
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"
|
||||||
|
target="_blank" title="Defined Properties">defined property</a>.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### DSL for other classes
|
||||||
|
There are similar DSLs for other decorated classes.
|
||||||
|
You can define a directive with `ng.core.Directive`:
|
||||||
|
|
||||||
|
<code-example>
|
||||||
|
app.MyDirective = ng.core.Directive({
|
||||||
|
selector: '[myDirective]'
|
||||||
|
}).Class({
|
||||||
|
...
|
||||||
|
});
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
and a pipe with `ng.core.Pipe`:
|
||||||
|
<code-example>
|
||||||
|
app.MyPipe = ng.core.Pipe({
|
||||||
|
name: 'myPipe'
|
||||||
|
}).Class({
|
||||||
|
...
|
||||||
|
});
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a interfaces}
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
|
||||||
|
A _TypeScript_ interface helps ensure that a class implements the interface's members correctly.
|
||||||
|
We strongly recommend Angular interfaces where appropriate.
|
||||||
|
For example, the component class that implements the `ngOnInit` lifecycle hook method
|
||||||
|
should implement the `OnInit` interface.
|
||||||
|
|
||||||
|
_TypeScript_ interfaces exist for developer convenience and are not used by Angular at runtime.
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-lifecycle.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-lifecycle.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a io-decorators}
|
||||||
|
|
||||||
|
## Input and Output Metadata
|
||||||
|
|
||||||
|
### Input and Output Decorators
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_, you often add metadata to class _properties_ with _property decorators_.
|
||||||
|
For example, you apply [`@Input` and `@Output` property decorators](../guide/template-syntax.html#inputs-outputs)
|
||||||
|
to public class properties that will be the target of data binding expressions in parent components.
|
||||||
|
|
||||||
|
There is no equivalent of a property decorator in _ES5_ or _plain ES6_.
|
||||||
|
Fortunately, every property decorator has an equivalent representation in a class decorator metadata property.
|
||||||
|
A _TypeScript_ `@Input` property decorator can be represented by an item in the `Component` metadata's `inputs` array.
|
||||||
|
|
||||||
|
You already know how to add `Component` or `Directive` class metadata in _any_ JavaScript dialect so
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/confirm.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/confirm.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/confirm.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
In the previous example, one of the public-facing binding names (`cancelMsg`)
|
||||||
|
differs from the corresponding class property name (`notOkMsg`).
|
||||||
|
That's OK but you must tell Angular about it so that it can map an external binding of `cancelMsg`
|
||||||
|
to the component's `notOkMsg` property.
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_,
|
||||||
|
you specify the special binding name in the argument to the property decorator.
|
||||||
|
|
||||||
|
In _ES5_ and _plain ES6_ code, convey this pairing with the `propertyName: bindingName` syntax in the class metadata.
|
||||||
|
|
||||||
|
## Dependency Injection
|
||||||
|
Angular relies heavily on [Dependency Injection](../guide/dependency-injection.html) to provide services to the objects it creates.
|
||||||
|
When Angular creates a new component, directive, pipe or another service,
|
||||||
|
it sets the class constructor parameters to instances of services provided by an _Injector_.
|
||||||
|
|
||||||
|
The developer must tell Angular what to inject into each parameter.
|
||||||
|
|
||||||
|
### Injection by Class Type
|
||||||
|
|
||||||
|
The easiest and most popular technique in _TypeScript_ and _ES6-with-decorators_ is to set the constructor parameter type
|
||||||
|
to the class associated with the service to inject.
|
||||||
|
|
||||||
|
The _TypeScript_ transpiler writes parameter type information into the generated JavaScript.
|
||||||
|
Angular reads that information at runtime and locates the corresponding service in the appropriate _Injector_..
|
||||||
|
The _ES6-with-decorators_ transpiler does essentially the same thing using the same parameter-typing syntax.
|
||||||
|
|
||||||
|
_ES5_ and _plain ES6_ lack types so you must identify "injectables" by attaching a **`parameters`** array to the constructor function.
|
||||||
|
Each item in the array specifies the service's injection token.
|
||||||
|
|
||||||
|
As with _TypeScript_ the most popular token is a class,
|
||||||
|
or rather a _constructor function_ that represents a class in _ES5_ and _plain ES6_.
|
||||||
|
The format of the `parameters` array varies:
|
||||||
|
|
||||||
|
* _plain ES6_ — nest each constructor function in a sub-array.
|
||||||
|
|
||||||
|
* _ES5_ — simply list the constructor functions.
|
||||||
|
|
||||||
|
When writing with _ES5 DSL_, set the `Class.constructor` property to
|
||||||
|
an array whose first parameters are the injectable constructor functions and whose
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-di.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-di.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-di.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Injection with the @Inject decorator
|
||||||
|
|
||||||
|
Sometimes the dependency injection token isn't a class or constructor function.
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_, you precede the class constructor parameter
|
||||||
|
by calling the `@Inject()` decorator with the injection token.
|
||||||
|
In the following example, the token is the string `'heroName'`.
|
||||||
|
|
||||||
|
The other JavaScript dialects add a `parameters` array to the class contructor function.
|
||||||
|
Each item constains a new instance of `Inject`:
|
||||||
|
|
||||||
|
* _plain ES6_ — each item is a new instance of `Inject(token)` in a sub-array.
|
||||||
|
|
||||||
|
* _ES5_ — simply list the string tokens.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-di-inject.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-di-inject.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Additional Injection Decorators
|
||||||
|
|
||||||
|
You can qualify injection behavior with injection decorators from `@angular/core`.
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_,
|
||||||
|
you precede the constructor parameters with injection qualifiers such as:
|
||||||
|
* [`@Optional`](../api/core/index/Optional-decorator.html) sets the parameter to `null` if the service is missing
|
||||||
|
* [`@Attribute`](../api/core/index/Attribute-interface.html) to inject a host element attribute value
|
||||||
|
* [`@ContentChild`](../api/core/index/ContentChild-decorator.html) to inject a content child
|
||||||
|
* [`@ViewChild`](../api/core/index/ViewChild-decorator.html) to inject a view child
|
||||||
|
* [`@Host`](../api/core/index/Host-decorator.html) to inject a service in this component or its host
|
||||||
|
* [`@SkipSelf`](../api/core/index/SkipSelf-decorator.html) to inject a service provided in an ancestor of this component
|
||||||
|
|
||||||
|
In _plain ES6_ and _ES5_, create an instance of the equivalent injection qualifier in a nested array within the `parameters` array.
|
||||||
|
For example, you'd write `new Optional()` in _plain ES6_ and `new ng.core.Optional()` in _ES5_.
|
||||||
|
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-title.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-title.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
Angular supports bindings to properties and events of the _host element_ which is the
|
||||||
|
element whose tag matches the component selector.
|
||||||
|
|
||||||
|
### Host Decorators
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_, you can use host property decorators to bind a host
|
||||||
|
element to a component or directive.
|
||||||
|
The [`@HostBinding`](../api/core/index/HostBinding-interface.html) decorator
|
||||||
|
binds host element properties to component data properties.
|
||||||
|
The [`@HostListener`](../api/core/index/HostListener-interface.html) decorator binds
|
||||||
|
host element events to component event handlers.
|
||||||
|
|
||||||
|
In _plain ES6_ or _ES5_, add a `host` attribute to the component metadata to achieve the
|
||||||
|
same effect as `@HostBinding` and `@HostListener`.
|
||||||
|
|
||||||
|
The `host` value is an object whose properties are host property and listener bindings:
|
||||||
|
|
||||||
|
* Each key follows regular Angular binding syntax: `[property]` for host bindings
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-host.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-host.component.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-host.component.js' region='dsl'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
### Host Metadata
|
||||||
|
Some developers prefer to specify host properties and listeners
|
||||||
|
in the component metadata.
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript with decorators">
|
||||||
|
{@example 'cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a view-child-decorators}
|
||||||
|
|
||||||
|
### View and Child Decorators
|
||||||
|
|
||||||
|
Several _property_ decorators query a component's nested view and content components.
|
||||||
|
|
||||||
|
_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.html) and
|
||||||
|
[`@ViewChildren`](../api/core/index/ViewChildren-decorator.html) property decorators
|
||||||
|
allow a component to query instances of other components that are used in
|
||||||
|
its view.
|
||||||
|
|
||||||
|
In _ES5_ and _ES6_, you access a component's view children by adding a `queries` property to the component metadata.
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-queries.component.es6' region='view'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-queries.component.js' region='view'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
The [`@ContentChild`](../api/core/index/ContentChild-decorator.html) and
|
||||||
|
[`@ContentChildren`](../api/core/index/ContentChildren-decorator.html) property decorators
|
||||||
|
allow a component to query instances of other components that have been projected
|
||||||
|
into its view from elsewhere.
|
||||||
|
|
||||||
|
They can be added in the same way as [`@ViewChild`](../api/core/index/ViewChild-decorator.html) and
|
||||||
|
[`@ViewChildren`](../api/core/index/ViewChildren-decorator.html).
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="TypeScript">
|
||||||
|
{@example 'cb-ts-to-js/ts/src/app/hero-queries.component.ts' region='content'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES6 JavaScript">
|
||||||
|
{@example 'cb-ts-to-js/js-es6/src/app/hero-queries.component.es6' region='content'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="ES5 JavaScript with DSL">
|
||||||
|
{@example 'cb-ts-to-js/js/src/app/hero-queries.component.js' region='content'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
In _TypeScript_ and _ES6-with-decorators_ you can also use the `queries` metadata
|
||||||
|
instead of the `@ViewChild` and `@ContentChild` property decorators.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a aot}
|
||||||
|
|
||||||
|
## AOT Compilation in _TypeScript_ only
|
||||||
|
|
||||||
|
Angular offers two modes of template compilation, JIT (_Just-in-Time_) and
|
||||||
|
[AOT (_Ahead-of-Time_)](aot-compiler.html).
|
||||||
|
Currently the AOT compiler only works with _TypeScript_ applications because, in part, it generates
|
||||||
|
_TypeScript_ files as an intermediate result.
|
||||||
|
**AOT is not an option for pure JavaScript applications** at this time.
|
|
@ -0,0 +1,171 @@
|
||||||
|
@title
|
||||||
|
Visual Studio 2015 QuickStart
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Use Visual Studio 2015 with the QuickStart files
|
||||||
|
|
||||||
|
@description
|
||||||
|
<a id="top"></a>Some developers prefer Visual Studio as their Integrated Development Environment (IDE).
|
||||||
|
|
||||||
|
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**.
|
||||||
|
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.
|
||||||
|
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](#prereq1): Install Node.js
|
||||||
|
- [Prerequisite](#prereq2): Install Visual Studio 2015 Update 3
|
||||||
|
- [Prerequisite](#prereq3): Configure External Web tools
|
||||||
|
- [Prerequisite](#prereq4): Install TypeScript 2 for Visual Studio 2015
|
||||||
|
- [Step 1](#download): Download the QuickStart files
|
||||||
|
- [Step 2](#create-project): Create the Visual Studio ASP.NET project
|
||||||
|
- [Step 3](#copy): Copy the QuickStart files into the ASP.NET project folder
|
||||||
|
- [Step 4](#restore): Restore required packages
|
||||||
|
- [Step 5](#build-and-run): Build and run the app
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='prereq1'>
|
||||||
|
Prerequisite: Node.js
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
||||||
|
if they are not already on your machine.
|
||||||
|
**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>
|
||||||
|
|
||||||
|
The minimum requirement for developing Angular applications with Visual Studio is Update 3.
|
||||||
|
Earlier versions do not follow the best practices for developing applications with TypeScript.
|
||||||
|
To view your version of Visual Studio 2015, go to `Help | About Visual Studio`.
|
||||||
|
|
||||||
|
If you don't have it, install **[Visual Studio 2015 Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2015-update3-vs)**.
|
||||||
|
Or use `Tools | Extensions and Updates` to update to Update 3 directly from Visual Studio 2015.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='prereq3'>
|
||||||
|
Prerequisite: Configure External Web tools
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Configure Visual Studio to use the global external web tools instead of the tools that ship with Visual Studio:
|
||||||
|
|
||||||
|
* Open the **Options** dialog with `Tools` | `Options`
|
||||||
|
* In the tree on the left, select `Projects and Solutions` | `External Web Tools`.
|
||||||
|
* On the right, move the `$(PATH)` entry above the `$(DevEnvDir`) entries. This tells Visual Studio to
|
||||||
|
use the external tools (such as npm) found in the global path before using its own version of the external tools.
|
||||||
|
* Click OK to close the dialog.
|
||||||
|
* Restart Visual Studio for this change to take effect.
|
||||||
|
|
||||||
|
Visual Studio will now look first for external tools in the current workspace and
|
||||||
|
if not found then look in the global path and if it is not found there, Visual Studio
|
||||||
|
will use its own versions of the tools.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='prereq4'>
|
||||||
|
Prerequisite: Install TypeScript 2 for Visual Studio 2015
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
While Visual Studio Update 3 ships with TypeScript support out of the box, it currently doesn’t ship with TypeScript 2,
|
||||||
|
which you need to develop Angular applications.
|
||||||
|
|
||||||
|
To install TypeScript 2:
|
||||||
|
* Download and install **[TypeScript 2.0 for Visual Studio 2015](http://download.microsoft.com/download/6/D/8/6D8381B0-03C1-4BD2-AE65-30FF0A4C62DA/TS2.0.3-TS-release20-nightly-20160921.1/TypeScript_Dev14Full.exe)**
|
||||||
|
* OR install it with npm: `npm install -g typescript@2.0`.
|
||||||
|
|
||||||
|
You can find out more about TypeScript 2 support in Visual studio **[here](https://blogs.msdn.microsoft.com/typescript/2016/09/22/announcing-typescript-2-0/)**
|
||||||
|
|
||||||
|
At this point, Visual Studio is ready. It’s a good idea to close Visual Studio and
|
||||||
|
restart it to make sure everything is clean.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='download'>
|
||||||
|
Step 1: Download the QuickStart files
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
[Download the QuickStart source](https://github.com/angular/quickstart)
|
||||||
|
from github. If you downloaded as a zip file, extract the files.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='create-project'>
|
||||||
|
Step 2: Create the Visual Studio ASP.NET project
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Create the ASP.NET 4.x project in the usual way as follows:
|
||||||
|
|
||||||
|
* In Visual Studio, select `File` | `New` | `Project` from the menu.
|
||||||
|
* In the template tree, select `Templates` | `Visual C#` (or `Visual Basic`) | `Web`.
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
Copy the QuickStart files we downloaded from github into the folder containing the `.csproj` file.
|
||||||
|
Include the files in the Visual Studio project as follows:
|
||||||
|
|
||||||
|
* Click the `Show All Files` button in Solution Explorer to reveal all of the hidden files in the project.
|
||||||
|
* Right-click on each folder/file to be included in the project and select `Include in Project`.
|
||||||
|
Minimally, include the following folder/files:
|
||||||
|
* app folder (answer *No* if asked to search for TypeScript Typings)
|
||||||
|
* styles.css
|
||||||
|
* index.html
|
||||||
|
* package.json
|
||||||
|
* tsconfig.json
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='restore'>
|
||||||
|
Step 4: Restore the required packages
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Restore the packages required for an Angular application as follows:
|
||||||
|
|
||||||
|
* Right-click on the `package.json` file in Solution Explorer and select `Restore Packages`.
|
||||||
|
<br>This uses `npm` to install all of the packages defined in the `package.json` file.
|
||||||
|
It may take some time.
|
||||||
|
* If desired, open the Output window (`View` | `Output`) to watch the npm commands execute.
|
||||||
|
* Ignore the warnings.
|
||||||
|
* When the restore is finished, a message should say: `npm command completed with exit code 0`.
|
||||||
|
* Click the `Refresh` icon in Solution Explorer.
|
||||||
|
* **Do not** include the `node_modules` folder in the project. Let it be a hidden project folder.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='build-and-run'>
|
||||||
|
Step 5: Build and run the app
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Try editing any of the project files. *Save* and refresh the browser to
|
||||||
|
see the changes.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='routing'>
|
||||||
|
Note on Routing Applications
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
If this application used the Angular router, a browser refresh could return a *404 - Page Not Found*.
|
||||||
|
Look at the address bar. Does it contain a navigation url (a "deep link") ... any path other than `/` or `/index.html`?
|
||||||
|
|
||||||
|
You'll have to configure the server to return `index.html` for these requests.
|
||||||
|
Until you do, remove the navigation path and refresh again.
|
|
@ -0,0 +1,370 @@
|
||||||
|
@title
|
||||||
|
Animations
|
||||||
|
|
||||||
|
@intro
|
||||||
|
A guide to Angular's animation system.
|
||||||
|
|
||||||
|
@description
|
||||||
|
Motion is an important aspect in the design of modern web applications. Good
|
||||||
|
user interfaces transition smoothly between states with engaging animations
|
||||||
|
that call attention where it's needed. Well-designed animations can make a UI not only
|
||||||
|
more fun but also easier to use.
|
||||||
|
|
||||||
|
Angular's animation system lets you build animations that run with the same kind of native
|
||||||
|
performance found in pure CSS animations. You can also tightly integrate your
|
||||||
|
animation logic with the rest of your application code, for ease of control.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
Angular animations are built on top of the standard [Web Animations API](https://w3c.github.io/web-animations/)
|
||||||
|
and run natively on [browsers that support it](http://caniuse.com/#feat=web-animation).
|
||||||
|
|
||||||
|
For other browsers, a polyfill is required. Grab
|
||||||
|
[`web-animations.min.js` from GitHub](https://github.com/web-animations/web-animations-js) and
|
||||||
|
add it to your page.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
# Contents
|
||||||
|
|
||||||
|
* [Example: Transitioning between two states](#example-transitioning-between-states).
|
||||||
|
* [States and transitions](#states-and-transitions).
|
||||||
|
* [Example: Entering and leaving](#example-entering-and-leaving).
|
||||||
|
* [Example: Entering and leaving from different states](#example-entering-and-leaving-from-different-states).
|
||||||
|
* [Animatable properties and units](#animatable-properties-and-units).
|
||||||
|
* [Automatic property calculation](#automatic-property-calculation).
|
||||||
|
* [Animation timing](#animation-timing).
|
||||||
|
* [Multi-step animations with keyframes](#multi-step-animations-with-keyframes).
|
||||||
|
* [Parallel animation groups](#parallel-animation-groups).
|
||||||
|
* [Animation callbacks](#animation-callbacks).
|
||||||
|
|
||||||
|
The examples in this page are available as a <live-example></live-example>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a example-transitioning-between-states}
|
||||||
|
|
||||||
|
## Quickstart example: Transitioning between two states
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_basic_click.gif" alt="A simple transition animation" align="right" style="width:220px;margin-left:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='imports'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
In this example, you are defining animation styles (color and transform) inline in the
|
||||||
|
animation metadata.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Now, using the `[@triggerName]` syntax, attach the animation that you just defined to
|
||||||
|
one or more elements in the component's template.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='template'}
|
||||||
|
|
||||||
|
Here, the animation trigger applies to every element repeated by an `ngFor`. Each of
|
||||||
|
the repeated elements animates independently. The value of the
|
||||||
|
attribute is bound to the expression `hero.state` and is always either `active` or `inactive`.
|
||||||
|
|
||||||
|
With this setup, an animated transition appears whenever a hero object changes state.
|
||||||
|
Here's the full component implementation:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-basic.component.ts'}
|
||||||
|
|
||||||
|
## States and transitions
|
||||||
|
|
||||||
|
Angular animations are defined as logical **states** and **transitions**
|
||||||
|
between states.
|
||||||
|
|
||||||
|
An animation state is a string value that you define in your application code. In the example
|
||||||
|
above, the states `'active'` and `'inactive'` are based on the logical state of
|
||||||
|
hero objects. The source of the state can be a simple object attribute, as it was in this case,
|
||||||
|
or it can be a value computed in a method. The important thing is that you can read it into the
|
||||||
|
component's template.
|
||||||
|
|
||||||
|
You can define *styles* for each animation state:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-basic.component.ts' region='states'}
|
||||||
|
|
||||||
|
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
|
||||||
|
*as long as it remains in that state*. In effect, you're defining what styles the element has in different states.
|
||||||
|
|
||||||
|
After you define states, you can define *transitions* between the states. Each transition
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/animations/ng_animate_transitions_inactive_active.png" alt="In Angular animations you define states and transitions between states" width="400"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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,
|
||||||
|
the element receives one set of styles immediately and is then animated to the next.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### The wildcard state `*`
|
||||||
|
|
||||||
|
The `*` ("wildcard") state matches *any* animation state. This is useful for defining styles and
|
||||||
|
transitions that apply regardless of which state the animation is in. For example:
|
||||||
|
|
||||||
|
* The `active => *` transition applies when the element's state changes from `active` to anything else.
|
||||||
|
* The `* => *` transition applies when *any* change between two states takes place.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### The `void` state
|
||||||
|
|
||||||
|
The special state called `void` can apply to any animation. It applies
|
||||||
|
when the element is *not* attached to a view, perhaps because it has not yet been
|
||||||
|
added or because it has been removed. The `void` state is useful for defining enter and
|
||||||
|
leave animations.
|
||||||
|
|
||||||
|
For example the `* => void` transition applies when the element leaves the view,
|
||||||
|
regardless of what state it was in before it left.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The wildcard state `*` also matches `void`.
|
||||||
|
|
||||||
|
## Example: Entering and leaving
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_enter_leave.gif" alt="Enter and leave animations" align="right" style="width:250px;"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Using the `void` and `*` states you can define transitions that animate the
|
||||||
|
entering and leaving of elements:
|
||||||
|
|
||||||
|
* Enter: `void => *`
|
||||||
|
* Leave: `* => void`
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-enter-leave.component.ts' region='animationdef'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
These two common animations have their own aliases:
|
||||||
|
<code-example language="typescript">
|
||||||
|
transition(':enter', [ ... ]); // void => *
|
||||||
|
transition(':leave', [ ... ]); // * => void
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
## Example: Entering and leaving from different states
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" align="right" style="width:200px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
You can also combine this animation with the earlier state transition animation by
|
||||||
|
using the hero state as the animation state. This lets you configure
|
||||||
|
different transitions for entering and leaving based on what the state of the hero
|
||||||
|
is:
|
||||||
|
|
||||||
|
* Inactive hero enter: `void => inactive`
|
||||||
|
* Active hero enter: `void => active`
|
||||||
|
* Inactive hero leave: `inactive => void`
|
||||||
|
* Active hero leave: `active => void`
|
||||||
|
|
||||||
|
This gives you fine-grained control over each transition:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-enter-leave-states.component.ts' region='animationdef'}
|
||||||
|
|
||||||
|
## Animatable properties and units
|
||||||
|
|
||||||
|
Since Angular's animation support builds on top of Web Animations, you can animate any property
|
||||||
|
that the browser considers *animatable*. This includes positions, sizes, transforms, colors,
|
||||||
|
borders, and many others. The W3C maintains
|
||||||
|
[a list of animatable properties](https://www.w3.org/TR/css3-transitions/#animatable-properties)
|
||||||
|
on its [CSS Transitions page](https://www.w3.org/TR/css3-transitions).
|
||||||
|
|
||||||
|
For positional properties that have a numeric value, you can define a unit by providing
|
||||||
|
the value as a string with the appropriate suffix:
|
||||||
|
|
||||||
|
* `'50px'`
|
||||||
|
* `'3em'`
|
||||||
|
* `'100%'`
|
||||||
|
|
||||||
|
If you don't provide a unit when specifying dimension, Angular assumes the default of `px`:
|
||||||
|
|
||||||
|
* `50` is the same as saying `'50px'`
|
||||||
|
|
||||||
|
## Automatic property calculation
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_auto.gif" alt="Animation with automated height calculation" align="right" style="width:220px;margin-left:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Sometimes you don't know the value of a dimensional style property until runtime.
|
||||||
|
For example, elements often have widths and heights that
|
||||||
|
depend on their content and the screen size. These properties are often tricky
|
||||||
|
to animate with CSS.
|
||||||
|
|
||||||
|
In these cases, you can use a special `*` property value so that the value of the
|
||||||
|
property is computed at runtime and then plugged into the animation.
|
||||||
|
|
||||||
|
In this example, the leave animation takes whatever height the element has before it
|
||||||
|
leaves and animates from that height to zero:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-auto.component.ts' region='animationdef'}
|
||||||
|
|
||||||
|
## Animation timing
|
||||||
|
|
||||||
|
There are three timing properties you can tune for every animated transition:
|
||||||
|
the duration, the delay, and the easing function. They are all combined into
|
||||||
|
a single transition *timing string*.
|
||||||
|
|
||||||
|
### Duration
|
||||||
|
|
||||||
|
The duration controls how long the animation takes to run from start to finish.
|
||||||
|
You can define a duration in three ways:
|
||||||
|
|
||||||
|
* As a plain number, in milliseconds: `100`
|
||||||
|
* In a string, as milliseconds: `'100ms'`
|
||||||
|
* In a string, as seconds: `'0.1s'`
|
||||||
|
|
||||||
|
### Delay
|
||||||
|
|
||||||
|
The delay controls the length of time between the animation trigger and the beginning
|
||||||
|
of the transition. You can define one by adding it to the same string
|
||||||
|
following the duration. It also has the same format options as the duration:
|
||||||
|
|
||||||
|
* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
|
||||||
|
|
||||||
|
### Easing
|
||||||
|
|
||||||
|
The [easing function](http://easings.net/) controls how the animation accelerates
|
||||||
|
and decelerates during its runtime. For example, an `ease-in` function causes
|
||||||
|
the animation to begin relatively slowly but pick up speed as it progresses. You
|
||||||
|
can control the easing by adding it as a *third* value in the string after the duration
|
||||||
|
and the delay (or as the *second* value when there is no delay):
|
||||||
|
|
||||||
|
* Wait for 100ms and then run for 200ms, with easing: `'0.2s 100ms ease-out'`
|
||||||
|
* Run for 200ms, with easing: `'0.2s ease-in-out'`
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_timings.gif" alt="Animations with specific timings" align="right" style="width:220px;margin-left:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
Here are a couple of custom timings in action. Both enter and leave last for
|
||||||
|
200 milliseconds but they have different easings. The leave begins after a
|
||||||
|
slight delay:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-timings.component.ts' region='animationdef'}
|
||||||
|
|
||||||
|
## Multi-step animations with keyframes
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" align="right" style="width:220px;margin-left:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Animation *keyframes* go beyond a simple transition to a more intricate animation
|
||||||
|
that goes through one or more intermediate styles when transitioning between two sets of styles.
|
||||||
|
|
||||||
|
For each keyframe, you specify an *offset* that defines at which point
|
||||||
|
in the animation that keyframe applies. The offset is a number between zero,
|
||||||
|
which marks the beginning of the animation, and one, which marks the end.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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
|
||||||
|
of keyframe offsets, duration, delay, and easing.
|
||||||
|
|
||||||
|
Defining offsets for keyframes is optional. If you omit them, offsets with even
|
||||||
|
spacing are automatically assigned. For example, three keyframes without predefined
|
||||||
|
offsets receive offsets `0`, `0.5`, and `1`.
|
||||||
|
## Parallel animation groups
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" align="right" style="width:220px;margin-left:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
You've seen how to animate multiple style properties at the same time:
|
||||||
|
just put all of them into the same `style()` definition.
|
||||||
|
|
||||||
|
But you may also want to configure different *timings* for animations that happen
|
||||||
|
in parallel. For example, you may want to animate two CSS properties but use a
|
||||||
|
different easing function for each one.
|
||||||
|
|
||||||
|
For this you can use animation *groups*. In this example, using groups both on
|
||||||
|
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'}
|
||||||
|
|
||||||
|
One group animates the element transform and width; the other group animates the opacity.
|
||||||
|
## Animation callbacks
|
||||||
|
|
||||||
|
A callback is fired when an animation is started and also when it is done.
|
||||||
|
|
||||||
|
In the keyframes example, you have a `trigger` called `@flyInOut`. There you can hook
|
||||||
|
those callbacks like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'animations/ts/src/app/hero-list-multistep.component.ts' region='template'}
|
||||||
|
|
||||||
|
The callbacks receive an `AnimationTransitionEvent` which contains useful properties such as `fromState`,
|
||||||
|
`toState` and `totalTime`.
|
||||||
|
|
||||||
|
Those callbacks will fire whether or not an animation is picked up.
|
|
@ -0,0 +1,157 @@
|
||||||
|
@title
|
||||||
|
AppModule: the root module
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Tell Angular how to construct and bootstrap the app in the root "AppModule".
|
||||||
|
|
||||||
|
@description
|
||||||
|
An Angular module class describes how the application parts fit together.
|
||||||
|
Every application has at least one Angular module, the _root_ module
|
||||||
|
that you [bootstrap](#main) to launch the application.
|
||||||
|
You can call it anything you want. The conventional name is `AppModule`.
|
||||||
|
|
||||||
|
The [setup](setup.html) instructions produce a new project with the following minimal `AppModule`.
|
||||||
|
You'll evolve this module as your application grows.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'setup/ts/src/app/app.module.ts'}
|
||||||
|
|
||||||
|
After the `import` statements, you come to a class adorned with the
|
||||||
|
**`@NgModule`** [_decorator_](glossary.html#decorator '"Decorator" explained').
|
||||||
|
|
||||||
|
The `@NgModule` decorator identifies `AppModule` as an Angular module class (also called an `NgModule` class).
|
||||||
|
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
|
||||||
|
|
||||||
|
* **_imports_** — the `BrowserModule` that this and every application needs to run in a browser.
|
||||||
|
* **_declarations_** — the application's lone component, which is also ...
|
||||||
|
* **_bootstrap_** — the _root_ component that Angular creates and inserts into the `index.html` host web page.
|
||||||
|
|
||||||
|
The [Angular Modules (NgModule)](ngmodule.html) guide dives deeply into the details of Angular modules.
|
||||||
|
All you need to know at the moment is a few basics about these three properties.
|
||||||
|
|
||||||
|
|
||||||
|
{@a imports}
|
||||||
|
### The _imports_ array
|
||||||
|
|
||||||
|
Angular modules are a way to consolidate features that belong together into discrete units.
|
||||||
|
Many features of Angular itself are organized as Angular modules.
|
||||||
|
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
|
||||||
|
Eventually you may create a feature module.
|
||||||
|
|
||||||
|
Add a module to the `imports` array when the application requires its features.
|
||||||
|
|
||||||
|
_This_ application, like most applications, executes in a browser.
|
||||||
|
Every application that executes in a browser needs the `BrowserModule` from `@angular/platform-browser`.
|
||||||
|
So every such application includes the `BrowserModule` in its _root_ `AppModule`'s `imports` array.
|
||||||
|
Other guide and cookbook pages will tell you when you need to add additional modules to this array.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
**Only `NgModule` classes** go in the `imports` array. Do not put any other kind of class in `imports`.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
The `import` statements at the top of the file and the Angular module's `imports` array
|
||||||
|
are unrelated and have completely different jobs.
|
||||||
|
|
||||||
|
The _JavaScript_ `import` statements give you access to symbols _exported_ by other files
|
||||||
|
so you can reference them within _this_ file.
|
||||||
|
You add `import` statements to almost every application file.
|
||||||
|
They have nothing to do with Angular and Angular knows nothing about them.
|
||||||
|
|
||||||
|
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
|
||||||
|
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
|
||||||
|
|
||||||
|
You tell Angular which components belong to the `AppModule` by listing it in the module's `declarations` array.
|
||||||
|
As you create more components, you'll add them to `declarations`.
|
||||||
|
|
||||||
|
You must declare _every_ component in an `NgModule` class.
|
||||||
|
If you use a component without declaring it, you'll see a clear error message in the browser console.
|
||||||
|
|
||||||
|
You'll learn to create two other kinds of classes —
|
||||||
|
[directives](attribute-directives.html) and [pipes](pipes.html) —
|
||||||
|
that you must also add to the `declarations` array.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
**Only _declarables_** — _components_, _directives_ and _pipes_ — belong in the `declarations` array.
|
||||||
|
Do not put any other kind of class in `declarations`; _not_ `NgModule` classes, _not_ service classes, _not_ model classes.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a bootstrap-array}
|
||||||
|
### The _bootstrap_ array
|
||||||
|
|
||||||
|
You launch the application by [_bootstrapping_](#main) the root `AppModule`.
|
||||||
|
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
||||||
|
and inserts each one into the browser DOM.
|
||||||
|
|
||||||
|
Each bootstrapped component is the base of its own tree of components.
|
||||||
|
Inserting a bootstrapped component usually triggers a cascade of component creations that fill out that tree.
|
||||||
|
|
||||||
|
While you can put more than one component tree on a host web page, that's not typical.
|
||||||
|
Most applications have only one component tree and they bootstrap a single _root_ component.
|
||||||
|
|
||||||
|
You can call the one _root_ component anything you want but most developers call it `AppComponent`.
|
||||||
|
|
||||||
|
Which brings us to the _bootstrapping_ process itself.
|
||||||
|
|
||||||
|
|
||||||
|
{@a main}
|
||||||
|
|
||||||
|
<l-main-section>
|
||||||
|
|
||||||
|
</l-main-section>
|
||||||
|
|
||||||
|
## Bootstrap in _main.ts_
|
||||||
|
|
||||||
|
There are many ways to bootstrap an application.
|
||||||
|
The variations depend upon how you want to compile the application and where you want to run it.
|
||||||
|
|
||||||
|
In the beginning, you will compile the application dynamically with the _Just-in-Time (JIT)_ compiler
|
||||||
|
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'}
|
||||||
|
|
||||||
|
This code creates a browser platform for dynamic (JIT) compilation and
|
||||||
|
bootstraps the `AppModule` described above.
|
||||||
|
|
||||||
|
The _bootstrapping_ process sets up the execution environment,
|
||||||
|
digs the _root_ `AppComponent` out of the module's `bootstrap` array,
|
||||||
|
creates an instance of the component and inserts it within the element tag identified by the component's `selector`.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
... and displays the `AppComponent` there.
|
||||||
|
|
||||||
|
This file is very stable. Once you've set it up, you may never change it again.
|
||||||
|
|
||||||
|
<l-main-section>
|
||||||
|
|
||||||
|
</l-main-section>
|
||||||
|
|
||||||
|
## More about Angular Modules
|
||||||
|
|
||||||
|
Your initial app has only a single module, the _root_ module.
|
||||||
|
As your app grows, you'll consider subdividing it into multiple "feature" modules,
|
||||||
|
so of which can be loaded later ("lazy loaded") if and when the user chooses
|
||||||
|
to visit those features.
|
||||||
|
|
||||||
|
When you're ready to explore these possibilities, visit the [Angular Modules (NgModule)](ngmodule.html) guide.
|
|
@ -0,0 +1,401 @@
|
||||||
|
@title
|
||||||
|
Architecture Overview
|
||||||
|
|
||||||
|
@intro
|
||||||
|
The basic building blocks of Angular applications
|
||||||
|
|
||||||
|
@description
|
||||||
|
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*.
|
||||||
|
|
||||||
|
Then you launch the app by *bootstrapping* the _root module_.
|
||||||
|
Angular takes over, presenting your application content in a browser and
|
||||||
|
responding to user interactions according to the instructions you've provided.
|
||||||
|
|
||||||
|
Of course, there is more to it than this.
|
||||||
|
You'll learn the details in the pages that follow. For now, focus on the big picture.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The architecture diagram identifies the eight main building blocks of an Angular application:
|
||||||
|
|
||||||
|
* [Modules](#modules)
|
||||||
|
* [Components](#components)
|
||||||
|
* [Templates](#templates)
|
||||||
|
* [Metadata](#metadata)
|
||||||
|
* [Data binding](#data-binding)
|
||||||
|
* [Directives](#directives)
|
||||||
|
* [Services](#services)
|
||||||
|
* [Dependency injection](#dependency-injection)
|
||||||
|
|
||||||
|
Learn these building blocks, and you're on your way.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The code referenced on this page is available as a <live-example></live-example>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Angular libraries
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/hero-component.png" alt="Component" align="left" style="width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
A _component_ controls a patch of screen called a *view*.
|
||||||
|
|
||||||
|
For example, the following views are controlled by components:
|
||||||
|
|
||||||
|
* The app root with the navigation links.
|
||||||
|
* The list of heroes.
|
||||||
|
* The hero editor.
|
||||||
|
|
||||||
|
You define a component's application logic—what it does to support the view—inside a class.
|
||||||
|
The class interacts with the view through an API of properties and methods.
|
||||||
|
|
||||||
|
<a id="component-code"></a>
|
||||||
|
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.
|
||||||
|
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](lifecycle-hooks.html), like `ngOnInit()` declared above.
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/template.png" alt="Template" align="left" style="width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
You define a component's view with its companion **template**. A template is a form of HTML
|
||||||
|
that tells Angular how to render the component.
|
||||||
|
|
||||||
|
A template looks like regular HTML, except for a few differences. Here is a
|
||||||
|
template for our `HeroListComponent`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'architecture/ts/src/app/hero-list.component.html'}
|
||||||
|
|
||||||
|
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](template-syntax.html).
|
||||||
|
|
||||||
|
|
||||||
|
In the last line of the template, the `<hero-detail>` tag is a custom element that represents a new component, `HeroDetailComponent`.
|
||||||
|
|
||||||
|
The `HeroDetailComponent` is a *different* component than the `HeroListComponent` you've been reviewing.
|
||||||
|
The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the
|
||||||
|
hero that the user selects from the list presented by the `HeroListComponent`.
|
||||||
|
The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/component-tree.png" alt="Metadata" align="left" style="width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Notice how `<hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
|
||||||
|
<br class="l-clear-both">
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/metadata.png" alt="Metadata" align="left" style="width:150px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<p style="padding-top:10px">Metadata tells Angular how to process a class.</p>
|
||||||
|
<br class="l-clear-both">[Looking back at the code](#component-code) for `HeroListComponent`, you can see that it's just a class.
|
||||||
|
There is no evidence of a framework, no "Angular" in it at all.
|
||||||
|
|
||||||
|
In fact, `HeroListComponent` really is *just a class*. It's not a component until you *tell Angular about it*.
|
||||||
|
|
||||||
|
To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class.
|
||||||
|
|
||||||
|
In !{_Lang}, you attach metadata by using !{_a} **!{_decorator}**.
|
||||||
|
Here's some metadata for `HeroListComponent`:
|
||||||
|
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>
|
||||||
|
|
||||||
|
- `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](#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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/template-metadata-component.png" alt="Metadata" align="left" style="height:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The metadata in the `@Component` tells Angular where to get the major building blocks you specify for the component.
|
||||||
|
|
||||||
|
The template, metadata, and component together describe a view.
|
||||||
|
|
||||||
|
Apply other metadata !{_decorator}s in a similar fashion to guide Angular behavior.
|
||||||
|
`@Injectable`, `@Input`, and `@Output` are a few of the more popular !{_decorator}s.<br class="l-clear-both">The architectural takeaway is that you must add metadata to your code
|
||||||
|
so that Angular knows what to do.
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Data binding
|
||||||
|
Without a framework, you would be responsible for pushing data values into the HTML controls and turning user responses
|
||||||
|
into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to
|
||||||
|
read as any experienced jQuery programmer can attest.
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/databinding.png" alt="Data Binding" style="width:220px; float:left; margin-left:-40px;margin-right:20px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Angular supports **data binding**,
|
||||||
|
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](#templates) template has three forms:
|
||||||
|
* The `{{hero.name}}` [*interpolation*](displaying-data.html#interpolation)
|
||||||
|
displays the component's `hero.name` property value within the `<li>` element.
|
||||||
|
|
||||||
|
* The `[hero]` [*property binding*](template-syntax.html#property-binding) passes the value of `selectedHero` from
|
||||||
|
the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||||
|
|
||||||
|
* The `(click)` [*event binding*](user-input.html#click) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||||
|
|
||||||
|
**Two-way data binding** is an important fourth form
|
||||||
|
that combines property and event binding in a single notation, using the `ngModel` directive.
|
||||||
|
Here's an example from the `HeroDetailComponent` template:
|
||||||
|
In two-way binding, a data property value flows to the input box from the component as with property binding.
|
||||||
|
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||||
|
as with event binding.
|
||||||
|
|
||||||
|
Angular processes *all* data bindings once per JavaScript event cycle,
|
||||||
|
from the root of the application component tree through all child components.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/component-databinding.png" alt="Data Binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Data binding plays an important role in communication
|
||||||
|
between a template and its component.<br class="l-clear-both">
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/parent-child-binding.png" alt="Parent/Child binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Data binding is also important for communication between parent and child components.<br class="l-clear-both">
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Directives
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/directive.png" alt="Parent child" style="float:left; width:150px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
||||||
|
according to the instructions given by **directives**.
|
||||||
|
|
||||||
|
A directive is a class with a `@Directive` !{_decorator}.
|
||||||
|
A 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">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
They tend to appear within an element tag as attributes do,
|
||||||
|
sometimes by name but more often as the target of an assignment or a binding.
|
||||||
|
|
||||||
|
**Structural** directives alter layout by adding, removing, and replacing elements in DOM.
|
||||||
|
|
||||||
|
The [example template](#templates) uses two built-in structural directives:
|
||||||
|
* [`*ngFor`](displaying-data.html#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||||
|
* [`*ngIf`](displaying-data.html#ngIf) includes the `HeroDetail` component only if a selected hero exists.
|
||||||
|
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||||
|
In templates they look like regular HTML attributes, hence the name.
|
||||||
|
|
||||||
|
The `ngModel` directive, which implements two-way data binding, is
|
||||||
|
an example of an attribute directive. `ngModel` modifies the behavior of
|
||||||
|
an existing element (typically an `<input>`)
|
||||||
|
by setting its display value property and responding to change events.
|
||||||
|
Angular has a few more directives that either alter the layout structure
|
||||||
|
(for example, [ngSwitch](template-syntax.html#ngSwitch))
|
||||||
|
or modify aspects of DOM elements and components
|
||||||
|
(for example, [ngStyle](template-syntax.html#ngStyle) and [ngClass](template-syntax.html#ngClass)).
|
||||||
|
|
||||||
|
Of course, you can also write your own directives. Components such as
|
||||||
|
`HeroListComponent` are one kind of custom directive.
|
||||||
|
<!-- PENDING: link to where to learn more about other kinds! -->
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Services
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/service.png" alt="Service" style="float:left; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
_Service_ is a broad category encompassing any value, function, or feature that your application needs.
|
||||||
|
|
||||||
|
Almost anything can be a service.
|
||||||
|
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.<br class="l-clear-both">Examples include:
|
||||||
|
* logging service
|
||||||
|
* data service
|
||||||
|
* message bus
|
||||||
|
* tax calculator
|
||||||
|
* application configuration
|
||||||
|
|
||||||
|
There is nothing specifically _Angular_ about services. Angular has no definition of a service.
|
||||||
|
There is no service base class, and no place to register a service.
|
||||||
|
|
||||||
|
Yet services are fundamental to any Angular application. Components are big consumers of services.
|
||||||
|
|
||||||
|
Here's an example of a service class that logs to the browser console:
|
||||||
|
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.
|
||||||
|
Services are everywhere.
|
||||||
|
|
||||||
|
Component classes should be lean. They don't fetch data from the server,
|
||||||
|
validate user input, or log directly to the console.
|
||||||
|
They delegate such tasks to services.
|
||||||
|
|
||||||
|
A component's job is to enable the user experience and nothing more. It mediates between the view (rendered by the template)
|
||||||
|
and the application logic (which often includes some notion of a _model_).
|
||||||
|
A good component presents properties and methods for data binding.
|
||||||
|
It delegates everything nontrivial to services.
|
||||||
|
|
||||||
|
Angular doesn't *enforce* these principles.
|
||||||
|
It won't complain if you write a "kitchen sink" component with 3000 lines.
|
||||||
|
|
||||||
|
Angular does help you *follow* these principles by making it easy to factor your
|
||||||
|
application logic into services and make those services available to components through *dependency injection*.
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Dependency injection
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/dependency-injection.png" alt="Service" style="float:left; width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
_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`:
|
||||||
|
When Angular creates a component, it first asks an **injector** for
|
||||||
|
the services that the component requires.
|
||||||
|
|
||||||
|
An injector maintains a container of service instances that it has previously created.
|
||||||
|
If a requested service instance is not in the container, the injector makes one and adds it to the container
|
||||||
|
before returning the service to Angular.
|
||||||
|
When all requested services have been resolved and returned,
|
||||||
|
Angular can call the component's constructor with those services as arguments.
|
||||||
|
This is *dependency injection*.
|
||||||
|
|
||||||
|
The process of `HeroService` injection looks a bit like this:
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/architecture/injector-injects.png" alt="Service"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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.
|
||||||
|
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
||||||
|
Registering at a component level means you get a new instance of the
|
||||||
|
service with each new instance of that component.
|
||||||
|
|
||||||
|
<!-- We've vastly oversimplified dependency injection for this overview.
|
||||||
|
The full story is in the [dependency injection](dependency-injection.html) page. -->
|
||||||
|
|
||||||
|
Points to remember about dependency injection:
|
||||||
|
|
||||||
|
* Dependency injection is wired into the Angular framework and used everywhere.
|
||||||
|
|
||||||
|
* The *injector* is the main mechanism.
|
||||||
|
* An injector maintains a *container* of service instances that it created.
|
||||||
|
* An injector can create a new service instance from a *provider*.
|
||||||
|
|
||||||
|
* A *provider* is a recipe for creating a service.
|
||||||
|
|
||||||
|
* Register *providers* with injectors.
|
||||||
|
|
||||||
|
<div class='l-hr'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Wrap up
|
||||||
|
|
||||||
|
You've learned the basics about the eight main building blocks of an Angular application:
|
||||||
|
|
||||||
|
* [Modules](#modules)
|
||||||
|
* [Components](#components)
|
||||||
|
* [Templates](#templates)
|
||||||
|
* [Metadata](#metadata)
|
||||||
|
* [Data binding](#data-binding)
|
||||||
|
* [Directives](#directives)
|
||||||
|
* [Services](#services)
|
||||||
|
* [Dependency injection](#dependency-injection)
|
||||||
|
|
||||||
|
That's a foundation for everything else in an Angular application,
|
||||||
|
and it's more than enough to get going.
|
||||||
|
But it doesn't include everything you need to know.
|
||||||
|
|
||||||
|
Here is a brief, alphabetical list of other important Angular features and services.
|
||||||
|
Most of them are covered in this documentation (or soon will be).
|
||||||
|
|
||||||
|
> [**Animations**](animations.html): Animate component behavior
|
||||||
|
without deep knowledge of animation techniques or CSS with Angular's animation library.
|
||||||
|
|
||||||
|
> **Change detection**: The change detection documentation will cover how Angular decides that a component property value has changed,
|
||||||
|
when to update the screen, and how it uses **zones** to intercept asynchronous activity and run its change detection strategies.
|
||||||
|
|
||||||
|
> **Events**: The events documentation will cover how to use components and services to raise events with mechanisms for
|
||||||
|
publishing and subscribing to events.
|
||||||
|
|
||||||
|
> [**Forms**](forms.html): Support complex data entry scenarios with HTML-based validation and dirty checking.
|
||||||
|
|
||||||
|
> [**HTTP**](server-communication.html): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client.
|
||||||
|
|
||||||
|
> [**Lifecycle hooks**](lifecycle-hooks.html): Tap into key moments in the lifetime of a component, from its creation to its destruction,
|
||||||
|
by implementing the lifecycle hook interfaces.
|
||||||
|
|
||||||
|
> [**Pipes**](pipes.html): Use pipes in your templates to improve the user experience by transforming values for display. Consider this `currency` pipe expression:
|
||||||
|
>
|
||||||
|
> > `price | currency:'USD':true`
|
||||||
|
>
|
||||||
|
> It displays a price of 42.33 as `$42.33`.
|
||||||
|
|
||||||
|
> [**Router**](router.html): Navigate from page to page within the client
|
||||||
|
application and never leave the browser.
|
|
@ -0,0 +1,396 @@
|
||||||
|
@title
|
||||||
|
Attribute Directives
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Attribute directives attach behavior to elements.
|
||||||
|
|
||||||
|
@description
|
||||||
|
An **Attribute** directive changes the appearance or behavior of a DOM element.
|
||||||
|
|
||||||
|
# Contents
|
||||||
|
|
||||||
|
|
||||||
|
* [Directives overview](#directive-overview)
|
||||||
|
* [Build a simple attribute directive](#write-directive)
|
||||||
|
* [Apply the attribute directive to an element in a template](#apply-directive)
|
||||||
|
* [Respond to user-initiated events](#respond-to-user)
|
||||||
|
* [Pass values into the directive with an _@Input_ data binding](#bindings)
|
||||||
|
* [Bind to a second property](#second-property)
|
||||||
|
|
||||||
|
Try the <live-example title="Attribute Directive example"></live-example>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a directive-overview}
|
||||||
|
## Directives overview
|
||||||
|
|
||||||
|
There are three kinds of directives in Angular:
|
||||||
|
|
||||||
|
1. Components—directives with a template.
|
||||||
|
1. Structural directives—change the DOM layout by adding and removing DOM elements.
|
||||||
|
1. Attribute directives—change the appearance or behavior of an element, component, or another directive.
|
||||||
|
|
||||||
|
*Components* are the most common of the three directives.
|
||||||
|
You saw a component for the first time in the [QuickStart](../quickstart.html) guide.
|
||||||
|
|
||||||
|
*Structural Directives* change the structure of the view.
|
||||||
|
Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf).
|
||||||
|
Learn about them in the [Structural Directives](structural-directives.html) guide.
|
||||||
|
|
||||||
|
*Attribute directives* are used as attributes of elements.
|
||||||
|
The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) guide, for example,
|
||||||
|
can change several element styles at the same time.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a write-directive}
|
||||||
|
## Build a simple attribute directive
|
||||||
|
An attribute directive minimally requires building a controller class annotated with
|
||||||
|
`@Directive`, which specifies the selector that identifies
|
||||||
|
the attribute.
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.1.html' region='applied'}
|
||||||
|
|
||||||
|
### Write the directive code
|
||||||
|
Follow the [setup](setup.html) instructions for creating a new local project
|
||||||
|
named <span ngio-ex>attribute-directives</span>.
|
||||||
|
Create the following source file in `src/app` with the following code:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.1.ts'}
|
||||||
|
|
||||||
|
`@Directive` requires a CSS selector to identify
|
||||||
|
the HTML in the template that is associated with the directive.
|
||||||
|
The [CSS selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
|
||||||
|
is the attribute name in square brackets.
|
||||||
|
Here, the directive's selector is `[myHighlight]`.
|
||||||
|
Angular locates all elements in the template that have an attribute named `myHighlight`.
|
||||||
|
### Why not call it "highlight"?
|
||||||
|
Though *highlight* is a more concise name than *myHighlight* and would work,
|
||||||
|
a best practice is to prefix selector names to ensure
|
||||||
|
they don't conflict with standard HTML attributes.
|
||||||
|
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.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
After the <code> @Directive </code> metadata comes the directive's controller class, called <code> HighlightDirective </code> , which contains the logic for the directive.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Angular creates a new instance of the directive's controller class for
|
||||||
|
each matching element, injecting an Angular `ElementRef`
|
||||||
|
into the constructor.
|
||||||
|
`ElementRef` is a service that grants direct access to the DOM element
|
||||||
|
through its `nativeElement` property.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a apply-directive}
|
||||||
|
## Apply the attribute directive
|
||||||
|
To use the new `HighlightDirective`, create a template that
|
||||||
|
applies the directive as an attribute to a paragraph (`<p>`) element.
|
||||||
|
In Angular terms, the `<p>` element is the attribute **host**.
|
||||||
|
<p>
|
||||||
|
Put the template in its own <code> </code> file that looks like this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.1.html'}
|
||||||
|
|
||||||
|
Now reference this template in the `AppComponent`:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.ts'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
### Your directive isn't working?
|
||||||
|
|
||||||
|
Did you remember to add the directive to the `declarations` attribute of `@NgModule`? It is easy to forget!
|
||||||
|
Open the console in the browser tools and look for an error like this:
|
||||||
|
<code-example format="nocode">
|
||||||
|
EXCEPTION: Template parse errors:
|
||||||
|
Can't bind to 'myHighlight' since it isn't a known property of 'p'.
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Angular detects that you're trying to bind to *something* but it can't find this directive
|
||||||
|
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
|
||||||
|
which sets the `<p>` element's background style to yellow.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a respond-to-user}
|
||||||
|
## Respond to user-initiated events
|
||||||
|
|
||||||
|
Currently, `myHighlight` simply sets an element color.
|
||||||
|
The directive could be more dynamic.
|
||||||
|
It could detect when the user mouses into or out of the element
|
||||||
|
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.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.ts' region='imports'}
|
||||||
|
|
||||||
|
Then add two eventhandlers that respond when the mouse enters or leaves, each adorned by the `HostListener` !{_decorator}.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts' region='mouse-methods'}
|
||||||
|
|
||||||
|
The `@HostListener` !{_decorator} lets you subscribe to events of the DOM element that hosts an attribute directive, the `<p>` in this case.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts' region='ctor'}
|
||||||
|
|
||||||
|
Here's the updated directive in full:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts'}
|
||||||
|
|
||||||
|
Run the app and confirm that the background color appears when the mouse hovers over the `p` and
|
||||||
|
disappears as it moves out.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a bindings}
|
||||||
|
## Pass values into the directive with an _@Input_ data binding
|
||||||
|
|
||||||
|
Currently the highlight color is hard-coded _within_ the directive. That's inflexible.
|
||||||
|
In this section, you give the developer the power to set the highlight color while applying the directive.
|
||||||
|
|
||||||
|
Start by adding a `highlightColor` property to the directive class like this:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts' region='color'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a input}
|
||||||
|
### Binding to an _@Input_ property
|
||||||
|
|
||||||
|
Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
||||||
|
|
||||||
|
It's called an *input* property because data flows from the binding expression _into_ the directive.
|
||||||
|
Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that.
|
||||||
|
|
||||||
|
Try it by adding the following directive binding variations to the `AppComponent` template:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.1.html' region='color-1'}
|
||||||
|
|
||||||
|
Add a `color` property to the `AppComponent`.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.1.ts' region='class'}
|
||||||
|
|
||||||
|
Let it control the highlight color with a property binding.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.1.html' region='color-2'}
|
||||||
|
|
||||||
|
That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.html' region='color'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts' region='color-2'}
|
||||||
|
|
||||||
|
This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||||
|
|
||||||
|
|
||||||
|
{@a input-alias}
|
||||||
|
### Bind to an _@Input_ alias
|
||||||
|
|
||||||
|
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`.
|
||||||
|
_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:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.html' region='color'}
|
||||||
|
|
||||||
|
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
|
||||||
|
If someone neglects to bind to `highlightColor`, highlight in "red" by default.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.3.ts' region='mouse-enter'}
|
||||||
|
|
||||||
|
Here's the latest version of the directive class.## Write a harness to try itIt may be difficult to imagine how this directive actually works.
|
||||||
|
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 `app.component.html` as follows:
|
||||||
|
Revise the `AppComponent.color` so that it has no initial value.Here is the harness and directive in action.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a second-property}
|
||||||
|
## Bind to a second property
|
||||||
|
This highlight directive has a single customizable property. In a real app, it may need more.
|
||||||
|
|
||||||
|
At the moment, the default color—the color that prevails until
|
||||||
|
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`: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.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.ts' region='mouse-enter'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.html' region='defaultColor'}
|
||||||
|
|
||||||
|
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||||
|
because you made it _public_ with the `@Input` !{_decorator}.
|
||||||
|
|
||||||
|
Here's how the harness should work when you're done coding.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
This page covered how to:
|
||||||
|
- [Build an **attribute directive**](#write-directive) that modifies the behavior of an element.
|
||||||
|
- [Apply the directive](#apply-directive) to an element in a template.
|
||||||
|
- [Respond to **events**](#respond-to-user) that change the directive's behavior.
|
||||||
|
- [**Bind** values to the directive](#bindings).
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/app.component.html">
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/highlight.directive.ts">
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/app.module.ts">
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="main.ts">
|
||||||
|
{@example 'attribute-directives/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="index.html">
|
||||||
|
{@example 'attribute-directives/ts/src/index.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
You can also experience and download the <live-example title="Attribute Directive example"></live-example>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a why-input}
|
||||||
|
|
||||||
|
### Appendix: Why add _@Input_?
|
||||||
|
|
||||||
|
In this demo, the `hightlightColor` property is an ***input*** property of
|
||||||
|
the `HighlightDirective`. You've seen it applied without an alias:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.2.ts' region='color'}
|
||||||
|
|
||||||
|
You've seen it with an alias:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/highlight.directive.ts' region='color'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You've bound template HTML to component properties before and never used `@Input`.
|
||||||
|
What's different?
|
||||||
|
|
||||||
|
The difference is a matter of trust.
|
||||||
|
Angular treats a component's template as _belonging_ to the component.
|
||||||
|
The component and its template trust each other implicitly.
|
||||||
|
Therefore, the component's own template may bind to _any_ property of that component,
|
||||||
|
with or without the `@Input` !{_decorator}.
|
||||||
|
|
||||||
|
But a component or directive shouldn't blindly trust _other_ components and directives.
|
||||||
|
The properties of a component or directive are hidden from binding by default.
|
||||||
|
They are _private_ from an Angular binding perspective.
|
||||||
|
When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective.
|
||||||
|
Only then can it be bound by some other component or directive.
|
||||||
|
|
||||||
|
You can tell if `@Input` is needed by the position of the property name in a binding.
|
||||||
|
|
||||||
|
* When it appears in the template expression to the ***right*** of the equals (=),
|
||||||
|
it belongs to the template's component and does not require the `@Input` !{_decorator}.
|
||||||
|
|
||||||
|
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
|
||||||
|
the property belongs to some _other_ component or directive;
|
||||||
|
that property must be adorned with the `@Input` !{_decorator}.
|
||||||
|
|
||||||
|
Now apply that reasoning to the following example:
|
||||||
|
|
||||||
|
{@example 'attribute-directives/ts/src/app/app.component.html' region='color'}
|
||||||
|
|
||||||
|
* The `color` property in the expression on the right belongs to the template's component.
|
||||||
|
The template and its component trust each other.
|
||||||
|
The `color` property doesn't require the `@Input` !{_decorator}.
|
||||||
|
|
||||||
|
* The `myHighlight` property on the left refers to an _aliased_ property of the `MyHighlightDirective`,
|
||||||
|
not a property of the template's component. There are trust issues.
|
||||||
|
Therefore, the directive property must carry the `@Input` !{_decorator}.
|
|
@ -0,0 +1,614 @@
|
||||||
|
@title
|
||||||
|
Browser support
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Browser support and polyfills guide.
|
||||||
|
|
||||||
|
@description
|
||||||
|
Angular supports most recent browsers. This includes the following specific versions:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Chrome
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Firefox
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Edge
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
IE
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Safari
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
iOS
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Android
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
IE mobile
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
latest
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
latest
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
14
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
11
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
10
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
10
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Marshmallow (6.0)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
11
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
13
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
10
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Lollipop<br>(5.0, 5.1)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
8
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
8
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
KitKat<br>(4.4)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
7
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
7
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Jelly Bean<br>(4.1, 4.2, 4.3)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You 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'}
|
||||||
|
|
||||||
|
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||||
|
You may need additional polyfills for specific features.
|
||||||
|
|
||||||
|
The tables below will help you determine which polyfills to load, depending on the browsers you target and the features you use.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
The suggested polyfills are the ones we know will run full Angular applications.
|
||||||
|
You may need additional polyfills to support features not covered by this list.
|
||||||
|
Note that polyfills cannot magically transform an old, slow browser into a modern, fast one.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Mandatory polyfills ##
|
||||||
|
These are the polyfills required to run an Angular application on each supported browser:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Browsers (desktop & mobile)
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Polyfills required
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Chrome, Firefox, Edge, Safari 9+
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
None
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Safari 7 & 8, IE10 & 11, Android 4.1+
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[ES6](#core-es6)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
IE9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[ES6<br>classList](#classlist)
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Optional browser features to polyfill ##
|
||||||
|
Some features of Angular may require additional polyfills.
|
||||||
|
|
||||||
|
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today.
|
||||||
|
You'll need a polyfill to use animations in other browsers.
|
||||||
|
|
||||||
|
Here are the features which may require additional polyfills:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Feature
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Polyfill
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th style="width: 50%">
|
||||||
|
Browsers (desktop & mobile)
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="./animations.html"> Animations </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[Web Animations](#web-animations)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
All but Chrome and Firefox<br>Not supported in IE9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="../api/common/index/DatePipe-pipe.html" target="_blank"> Date </a> <span> , </span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank"> currency </a> <span> , </span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank"> decimal </a> <span> and </span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank"> percent </a> <span> pipes </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[Intl API](#intl)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="../api/common/index/NgClass-directive.html" target="_blank"> NgClass </a> <span> on SVG elements </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[classList](#classlist)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
IE10, IE11
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="./server-communication.html"> Http </a> <span> when sending and receiving binary data </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[Typed Array](#typedarray) <br>[Blob](#blob)<br>[FormData](#formdata)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
IE 9
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Suggested polyfills ##
|
||||||
|
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Polyfill
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Licence
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Size*
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='core-es6' href="https://github.com/zloirock/core-js" target="_blank"> ES6 </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
MIT
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
27.4KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='classlist' href="https://github.com/eligrey/classList.js" target="_blank"> classList </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Public domain
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
1KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='intl' href="https://github.com/andyearnshaw/Intl.js" target="_blank"> Intl </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
MIT / Unicode licence
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
13.5KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='web-animations' href="https://github.com/web-animations/web-animations-js" target="_blank"> Web Animations </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Apache
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
14.8KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='typedarray' href="https://github.com/inexorabletash/polyfill/blob/master/typedarray.js" target="_blank"> Typed Array </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
MIT
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
4KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='blob' href="https://github.com/eligrey/Blob.js" target="_blank"> Blob </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
MIT
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
1.3KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a id='formdata' href="https://github.com/francois2metz/html5-formdata" target="_blank"> FormData </a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
MIT
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
0.4KB
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
\* Figures are for minified and gzipped code, computed with the <a href="http://closure-compiler.appspot.com/home" target="_blank">closure compiler</a>
|
|
@ -0,0 +1,187 @@
|
||||||
|
@title
|
||||||
|
Change Log
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Template Syntax/Structural Directives: refreshed (2017-02-06)
|
||||||
|
The [_Template-Syntax_](template-syntax.html) and [_Structural Directives_](structural-directives.html)
|
||||||
|
guides were significantly revised for clarity, accuracy, and current recommended practices.
|
||||||
|
Discusses `<ng-container>`.
|
||||||
|
Revised samples are more clear and cover all topics discussed.
|
||||||
|
|
||||||
|
## NEW: Samples re-structured with `src/` folder (2017-02-02)
|
||||||
|
All documentation samples have been realigned with the default folder structure of the angular-cli.
|
||||||
|
That's a step along the road to basing our sample in the angular-cli.
|
||||||
|
But it's also good in its own right.
|
||||||
|
It helps clearly separate app code from setup and configuration files.
|
||||||
|
|
||||||
|
We've updated all samples with an `src/` folder at the project root.
|
||||||
|
The former `app/` folder moves under `src/`.
|
||||||
|
Read about moving your existing project to this structure in
|
||||||
|
<a href="https://github.com/angular/quickstart#updating-to-a-newer-version-of-the-quickstart-repo" target="_blank" target="Migrating samples/quickstart app to the src folder">
|
||||||
|
the QuickStart repo update instructions</a>.
|
||||||
|
|
||||||
|
Notably:
|
||||||
|
* `app/main.ts` moved to `src/main.ts`.
|
||||||
|
* `app/` moved to `src/app/`.
|
||||||
|
* `index.html`, `styles.css` and `tsconfig.json` moved inside `src/`.
|
||||||
|
* `systemjs.config.js` now imports `main.js` instead of `app`.
|
||||||
|
* Added `lite-server` configuration (`bs-config.json`) to serve `src/`.
|
||||||
|
|
||||||
|
## NEW: Reactive Forms guide (2017-01-31)
|
||||||
|
The new [**Reactive Forms**](reactive-forms.html) guide explains how and why to build a "reactive form".
|
||||||
|
"Reactive Forms" are the code-based counterpart to the declarative "Template Driven" forms approach
|
||||||
|
introduced in the [Forms](forms.html) guide.
|
||||||
|
Check it out before you decide how to add forms to your app.
|
||||||
|
Remember also that you can use both techniques in the same app,
|
||||||
|
choosing the approach that best fits each scenario.
|
||||||
|
|
||||||
|
## NEW: Deployment guide (2017-01-30)
|
||||||
|
The new [Deployment](deployment.html) guide describes techniques for putting your application on a server.
|
||||||
|
It includes important advice on optimizing for production.
|
||||||
|
|
||||||
|
## Hierarchical Dependency Injection: refreshed (2017-01-13)
|
||||||
|
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) guide significantly revised.
|
||||||
|
Closes issue #3086
|
||||||
|
Revised samples are more clear and cover all topics discussed.
|
||||||
|
|
||||||
|
## Miscellaneous (2017-01-05)
|
||||||
|
* [Setup](setup.html) guide:
|
||||||
|
added (optional) instructions on how to remove _non-essential_ files.
|
||||||
|
* No longer consolidate RxJS operator imports in `rxjs-extensions` file; each file should import what it needs.
|
||||||
|
* All samples prepend template/style URLS URLs w/ `./` ... and so should you.
|
||||||
|
* [Style Guide](style-guide.html): copy edits and revised rules.
|
||||||
|
|
||||||
|
## Router: more detail (2016-12-21)
|
||||||
|
Added more information to the [Router](router.html) guide
|
||||||
|
including sections named outlets, wildcard routes, and preload strategies.
|
||||||
|
|
||||||
|
## Http: how to set default request headers (and other request options) (2016-12-14)
|
||||||
|
Added section on how to set default request headers (and other request options) to
|
||||||
|
[Http](server-communication.html#override-default-request-options) guide.
|
||||||
|
|
||||||
|
## Testing: added component test plunkers (2016-12-02)
|
||||||
|
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
|
||||||
|
Linked to these plunkers in [Testing](testing.html#live-examples) and [Setup anatomy](setup-systemjs-anatomy) guides.
|
||||||
|
|
||||||
|
## Internationalization: pluralization and _select_ (2016-11-30)
|
||||||
|
The [Internationalization (i18n)](../cookbook/i18n.html) guide explains how to handle pluralization and
|
||||||
|
translation of alternative texts with `select`.
|
||||||
|
The sample demonstrates these features too.
|
||||||
|
|
||||||
|
## Testing: karma file updates (2016-11-30)
|
||||||
|
* karma.config + karma-test-shim can handle multiple spec source paths;
|
||||||
|
see quickstart issue: [angular/quickstart#294](https://github.com/angular/quickstart/issues/294)
|
||||||
|
* Displays Jasmine Runner output in the karma-launched browser
|
||||||
|
|
||||||
|
## QuickStart Rewrite (2016-11-18)
|
||||||
|
The QuickStart is completely rewritten so that it actually is quick.
|
||||||
|
It references a minimal "Hello Angular" app running in Plunker.
|
||||||
|
The new [Setup](setup.html) page tells you how to install a local development environment
|
||||||
|
by downloading (or cloning) the QuickStart github repository.
|
||||||
|
You are no longer asked to copy-and-paste code into setup files that were not explained anyway.
|
||||||
|
|
||||||
|
## Sync with Angular v.2.2.0 (2016-11-14)
|
||||||
|
Docs and code samples updated and tested with Angular v.2.2.0
|
||||||
|
|
||||||
|
## UPDATE: NgUpgrade Guide for the AOT friendly _upgrade/static_ module (2016-11-14)
|
||||||
|
The updated [NgUpgrade Guide](upgrade.html) guide covers the
|
||||||
|
new AOT friendly `upgrade/static` module
|
||||||
|
released in v.2.2.0, which is the recommended
|
||||||
|
facility for migrating from AngularJS to Angular.
|
||||||
|
The documentation for the version prior to v.2.2.0 has been removed.
|
||||||
|
|
||||||
|
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
|
||||||
|
The updated "[TypeScript to JavaScript](../cookbook/ts-to-js.html)" cookbook
|
||||||
|
now explains how to write apps in ES6/7
|
||||||
|
by translating the common idioms in the TypeScript documentation examples
|
||||||
|
(and elsewhere on the web) to ES6/7 and ES5.
|
||||||
|
|
||||||
|
## Sync with Angular v.2.1.1 (2016-10-21)
|
||||||
|
Docs and code samples updated and tested with Angular v.2.1.0
|
||||||
|
|
||||||
|
## npm _@types_ packages replace _typings_ (2016-10-20)
|
||||||
|
Documentation samples now get TypeScript type information for 3rd party libraries
|
||||||
|
from npm `@types` packages rather than with the _typings_ tooling.
|
||||||
|
The `typings.json` file is gone.
|
||||||
|
|
||||||
|
The "[AngularJS Upgrade](upgrade.html)" guide reflects this change.
|
||||||
|
The `package.json` installs `@types/angular` and several `@types/angular-...`
|
||||||
|
packages in support of upgrade; these are not needed for pure Angular development.
|
||||||
|
|
||||||
|
## "Template Syntax" explains two-way data binding syntax (2016-10-20)
|
||||||
|
Demonstrates how to two-way data bind to a custom Angular component and
|
||||||
|
re-explains `[(ngModel)]` in terms of the basic `[()]` syntax.
|
||||||
|
|
||||||
|
## BREAKING CHANGE: `in-memory-web-api` (v.0.1.11) delivered as esm umd (2016-10-19)
|
||||||
|
This change supports ES6 developers and aligns better with typical Angular libraries.
|
||||||
|
It does not affect the module's API but it does affect how you load and import it.
|
||||||
|
See the <a href="https://github.com/angular/in-memory-web-api/blob/master/CHANGELOG.md#0113-2016-10-20" target="_blank">change note</a>
|
||||||
|
in the `in-memory-web-api` repo.
|
||||||
|
|
||||||
|
## "Router" _preload_ syntax and _:enter_/_:leave_ animations (2016-10-19)
|
||||||
|
The router can lazily _preload_ modules _after_ the app starts and
|
||||||
|
_before_ the user navigates to them for improved perceived performance.
|
||||||
|
|
||||||
|
New `:enter` and `:leave` aliases make animation more natural.
|
||||||
|
|
||||||
|
## Sync with Angular v.2.1.0 (2016-10-12)
|
||||||
|
Docs and code samples updated and tested with Angular v.2.1.0
|
||||||
|
|
||||||
|
## NEW "Ahead of time (AOT) Compilation" cookbook (2016-10-11)
|
||||||
|
The NEW [Ahead of time (AOT) Compilation](../cookbook/aot-compiler.html) cookbook
|
||||||
|
explains what AOT compilation is and why you'd want it.
|
||||||
|
It demonstrates the basics with a QuickStart app
|
||||||
|
followed by the more advanced considerations of compiling and bundling the Tour of Heroes.
|
||||||
|
|
||||||
|
## Sync with Angular v.2.0.2 (2016-10-6)
|
||||||
|
Docs and code samples updated and tested with Angular v.2.0.2
|
||||||
|
|
||||||
|
## "Routing and Navigation" guide with the _Router Module_ (2016-10-5)
|
||||||
|
The [Routing and Navigation](router.html) guide now locates route configuration
|
||||||
|
in a _Routing Module_.
|
||||||
|
The _Routing Module_ replaces the previous _routing object_ involving the `ModuleWithProviders`.
|
||||||
|
|
||||||
|
All guided samples with routing use the _Routing Module_ and prose content has been updated,
|
||||||
|
most conspicuously in the
|
||||||
|
[NgModule](ngmodule.html) guide and [NgModule FAQ](../cookbook/ngmodule-faq.html) cookbook.
|
||||||
|
|
||||||
|
## New "Internationalization" Cookbook (2016-09-30)
|
||||||
|
|
||||||
|
Added a new [Internationalization (i18n)](../cookbook/i18n.html) cookbook that shows how
|
||||||
|
to use Angular "i18n" facilities to translate template text into multiple languages.
|
||||||
|
|
||||||
|
## "angular-in-memory-web-api" package rename (2016-09-27)
|
||||||
|
|
||||||
|
Many samples use the `angular-in-memory-web-api` to simulate a remote server.
|
||||||
|
This library is also useful to you during early development before you have a server to talk to.
|
||||||
|
|
||||||
|
The package name was changed from "angular2-in-memory-web-api" which is still frozen-in-time on npm.
|
||||||
|
The new "angular-in-memory-web-api" has new features.
|
||||||
|
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md" target="_blank">Read about them on github</a>.
|
||||||
|
|
||||||
|
## "Style Guide" with _NgModules_ (2016-09-27)
|
||||||
|
|
||||||
|
[StyleGuide](style-guide.html) explains our recommended conventions for Angular modules (NgModule).
|
||||||
|
Barrels now are far less useful and have been removed from the style guide;
|
||||||
|
they remain valuable but are not a matter of Angular style.
|
||||||
|
We also relaxed the rule that discouraged use of the `@Component.host` property.
|
||||||
|
|
||||||
|
## _moduleId: module.id_ everywhere (2016-09-25)
|
||||||
|
|
||||||
|
Sample components that get their templates or styles with `templateUrl` or `styleUrls`
|
||||||
|
have been converted to _module-relative_ URLs.
|
||||||
|
We added the `moduleId: module.id` property-and-value to their `@Component` metadata.
|
||||||
|
|
||||||
|
This change is a requirement for compilation with AOT compiler when the app loads
|
||||||
|
modules with SystemJS as the samples currently do.
|
||||||
|
|
||||||
|
## "Lifecycle Hooks" guide simplified (2016-09-24)
|
||||||
|
|
||||||
|
The [Lifecycle Hooks](lifecycle-hooks.html) guide is shorter, simpler, and
|
||||||
|
draws more attention to the order in which Angular calls the hooks.
|
|
@ -0,0 +1,3 @@
|
||||||
|
@title
|
||||||
|
Cheat Sheet
|
||||||
|
|
|
@ -0,0 +1,291 @@
|
||||||
|
@title
|
||||||
|
Component Styles
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Learn how to apply CSS styles to components.
|
||||||
|
|
||||||
|
@description
|
||||||
|
Angular applications are styled with regular CSS. That means we can apply
|
||||||
|
everything we know about CSS stylesheets, selectors, rules, and media queries
|
||||||
|
to our Angular applications directly.
|
||||||
|
|
||||||
|
On top of this, Angular has the ability to bundle *component styles*
|
||||||
|
with our components enabling a more modular design than regular stylesheets.
|
||||||
|
|
||||||
|
In this chapter we learn how to load and apply these *component styles*.
|
||||||
|
|
||||||
|
## Table Of Contents
|
||||||
|
|
||||||
|
* [Using Component Styles](#using-component-styles)
|
||||||
|
* [Special selectors](#special-selectors)
|
||||||
|
* [Loading Styles into Components](#loading-styles)
|
||||||
|
* [Controlling View Encapsulation: Emulated, Native, and None](#view-encapsulation)
|
||||||
|
* [Appendix 1: Inspecting the generated runtime component styles](#inspect-generated-css)
|
||||||
|
* [Appendix 2: Loading Styles with Relative URLs](#relative-urls)
|
||||||
|
|
||||||
|
Run the <live-example></live-example> of the code shown in this chapter.
|
||||||
|
|
||||||
|
## Using Component Styles
|
||||||
|
|
||||||
|
For every Angular component we write, we may define not only an HTML template,
|
||||||
|
but also the CSS styles that go with that template,
|
||||||
|
specifying any selectors, rules, and media queries that we need.
|
||||||
|
|
||||||
|
One way to do this is to set the `styles` property in the component metadata.
|
||||||
|
The `styles` property takes #{_an} #{_array} of strings that contain CSS code.
|
||||||
|
Usually we give it one string as in this example:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-app.component.ts'}
|
||||||
|
|
||||||
|
Component styles differ from traditional, global styles in a couple of ways.
|
||||||
|
|
||||||
|
Firstly, the selectors we put into a component's styles *only apply within the template
|
||||||
|
of that component*. The `h1` selector in the example above only applies to the `<h1>` tag
|
||||||
|
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
|
||||||
|
the application are unaffected.
|
||||||
|
|
||||||
|
This is a big improvement in modularity compared to how CSS traditionally works:
|
||||||
|
|
||||||
|
1. We can use the CSS class names and selectors that make the most sense in the context of each component.
|
||||||
|
|
||||||
|
1. Class names and selectors are local to the component and won't collide with
|
||||||
|
classes and selectors used elsewhere in the application.
|
||||||
|
|
||||||
|
1. Our component's styles *cannot* be changed by changes to styles elsewhere in the application.
|
||||||
|
|
||||||
|
1. We can co-locate the CSS code of each component with the TypeScript and HTML code of the component,
|
||||||
|
which leads to a neat and tidy project structure.
|
||||||
|
|
||||||
|
1. We can change or remove component CSS code in the future without trawling through the
|
||||||
|
whole application to see where else it may have been used. We just look at the component we're in.
|
||||||
|
|
||||||
|
|
||||||
|
{@a special-selectors}
|
||||||
|
|
||||||
|
## Special selectors
|
||||||
|
|
||||||
|
Component styles have a few special *selectors* from the world of
|
||||||
|
[shadow DOM style scoping](https://www.w3.org/TR/css-scoping-1):
|
||||||
|
|
||||||
|
### :host
|
||||||
|
|
||||||
|
Use the `:host` pseudo-class selector to target styles in the element that *hosts* the component (as opposed to
|
||||||
|
targeting elements *inside* the component's template):
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-details.component.css' region='host'}
|
||||||
|
|
||||||
|
This is the *only* way we can target the host element. We cannot reach
|
||||||
|
it from inside the component with other selectors, because it is not part of the
|
||||||
|
component's own template. It is in a parent component's template.
|
||||||
|
|
||||||
|
Use the *function form* to apply host styles conditionally by
|
||||||
|
including another selector inside parentheses after `:host`.
|
||||||
|
|
||||||
|
In the next example we target the host element again, but only when it also has the `active` CSS class.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-details.component.css' region='hostfunction'}
|
||||||
|
|
||||||
|
### :host-context
|
||||||
|
|
||||||
|
Sometimes it is useful to apply styles based on some condition *outside* a component's view.
|
||||||
|
For example, there may be a CSS theme class applied to the document `<body>` element, and
|
||||||
|
we want to change how our component looks based on that.
|
||||||
|
|
||||||
|
Use the `:host-context()` pseudo-class selector. It works just like the function
|
||||||
|
form of `:host()`. It looks for a CSS class in *any ancestor* of the component host element, all the way
|
||||||
|
up to the document root. It's useful when combined with another selector.
|
||||||
|
|
||||||
|
In the following example, we apply a `background-color` style to all `<h2>` elements *inside* the component, only
|
||||||
|
if some ancestor element has the CSS class `theme-light`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-details.component.css' region='hostcontext'}
|
||||||
|
|
||||||
|
### /deep/
|
||||||
|
|
||||||
|
Component styles normally apply only to the HTML in the component's own template.
|
||||||
|
|
||||||
|
We can use the `/deep/` selector to force a style down through the child component tree into all the child component views.
|
||||||
|
The `/deep/` selector works to any depth of nested components, and it applies *both to the view
|
||||||
|
children and the content children* of the component.
|
||||||
|
|
||||||
|
In this example, we target all `<h3>` elements, from the host element down
|
||||||
|
through this component to all of its child elements in the DOM:
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-details.component.css' region='deep'}
|
||||||
|
|
||||||
|
The `/deep/` selector also has the alias `>>>`. We can use either of the two interchangeably.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
The `/deep/` and `>>>` selectors should only be used with **emulated** view encapsulation.
|
||||||
|
This is the default and it is what we use most of the time. See the
|
||||||
|
[Controlling View Encapsulation](#view-encapsulation)
|
||||||
|
section for more details.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a loading-styles}
|
||||||
|
|
||||||
|
## Loading Styles into Components
|
||||||
|
|
||||||
|
We have several ways to add styles to a component:
|
||||||
|
* inline in the template HTML
|
||||||
|
* by setting `styles` or `styleUrls` metadata
|
||||||
|
* with CSS imports
|
||||||
|
|
||||||
|
The scoping rules outlined above apply to each of these loading patterns.
|
||||||
|
|
||||||
|
### Styles in Metadata
|
||||||
|
|
||||||
|
We can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
||||||
|
Each string in the #{_array} (usually just one string) defines the CSS.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-app.component.ts'}
|
||||||
|
|
||||||
|
### Template Inline Styles
|
||||||
|
|
||||||
|
We 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'}
|
||||||
|
|
||||||
|
### Style URLs in Metadata
|
||||||
|
|
||||||
|
We 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'}
|
||||||
|
|
||||||
|
### Template Link Tags
|
||||||
|
|
||||||
|
We can also embed `<link>` tags into the component's HTML template.
|
||||||
|
|
||||||
|
As with `styleUrls`, the link tag's `href` URL is relative to the
|
||||||
|
application root, not relative to the component file.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-team.component.ts' region='stylelink'}
|
||||||
|
|
||||||
|
### CSS @imports
|
||||||
|
|
||||||
|
We can also import CSS files into our CSS files by using the standard CSS
|
||||||
|
[`@import` rule](https://developer.mozilla.org/en/docs/Web/CSS/@import).
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/hero-details.component.css' region='import'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a view-encapsulation}
|
||||||
|
|
||||||
|
## Controlling View Encapsulation: Native, Emulated, and None
|
||||||
|
|
||||||
|
As discussed above, component CSS styles are *encapsulated* into the component's own view and do
|
||||||
|
not affect the rest of the application.
|
||||||
|
|
||||||
|
We can control how this encapsulation happens on a *per
|
||||||
|
component* basis by setting the *view encapsulation mode* in the component metadata. There
|
||||||
|
are three modes to choose from:
|
||||||
|
|
||||||
|
* `Native` view encapsulation uses the browser's native [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||||
|
implementation to attach a Shadow DOM to the component's host element, and then puts the component
|
||||||
|
view inside that Shadow DOM. The component's styles are included within the Shadow DOM.
|
||||||
|
|
||||||
|
* `Emulated` view encapsulation (**the default**) emulates the behavior of Shadow DOM by preprocessing
|
||||||
|
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||||
|
See [Appendix 1](#inspect-generated-css) for details.
|
||||||
|
|
||||||
|
* `None` means that Angular does no view encapsulation.
|
||||||
|
Angular adds the CSS to the global styles.
|
||||||
|
The scoping rules, isolations, and protections discussed earlier do not apply.
|
||||||
|
This is essentially the same as pasting the component's styles into the HTML.
|
||||||
|
|
||||||
|
Set the components encapsulation mode using the `encapsulation` property in the component metadata:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'component-styles/ts/src/app/quest-summary.component.ts' region='encapsulation.native'}
|
||||||
|
|
||||||
|
`Native` view encapsulation only works on [browsers that have native support
|
||||||
|
for Shadow DOM](http://caniuse.com/#feat=shadowdom). The support is still limited,
|
||||||
|
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||||
|
in most cases.
|
||||||
|
|
||||||
|
|
||||||
|
{@a inspect-generated-css}
|
||||||
|
|
||||||
|
## Appendix 1: Inspecting The CSS Generated in Emulated View Encapsulation
|
||||||
|
|
||||||
|
When using the default emulated view encapsulation, Angular preprocesses
|
||||||
|
all component styles so that they approximate the standard Shadow CSS scoping rules.
|
||||||
|
|
||||||
|
When we inspect the DOM of a running Angular application with emulated view
|
||||||
|
encapsulation enabled, we see that each DOM element has some extra attributes
|
||||||
|
attached to it:
|
||||||
|
|
||||||
|
<code-example format="">
|
||||||
|
<hero-details _nghost-pmm-5>
|
||||||
|
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||||
|
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||||
|
<h3 _ngcontent-pmm-6>Team</h3>
|
||||||
|
</hero-team>
|
||||||
|
</hero-detail>
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
We see two kinds of generated attributes:
|
||||||
|
* An element that would be a Shadow DOM host in native encapsulation has a
|
||||||
|
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||||
|
|
||||||
|
* An element within a component's view has a `_ngcontent` attribute
|
||||||
|
that identifies to which host's emulated Shadow DOM this element belongs.
|
||||||
|
|
||||||
|
The exact values of these attributes are not important. They are automatically
|
||||||
|
generated and we never refer to them in application code. But they are targeted
|
||||||
|
by the generated component styles, which we'll find in the `<head>` section of the DOM:
|
||||||
|
|
||||||
|
<code-example format="">
|
||||||
|
[_nghost-pmm-5] {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3[_ngcontent-pmm-6] {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
These are the styles we wrote, post-processed so that each selector is augmented
|
||||||
|
with `_nghost` or `_ngcontent` attribute selectors.
|
||||||
|
These extra selectors enable the scoping rules described in this guide.
|
||||||
|
|
||||||
|
We'll likely live with *emulated* mode until shadow DOM gains traction.
|
||||||
|
|
||||||
|
|
||||||
|
{@a relative-urls}
|
||||||
|
|
||||||
|
## Appendix 2: Loading Styles with Relative URLs
|
||||||
|
|
||||||
|
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
|
||||||
|
<code-example format="nocode">
|
||||||
|
quest-summary.component.ts
|
||||||
|
quest-summary.component.html
|
||||||
|
quest-summary.component.css
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
We include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
||||||
|
Because these files are co-located with the component,
|
||||||
|
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
|
@ -0,0 +1,737 @@
|
||||||
|
@title
|
||||||
|
Dependency Injection
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Angular's dependency injection system creates and delivers dependent services "just-in-time".
|
||||||
|
|
||||||
|
@description
|
||||||
|
**Dependency injection** is an important application design pattern.
|
||||||
|
Angular has its own dependency injection framework, and
|
||||||
|
we really can't build an Angular application without it.
|
||||||
|
It's used so widely that almost everyone just calls it _DI_.
|
||||||
|
|
||||||
|
In this chapter we'll learn what DI is and why we want it.
|
||||||
|
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
||||||
|
|
||||||
|
- [Why dependency injection?](#why-dependency-injection)
|
||||||
|
- [Angular dependency injection](#angular-dependency-injection)
|
||||||
|
- [Injector providers](#injector-providers)
|
||||||
|
- [Dependency injection tokens](#dependency-injection-tokens)
|
||||||
|
- [Summary](#summary)
|
||||||
|
|
||||||
|
Run the <live-example></live-example>.
|
||||||
|
|
||||||
|
## Why dependency injection?
|
||||||
|
|
||||||
|
Let's start with the following code.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-no-di.ts' region='car'}
|
||||||
|
|
||||||
|
Our `Car` creates everything it needs inside its constructor.
|
||||||
|
What's the problem?
|
||||||
|
The problem is that our `Car` class is brittle, inflexible, and hard to test.
|
||||||
|
|
||||||
|
Our `Car` needs an engine and tires. Instead of asking for them,
|
||||||
|
the `Car` constructor instantiates its own copies from
|
||||||
|
the very specific classes `Engine` and `Tires`.
|
||||||
|
|
||||||
|
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||||
|
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
||||||
|
`#{_thisDot}engine = new Engine(theNewParameter)`.
|
||||||
|
We didn't care about `Engine` constructor parameters when we first wrote `Car`.
|
||||||
|
We don't really care about them now.
|
||||||
|
But we'll *have* to start caring because
|
||||||
|
when the definition of `Engine` changes, our `Car` class must change.
|
||||||
|
That makes `Car` brittle.
|
||||||
|
|
||||||
|
What if we want to put a different brand of tires on our `Car`? Too bad.
|
||||||
|
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
||||||
|
|
||||||
|
Right now each new car gets its own engine. It can't share an engine with other cars.
|
||||||
|
While that makes sense for an automobile engine,
|
||||||
|
we can think of other dependencies that should be shared, such as the onboard
|
||||||
|
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
||||||
|
to share services that have been created previously for other consumers.
|
||||||
|
|
||||||
|
When we write tests for our `Car` we're at the mercy of its hidden dependencies.
|
||||||
|
Is it even possible to create a new `Engine` in a test environment?
|
||||||
|
What does `Engine`itself depend upon? What does that dependency depend on?
|
||||||
|
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||||
|
We certainly don't want that going on during our tests.
|
||||||
|
|
||||||
|
What if our `Car` should flash a warning signal when tire pressure is low?
|
||||||
|
How do we confirm that it actually does flash a warning
|
||||||
|
if we can't swap in low-pressure tires during the test?
|
||||||
|
|
||||||
|
We have no control over the car's hidden dependencies.
|
||||||
|
When we can't control the dependencies, a class becomes difficult to test.
|
||||||
|
|
||||||
|
How can we make `Car` more robust, flexible, and testable?
|
||||||
|
|
||||||
|
<a id="ctor-injection"></a>
|
||||||
|
That's super easy. We change our `Car` constructor to a version with DI:
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/car/car.ts (excerpt with DI)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car.ts' region='car-ctor'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/car/car.ts (excerpt without DI)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-no-di.ts' region='car-ctor'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
See what happened? We moved the definition of the dependencies to the constructor.
|
||||||
|
Our `Car` class no longer creates an engine or tires.
|
||||||
|
It just consumes them.
|
||||||
|
Now we create a car by passing the engine and tires to the constructor.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-creations.ts' region='car-ctor-instantiation'}
|
||||||
|
|
||||||
|
How cool is that?
|
||||||
|
The definition of the engine and tire dependencies are
|
||||||
|
decoupled from the `Car` class itself.
|
||||||
|
We can pass in any kind of engine or tires we like, as long as they
|
||||||
|
conform to the general API requirements of an engine or tires.
|
||||||
|
|
||||||
|
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||||
|
|
||||||
|
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||||
|
something like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-creations.ts' region='car-ctor-instantiation-with-param'}
|
||||||
|
|
||||||
|
The critical point is this: `Car` itself did not have to change.
|
||||||
|
We'll take care of the consumer's problem soon enough.
|
||||||
|
The `Car` class is much easier to test because we are in complete control
|
||||||
|
of its dependencies.
|
||||||
|
We can pass mocks to the constructor that do exactly what we want them to do
|
||||||
|
during each test:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-creations.ts' region='car-ctor-instantiation-with-mocks'}
|
||||||
|
|
||||||
|
**We just learned what dependency injection is**.
|
||||||
|
|
||||||
|
It's a coding pattern in which a class receives its dependencies from external
|
||||||
|
sources rather than creating them itself.
|
||||||
|
|
||||||
|
Cool! But what about that poor consumer?
|
||||||
|
Anyone who wants a `Car` must now
|
||||||
|
create all three parts: the `Car`, `Engine`, and `Tires`.
|
||||||
|
The `Car` class shed its problems at the consumer's expense.
|
||||||
|
We need something that takes care of assembling these parts for us.
|
||||||
|
|
||||||
|
We could write a giant class to do that:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-factory.ts'}
|
||||||
|
|
||||||
|
It's not so bad now with only three creation methods.
|
||||||
|
But maintaining it will be hairy as the application grows.
|
||||||
|
This factory is going to become a huge spiderweb of
|
||||||
|
interdependent factory methods!
|
||||||
|
|
||||||
|
Wouldn't it be nice if we could simply list the things we want to build without
|
||||||
|
having to define which dependency gets injected into what?
|
||||||
|
|
||||||
|
This is where the dependency injection framework comes into play.
|
||||||
|
Imagine the framework had something called an _injector_.
|
||||||
|
We register some classes with this injector, and it figures out how to create them.
|
||||||
|
|
||||||
|
When we need a `Car`, we simply ask the injector to get it for us and we're good to go.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-injector.ts' region='injector-call'}
|
||||||
|
|
||||||
|
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||||
|
The consumer knows nothing about creating a `Car`.
|
||||||
|
We don't have a gigantic factory class to maintain.
|
||||||
|
Both `Car` and consumer simply ask for what they need and the injector delivers.
|
||||||
|
|
||||||
|
This is what a **dependency injection framework** is all about.
|
||||||
|
|
||||||
|
Now that we know what dependency injection is and appreciate its benefits,
|
||||||
|
let's see how it is implemented in Angular.
|
||||||
|
|
||||||
|
## Angular dependency injection
|
||||||
|
|
||||||
|
Angular ships with its own dependency injection framework. This framework can also be used
|
||||||
|
as a standalone module by other applications and frameworks.
|
||||||
|
|
||||||
|
That sounds nice. What does it do for us when building components in Angular?
|
||||||
|
Let's see, one step at a time.
|
||||||
|
|
||||||
|
We'll begin with a simplified version of the `HeroesComponent`
|
||||||
|
that we built in the [The Tour of Heroes](../tutorial/).
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/heroes.component.ts">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/heroes.component.1.ts' region='v1'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero-list.component.ts">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero-list.component.1.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero.ts">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/mock-heroes.ts">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/mock-heroes.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
||||||
|
It governs all the child components of this area.
|
||||||
|
Our stripped down version has only one child, `HeroListComponent`,
|
||||||
|
which displays a list of heroes.
|
||||||
|
Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection
|
||||||
|
defined in another file.
|
||||||
|
That may suffice in the early stages of development, but it's far from ideal.
|
||||||
|
As soon as we try to test this component or want to get our heroes data from a remote server,
|
||||||
|
we'll have to change the implementation of `heroes` and
|
||||||
|
fix every other use of the `HEROES` mock data.
|
||||||
|
|
||||||
|
Let's make a service that hides how we get hero data.
|
||||||
|
|
||||||
|
Given that the service is a
|
||||||
|
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
||||||
|
we suggest that you
|
||||||
|
write the service code in its own file.
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.1.ts'}
|
||||||
|
|
||||||
|
Our `HeroService` exposes a `getHeroes` method that returns
|
||||||
|
the same mock data as before, but none of its consumers need to know that.
|
||||||
|
Notice the `@Injectable()` #{_decorator} above the service class.
|
||||||
|
We'll discuss its purpose [shortly](#injectable).
|
||||||
|
|
||||||
|
We aren't even pretending this is a real service.
|
||||||
|
If we were actually getting data from a remote server, the API would have to be
|
||||||
|
asynchronous, #{_perhaps} returning a !{_PromiseLinked}.
|
||||||
|
We'd also have to rewrite the way components consume our service.
|
||||||
|
This is important in general, but not to our current story.
|
||||||
|
A service is nothing more than a class in Angular.
|
||||||
|
It remains nothing more than a class until we register it with an Angular injector.
|
||||||
|
|
||||||
|
<div id='bootstrap'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Configuring the injector
|
||||||
|
|
||||||
|
We don't have to create an Angular injector.
|
||||||
|
Angular creates an application-wide injector for us during the bootstrap process.
|
||||||
|
We do have to configure the injector by registering the **providers**
|
||||||
|
that create the services our application requires.
|
||||||
|
We'll explain what [providers](#providers) are later in this chapter.
|
||||||
|
### Registering providers in a component
|
||||||
|
|
||||||
|
Here's a revised `HeroesComponent` that registers the `HeroService`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/heroes.component.1.ts' region='full'}
|
||||||
|
|
||||||
|
### Preparing the HeroListComponent for injection
|
||||||
|
|
||||||
|
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
||||||
|
Per the dependency injection pattern, the component must ask for the service in its
|
||||||
|
constructor, [as we explained earlier](#ctor-injection).
|
||||||
|
It's a small change:
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero-list.component (with DI)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero-list.component.2.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero-list.component (without DI)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero-list.component.1.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
#### Focus on the constructor
|
||||||
|
|
||||||
|
Adding a parameter to the constructor isn't all that's happening here.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero-list.component.2.ts' region='ctor'}
|
||||||
|
|
||||||
|
Note that the constructor parameter has the type `HeroService`, and that
|
||||||
|
the `HeroListComponent` class has an `@Component` #{_decorator}
|
||||||
|
(scroll up to confirm that fact).
|
||||||
|
Also recall that the parent component (`HeroesComponent`)
|
||||||
|
has `providers` information for `HeroService`.
|
||||||
|
|
||||||
|
The constructor parameter type, the `@Component` #{_decorator},
|
||||||
|
and the parent's `providers` information combine to tell the
|
||||||
|
Angular injector to inject an instance of
|
||||||
|
`HeroService` whenever it creates a new `HeroListComponent`.
|
||||||
|
|
||||||
|
<div id='di-metadata'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Implicit injector creation
|
||||||
|
|
||||||
|
When we introduced the idea of an injector above, we showed how to
|
||||||
|
use it to create a new `Car`. Here we also show how such an injector
|
||||||
|
would be explicitly created:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/car/car-injector.ts' region='injector-create-and-call'}
|
||||||
|
|
||||||
|
We won't find code like that in the Tour of Heroes or any of our other samples.
|
||||||
|
We *could* write code that [explicitly creates an injector](#explicit-injector) if we *had* to, but we rarely do.
|
||||||
|
Angular takes care of creating and calling injectors
|
||||||
|
when it creates components for us — whether through HTML markup, as in `<hero-list></hero-list>`,
|
||||||
|
or after navigating to a component with the [router](./router.html).
|
||||||
|
If we let Angular do its job, we'll enjoy the benefits of automated dependency injection.
|
||||||
|
### Singleton services
|
||||||
|
|
||||||
|
Dependencies are singletons within the scope of an injector.
|
||||||
|
In our example, a single `HeroService` instance is shared among the
|
||||||
|
`HeroesComponent` and its `HeroListComponent` children.
|
||||||
|
|
||||||
|
However, Angular DI is an hierarchical injection
|
||||||
|
system, which means that nested injectors can create their own service instances.
|
||||||
|
Learn more about that in the [Hierarchical Injectors](./hierarchical-dependency-injection.html) chapter.
|
||||||
|
### Testing the component
|
||||||
|
|
||||||
|
We emphasized earlier that designing a class for dependency injection makes the class easier to test.
|
||||||
|
Listing dependencies as constructor parameters may be all we need to test application parts effectively.
|
||||||
|
|
||||||
|
For example, we can create a new `HeroListComponent` with a mock service that we can manipulate
|
||||||
|
under test:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/test.component.ts' region='spec'}
|
||||||
|
|
||||||
|
|
||||||
|
Learn more in [Testing](./testing.html).
|
||||||
|
### When the service needs a service
|
||||||
|
|
||||||
|
Our `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||||
|
|
||||||
|
|
||||||
|
What if it had a dependency? What if it reported its activities through a logging service?
|
||||||
|
We'd apply the same *constructor injection* pattern,
|
||||||
|
adding a constructor that takes a `Logger` parameter.
|
||||||
|
|
||||||
|
Here is the revision compared to the original.
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero.service (v2)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.2.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/hero.service (v1)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.1.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `#{_priv}logger`.
|
||||||
|
We call that property within our `getHeroes` method when anyone asks for heroes.
|
||||||
|
|
||||||
|
<h3 id='injectable'>
|
||||||
|
Why @Injectable()?
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
**<a href="#{injUrl}">@Injectable()</a>** marks a class as available to an
|
||||||
|
injector for instantiation. Generally speaking, an injector will report an
|
||||||
|
error when trying to instantiate a class that is not marked as
|
||||||
|
`@Injectable()`.
|
||||||
|
Injectors are also responsible for instantiating components
|
||||||
|
like `HeroesComponent`. Why haven't we marked `HeroesComponent` as
|
||||||
|
`@Injectable()`?
|
||||||
|
|
||||||
|
We *can* add it if we really want to. It isn't necessary because the
|
||||||
|
`HeroesComponent` is already marked with `@Component`, and this
|
||||||
|
!{_decorator} class (like `@Directive` and `@Pipe`, which we'll learn about later)
|
||||||
|
is a subtype of <a href="#{injUrl}">Injectable</a>. It is in
|
||||||
|
fact `Injectable` #{_decorator}s that
|
||||||
|
identify a class as a target for instantiation by an injector.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-critical}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
Always include the parentheses
|
||||||
|
</header>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
## Creating and registering a logger service
|
||||||
|
|
||||||
|
We're injecting a logger into our `HeroService` in two steps:
|
||||||
|
1. Create the logger service.
|
||||||
|
1. Register it with the application.
|
||||||
|
|
||||||
|
Our logger service is quite simple:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/logger.service.ts'}
|
||||||
|
|
||||||
|
We're likely to need the same logger service everywhere in our application,
|
||||||
|
so we put it in the project's `#{_appDir}` folder, and
|
||||||
|
we register it in the `providers` #{_array} of our application !{_moduleVsComp}, `!{_AppModuleVsAppComp}`.
|
||||||
|
If we forget to register the logger, Angular throws an exception when it first looks for the logger:
|
||||||
|
<code-example format="nocode">
|
||||||
|
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
That's Angular telling us that the dependency injector couldn't find the *provider* for the logger.
|
||||||
|
It needed that provider to create a `Logger` to inject into a new
|
||||||
|
`HeroService`, which it needed to
|
||||||
|
create and inject into a new `HeroListComponent`.
|
||||||
|
|
||||||
|
The chain of creations started with the `Logger` provider. *Providers* are the subject of our next section.
|
||||||
|
|
||||||
|
## Injector providers
|
||||||
|
|
||||||
|
A provider *provides* the concrete, runtime version of a dependency value.
|
||||||
|
The injector relies on **providers** to create instances of the services
|
||||||
|
that the injector injects into components and other services.
|
||||||
|
|
||||||
|
We must register a service *provider* with the injector, or it won't know how to create the service.
|
||||||
|
|
||||||
|
Earlier we registered the `Logger` service in the `providers` #{_array} of the metadata for the `AppModule` like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-logger'}
|
||||||
|
|
||||||
|
There are many ways to *provide* something that #{implements} `Logger`.
|
||||||
|
The `Logger` class itself is an obvious and natural provider.
|
||||||
|
But it's not the only way.
|
||||||
|
|
||||||
|
We can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
||||||
|
We could provide a substitute class. #{loggerlike}
|
||||||
|
We could give it a provider that calls a logger factory function.
|
||||||
|
Any of these approaches might be a good choice under the right circumstances.
|
||||||
|
|
||||||
|
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||||
|
|
||||||
|
<div id='provide'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### The *Provider* class !{_andProvideFn}
|
||||||
|
We wrote the `providers` #{_array} like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-1'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-3'}
|
||||||
|
|
||||||
|
The first is the [token](#token) that serves as the key for both locating a dependency value
|
||||||
|
and registering the provider.
|
||||||
|
|
||||||
|
The second is a !{_secondParam},
|
||||||
|
which we can think of as a *recipe* for creating the dependency value.
|
||||||
|
There are many ways to create dependency values ... and many ways to write a recipe.
|
||||||
|
|
||||||
|
<div id='class-provider'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Alternative class providers
|
||||||
|
|
||||||
|
Occasionally we'll ask a different class to provide the service.
|
||||||
|
The following code tells the injector
|
||||||
|
to return a `BetterLogger` when something asks for the `Logger`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-4'}
|
||||||
|
|
||||||
|
### Class provider with dependencies
|
||||||
|
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||||
|
This logger gets the user from the injected `UserService`,
|
||||||
|
which happens also to be injected at the application level.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='EvenBetterLogger'}
|
||||||
|
|
||||||
|
Configure it like we did `BetterLogger`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-5'}
|
||||||
|
|
||||||
|
### Aliased class providers
|
||||||
|
|
||||||
|
Suppose an old component depends upon an `OldLogger` class.
|
||||||
|
`OldLogger` has the same interface as the `NewLogger`, but for some reason
|
||||||
|
we can't update the old component to use it.
|
||||||
|
|
||||||
|
When the *old* component logs a message with `OldLogger`,
|
||||||
|
we want the singleton instance of `NewLogger` to handle it instead.
|
||||||
|
|
||||||
|
The dependency injector should inject that singleton instance
|
||||||
|
when a component asks for either the new or the old logger.
|
||||||
|
The `OldLogger` should be an alias for `NewLogger`.
|
||||||
|
|
||||||
|
We certainly do not want two different `NewLogger` instances in our app.
|
||||||
|
Unfortunately, that's what we get if we try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-6a'}
|
||||||
|
|
||||||
|
The solution: alias with the `useExisting` option.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-6b'}
|
||||||
|
|
||||||
|
|
||||||
|
<div id='value-provider'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Value providers
|
||||||
|
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='silent-logger'}
|
||||||
|
|
||||||
|
Then we register a provider with the `useValue` option,
|
||||||
|
which makes this object play the logger role.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-7'}
|
||||||
|
|
||||||
|
See more `useValue` examples in the
|
||||||
|
[Non-class dependencies](#non-class-dependencies) and
|
||||||
|
[OpaqueToken](#opaquetoken) sections.
|
||||||
|
|
||||||
|
<div id='factory-provider'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Factory providers
|
||||||
|
|
||||||
|
Sometimes we need to create the dependent value dynamically,
|
||||||
|
based on information we won't have until the last possible moment.
|
||||||
|
Maybe the information changes repeatedly in the course of the browser session.
|
||||||
|
|
||||||
|
Suppose also that the injectable service has no independent access to the source of this information.
|
||||||
|
|
||||||
|
This situation calls for a **factory provider**.
|
||||||
|
|
||||||
|
Let's illustrate by adding a new business requirement:
|
||||||
|
the HeroService must hide *secret* heroes from normal users.
|
||||||
|
Only authorized users should see secret heroes.
|
||||||
|
|
||||||
|
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||||
|
It needs to know if the user is authorized to see secret heroes.
|
||||||
|
That authorization can change during the course of a single application session,
|
||||||
|
as when we log in a different user.
|
||||||
|
|
||||||
|
Unlike `EvenBetterLogger`, we can't inject the `UserService` into the `HeroService`.
|
||||||
|
The `HeroService` won't have direct access to the user information to decide
|
||||||
|
who is authorized and who is not.
|
||||||
|
|
||||||
|
Why? We don't know either. Stuff like this happens.
|
||||||
|
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.ts' region='internals'}
|
||||||
|
|
||||||
|
We can inject the `Logger`, but we can't inject the boolean `isAuthorized`.
|
||||||
|
We'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||||
|
|
||||||
|
A factory provider needs a factory function:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.provider.ts' region='factory'}
|
||||||
|
|
||||||
|
Although the `HeroService` has no access to the `UserService`, our factory function does.
|
||||||
|
|
||||||
|
We inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero.service.provider.ts' region='provider'}
|
||||||
|
|
||||||
|
|
||||||
|
The `useFactory` field tells Angular that the provider is a factory function
|
||||||
|
whose implementation is the `heroServiceFactory`.
|
||||||
|
|
||||||
|
The `deps` property is #{_an} #{_array} of [provider tokens](#token).
|
||||||
|
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||||
|
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||||
|
Notice that we captured the factory provider in #{_an} #{exportedvar}, `heroServiceProvider`.
|
||||||
|
This extra step makes the factory provider reusable.
|
||||||
|
We can register our `HeroService` with this #{variable} wherever we need it.
|
||||||
|
|
||||||
|
In our sample, we need it only in the `HeroesComponent`,
|
||||||
|
where it replaces the previous `HeroService` registration in the metadata `providers` #{_array}.
|
||||||
|
Here we see the new and the old implementation side-by-side:
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/heroes.component (v3)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/heroes.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/heroes/heroes.component (v2)">
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/heroes.component.1.ts' region='full'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
## Dependency injection tokens
|
||||||
|
|
||||||
|
When we register a provider with an injector, we associate that provider with a dependency injection token.
|
||||||
|
The injector maintains an internal *token-provider* map that it references when
|
||||||
|
asked for a dependency. The token is the key to the map.
|
||||||
|
|
||||||
|
In all previous examples, the dependency value has been a class *instance*, and
|
||||||
|
the class *type* served as its own lookup key.
|
||||||
|
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/injector.component.ts' region='get-hero-service'}
|
||||||
|
|
||||||
|
We have similar good fortune when we write a constructor that requires an injected class-based dependency.
|
||||||
|
We define a constructor parameter with the `HeroService` class type,
|
||||||
|
and Angular knows to inject the
|
||||||
|
service associated with that `HeroService` class token:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/heroes/hero-list.component.ts' region='ctor-signature'}
|
||||||
|
|
||||||
|
This is especially convenient when we consider that most dependency values are provided by classes.
|
||||||
|
### Non-class dependencies
|
||||||
|
<p>
|
||||||
|
What if the dependency value isn't a class? Sometimes the thing we want to inject is a
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Applications often define configuration objects with lots of small facts
|
||||||
|
(like the title of the application or the address of a web API endpoint) such as this one:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/app.config.ts' region='config'}
|
||||||
|
|
||||||
|
We'd like to make this configuration object available for injection.
|
||||||
|
We know we can register an object with a [value provider](#value-provider).
|
||||||
|
### OpaqueToken
|
||||||
|
|
||||||
|
One solution to choosing a provider token for non-class dependencies is
|
||||||
|
to define and use an !{opaquetoken}.
|
||||||
|
The definition looks like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/app.config.ts' region='token'}
|
||||||
|
|
||||||
|
We register the dependency provider using the `OpaqueToken` object:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='providers-9'}
|
||||||
|
|
||||||
|
Now we can inject the configuration object into any constructor that needs it, with
|
||||||
|
the help of an `@Inject` #{_decorator}:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/app.component.2.ts' region='ctor'}
|
||||||
|
|
||||||
|
|
||||||
|
Although the !{configType} interface plays no role in dependency injection,
|
||||||
|
it supports typing of the configuration object within the class.
|
||||||
|
|
||||||
|
<div id='optional'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Optional dependencies
|
||||||
|
|
||||||
|
Our `HeroService` *requires* a `Logger`, but what if it could get by without
|
||||||
|
a logger?
|
||||||
|
We can tell Angular that the dependency is optional by annotating the
|
||||||
|
constructor argument with `@Optional()`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/providers.component.ts' region='provider-10-ctor'}
|
||||||
|
|
||||||
|
When using `@Optional()`, our code must be prepared for a null value. If we
|
||||||
|
don't register a logger somewhere up the line, the injector will set the
|
||||||
|
value of `logger` to null.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
We learned the basics of Angular dependency injection in this chapter.
|
||||||
|
We can register various kinds of providers,
|
||||||
|
and we know how to ask for an injected object (such as a service) by
|
||||||
|
adding a parameter to a constructor.
|
||||||
|
|
||||||
|
Angular dependency injection is more capable than we've described.
|
||||||
|
We can learn more about its advanced features, beginning with its support for
|
||||||
|
nested injectors, in the
|
||||||
|
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
||||||
|
|
||||||
|
## Appendix: Working with injectors directly
|
||||||
|
|
||||||
|
We rarely work directly with an injector, but
|
||||||
|
here's an `InjectorComponent` that does.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'dependency-injection/ts/src/app/injector.component.ts' region='injector'}
|
||||||
|
|
||||||
|
An `Injector` is itself an injectable service.
|
||||||
|
|
||||||
|
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
||||||
|
The component then asks the injected injector for the services it wants.
|
||||||
|
|
||||||
|
Note that the services themselves are not injected into the component.
|
||||||
|
They are retrieved by calling `injector.get`.
|
||||||
|
|
||||||
|
The `get` method throws an error if it can't resolve the requested service.
|
||||||
|
We can call `get` with a second parameter (the value to return if the service is not found)
|
||||||
|
instead, which we do in one case
|
||||||
|
to retrieve a service (`ROUS`) that isn't registered with this or any ancestor injector.
|
||||||
|
|
||||||
|
The technique we just described is an example of the
|
||||||
|
[service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||||
|
|
||||||
|
We **avoid** this technique unless we genuinely need it.
|
||||||
|
It encourages a careless grab-bag approach such as we see here.
|
||||||
|
It's difficult to explain, understand, and test.
|
||||||
|
We can't know by inspecting the constructor what this class requires or what it will do.
|
||||||
|
It could acquire services from any ancestor component, not just its own.
|
||||||
|
We're forced to spelunk the implementation to discover what it does.
|
||||||
|
|
||||||
|
Framework developers may take this approach when they
|
||||||
|
must acquire services generically and dynamically.
|
|
@ -0,0 +1,549 @@
|
||||||
|
@title
|
||||||
|
Deployment
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Learn how to deploy your Angular app.
|
||||||
|
|
||||||
|
@description
|
||||||
|
This page describes tools and techniques for deploy and optimize your Angular application.
|
||||||
|
|
||||||
|
|
||||||
|
{@a toc}
|
||||||
|
## Table of contents
|
||||||
|
* [Overview](#overview)
|
||||||
|
* [Simplest deployment possible](#dev-deploy)
|
||||||
|
* [Optimize for production](#optimize)
|
||||||
|
* [Ahead-of-Time (AOT) compilation](#aot)
|
||||||
|
* [Webpack](#webpack)
|
||||||
|
* [Tree shaking with _rollup_](#rollup)
|
||||||
|
* [Pruned libraries](#prune)
|
||||||
|
* [Measure performance first](#measure)
|
||||||
|
* [Angular configuration](#angular-configuration)
|
||||||
|
* [The `base` tag](#base-tag)
|
||||||
|
* [Enable production mode](#enable-prod-mode)
|
||||||
|
* [Lazy loading](#lazy-loading)
|
||||||
|
* [Server configuration](#server-configuration)
|
||||||
|
* [Routed apps must fallback to `index.html`](#fallback)
|
||||||
|
* [CORS: requesting services from a different server](#cors)
|
||||||
|
|
||||||
|
|
||||||
|
{@a overview}
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide describes techniques for preparing and deploying an Angular application to a server running remotely.
|
||||||
|
The techniques progress from _easy but suboptimal_ to _more optimal and more involved_.
|
||||||
|
|
||||||
|
* The [simple way](#dev-deploy "Simplest deployment possible") is to copy the development environment to the server.
|
||||||
|
|
||||||
|
* [_Ahead of Time_ compilation (AOT)](#aot "AOT Compilation") is the first of
|
||||||
|
[several optimization strategies](#optimize).
|
||||||
|
You'll also want to read the [detailed instructions in the AOT Cookbook](../cookbook/aot-compiler.html "AOT Cookbook").
|
||||||
|
|
||||||
|
* [Webpack](#webpack "Webpack Optimization") is a popular general purpose packaging tool with a rich ecosystem, including plugins for AOT.
|
||||||
|
The Angular [webpack guide](webpack.html "Webpack: an introduction") can get you started and
|
||||||
|
_this_ page provides additional optimization advice, but you'll probably have to learn more about webpack on your own.
|
||||||
|
|
||||||
|
* The [Angular configuration](#angular-configuration "Angular configuration") section calls attention to
|
||||||
|
specific client application changes that could improve performance.
|
||||||
|
|
||||||
|
* The [Server configuration](#server-configuration "Server configuration") section describes
|
||||||
|
server-side changes that may be necessary, _no matter how you deploy the application_.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a dev-deploy}
|
||||||
|
## Simplest deployment possible
|
||||||
|
|
||||||
|
The simplest way to deploy the app is to publish it to a web server
|
||||||
|
directly out of the development environment.
|
||||||
|
|
||||||
|
It's already running locally. You'll just copy it, almost _as is_,
|
||||||
|
to a non-local server that others can reach.
|
||||||
|
|
||||||
|
1. Copy _everything_ (or [_almost_ everything](#node-modules "Loading npm packages from the web"))
|
||||||
|
from the local project folder to a folder on the server.
|
||||||
|
|
||||||
|
1. If you're serving the app out of a subfolder,
|
||||||
|
edit a version of `index.html` to set the `<base href>` appropriately.
|
||||||
|
For example, if the URL to `index.html` is `www.mysite.com/my/app/`, set the _base href_ to
|
||||||
|
`<base href="/my/app/">`.
|
||||||
|
Otherwise, leave it alone.
|
||||||
|
[More on this below](#base-tag).
|
||||||
|
|
||||||
|
1. Configure the server to redirect requests for missing files to `index.html`.
|
||||||
|
[More on this below](#fallback).
|
||||||
|
|
||||||
|
1. Enable production mode as [described below](#enable-prod-mode) (optional).
|
||||||
|
|
||||||
|
That's the simplest deployment you can do.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
This is _not_ a production deployment. It's not optimized and it won't be fast for users.
|
||||||
|
It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders.
|
||||||
|
Be sure to read about [optimizing for production](#optimize "Optimizing for production") below.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a node-modules}
|
||||||
|
### Load npm package files from the web (SystemJS)
|
||||||
|
|
||||||
|
The `node_modules` folder of _npm packages_ contains much more code
|
||||||
|
than is needed to actually run your app in the browser.
|
||||||
|
The `node_modules` for the Quickstart installation is typically 20,500+ files and 180+ MB.
|
||||||
|
The application itself requires a tiny fraction of that to run.
|
||||||
|
|
||||||
|
It takes a long time to upload all of that useless bulk and
|
||||||
|
users will wait unnecessarily while library files download piecemeal.
|
||||||
|
|
||||||
|
Load the few files you need from the web instead.
|
||||||
|
|
||||||
|
(1) Make a copy of `index.html` for deployment and replace all `node_module` scripts
|
||||||
|
with versions that load from the web. It might look like this.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'deployment/ts/src/index.html' region='node-module-scripts'}
|
||||||
|
|
||||||
|
(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'}
|
||||||
|
|
||||||
|
(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
|
||||||
|
(and other third-party packages) from the web.
|
||||||
|
|
||||||
|
Modify `systemjs.config.server.js` as necessary to stay in sync with changes
|
||||||
|
you make to `systemjs.config.js`.
|
||||||
|
|
||||||
|
Notice the `paths` key:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'deployment/ts/src/systemjs.config.server.js' region='paths'}
|
||||||
|
|
||||||
|
In the standard SystemJS config, the `npm` path points to the `node_modules/`.
|
||||||
|
In this server config, it points to
|
||||||
|
<a href="https://unpkg.com/" target="_blank" title="unpkg.com">https://unpkg.com</a>,
|
||||||
|
a site that hosts _npm packages_,
|
||||||
|
and loads them from the web directly.
|
||||||
|
There are other service providers that do the same thing.
|
||||||
|
|
||||||
|
If you are unwilling or unable to load packages from the open web,
|
||||||
|
the inventory in `systemjs.config.server.js` identifies the files and folders that
|
||||||
|
you would copy to a library folder on the server.
|
||||||
|
Then change the config's `'npm'` path to point to that folder.
|
||||||
|
|
||||||
|
### Practice with an example
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="systemjs.config.server.js">
|
||||||
|
{@example 'deployment/ts/src/systemjs.config.server.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="main.ts">
|
||||||
|
{@example 'deployment/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/app.module.ts">
|
||||||
|
{@example 'deployment/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/app.component.ts">
|
||||||
|
{@example 'deployment/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/crisis-list.component.ts">
|
||||||
|
{@example 'deployment/ts/src/app/crisis-list.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app/hero-list.component.ts">
|
||||||
|
{@example 'deployment/ts/src/app/hero-list.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
Practice with this sample before attempting these techniques on your application.
|
||||||
|
|
||||||
|
1. Follow the [setup instructions](../guide/setup.html "Angular QuickStart setup") for creating a new project
|
||||||
|
named <ngio-ex path="simple-deployment"></ngio-ex>.
|
||||||
|
|
||||||
|
1. Add the "Simple deployment" sample files shown above.
|
||||||
|
|
||||||
|
1. Run it with `npm start` as you would any project.
|
||||||
|
|
||||||
|
1. Inspect the network traffic in the browser developer tools.
|
||||||
|
Notice that it loads all packages from the web.
|
||||||
|
You could delete the `node_modules` folder and the app would still run
|
||||||
|
(although you wouldn't be able to recompile or launch `lite-server`
|
||||||
|
until you restored it).
|
||||||
|
|
||||||
|
1. Deploy the sample to the server (minus the `node_modules` folder!).
|
||||||
|
|
||||||
|
When you have that working, try the same process on your application.
|
||||||
|
|
||||||
|
|
||||||
|
{@a optimize}
|
||||||
|
|
||||||
|
## Optimize for production
|
||||||
|
|
||||||
|
Although deploying directly from the development environment works, it's far from optimal.
|
||||||
|
|
||||||
|
The client makes many small requests for individual application code and template files,
|
||||||
|
a fact you can quickly confirm by looking at the network tab in a browser's developer tools.
|
||||||
|
Each small file download can spend more time communicating with the server than tranfering data.
|
||||||
|
|
||||||
|
Development files are full of comments and whitespace for easy reading and debugging.
|
||||||
|
The browser downloads entire libraries, instead of just the parts the app needs.
|
||||||
|
The volume of code passed from server to client (the "payload")
|
||||||
|
can be significantly larger than is strictly necessary to execute the application.
|
||||||
|
|
||||||
|
The many requests and large payloads mean
|
||||||
|
the app takes longer to launch than it would if you optimized it.
|
||||||
|
Several seconds may pass (or worse) before the user can see or do anything userful.
|
||||||
|
|
||||||
|
Does it matter? That depends upon business and technical factors you must evaluate for yourself.
|
||||||
|
|
||||||
|
If it _does_ matter, there are tools and techniques to reduce the number of requests and the size of responses.
|
||||||
|
|
||||||
|
- Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
|
||||||
|
- Bundling: concatenates modules into a single file (bundle).
|
||||||
|
- Inlining: pulls template html and css into the components.
|
||||||
|
- Minification: removes excess whitespace, comments, and optional tokens.
|
||||||
|
- Uglification: rewrites code to use short, cryptic variable and function names.
|
||||||
|
- Dead code elimination: removes unreferenced modules and unused code.
|
||||||
|
- Pruned libraries: drop unused libraries and pare others down to the features you need.
|
||||||
|
- Performance measurement: focus on optimizations that make a measurable difference.
|
||||||
|
|
||||||
|
Each tool does something different.
|
||||||
|
They work best in combination and are mutually reinforcing.
|
||||||
|
|
||||||
|
You can use any build system you like.
|
||||||
|
Whatever system you choose, be sure to automate it so that
|
||||||
|
building for production is a single step.
|
||||||
|
|
||||||
|
|
||||||
|
{@a aot}
|
||||||
|
### Ahead-of-Time (AOT) compilation
|
||||||
|
|
||||||
|
The Angular _Ahead-of-Time_ compiler pre-compiles application components and their templates
|
||||||
|
during the build process.
|
||||||
|
|
||||||
|
Apps compiled with AOT launch faster for several reasons.
|
||||||
|
* Application components execute immediately, without client-side compilation.
|
||||||
|
* Templates are embedded as code within their components so there is no client-side request for template files.
|
||||||
|
* You don't download the Angular compiler, which is pretty big on its own.
|
||||||
|
* The compiler discards unused Angular directives that a tree-shaking tool can then exclude.
|
||||||
|
|
||||||
|
Learn more about AOT Compilation in the [AOT Cookbook](../cookbook/aot-compiler.html "AOT Cookbook")
|
||||||
|
which describes running the AOT compiler from the command line
|
||||||
|
and using [_rollup_](#rollup) for bundling, minification, uglification and tree shaking.
|
||||||
|
|
||||||
|
|
||||||
|
{@a webpack}
|
||||||
|
### Webpack (and AOT)
|
||||||
|
|
||||||
|
<a href="https://webpack.js.org/" target="_blank" title="Webpack 2">Webpack 2</a> is another
|
||||||
|
great option for inlining templates and style-sheets, for bundling, minifying, and uglifying the application.
|
||||||
|
The "[Webpack: an introduction](webpack.html "Webpack: an introduction")" guide will get you started
|
||||||
|
using webpack with Angular.
|
||||||
|
|
||||||
|
Consider configuring _Webpack_ with the official
|
||||||
|
<a href="https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack" target="_blank" title="Ahead-of-Time Webpack Plugin">
|
||||||
|
Angular Ahead-of-Time Webpack Plugin</a>.
|
||||||
|
This plugin transpiles the TypeScript application code,
|
||||||
|
bundles lazy loaded `NgModules` separately,
|
||||||
|
and performs AOT compilation — without any changes to the source code.
|
||||||
|
|
||||||
|
|
||||||
|
{@a rollup}
|
||||||
|
### Dead code elimination with _rollup_
|
||||||
|
|
||||||
|
Any code that you don't call is _dead code_.
|
||||||
|
You can reduce the total size of the application substantially by removing dead code from the application and from third-party libraries.
|
||||||
|
|
||||||
|
_Tree shaking_ is a _dead code elimination_ technique that removes entire exports from JavaScript modules.
|
||||||
|
If a library exports something that the application doesn't import, a tree shaking tool removes it from the code base.
|
||||||
|
|
||||||
|
Tree shaking was popularized by
|
||||||
|
<a href="http://rollupjs.org/" target="_blank" title="Rollup">Rollup</a>, a popular tool with an ecosystem of
|
||||||
|
plugins for bundling, minification, and uglification.
|
||||||
|
Learn more about tree shaking and dead code elmination in
|
||||||
|
<a href="https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.15ih9cyvl" target="_blank" title="Tree-shaking and Dead Code Elimination">
|
||||||
|
this post</a> by rollup-creator, Rich Harris.
|
||||||
|
|
||||||
|
|
||||||
|
{@a prune}
|
||||||
|
### Pruned libraries
|
||||||
|
|
||||||
|
Don't count on automation to remove all dead code.
|
||||||
|
|
||||||
|
Remove libraries that you don't use, especially unnecessary scripts in `index.html`.
|
||||||
|
Consider smaller alternatives to the libraries that you do use.
|
||||||
|
|
||||||
|
Some libraries offer facilities for building a custom, skinny version with just the features you need.
|
||||||
|
Other libraries let you import features _a la carte_.
|
||||||
|
**RxJS** is a good example; import RxJS `Observable` operators individually instead of the entire library.
|
||||||
|
|
||||||
|
|
||||||
|
{@a measure}
|
||||||
|
### Measure performance first
|
||||||
|
|
||||||
|
You can make better decisions about what to optimize and how when you have a clear and accurate understanding of
|
||||||
|
what's making the application slow.
|
||||||
|
The cause may not be what you think it is.
|
||||||
|
You can waste a lot of time and money optimizing something that has no tangible benefit or even makes the app slower.
|
||||||
|
You should measure the app's actual behavior when running in the environments that are important to you.
|
||||||
|
|
||||||
|
The
|
||||||
|
<a href="https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource-timing" target="_blank" title="Chrome DevTools Network Performance">
|
||||||
|
Chrome DevTools Network Performance page</a> is a good place to start learning about measuring performance.
|
||||||
|
|
||||||
|
The [WebPageTest](https://www.webpagetest.org/) tool is another good choice
|
||||||
|
that can also help verify that your deployment was successful.
|
||||||
|
|
||||||
|
|
||||||
|
{@a angular-configuration}
|
||||||
|
|
||||||
|
## Angular configuration
|
||||||
|
|
||||||
|
Angular configuration can make the difference between whether the app launches quickly or doesn't load at all.
|
||||||
|
|
||||||
|
|
||||||
|
{@a base-tag}
|
||||||
|
### The `base` tag
|
||||||
|
|
||||||
|
The HTML [_<base href="..."/>_](https://angular.io/docs/ts/latest/guide/router.html#!#base-href)
|
||||||
|
specifies a base path for resolving relative URLs to assets such as images, scripts, and style sheets.
|
||||||
|
For example, given the `<base href="/my/app/">`, the browser resolves a URL such as `some/place/foo.jpg`
|
||||||
|
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.html "API: APP_BASE_HREF") 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.
|
||||||
|
For example, when the URL to load the app is something like `http://www.mysite.com/mysrc/app/`,
|
||||||
|
the subfolder is `my/app/` and you should add `<base href="/my/app/">` to the server version of the `index.html`.
|
||||||
|
|
||||||
|
When the `base` tag is misconfigured, the app fails to load and the browser console displays `404 - Not Found` errors
|
||||||
|
for the missing files. Look at where it _tried_ to find those files and adjust the base tag appropriately.
|
||||||
|
|
||||||
|
|
||||||
|
{@a enable-prod-mode}
|
||||||
|
### Enable production mode
|
||||||
|
|
||||||
|
Angular apps run in development mode by default, as you can see by the following message on the browser
|
||||||
|
console:
|
||||||
|
|
||||||
|
<code-example format="nocode">
|
||||||
|
Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Switching to production mode can make it run faster by disabling development specific checks such as the dual change detection cycles.
|
||||||
|
|
||||||
|
To enable [production mode](../api/core/index/enableProdMode-function.html) when running remotely, add the following code to the `main.ts`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'deployment/ts/src/main.ts' region='enableProdMode'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a lazy-loading}
|
||||||
|
### Lazy loading
|
||||||
|
|
||||||
|
You can dramatically reduce launch time by only loading the application modules that
|
||||||
|
absolutely must be present when the app starts.
|
||||||
|
|
||||||
|
Configure the Angular Router to defer loading of all other modules (and their associated code), either by
|
||||||
|
[waiting until the app has launched](router.html#preloading "Preloading")
|
||||||
|
or by [_lazy loading_](router.html#asynchronous-routing "Lazy loading")
|
||||||
|
them on demand.
|
||||||
|
|
||||||
|
#### Don't eagerly import something from a lazy loaded module
|
||||||
|
|
||||||
|
It's a common mistake.
|
||||||
|
You've arranged to lazy load a module.
|
||||||
|
But you unintentionally import it, with a JavaScript `import` statement,
|
||||||
|
in a file that's eagerly loaded when the app starts, a file such as the root `AppModule`.
|
||||||
|
If you do that, the module will be loaded immediately.
|
||||||
|
|
||||||
|
The bundling configuration must take lazy loading into consideration.
|
||||||
|
Because lazy loaded modules aren't imported in JavaScript (as just noted), bundlers exclude them by default.
|
||||||
|
Bundlers don't know about the router configuration and won't create separate bundles for lazy loaded modules.
|
||||||
|
You have to create these bundles manually.
|
||||||
|
|
||||||
|
The
|
||||||
|
[Angular Ahead-of-Time Webpack Plugin](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack)
|
||||||
|
automatically recognizes lazy loaded `NgModules` and creates separate bundles for them.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a server-configuration}
|
||||||
|
|
||||||
|
## Server configuration
|
||||||
|
|
||||||
|
This section covers changes you may have make to the server or to files deployed to the server.
|
||||||
|
|
||||||
|
|
||||||
|
{@a fallback}
|
||||||
|
### Routed apps must fallback to `index.html`
|
||||||
|
|
||||||
|
Angular apps are perfect candidates for serving with a simple static HTML server.
|
||||||
|
You don't need a server-side engine to dynamically compose application pages because
|
||||||
|
Angular does that on the client-side.
|
||||||
|
|
||||||
|
If the app uses the Angular router, you must configure the server
|
||||||
|
to return the application's host page (`index.html`) when asked for a file that it does not have.
|
||||||
|
|
||||||
|
|
||||||
|
{@a deep-link}
|
||||||
|
A routed application should support "deep links".
|
||||||
|
A _deep link_ is a URL that specifies a path to a component inside the app.
|
||||||
|
For example, `http://www.mysite.com/heroes/42` is a _deep link_ to the hero detail page
|
||||||
|
that displays the hero with `id: 42`.
|
||||||
|
|
||||||
|
There is no issue when the user navigates to that URL from within a running client.
|
||||||
|
The Angular router interprets the URL and routes to that page and hero.
|
||||||
|
|
||||||
|
But clicking a link in an email, entering it in the browser address bar,
|
||||||
|
or merely refreshing the browser while on the hero detail page —
|
||||||
|
all of these actions are handled by the browser itself, _outside_ the running application.
|
||||||
|
The browser makes a direct request to the server for that URL, bypassing the router.
|
||||||
|
|
||||||
|
A static server routinely returns `index.html` when it receives a request for `http://www.mysite.com/`.
|
||||||
|
But it rejects `http://www.mysite.com/heroes/42` and returns a `404 - Not Found` error *unless* it is
|
||||||
|
configured to return `index.html` instead.
|
||||||
|
|
||||||
|
#### Fallback configuration examples
|
||||||
|
|
||||||
|
There is no single configuration that works for every server.
|
||||||
|
The following sections describe configurations for some of the most popular servers.
|
||||||
|
The list is by no means exhaustive, but should provide you with a good starting point.
|
||||||
|
|
||||||
|
#### Development servers
|
||||||
|
|
||||||
|
- [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
|
||||||
|
[Quickstart repo](https://github.com/angular/quickstart) is pre-configured to fallback to `index.html`.
|
||||||
|
|
||||||
|
- [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
|
||||||
|
`historyApiFallback` entry in the dev server options as follows:
|
||||||
|
|
||||||
|
<code-example>
|
||||||
|
historyApiFallback: {
|
||||||
|
disableDotRule: true,
|
||||||
|
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||||
|
}
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
#### Production servers
|
||||||
|
|
||||||
|
- [Apache](https://httpd.apache.org/): add a
|
||||||
|
[rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html)
|
||||||
|
to the `.htaccess` file as show
|
||||||
|
[here](https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
|
||||||
|
<code-example format=".">
|
||||||
|
RewriteEngine On
|
||||||
|
# If an existing asset or directory is requested go to it as it is
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||||
|
RewriteRule ^ - [L]
|
||||||
|
|
||||||
|
# If the requested resource doesn't exist, use index.html
|
||||||
|
RewriteRule ^ /index.html
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
- [NGinx](http://nginx.org/): use `try_files`, as described in
|
||||||
|
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
|
||||||
|
modified to serve `index.html`:
|
||||||
|
|
||||||
|
<code-example format=".">
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
- [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||||
|
[here](http://stackoverflow.com/a/26152011/2116927):
|
||||||
|
<code-example format="." escape="html">
|
||||||
|
<system.webServer>
|
||||||
|
<rewrite>
|
||||||
|
<rules>
|
||||||
|
<rule name="Angular Routes" stopProcessing="true">
|
||||||
|
<match url=".*" />
|
||||||
|
<conditions logicalGrouping="MatchAll">
|
||||||
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||||
|
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||||
|
</conditions>
|
||||||
|
<action type="Rewrite" url="/" />
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</rewrite>
|
||||||
|
</system.webServer>
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
- [GitHub Pages](https://pages.github.com/): you can't
|
||||||
|
[directly configure](https://github.com/isaacs/github/issues/408)
|
||||||
|
the GitHub Pages server, but you can add a 404 page.
|
||||||
|
Copy `index.html` into `404.html`.
|
||||||
|
It will still be served as the 404 response, but the browser will process that page and load the app properly.
|
||||||
|
It's also a good idea to
|
||||||
|
[serve from `docs/` on master](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch)
|
||||||
|
and to
|
||||||
|
[create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)
|
||||||
|
|
||||||
|
- [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||||
|
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
|
||||||
|
|
||||||
|
<code-example format=".">
|
||||||
|
"rewrites": [ {
|
||||||
|
"source": "**",
|
||||||
|
"destination": "/index.html"
|
||||||
|
} ]
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a cors}
|
||||||
|
|
||||||
|
### Requesting services from a different server (CORS)
|
||||||
|
|
||||||
|
Angular developers may encounter a
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" target="_blank" title="Cross-origin resource sharing">
|
||||||
|
<i>cross-origin resource sharing</i></a> error when making a service request (typically a data service request).
|
||||||
|
to a server other than the application's own host server.
|
||||||
|
Browsers forbid such requests unless the server permits them explicitly.
|
||||||
|
|
||||||
|
There isn't anything the client application can do about these errors.
|
||||||
|
The server must be configured to accept the application's requests.
|
||||||
|
Read about how to enable CORS for specific servers at
|
||||||
|
<a href="http://enable-cors.org/server.html" target="_blank" title="Enabling CORS server">enable-cors.org</a>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a next-steps}
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
If you want to go beyond the [simple _copy-deploy_](#dev-deploy "Simplest deployment possible") approach,
|
||||||
|
read the [AOT Cookbook](../cookbook/aot-compiler.html "AOT Cookbook") next.
|
|
@ -0,0 +1,186 @@
|
||||||
|
@title
|
||||||
|
Displaying Data
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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.
|
||||||
|
You'll display the list of hero names and
|
||||||
|
conditionally show a message below the list.
|
||||||
|
|
||||||
|
The final UI looks like this:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/displaying-data/final.png" alt="Final UI"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
# Contents
|
||||||
|
|
||||||
|
* [Showing component properties with interpolation](#interpolation).
|
||||||
|
* [Showing !{_an} !{_array} property with NgFor](#ngFor).
|
||||||
|
* [Conditional display with NgIf](#ngIf).
|
||||||
|
|
||||||
|
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.
|
||||||
|
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||||
|
|
||||||
|
Follow the [setup](setup.html) instructions for creating a new project
|
||||||
|
named <ngio-ex path="displaying-data"></ngio-ex>.
|
||||||
|
|
||||||
|
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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:
|
||||||
|
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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
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.
|
||||||
|
|
||||||
|
Now run the app. It should display the title and hero name:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
## Template inline or template file?
|
||||||
|
|
||||||
|
You can store your component's template in one of two places.
|
||||||
|
You can define it *inline* using the `template` property, or you can define
|
||||||
|
the template in a separate HTML file and link to it in
|
||||||
|
the component metadata using the `@Component` !{_decorator}'s `templateUrl` property.
|
||||||
|
|
||||||
|
The choice between inline and separate HTML is a matter of taste,
|
||||||
|
circumstances, and organization policy.
|
||||||
|
Here the app uses inline HTML because the template is small and the demo
|
||||||
|
is simpler without the additional HTML file.
|
||||||
|
|
||||||
|
In either style, the template data bindings have the same access to the component's properties.
|
||||||
|
|
||||||
|
## 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}.
|
||||||
|
Now use the Angular `ngFor` directive in the template to display
|
||||||
|
each item in the `heroes` list.
|
||||||
|
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":
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
|
||||||
|
For more information, see the [Template Syntax](./template-syntax.html#ngFor) page.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Notice the `hero` in the `ngFor` double-quoted instruction;
|
||||||
|
it is an example of a template input variable. Read
|
||||||
|
more about template input variables in the [microsyntax](./template-syntax.html#ngForMicrosyntax) section of
|
||||||
|
the [Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
|
Angular duplicates the `<li>` for each item in the list, setting the `hero` variable
|
||||||
|
to the item (the hero) in the current iteration. Angular uses that variable as the
|
||||||
|
context for the interpolation in the double curly braces.
|
||||||
|
|
||||||
|
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
|
||||||
|
repeat items for any [iterable](!{_iterableUrl}) object.Now the heroes appear in an unordered list.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## Creating a class for the data
|
||||||
|
|
||||||
|
The app's code defines the data directly inside the component, which isn't best practice.
|
||||||
|
In a simple demo, however, it's fine.
|
||||||
|
|
||||||
|
At the moment, the binding is to !{_an} !{_array} of strings.
|
||||||
|
In real applications, most bindings are to more specialized objects.
|
||||||
|
|
||||||
|
To convert this binding to use specialized objects, turn the !{_array}
|
||||||
|
of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `Hero` class.
|
||||||
|
|
||||||
|
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code:
|
||||||
|
|
||||||
|
## Using the Hero class
|
||||||
|
|
||||||
|
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ !{_array}
|
||||||
|
of `Hero` objects:
|
||||||
|
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.
|
||||||
|
The display looks the same, but the code is clearer.
|
||||||
|
|
||||||
|
## Conditional display with NgIf
|
||||||
|
|
||||||
|
Sometimes an app needs to display a view or a portion of a view only under specific circumstances.
|
||||||
|
|
||||||
|
Let's change the example to display a message if there are more than three heroes.
|
||||||
|
|
||||||
|
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition.
|
||||||
|
To see it in action, add the following paragraph at the bottom of the template:
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
|
||||||
|
Read more about `ngIf` and `*` in the [ngIf section](./template-syntax.html#ngIf) of the [Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The template expression inside the double quotes,
|
||||||
|
`*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}.
|
||||||
|
When the component's list of heroes has more than three items, Angular adds the paragraph
|
||||||
|
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
||||||
|
paragraph, so no message appears. For more information,
|
||||||
|
see the [template expressions](./template-syntax.html#template-expressions) section of the
|
||||||
|
[Template Syntax](./template-syntax.html) page.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM. That improves performance, especially in larger projects when conditionally including or excluding
|
||||||
|
big chunks of HTML with many data bindings.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Try it out. Because the !{_array} has four items, the message should appear.
|
||||||
|
Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}.
|
||||||
|
The browser should refresh automatically and the message should disappear.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Now you know how to use:
|
||||||
|
- **Interpolation** with double curly braces to display a component property.
|
||||||
|
- **ngFor** to display !{_an} !{_array} of items.
|
||||||
|
- A !{_Lang} class to shape the **model data** for your component and display properties of that model.
|
||||||
|
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||||
|
|
||||||
|
Here's the final code:
|
|
@ -0,0 +1,736 @@
|
||||||
|
@title
|
||||||
|
Forms
|
||||||
|
|
||||||
|
@intro
|
||||||
|
A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.
|
||||||
|
|
||||||
|
@description
|
||||||
|
We’ve all used a form to log in, submit a help request, place an order, book a flight,
|
||||||
|
schedule a meeting, and perform countless other data entry tasks.
|
||||||
|
Forms are the mainstay of business applications.
|
||||||
|
|
||||||
|
Any seasoned web developer can slap together an HTML form with all the right tags.
|
||||||
|
It's more challenging to create a cohesive data entry experience that guides the
|
||||||
|
user efficiently and effectively through the workflow behind the form.
|
||||||
|
|
||||||
|
*That* takes design skills that are, to be frank, well out of scope for this guide.
|
||||||
|
|
||||||
|
It also takes framework support for
|
||||||
|
**two-way data binding, change tracking, validation, and error handling**
|
||||||
|
... which we shall cover in this guide on Angular forms.
|
||||||
|
|
||||||
|
We will build a simple form from scratch, one step at a time. Along the way we'll learn how to:
|
||||||
|
|
||||||
|
- Build an Angular form with a component and template
|
||||||
|
- Use `ngModel` to create two-way data bindings for reading and writing input control values
|
||||||
|
- Track state changes and the validity of form controls
|
||||||
|
- Provide visual feedback using special CSS classes that track the state of the controls
|
||||||
|
- Display validation errors to users and enable/disable form controls
|
||||||
|
- Share information across HTML elements using template reference variables
|
||||||
|
|
||||||
|
Run the <live-example></live-example>.
|
||||||
|
|
||||||
|
## Template-driven forms
|
||||||
|
|
||||||
|
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
|
||||||
|
the form-specific directives and techniques described in this guide.
|
||||||
|
|
||||||
|
That's not the only way to create a form but it's the way we'll cover in this guide.We can build almost any form we need with an Angular template — login forms, contact forms, pretty much any business form.
|
||||||
|
We 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.
|
||||||
|
|
||||||
|
It will be pretty easy because Angular handles many of the repetitive, boilerplate tasks we'd
|
||||||
|
otherwise wrestle with ourselves.
|
||||||
|
|
||||||
|
We'll discuss and learn to build a template-driven form that looks like this:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Here at the *Hero Employment Agency* we use this form to maintain personal information about heroes.
|
||||||
|
Every hero needs a job. It's our company mission to match the right hero with the right crisis!
|
||||||
|
|
||||||
|
Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.
|
||||||
|
|
||||||
|
If we delete the hero name, the form displays a validation error in an attention-grabbing style:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Note that the submit button is disabled, and the "required" bar to the left of the input control changed from green to red.
|
||||||
|
|
||||||
|
We'll customize the colors and location of the "required" bar with standard CSS.
|
||||||
|
We'll build this form in small steps:
|
||||||
|
|
||||||
|
1. Create the `Hero` model class.
|
||||||
|
1. Create the component that controls the form.
|
||||||
|
1. Create a template with the initial form layout.
|
||||||
|
1. Bind data properties to each form control using the `ngModel` two-way data binding syntax.
|
||||||
|
1. Add a `name` attribute to each form input control.
|
||||||
|
1. Add custom CSS to provide visual feedback.
|
||||||
|
1. Show and hide validation error messages.
|
||||||
|
1. Handle form submission with **ngSubmit**.
|
||||||
|
1. Disable the form’s submit button until the form is valid.
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Follow the [setup](setup.html) instructions for creating a new project
|
||||||
|
named <span ngio-ex>angular-forms</span>.
|
||||||
|
|
||||||
|
## Create the Hero model class
|
||||||
|
|
||||||
|
As users enter form data, we'll capture their changes and update an instance of a model.
|
||||||
|
We can't lay out the form until we know what the model looks like.
|
||||||
|
|
||||||
|
A model can be as simple as a "property bag" that holds facts about a thing of application importance.
|
||||||
|
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||||
|
and one optional field (`alterEgo`).
|
||||||
|
|
||||||
|
In the `!{_appDir}` directory, create the following file with the given content:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/app/hero.ts'}
|
||||||
|
|
||||||
|
It's an anemic model with few requirements and no behavior. Perfect for our demo.
|
||||||
|
|
||||||
|
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
||||||
|
assigns the parameter’s value to that field automatically when we create new heroes.
|
||||||
|
|
||||||
|
The `alterEgo` is optional, so the constructor lets us omit it; note the (?) in `alterEgo?`.
|
||||||
|
|
||||||
|
We can create a new hero like this:
|
||||||
|
|
||||||
|
## Create a form component
|
||||||
|
|
||||||
|
An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||||
|
to handle data and user interactions programmatically.
|
||||||
|
We begin with the class because it states, in brief, what the hero editor can do.
|
||||||
|
|
||||||
|
Create the following file with the given content:
|
||||||
|
There’s nothing special about this component, nothing form-specific,
|
||||||
|
nothing to distinguish it from any component we've written before.
|
||||||
|
|
||||||
|
Understanding this component requires only the Angular concepts covered in previous guides.
|
||||||
|
|
||||||
|
1. The code imports the Angular core library, and the `Hero` model we just created.
|
||||||
|
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
|
||||||
|
1. The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`.
|
||||||
|
1. The `templateUrl` property points to a separate file for the template HTML.
|
||||||
|
1. We defined dummy data for `model` and `powers`, as befits a demo.
|
||||||
|
Down the road, we can inject a data service to get and save real data
|
||||||
|
or perhaps expose these properties as
|
||||||
|
[inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a
|
||||||
|
parent component. None of this concerns us now and these future changes won't affect our form.
|
||||||
|
1. We threw in a `diagnostic` property to return a JSON representation of our model.
|
||||||
|
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
|
||||||
|
|
||||||
|
### Why the separate template file?
|
||||||
|
|
||||||
|
Why don't we write the template inline in the component file as we often do elsewhere?
|
||||||
|
|
||||||
|
There is no “right” answer for all occasions. We like inline templates when they are short.
|
||||||
|
Most form templates won't be short. TypeScript and JavaScript files generally aren't the best place to
|
||||||
|
write (or read) large stretches of HTML and few editors are much help with files that have a mix of HTML and code.
|
||||||
|
We also like short files with a clear and obvious purpose like this one.
|
||||||
|
|
||||||
|
Form templates tend to be quite large even when displaying a small number of fields
|
||||||
|
so it's usually best to put the HTML template in a separate file.
|
||||||
|
We'll write that template file in a moment. Before we do, we'll take a step back
|
||||||
|
and revise the `app.module.ts` and `app.component.ts` to make use of the new `HeroFormComponent`.
|
||||||
|
|
||||||
|
## Revise *app.module.ts*
|
||||||
|
|
||||||
|
`app.module.ts` defines the application's root module. In it we identify the external modules we'll use in our application
|
||||||
|
and declare the components that belong to this module, such as our `HeroFormComponent`.
|
||||||
|
|
||||||
|
Because template-driven forms are in their own module, we need to add the `FormsModule` to the array of
|
||||||
|
`imports` for our application module before we can use forms.
|
||||||
|
|
||||||
|
Replace the contents of the "QuickStart" version with the following:
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/app/app.module.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
There are three changes:
|
||||||
|
|
||||||
|
1. We import `FormsModule` and our new `HeroFormComponent`.
|
||||||
|
|
||||||
|
1. We add the `FormsModule` to the list of `imports` defined in the `ngModule` decorator. This gives our application
|
||||||
|
access to all of the template-driven forms features, including `ngModel`.
|
||||||
|
|
||||||
|
1. We add the `HeroFormComponent` to the list of `declarations` defined in the `ngModule` decorator. This makes
|
||||||
|
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.
|
||||||
|
If you wrote it and it should belong to this module, _DO_ declare it in the `declarations` array.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
## Revise *app.component.ts*
|
||||||
|
|
||||||
|
`AppComponent` is the application's root component. It will host our new `HeroFormComponent`.
|
||||||
|
|
||||||
|
Replace the contents of the "QuickStart" version with the following:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/app/app.component.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
There are only two changes.
|
||||||
|
The `template` is simply the new element tag identified by the component's `selector` property.
|
||||||
|
This will display the hero form when the application component is loaded.
|
||||||
|
We've also dropped the `name` field from the class body.
|
||||||
|
|
||||||
|
## Create an initial HTML form template
|
||||||
|
|
||||||
|
Create the new template file with the following contents:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/app/hero-form.component.html' region='start'}
|
||||||
|
|
||||||
|
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
|
||||||
|
opening them up for user input in input boxes.
|
||||||
|
|
||||||
|
The *Name* `<input>` control has the HTML5 `required` attribute;
|
||||||
|
the *Alter Ego* `<input>` control does not because `alterEgo` is optional.
|
||||||
|
|
||||||
|
We've got a *Submit* button at the bottom with some classes on it for styling.
|
||||||
|
|
||||||
|
**We are not using Angular yet**. There are no bindings, no extra directives, just layout.
|
||||||
|
|
||||||
|
The `container`, `form-group`, `form-control`, and `btn` classes
|
||||||
|
come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic.
|
||||||
|
We're using Bootstrap to give the form a little style!
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-important}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
Angular forms do not require a style library
|
||||||
|
</header>
|
||||||
|
|
||||||
|
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
|
||||||
|
the styles of any external library. Angular apps can use any CSS library, or none at all.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Let's add the stylesheet. Open `index.html` and add the following link to the `<head>`:
|
||||||
|
|
||||||
|
## Add powers with _*ngFor_
|
||||||
|
|
||||||
|
Our hero must choose one super power from a fixed list of Agency-approved powers.
|
||||||
|
We maintain that list internally (in `HeroFormComponent`).
|
||||||
|
|
||||||
|
We'll add a `select` to our
|
||||||
|
form and bind the options to the `powers` list using `ngFor`,
|
||||||
|
a technique seen previously in the [Displaying Data](./displaying-data.html) guide.
|
||||||
|
|
||||||
|
Add the following HTML *immediately below* the *Alter Ego* group:
|
||||||
|
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;
|
||||||
|
we display its name using the interpolation syntax.
|
||||||
|
|
||||||
|
## Two-way data binding with _ngModel_
|
||||||
|
|
||||||
|
Running the app right now would be disappointing.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/hero-form-3.png" width="400px" alt="Early form with no binding"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We don't see hero data because we are not binding to the `Hero` yet.
|
||||||
|
We know how to do that from earlier guides.
|
||||||
|
[Displaying Data](./displaying-data.html) taught us property binding.
|
||||||
|
[User Input](./user-input.html) showed us how to listen for DOM events with an
|
||||||
|
event binding and how to update a component property with the displayed value.
|
||||||
|
|
||||||
|
Now we need to display, listen, and extract at the same time.
|
||||||
|
|
||||||
|
We could use the techniques we already know, but
|
||||||
|
instead we'll introduce something new: the `[(ngModel)]` syntax, which
|
||||||
|
makes binding the form to the model super easy.
|
||||||
|
|
||||||
|
Find the `<input>` tag for *Name* and update it like this:
|
||||||
|
|
||||||
|
We added a diagnostic interpolation after the input tag
|
||||||
|
so we can see what we're doing.
|
||||||
|
We left ourselves a note to throw it away when we're done.
|
||||||
|
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||||
|
|
||||||
|
If we run the app right now and started typing in the *Name* input box,
|
||||||
|
adding and deleting characters, we'd see them appearing and disappearing
|
||||||
|
from the interpolated text.
|
||||||
|
At some point it might look like this.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The diagnostic is evidence that values really are flowing from the input box to the model and
|
||||||
|
back again.
|
||||||
|
|
||||||
|
That's **two-way data binding**!
|
||||||
|
For more information about `[(ngModel)]` and two-way data bindings, see
|
||||||
|
the [Template Syntax](template-syntax.html#ngModel) page.
|
||||||
|
Notice that we also added a `name` attribute to our `<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.
|
||||||
|
|
||||||
|
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 we assigned to the `name` attribute.
|
||||||
|
We'll talk about `NgForm` [later in this guide](#ngForm).
|
||||||
|
Let's add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
|
||||||
|
We'll ditch the input box binding message
|
||||||
|
and add a new binding (at the top) to the component's `diagnostic` property.
|
||||||
|
Then we can confirm that two-way data binding works *for the entire hero model*.
|
||||||
|
|
||||||
|
After revision, the core of our form should look like this:
|
||||||
|
|
||||||
|
- 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 we run the app now and changed every hero model property, the form might display like this:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The diagnostic near the top of the form
|
||||||
|
confirms that all of our changes are reflected in the model.
|
||||||
|
|
||||||
|
**Delete** the `{{diagnostic}}` binding at the top as it has served its purpose.
|
||||||
|
|
||||||
|
## Track control state and validity with _ngModel_
|
||||||
|
|
||||||
|
A form isn't just about data binding. We'd also like to know the state of the controls in our form.
|
||||||
|
|
||||||
|
Using `ngModel` in a form gives us more than just a two way data binding. It also tells
|
||||||
|
us if the user touched the control, if the value changed, or if the value became invalid.
|
||||||
|
|
||||||
|
The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state.
|
||||||
|
We can leverage those class names to change the appearance of the control.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
State
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Class if true
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Class if false
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Control has been visited
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-touched</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-untouched</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Control's value has changed
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-dirty</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-pristine</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Control's value is valid
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-valid</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>ng-invalid</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Let's temporarily add a [template reference variable](./template-syntax.html#ref-vars) named `spy`
|
||||||
|
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||||
|
Now run the app, and look at the _Name_ input box.
|
||||||
|
Follow the next four steps *precisely*:
|
||||||
|
|
||||||
|
1. Look but don't touch.
|
||||||
|
1. Click inside the name box, then click outside it.
|
||||||
|
1. Add slashes to the end of the name.
|
||||||
|
1. Erase the name.
|
||||||
|
|
||||||
|
The actions and effects are as follows:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We should see the following transitions and class names:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/ng-control-class-changes.png" width="500px" alt="Control state transitions"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The `ng-valid`/`ng-invalid` pair is the most interesting to us, because we want to send a
|
||||||
|
strong visual signal when the values are invalid. We also want to mark required fields.
|
||||||
|
To create such visual feedback, let's add definitions for the `ng-*` CSS classes.
|
||||||
|
|
||||||
|
**Delete** the `#spy` template reference variable and the `TODO` as they have served their purpose.
|
||||||
|
|
||||||
|
## Add custom CSS for visual feedback
|
||||||
|
|
||||||
|
We can mark required fields and invalid data at the same time with a colored bar
|
||||||
|
on the left of the input box:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We achieve this effect by adding these class definitions to a new `forms.css` file
|
||||||
|
that we add to our project as a sibling to `index.html`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/forms.css'}
|
||||||
|
|
||||||
|
Update the `<head>` of `index.html` to include this style sheet:
|
||||||
|
## Show and hide validation error messages
|
||||||
|
|
||||||
|
We can do better. The _Name_ input box is required and clearing it turns the bar red.
|
||||||
|
That says *something* is wrong but we don't know *what* is wrong or what to do about it.
|
||||||
|
We can leverage the control's state to reveal a helpful message.
|
||||||
|
|
||||||
|
Here's the way it should look when the user deletes the name:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
To achieve this effect we extend the `<input>` tag with
|
||||||
|
1. a [template reference variable](./template-syntax.html#ref-vars)
|
||||||
|
1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
|
||||||
|
|
||||||
|
Here's an example of adding an error message to the _name_ input box:
|
||||||
|
We need a template reference variable to access the input box's Angular control from within the template.
|
||||||
|
Here we created a variable called `name` and gave it the value "ngModel".
|
||||||
|
|
||||||
|
Why "ngModel"?
|
||||||
|
A directive's [exportAs](../api/core/index/Directive-decorator.html) property
|
||||||
|
tells Angular how to link the reference variable to the directive.
|
||||||
|
We set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||||
|
We control visibility of the name error message by binding properties of the `name`
|
||||||
|
control to the message `<div>` element's `hidden` property.
|
||||||
|
In this example, we 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.
|
||||||
|
|
||||||
|
This user experience is the developer's choice. Some folks want to see the message at all times.
|
||||||
|
If we ignore the `pristine` state, we would hide the message only when the value is valid.
|
||||||
|
If we arrive in this component with a new (blank) hero or an invalid hero,
|
||||||
|
we'll see the error message immediately, before we've done anything.
|
||||||
|
|
||||||
|
Some folks find that behavior disconcerting.
|
||||||
|
They only want to see the message when the user makes an invalid change.
|
||||||
|
Hiding the message while the control is "pristine" achieves that goal.
|
||||||
|
We'll see the significance of this choice when we [add a new hero](#new-hero) to the form.
|
||||||
|
|
||||||
|
The hero *Alter Ego* is optional so we can leave that be.
|
||||||
|
|
||||||
|
Hero *Power* selection is required.
|
||||||
|
We can add the same kind of error handling to the `<select>` if we want,
|
||||||
|
but it's not imperative because the selection box already constrains the
|
||||||
|
power to valid values.
|
||||||
|
|
||||||
|
We'd like to add a new hero in this form.
|
||||||
|
We 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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'forms/ts/src/app/hero-form.component.ts' region='new-hero'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
That's understandable as these are required fields.
|
||||||
|
The error messages are hidden because the form is pristine; we haven't changed anything yet.
|
||||||
|
|
||||||
|
Enter a name and click *New Hero* again.
|
||||||
|
The app displays a **_Name is required_** error message!
|
||||||
|
We don't want error messages when we create a new (empty) hero.
|
||||||
|
Why are we getting one now?
|
||||||
|
|
||||||
|
Inspecting the element in the browser tools reveals that the *name* input box is _no longer pristine_.
|
||||||
|
The form remembers that we entered a name before clicking *New Hero*.
|
||||||
|
Replacing the hero object *did not restore the pristine state* of the form controls.
|
||||||
|
|
||||||
|
We have to clear all of the flags imperatively which we 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'}
|
||||||
|
|
||||||
|
Now clicking "New Hero" both resets the form and its control flags.
|
||||||
|
|
||||||
|
## Submit the form with _ngSubmit_
|
||||||
|
|
||||||
|
The user should be able to submit this form after filling it in.
|
||||||
|
The Submit button at the bottom of the form
|
||||||
|
does nothing on its own, but it will
|
||||||
|
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:
|
||||||
|
We slipped in something extra there at the end! We 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.
|
||||||
|
|
||||||
|
### The _NgForm_ directive
|
||||||
|
|
||||||
|
What `NgForm` directive?
|
||||||
|
We didn't add an [NgForm](../api/forms/index/NgForm-directive.html) directive!
|
||||||
|
|
||||||
|
Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically.
|
||||||
|
|
||||||
|
The `NgForm` directive supplements the `form` element with additional features.
|
||||||
|
It holds the controls we 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.
|
||||||
|
We'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:
|
||||||
|
If we run the application now, we find that the button is enabled
|
||||||
|
— although it doesn't do anything useful yet.
|
||||||
|
|
||||||
|
Now if we delete the Name, we violate the "required" rule, which
|
||||||
|
is duly noted in the error message.
|
||||||
|
The Submit button is also disabled.
|
||||||
|
|
||||||
|
Not impressed? Think about it for a moment. What would we have to do to
|
||||||
|
wire the button's enable/disabled state to the form's validity without Angular's help?
|
||||||
|
|
||||||
|
For us, it was as simple as:
|
||||||
|
|
||||||
|
1. Define a template reference variable on the (enhanced) form element.
|
||||||
|
2. Refer to that variable in a button many lines away.
|
||||||
|
|
||||||
|
## Toggle two form regions (extra credit)
|
||||||
|
|
||||||
|
Submitting the form isn't terribly dramatic at the moment.
|
||||||
|
|
||||||
|
An unsurprising observation for a demo. To be honest,
|
||||||
|
jazzing it up won't teach us anything new about forms.
|
||||||
|
But this is an opportunity to exercise some of our newly won
|
||||||
|
binding skills.
|
||||||
|
If you aren't interested, go ahead and skip to this guide's conclusion.
|
||||||
|
Let's do something more strikingly visual.
|
||||||
|
Let's hide the data entry area and display something else.
|
||||||
|
|
||||||
|
Start by wrapping the form in a `<div>` and bind
|
||||||
|
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||||
|
The main form is visible from the start because the
|
||||||
|
`submitted` property is false until we submit the form,
|
||||||
|
as this fragment from the `HeroFormComponent` shows:
|
||||||
|
When we 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 we just wrote:
|
||||||
|
There's our hero again, displayed read-only with interpolation bindings.
|
||||||
|
This `<div>` appears only while the component is in the submitted state.
|
||||||
|
|
||||||
|
The HTML includes an _Edit_ button whose click event is bound to an expression
|
||||||
|
that clears the `submitted` flag.
|
||||||
|
|
||||||
|
When we click the _Edit_ button, this block disappears and the editable form reappears.
|
||||||
|
|
||||||
|
That's as much drama as we can muster for now.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The Angular form discussed in this guide takes advantage of the following
|
||||||
|
framework features to provide support for data modification, validation, and more:
|
||||||
|
|
||||||
|
- An Angular HTML form template.
|
||||||
|
- A form component class with a `@Component` decorator.
|
||||||
|
- Handling form submission by binding to the `NgForm.ngSubmit` event property.
|
||||||
|
- Template reference variables such as `#heroForm` and `#name`.
|
||||||
|
- `[(ngModel)]` syntax for two-way data binding.
|
||||||
|
- The use of `name` attributes for validation and form element change tracking.
|
||||||
|
- The reference variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
||||||
|
- Controlling the submit button's enabled state by binding to `NgForm` validity.
|
||||||
|
- Custom CSS classes that provide visual feedback to users about invalid controls.
|
||||||
|
|
||||||
|
Our final project folder structure should look like this:
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-forms
|
||||||
|
<aio-folder>
|
||||||
|
src
|
||||||
|
<aio-folder>
|
||||||
|
app
|
||||||
|
<aio-file>
|
||||||
|
app.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
app.module.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero-form.component.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero-form.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
main.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="hero-form.component.html">
|
||||||
|
{@example 'forms/ts/src/app/hero-form.component.html' region='final'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="hero.ts">
|
||||||
|
{@example 'forms/ts/src/app/hero.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.module.ts">
|
||||||
|
{@example 'forms/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.component.ts">
|
||||||
|
{@example 'forms/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="main.ts">
|
||||||
|
{@example 'forms/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="index.html">
|
||||||
|
{@example 'forms/ts/src/index.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="forms.css">
|
||||||
|
{@example 'forms/ts/src/forms.css'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
@title
|
||||||
|
Glossary
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Brief definitions of the most important words in the Angular vocabulary
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
@title
|
||||||
|
Hierarchical Dependency Injectors
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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](./dependency-injection.html) guide.
|
||||||
|
|
||||||
|
Angular has a _Hierarchical Dependency Injection_ system.
|
||||||
|
There is actually a tree of injectors that parallel an application's component tree.
|
||||||
|
You can reconfigure the injectors at any level of that component tree.
|
||||||
|
|
||||||
|
This guide explores this system and how to use it to your advantage.
|
||||||
|
|
||||||
|
Try the <live-example></live-example>.
|
||||||
|
|
||||||
|
## The injector tree
|
||||||
|
|
||||||
|
In the [Dependency Injection](./dependency-injection.html) guide,
|
||||||
|
you learned how to configure a dependency injector and how to retrieve dependencies where you need them.
|
||||||
|
|
||||||
|
In fact, there is no such thing as ***the*** injector.
|
||||||
|
An application may have multiple injectors.
|
||||||
|
An Angular application is a tree of components. Each component instance has its own injector.
|
||||||
|
The tree of components parallels the tree of injectors.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
The `HeroesListComponent` holds and manages multiple instances of the `HeroTaxReturnComponent`.
|
||||||
|
The following diagram represents the state of the this guide's three-level component tree when there are three instances of `HeroTaxReturnComponent`
|
||||||
|
open simultaneously.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="600"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Injector bubbling
|
||||||
|
|
||||||
|
When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||||
|
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
||||||
|
If that injector can't satisfy the request, it passes it along to *its* parent injector.
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
You don't *have* to re-register providers. You shouldn't do so unless you have a good reason.
|
||||||
|
But you *can*.
|
||||||
|
|
||||||
|
As the resolution logic works upwards, the first provider encountered wins.
|
||||||
|
Thus, a provider in an intermediate injector intercepts a request for a service from something lower in the tree.
|
||||||
|
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
|
||||||
|
|
||||||
|
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||||
|
All requests bubble up to the root <span if-docs="ts"><code>NgModule</code></span> injector that you configured with the `!{_bootstrapModule}` method.
|
||||||
|
|
||||||
|
## Component injectors
|
||||||
|
|
||||||
|
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||||
|
### Scenario: service isolation
|
||||||
|
|
||||||
|
Architectural reasons may lead you to restrict access to a service to the application domain where it belongs.
|
||||||
|
|
||||||
|
The guide sample includes a `VillainsListComponent` that displays a list of villains.
|
||||||
|
It gets those villains from a `VillainsService`.
|
||||||
|
|
||||||
|
While you could provide `VillainsService` in the root `AppModule` (that's where you'll find the `HeroesService`),
|
||||||
|
that would make the `VillainsService` available everywhere in the application, including the _Hero_ workflows.
|
||||||
|
|
||||||
|
If you later modify the `VillainsService`, you could break something in a hero component somewhere.
|
||||||
|
That's not supposed to happen but the way you've provided the service creates that risk.
|
||||||
|
|
||||||
|
Instead, provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'hierarchical-dependency-injection/ts/src/app/villains-list.component.ts' region='metadata'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You are confident that a hero component can't access it. You've reduced your exposure to error.
|
||||||
|
|
||||||
|
### Scenario: multiple edit sessions
|
||||||
|
|
||||||
|
Many applications allow users to work on several open tasks at the same time.
|
||||||
|
For example, in a tax preparation application, the preparer could be working several tax returns,
|
||||||
|
switching from one to the other throughout the day.
|
||||||
|
|
||||||
|
This guide demonstrates that scenario with an example in the Tour of Heroes theme.
|
||||||
|
Imagine an outer `HeroListComponent` that displays a list of super heroes.
|
||||||
|
|
||||||
|
To open a hero's tax return, the preparer clicks on a hero name, which opens a component for editing that return.
|
||||||
|
Each selected hero tax return opens in its own component and multiple returns can be open at the same time.
|
||||||
|
|
||||||
|
Each tax return component
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/dependency-injection/hid-heroes-anim.gif" width="400" alt="Heroes in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
One might suppose that the `TaxReturnComponent` has logic to manage and restore changes.
|
||||||
|
That would be a pretty easy task for a simple hero tax return.
|
||||||
|
In the real world, with a rich tax return data model, the change management would be tricky.
|
||||||
|
You might delegate that management to a helper service, as this example does.
|
||||||
|
|
||||||
|
Here is the `HeroTaxReturnService`.
|
||||||
|
It caches a single `HeroTaxReturn`, tracks changes to that return, and can save or restore it.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Here is the `HeroTaxReturnComponent` that makes use of it.
|
||||||
|
|
||||||
|
{@example 'hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
The getter always returns what that service says is the current state of the hero.
|
||||||
|
The component also asks the service to save and restore this tax return.
|
||||||
|
|
||||||
|
There'd be big trouble if _this_ service were an application-wide singleton.
|
||||||
|
Every component would share the same service instance.
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts' region='providers'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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,
|
||||||
|
deeper in the component tree.
|
||||||
|
|
||||||
|
Consider again the Car example from the [Dependency Injection](./dependency-injection.html) guide.
|
||||||
|
Suppose you configured the root injector (marked as A) with _generic_ providers for
|
||||||
|
`CarService`, `EngineService` and `TiresService`.
|
||||||
|
|
||||||
|
You create a car component (A) that displays a car constructed from these three generic services.
|
||||||
|
|
||||||
|
Then you create a child component (B) that defines its own, _specialized_ providers for `CarService` and `EngineService`
|
||||||
|
that have special capabilites suitable for whatever is going on in component (B).
|
||||||
|
|
||||||
|
Component (B) is the parent of another component (C) that defines its own, even _more specialized_ provider for `CarService`.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/dependency-injection/car-components.png" alt="car components" width="220"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.
|
||||||
|
|
||||||
|
When you resolve an instance of `Car` at the deepest component (C),
|
||||||
|
its injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
|
||||||
|
`Tires` resolved by the root injector (A).
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="car injector tree" width="600"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
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>
|
|
@ -0,0 +1,152 @@
|
||||||
|
@title
|
||||||
|
Documentation Overview
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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](learning-angular.html)" first.
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
The documentation is divided into major thematic sections, each
|
||||||
|
a collection of pages devoted to that theme.
|
||||||
|
|
||||||
|
<table width="100%">
|
||||||
|
|
||||||
|
<col width="15%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<col>
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b><a href="../quickstart.html">QuickStart</a></b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
A first taste of Angular<span if-docs="ts"> with zero installation.
|
||||||
|
Run "Hello World" in an online code editor and start playing with live code</span>.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b>Guide</b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Learn the Angular basics (you're already here!) like the setup for local development,
|
||||||
|
displaying data and accepting user input, injecting application services into components,
|
||||||
|
and building simple forms.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b><a href="../api/">API Reference</a></b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Authoritative details about each of the Angular libraries.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b><a href="../tutorial/">Tutorial</a></b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
A step-by-step, immersive approach to learning Angular that
|
||||||
|
introduces the major features of Angular in an application context.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b><a href=" ">Advanced</a></b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
In-depth analysis of Angular features and development practices.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top if-docs="ts">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<b><a href="../cookbook/">Cookbook</a></b>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Recipes for specific application challenges, mostly code snippets with a minimum of exposition.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
A few early pages are written as tutorials and are clearly marked as such.
|
||||||
|
The rest of the pages highlight key points in code rather than explain each step necessary to build the sample.
|
||||||
|
You can always get the full source through the #{_liveLink}s.
|
||||||
|
|
||||||
|
## Code samples
|
||||||
|
|
||||||
|
Each page includes code snippets from a sample application that accompanies the page.
|
||||||
|
You can reuse these snippets in your applications.
|
||||||
|
|
||||||
|
Look for a link to a running version of that sample, often near the top of the page,
|
||||||
|
such as this <live-example name="architecture"></live-example> from the [Architecture](architecture.html) page.
|
||||||
|
<span if-docs="ts">
|
||||||
|
The link launches a browser-based, code editor where you can inspect, modify, save, and download the code.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
## Reference pages
|
||||||
|
|
||||||
|
* The [Cheat Sheet](cheatsheet.html) lists Angular syntax for common scenarios.
|
||||||
|
* The [Glossary](glossary.html) defines terms that Angular developers should know.
|
||||||
|
<li if-docs="ts">The [Change Log](change-log.html) announces what's new and changed in the documentation.</li>
|
||||||
|
* The [API Reference](../api/) is the authority on every public-facing member of the Angular libraries.
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
|
||||||
|
We welcome feedback!
|
||||||
|
|
||||||
|
* Use the <a href="!{_ngDocRepoURL}" target="_blank" title="angular docs on github">!{_angular_io} Github repository</a> for **documentation** issues and pull requests.
|
||||||
|
* Use the <a href="!{_ngRepoURL}" target="_blank" title="angular source on github">Angular Github repository</a> to report issues with **Angular** itself.
|
|
@ -0,0 +1,46 @@
|
||||||
|
@title
|
||||||
|
Learning Angular
|
||||||
|
|
||||||
|
@intro
|
||||||
|
A suggested path through the documentation for Angular newcomers
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/intro/people.png" width="200px" height="152px" alt="Us" align="left" style="margin-left:-40px;margin-right:10px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Everyone learns differently.
|
||||||
|
You don't have to read the documentation straight through. Most pages stand on their own.
|
||||||
|
Those new to Angular may wish to follow this popular learning path.
|
||||||
|
<br class="l-clear-left">
|
||||||
|
|
||||||
|
1. [Setup](setup.html "Setup locally withe Quickstart seed") for local Angular development, if you haven't already done so.
|
||||||
|
|
||||||
|
1. Take the [*Tour of Heroes* tutorial](../tutorial "Tour of Heroes").
|
||||||
|
|
||||||
|
The *Tour of Heroes* takes you step-by-step from [setup](setup.html)
|
||||||
|
to a full-featured example that demonstrates the essential characteristics of a professional application:
|
||||||
|
a sensible project structure, data binding, master/detail, services, dependency injection, navigation, and remote data access.
|
||||||
|
|
||||||
|
1. <a id="architecture"></a>Read the [Architecture](architecture.html) overview for the big picture.
|
||||||
|
|
||||||
|
1. [The Root Module](appmodule.html) introduces the `NgModule` class that tells Angular how to compile and run your application.
|
||||||
|
|
||||||
|
1. [Displaying Data](displaying-data.html) shows how data binding puts component property values on screen.
|
||||||
|
|
||||||
|
1. [User Input](user-input.html) explains how to respond to user-initiated DOM events.
|
||||||
|
|
||||||
|
1. [Forms](forms.html) covers data entry and validation within the UI.
|
||||||
|
|
||||||
|
1. [Dependency Injection](dependency-injection.html) is the way to build large, maintainable applications
|
||||||
|
from small, single-purpose parts.
|
||||||
|
|
||||||
|
1. [Template Syntax](template-syntax.html) is a comprehensive study of Angular template HTML.
|
||||||
|
|
||||||
|
After reading the above sections, feel free to skip around among the other pages on this site.
|
||||||
|
|
||||||
|
### Next Step
|
||||||
|
|
||||||
|
Try the [tutorial](../tutorial "Tour of Heroes") if you're ready to start coding or
|
||||||
|
visit the [Architecture](architecture.html "Basic Concepts") page if you prefer to learn the basic concepts first.
|
|
@ -0,0 +1,679 @@
|
||||||
|
@title
|
||||||
|
Lifecycle Hooks
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them.
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
A component has a lifecycle managed by Angular itself.
|
||||||
|
|
||||||
|
Angular creates it, renders it, creates and renders its children,
|
||||||
|
checks it when its data-bound properties change, and destroys it before removing it from the DOM.
|
||||||
|
|
||||||
|
Angular offers **lifecycle hooks**
|
||||||
|
that provide visibility into these key life moments and the ability to act when they occur.
|
||||||
|
|
||||||
|
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||||
|
<br class="l-clear-both">Try the <live-example></live-example>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a hooks-overview}
|
||||||
|
|
||||||
|
## Component lifecycle hooks
|
||||||
|
Directive and component instances have a lifecycle
|
||||||
|
as Angular creates, updates, and destroys them.
|
||||||
|
Developers can tap into key moments in that lifecycle by implementing
|
||||||
|
one or more of the *Lifecycle Hook* interfaces in the Angular `core` library.
|
||||||
|
|
||||||
|
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||||||
|
For example, the `OnInit` interface has a hook method named `ngOnInit`
|
||||||
|
that Angular calls shortly after creating the component:
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/peek-a-boo.component.ts' region='ngOnInit'}
|
||||||
|
|
||||||
|
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
|
||||||
|
Angular only calls a directive/component hook method *if it is defined*.
|
||||||
|
|
||||||
|
|
||||||
|
{@a hooks-purpose-timing}
|
||||||
|
|
||||||
|
## Lifecycle sequence
|
||||||
|
*After* creating a component/directive by calling its constructor, Angular
|
||||||
|
calls the lifecycle hook methods in the following sequence at specific moments:
|
||||||
|
<table width="100%">
|
||||||
|
|
||||||
|
<col width="20%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<col width="80%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Hook
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Purpose and Timing
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngOnChanges
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
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.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngOnInit
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
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`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngDoCheck
|
||||||
|
</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`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngAfterContentInit
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Respond after Angular projects external content into the component's view.
|
||||||
|
|
||||||
|
Called _once_ after the first `NgDoCheck`.
|
||||||
|
|
||||||
|
_A component-only hook_.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngAfterContentChecked
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Respond after Angular checks the content projected into the component.
|
||||||
|
|
||||||
|
Called after the `ngAfterContentInit` and every subsequent `NgDoCheck`.
|
||||||
|
|
||||||
|
_A component-only hook_.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngAfterViewInit
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Respond after Angular initializes the component's views and child views.
|
||||||
|
|
||||||
|
Called _once_ after the first `ngAfterContentChecked`.
|
||||||
|
|
||||||
|
_A component-only hook_.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngAfterViewChecked
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Respond after Angular checks the component's views and child views.
|
||||||
|
|
||||||
|
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked`.
|
||||||
|
|
||||||
|
_A component-only hook_.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
ngOnDestroy
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Cleanup just before Angular destroys the directive/component.
|
||||||
|
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||||||
|
|
||||||
|
Called _just before_ Angular destroys the directive/component.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a other-lifecycle-hooks}
|
||||||
|
|
||||||
|
## Other 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
|
||||||
|
|
||||||
|
The <live-example></live-example>
|
||||||
|
demonstrates the lifecycle hooks in action through a series of exercises
|
||||||
|
presented as components under the control of the root `AppComponent`.
|
||||||
|
|
||||||
|
They follow a common pattern: a *parent* component serves as a test rig for
|
||||||
|
a *child* component that illustrates one or more of the lifecycle hook methods.
|
||||||
|
|
||||||
|
Here's a brief description of each exercise:
|
||||||
|
|
||||||
|
<table width="100%">
|
||||||
|
|
||||||
|
<col width="20%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<col width="80%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Component
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Description
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#peek-a-boo">Peek-a-boo</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Demonstrates every lifecycle hook.
|
||||||
|
Each hook method writes to the on-screen log.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#spy">Spy</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Directives have lifecycle hooks too.
|
||||||
|
A `SpyDirective` can log when the element it spies upon is
|
||||||
|
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||||||
|
|
||||||
|
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||||
|
managed by the parent `SpyComponent`.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#onchanges">OnChanges</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#docheck">DoCheck</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Implements an `ngDoCheck` method with custom change detection.
|
||||||
|
See how often Angular calls this hook and watch it post changes to a log.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#afterview">AfterView</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Shows what Angular means by a *view*.
|
||||||
|
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="#aftercontent">AfterContent</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Shows how to project external content into a component and
|
||||||
|
how to distinguish projected content from a component's view children.
|
||||||
|
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style=top>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Counter
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Demonstrates a combination of a component and a directive
|
||||||
|
each with its own hooks.
|
||||||
|
|
||||||
|
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||||||
|
every time the parent component increments its input counter property.
|
||||||
|
Meanwhile, the `SpyDirective` from the previous example is applied
|
||||||
|
to the `CounterComponent` log where it watches log entries being created and destroyed.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
The remainder of this chapter discusses selected exercises in further detail.
|
||||||
|
|
||||||
|
|
||||||
|
{@a peek-a-boo}
|
||||||
|
|
||||||
|
## Peek-a-boo: all hooks
|
||||||
|
The `PeekABooComponent` demonstrates all of the hooks in one component.
|
||||||
|
|
||||||
|
You would rarely, if ever, implement all of the interfaces like this.
|
||||||
|
The peek-a-boo exists to show how Angular calls the hooks in the expected order.
|
||||||
|
|
||||||
|
This snapshot reflects the state of the log after the user clicked the *Create...* button and then the *Destroy...* button.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src="/resources/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The sequence of log messages follows the prescribed hook calling order:
|
||||||
|
`OnChanges`, `OnInit`, `DoCheck` (3x), `AfterContentInit`, `AfterContentChecked` (3x),
|
||||||
|
`AfterViewInit`, `AfterViewChecked` (3x), and `OnDestroy`.
|
||||||
|
|
||||||
|
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
|
||||||
|
`DoCheck`, `AfterContentChecked` and `AfterViewChecked`.
|
||||||
|
Clearly these three hooks fire a *often*. Keep the logic in these hooks as lean as possible!
|
||||||
|
|
||||||
|
The next examples focus on hook details.
|
||||||
|
|
||||||
|
|
||||||
|
{@a spy}
|
||||||
|
|
||||||
|
## Spying *OnInit* and *OnDestroy*
|
||||||
|
|
||||||
|
Go undercover with these two spy hooks to discover when an element is initialized or destroyed.
|
||||||
|
|
||||||
|
This is the perfect infiltration job for a directive.
|
||||||
|
The heroes will never know they're being watched.
|
||||||
|
|
||||||
|
Kidding aside, pay attention to two key points:
|
||||||
|
|
||||||
|
1. Angular calls hook methods for *directives* as well as components.<br><br>
|
||||||
|
|
||||||
|
2. A spy directive can provide insight into a DOM object that you cannot change directly.
|
||||||
|
Obviously you can't touch the implementation of a native `div`.
|
||||||
|
You can't modify a third party component either.
|
||||||
|
But you can watch both with a directive.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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>`
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/spy.component.html' region='template'}
|
||||||
|
|
||||||
|
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||||
|
with an entry in the *Hook Log* as seen here:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/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.
|
||||||
|
|
||||||
|
The *Reset* button clears the `heroes` list.
|
||||||
|
Angular removes all hero `<div>` elements from the DOM and destroys their spy directives at the same time.
|
||||||
|
The spy's `ngOnDestroy` method reports its last moments.
|
||||||
|
|
||||||
|
The `ngOnInit` and `ngOnDestroy` methods have more vital roles to play in real applications.
|
||||||
|
|
||||||
|
### OnInit
|
||||||
|
|
||||||
|
Use `ngOnInit` for two main reasons:
|
||||||
|
1. to perform complex initializations shortly after construction
|
||||||
|
1. to set up the component after Angular sets the input properties
|
||||||
|
|
||||||
|
Experienced developers agree that components should be cheap and safe to construct.
|
||||||
|
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.html#oninit) and [HTTP](server-communication.html#oninit) chapter
|
||||||
|
show how.
|
||||||
|
|
||||||
|
|
||||||
|
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||||
|
That's a problem if you need to initialize the directive based on those properties.
|
||||||
|
They'll have been set when `ngOninit` runs.
|
||||||
|
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.
|
||||||
|
|
||||||
|
This is the time to notify another part of the application that the component is going away.
|
||||||
|
|
||||||
|
This is the place to free resources that won't be garbage collected automatically.
|
||||||
|
Unsubscribe from observables and DOM events. Stop interval timers.
|
||||||
|
Unregister all callbacks that this directive registered with global or application services.
|
||||||
|
You risk memory leaks if you neglect to do so.
|
||||||
|
|
||||||
|
## 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'}
|
||||||
|
|
||||||
|
The `ngOnChanges` method takes an object that maps each changed property name to a
|
||||||
|
[SimpleChange](../api/core/index/SimpleChange-class.html) object holding the current and previous property values.
|
||||||
|
This hook iterates over the changed properties and logs them.
|
||||||
|
|
||||||
|
The example component, `OnChangesComponent`, has two input properties: `hero` and `power`.
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/on-changes.component.ts' region='inputs'}
|
||||||
|
|
||||||
|
The host `OnChangesParentComponent` binds to them like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/on-changes-parent.component.html' region='on-changes'}
|
||||||
|
|
||||||
|
Here's the sample in action as the user makes changes.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The log entries appear as the string value of the *power* property changes.
|
||||||
|
But the `ngOnChanges` does not catch changes to `hero.name`
|
||||||
|
That's surprising at first.
|
||||||
|
|
||||||
|
Angular only calls the hook when the value of the input property changes.
|
||||||
|
The value of the `hero` property is the *reference to the hero object*.
|
||||||
|
Angular doesn't care that the hero's own `name` property changed.
|
||||||
|
The hero object *reference* didn't change so, from Angular's perspective, there is no change to report!
|
||||||
|
|
||||||
|
## 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'}
|
||||||
|
|
||||||
|
This code inspects certain _values-of-interest_, capturing and comparing their current state against previous values.
|
||||||
|
It writes a special message to the log when there are no substantive changes to the `hero` or the `power`
|
||||||
|
so you can see how often `DoCheck` is called. The results are illuminating:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/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.
|
||||||
|
It's called over twenty times in this example before the user can do anything.
|
||||||
|
|
||||||
|
Most of these initial checks are triggered by Angular's first rendering of *unrelated data elsewhere on the page*.
|
||||||
|
Mere mousing into another input box triggers a call.
|
||||||
|
Relatively few calls reveal actual changes to pertinent data.
|
||||||
|
Clearly our implementation must be very lightweight or the user experience will suffer.
|
||||||
|
|
||||||
|
## AfterView
|
||||||
|
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
||||||
|
*after* it creates a component's child views.
|
||||||
|
|
||||||
|
Here's a child view that displays a hero's name in an input box:
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='child-view'}
|
||||||
|
|
||||||
|
The `AfterViewComponent` displays this child view *within its template*:
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='template'}
|
||||||
|
|
||||||
|
The following hooks take action based on changing values *within the child view*
|
||||||
|
which can only be reached by querying for the child view via the property decorated with
|
||||||
|
[@ViewChild](../api/core/index/ViewChild-decorator.html).
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-view.component.ts' region='hooks'}
|
||||||
|
|
||||||
|
|
||||||
|
<div id='wait-a-tick'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Abide by the unidirectional data flow rule
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/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.
|
||||||
|
Write lean hook methods to avoid performance problems.
|
||||||
|
|
||||||
|
## AfterContent
|
||||||
|
The *AfterContent* sample explores the `AfterContentInit` and `AfterContentChecked` hooks that Angular calls
|
||||||
|
*after* Angular projects external content into the component.
|
||||||
|
|
||||||
|
### Content projection
|
||||||
|
*Content projection* is a way to import HTML content from outside the component and insert that content
|
||||||
|
into the component's template in a designated spot.
|
||||||
|
|
||||||
|
AngularJS developers know this technique as *transclusion*.
|
||||||
|
Consider this variation on the [previous _AfterView_](#afterview) example.
|
||||||
|
This time, instead of including the child view within the template, it imports the content from
|
||||||
|
the `AfterContentComponent`'s parent. Here's the parent's template.
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='parent-template'}
|
||||||
|
|
||||||
|
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||||||
|
Never put content between a component's element tags *unless you intend to project that content
|
||||||
|
into the component*.
|
||||||
|
|
||||||
|
Now look at the component's template:
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='template'}
|
||||||
|
|
||||||
|
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||||
|
It tells Angular where to insert that content.
|
||||||
|
In this case, the projected content is the `<my-child>` from the parent.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content"> </img>
|
||||||
|
</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
|
||||||
|
*AfterContent* hooks are similar to the *AfterView* hooks.
|
||||||
|
The key difference is in the child component
|
||||||
|
|
||||||
|
* The *AfterView* hooks concern `ViewChildren`, the child components whose element tags
|
||||||
|
appear *within* the component's template.
|
||||||
|
|
||||||
|
* The *AfterContent* hooks concern `ContentChildren`, the child components that Angular
|
||||||
|
projected into the component.
|
||||||
|
|
||||||
|
The following *AfterContent* hooks take action based on changing values in a *content child*
|
||||||
|
which can only be reached by querying for it via the property decorated with
|
||||||
|
[@ContentChild](../api/core/index/ContentChild-decorator.html).
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'lifecycle-hooks/ts/src/app/after-content.component.ts' region='hooks'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a no-unidirectional-flow-worries}
|
||||||
|
### No unidirectional flow worries with _AfterContent..._
|
||||||
|
|
||||||
|
This component's `doSomething` method update's the component's data-bound `comment` property immediately.
|
||||||
|
There's no [need to wait](#wait-a-tick).
|
||||||
|
|
||||||
|
Recall that Angular calls both *AfterContent* hooks before calling either of the *AfterView* hooks.
|
||||||
|
Angular completes composition of the projected content *before* finishing the composition of this component's view.
|
||||||
|
There is a small window between the `AfterContent...` and `AfterView...` hooks to modify the host view.
|
|
@ -0,0 +1,12 @@
|
||||||
|
@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%; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
This guide has been withdrawn.
|
||||||
|
The essential information about this feature
|
||||||
|
is in the [Structural Directives](structural-directives.html#ngcontainer) guide.
|
||||||
|
The original draft has been retained for possible future use.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,206 @@
|
||||||
|
@title
|
||||||
|
Npm Packages
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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>).
|
||||||
|
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">
|
||||||
|
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.
|
||||||
|
We recommend a comprehensive starter-set of packages as specified in the `dependencies` and `devDependencies`
|
||||||
|
sections of the <a href="https://docs.npmjs.com/files/package.json" target="_blank">package.json</a> file
|
||||||
|
installed as described during [Setup](setup.html).You can use other packages but we recommend *this particular set* to start with because (a) they work well together and
|
||||||
|
(b) they include everything you'll 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 QuickStart.
|
||||||
|
No worries!
|
||||||
|
You only serve to the client those packages that the application actually requests.
|
||||||
|
|
||||||
|
This page explains what each package does. You can make substitutions later to suit your tastes and experience.
|
||||||
|
|
||||||
|
## *dependencies* and *devDependencies*
|
||||||
|
The `package.json` includes two sets of packages,
|
||||||
|
[dependencies](#dependencies) and [devDependencies](#dev-dependencies).
|
||||||
|
|
||||||
|
The *dependencies* are essential to *running* the application.
|
||||||
|
The *devDependencies* are only necessary to *develop* the application.
|
||||||
|
You can exclude them from production installations by adding `--production` to the install command, as follows:
|
||||||
|
<code-example format="." language="bash">
|
||||||
|
npm install my-application --production
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a dependencies}
|
||||||
|
|
||||||
|
## *dependencies*
|
||||||
|
The `dependencies` section of `package.json` contains:
|
||||||
|
|
||||||
|
* ***Features*** - Feature packages give the application framework and utility capabilities.
|
||||||
|
|
||||||
|
* ***Polyfills*** - Polyfills plug gaps in the browser's JavaScript implementation.
|
||||||
|
|
||||||
|
* ***Other*** - Other libraries that support the application such as `bootstrap` for HTML widgets and styling.
|
||||||
|
|
||||||
|
### Feature Packages
|
||||||
|
|
||||||
|
***@angular/core*** - Critical runtime parts of the framework needed by every application.
|
||||||
|
Includes all metadata decorators, `Component`, `Directive`, dependency injection, and the component lifecycle hooks.
|
||||||
|
|
||||||
|
***@angular/common*** - The commonly needed services, pipes, and directives provided by the Angular team.
|
||||||
|
|
||||||
|
***@angular/compiler*** - Angular's *Template Compiler*.
|
||||||
|
It understands templates and can convert them to code that makes the application run and render.
|
||||||
|
Typically you don’t interact with the compiler directly; rather, you use it indirectly via `platform-browser-dynamic` or the offline template compiler.
|
||||||
|
|
||||||
|
***@angular/platform-browser*** - Everything DOM and browser related, especially the pieces that help render into DOM.
|
||||||
|
This package also includes the bootstrapStatic method for bootstrapping applications for production builds that pre-compile templates offline.
|
||||||
|
|
||||||
|
***@angular/platform-browser-dynamic*** - Includes [Providers](../api/core/index/Provider-type-alias.html) and a [bootstrap](ngmodule.html#bootstrap) method for applications that
|
||||||
|
compile templates on the client. Don’t use offline compilation.
|
||||||
|
Use this package for bootstrapping during development and for bootstrapping plunker samples.
|
||||||
|
|
||||||
|
***@angular/http*** - Angular's http client.
|
||||||
|
|
||||||
|
***@angular/router*** - Component router.
|
||||||
|
|
||||||
|
***@angular/upgrade*** - Set of utilities for upgrading AngularJS applications to Angular.
|
||||||
|
|
||||||
|
***[system.js](https://github.com/systemjs/systemjs)*** - A dynamic module loader compatible with the
|
||||||
|
[ES2015 module](http://www.2ality.com/2014/09/es6-modules-final.html) specification.
|
||||||
|
Other viable choices include the well-regarded [webpack](https://webpack.github.io/).
|
||||||
|
|
||||||
|
Your future applications are likely to require additional packages that provide
|
||||||
|
HTML controls, themes, data access, and various utilities.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a polyfills}
|
||||||
|
|
||||||
|
### Polyfill packages
|
||||||
|
|
||||||
|
Angular requires certain [polyfills](https://en.wikipedia.org/wiki/Polyfill) in the application environment.
|
||||||
|
Install these polyfills using the npm packages that Angular lists in the *peerDependencies* section of its `package.json`.
|
||||||
|
|
||||||
|
You must list these packages in the `dependencies` section of your own `package.json`.
|
||||||
|
|
||||||
|
For background on this requirement, see [Why peerDependencies?](#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.
|
||||||
|
|
||||||
|
***rxjs*** - A polyfill for the [Observables specification](https://github.com/zenparsing/es-observable) currently before the
|
||||||
|
[TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.
|
||||||
|
You can pick a preferred version of *rxjs* (within a compatible version range)
|
||||||
|
without waiting for Angular updates.
|
||||||
|
|
||||||
|
***zone.js*** - A polyfill for the [Zone specification](https://gist.github.com/mhevery/63fdcdf7c65886051d55) currently before the
|
||||||
|
[TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.
|
||||||
|
You can pick a preferred version of *zone.js* to use (within a compatible version range)
|
||||||
|
without waiting for Angular updates.
|
||||||
|
|
||||||
|
|
||||||
|
{@a other}
|
||||||
|
|
||||||
|
### Other helper libraries
|
||||||
|
|
||||||
|
***angular-in-memory-web-api*** - An Angular-supported library that simulates a remote server's web api
|
||||||
|
without requiring an actual server or real http calls.
|
||||||
|
Good for demos, samples, and early stage development (before we even have a server).
|
||||||
|
Read about it in the [Http Client](server-communication.html#appendix-tour-of-heroes-in-memory-server) page.
|
||||||
|
|
||||||
|
***bootstrap*** - [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
|
||||||
|
Some of the samples improve their appearance with *bootstrap*.
|
||||||
|
|
||||||
|
|
||||||
|
{@a dev-dependencies}
|
||||||
|
|
||||||
|
## *devDependencies*
|
||||||
|
The packages listed in the *devDependencies* section of the `package.json` help you develop the application.
|
||||||
|
You don't have to deploy them with the production application although there is no harm in doing so.
|
||||||
|
|
||||||
|
***[concurrently](https://www.npmjs.com/package/concurrently)*** -
|
||||||
|
A utility to run multiple *npm* commands concurrently on OS/X, Windows, and Linux operating systems.
|
||||||
|
|
||||||
|
***[lite-server](https://www.npmjs.com/package/lite-server)*** -
|
||||||
|
A light-weight, static file server, by [John Papa](http://johnpapa.net/)
|
||||||
|
with excellent support for Angular apps that use routing.
|
||||||
|
|
||||||
|
***[typescript](https://www.npmjs.com/package/typescript)*** -
|
||||||
|
the TypeScript language server, including the *tsc* TypeScript compiler.
|
||||||
|
|
||||||
|
***@types/\**** - TypeScript definition files.
|
||||||
|
Learn more about it in the [TypeScript Configuration](typescript-configuration.html#typings) chapter.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a why-peer-dependencies}
|
||||||
|
## Why *peerDependencies*?
|
||||||
|
|
||||||
|
There isn't a *peerDependencies* section in the QuickStart `package.json`.
|
||||||
|
But Angular has a *peerDependencies* section in
|
||||||
|
*its* package.json, which has important consequences for your application.
|
||||||
|
|
||||||
|
It explains why you load the [polyfill](#polyfills) *dependency* packages in the QuickStart `package.json`,
|
||||||
|
and why you'll need those packages in your own applications.
|
||||||
|
|
||||||
|
An explanation of [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies/) follows.
|
||||||
|
|
||||||
|
Packages depend on other packages. For example, your application depends on the Angular package.
|
||||||
|
|
||||||
|
Two packages, "A" and "B", could depend on the same third package "C".
|
||||||
|
"A" and "B" might both list "C" among their *dependencies*.
|
||||||
|
|
||||||
|
What if "A" and "B" depend on different versions of "C" ("C1" and "C2"). The npm package system supports that.
|
||||||
|
It installs "C1" in the `node_modules` folder for "A" and "C2" in the `node_modules` folder for "B".
|
||||||
|
Now "A" and "B" have their own copies of "C" and they run without interferring with one another.
|
||||||
|
|
||||||
|
But there is a problem. Package "A" may require the presence of "C1" without actually calling upon it directly.
|
||||||
|
"A" may only work if *everyone is using "C1"*. It falls down if any part of the application relies on "C2".
|
||||||
|
|
||||||
|
The solution is for "A" to declare that "C1" is a *peer dependency*.
|
||||||
|
|
||||||
|
The difference between a `dependency` and a `peerDependency` is roughly this:
|
||||||
|
|
||||||
|
>A **dependency** says, "I need this thing directly available to *me*."
|
||||||
|
>
|
||||||
|
>A **peerDependency** says, "If you want to use me, you need this thing available to *you*."
|
||||||
|
|
||||||
|
The Angular `package.json` specifies several *peer dependency* packages,
|
||||||
|
each pinned to a particular version of a third-party package.
|
||||||
|
|
||||||
|
### We must install Angular's *peerDependencies* ourselves.
|
||||||
|
|
||||||
|
When *npm* installs packages listed in *your* `dependencies` section,
|
||||||
|
it also installs the packages listed within *their* packages `dependencies` sections.
|
||||||
|
The process is recursive.
|
||||||
|
|
||||||
|
However, as of version 3, *npm* does *not* install packages listed in *peerDependencies* sections.
|
||||||
|
|
||||||
|
This means that when your application installs Angular, ***npm* doesn't automatically install
|
||||||
|
the packages listed in Angular's *peerDependencies* section**.
|
||||||
|
|
||||||
|
Fortunately, *npm* issues a warning (a) When any *peer dependencies* are missing, or (b)
|
||||||
|
When the application or any of its other dependencies
|
||||||
|
installs a different version of a *peer dependency*.
|
||||||
|
|
||||||
|
These warnings guard against accidental failures due to version mismatches.
|
||||||
|
They leave you in control of package and version resolution.
|
||||||
|
|
||||||
|
It is your responsibility to list all *peer dependency* packages **among your own *devDependencies***.
|
||||||
|
|
||||||
|
#### 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.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,142 @@
|
||||||
|
@title
|
||||||
|
Security
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Developing for content security in Angular applications
|
||||||
|
|
||||||
|
@description
|
||||||
|
This section describes Angular's built-in
|
||||||
|
protections against common web application vulnerabilities and attacks such as cross-site
|
||||||
|
scripting attacks. It does not cover application-level security, such as authentication (_Who is
|
||||||
|
this user?_) or authorization (_What can this user do?_).
|
||||||
|
|
||||||
|
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
||||||
|
Try the <live-example></live-example> of the code shown in this page.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='report-issues'>
|
||||||
|
Reporting vulnerabilities
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Email us at [security@angular.io](mailto:security@angular.io) to report vulnerabilities in
|
||||||
|
Angular itself.
|
||||||
|
|
||||||
|
For more information about how Google handles security issues, see [Google's security
|
||||||
|
philosophy](https://www.google.com/about/appsecurity/).
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='best-practices'>
|
||||||
|
Best practices
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
* **Keep current with the latest Angular library releases.**
|
||||||
|
We regularly update our Angular libraries, and these updates may fix security defects discovered in
|
||||||
|
previous versions. Check the Angular [change
|
||||||
|
log](https://github.com/angular/angular/blob/master/CHANGELOG.md) for security-related updates.
|
||||||
|
|
||||||
|
* **Don't modify your copy of Angular.**
|
||||||
|
Private, customized versions of Angular tend to fall behind the current version and may not include
|
||||||
|
important security fixes and enhancements. Instead, share your Angular improvements with the
|
||||||
|
community and make a pull request.
|
||||||
|
|
||||||
|
* **Avoid Angular APIs marked in the documentation as “[_Security Risk_](#bypass-security-apis).”**
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='xss'>
|
||||||
|
Preventing cross-site scripting (XSS)
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
[Cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) enables attackers
|
||||||
|
to inject malicious code into web pages. Such code can then, for example, steal user data (in
|
||||||
|
particular, their login data) or perform actions impersonating the user. This is one of the most
|
||||||
|
common attacks on the web.
|
||||||
|
|
||||||
|
To block XSS attacks, you must prevent malicious code from entering the DOM (Document Object Model). For example, if an
|
||||||
|
attacker can trick you into inserting a `<script>` tag in the DOM, they can run arbitrary code on
|
||||||
|
your website. The attack is not limited to `<script>` tags—many elements and properties in the
|
||||||
|
DOM allow code execution, for example, `<img onerror="...">` and `<a href="javascript:...">`. If
|
||||||
|
attacker-controlled data enters the DOM, expect security vulnerabilities.
|
||||||
|
|
||||||
|
### Angular’s cross-site scripting security model
|
||||||
|
|
||||||
|
To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value
|
||||||
|
is inserted into the DOM from a template, via property, attribute, style, class binding, or interpolation, Angular sanitizes and escapes untrusted values.
|
||||||
|
|
||||||
|
_Angular templates are the same as executable code_: HTML, attributes, and binding expressions
|
||||||
|
(but not the values bound!) in templates are trusted to be safe. This means that applications must
|
||||||
|
prevent values that an attacker can control from ever making it into the source code of a
|
||||||
|
template. Never generate template source code by concatenating user input and templates! Using
|
||||||
|
the [offline template compiler](#offline-template-compiler) is an effective way to prevent these
|
||||||
|
vulnerabilities, also known as _template injection_.
|
||||||
|
|
||||||
|
### Sanitization and security contexts
|
||||||
|
|
||||||
|
_Sanitization_ is the inspection of an untrusted value, turning it into a value that is safe to insert into
|
||||||
|
the DOM. In many cases, sanitization does not change a value at all. Sanitization depends on context:
|
||||||
|
a value that is harmless in CSS is potentially dangerous in a URL.
|
||||||
|
|
||||||
|
Angular defines four security contexts—HTML, style, URL, and resource URL:
|
||||||
|
|
||||||
|
* **HTML** is used when interpreting a value as HTML, for example, when binding to `innerHtml`
|
||||||
|
* **Style** is used when binding CSS into the `style` property
|
||||||
|
* **URL** is used for URL properties such as `<a href>`
|
||||||
|
* **Resource URL** is a URL that will be loaded and executed as code, for example, in `<script src>`
|
||||||
|
|
||||||
|
Angular sanitizes untrusted values for the first three items; sanitizing resource URLs is not
|
||||||
|
possible because they contain arbitrary code. In development mode, Angular prints a console warning
|
||||||
|
when it has to change a value during sanitization.
|
||||||
|
|
||||||
|
### Sanitization example
|
||||||
|
|
||||||
|
The template below binds the value of `htmlSnippet`, once by interpolating it into an element's
|
||||||
|
content, and once by binding it to the `innerHTML` property of an element:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'security/ts/src/app/inner-html-binding.component.html'}
|
||||||
|
|
||||||
|
Interpolated content is always escaped—the HTML is not interpreted, and the browser displays
|
||||||
|
angle brackets in the element's text content.
|
||||||
|
|
||||||
|
For the HTML to be interpreted, you must 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:
|
||||||
|
### Avoid direct use of the DOM APIs
|
||||||
|
|
||||||
|
The built-in browser DOM APIs do not automatically protect you from security vulnerabilities.
|
||||||
|
For example, `document`, the node available through `ElementRef`, and many third-party APIs
|
||||||
|
contain unsafe methods. Avoid directly interacting with the DOM and instead use Angular
|
||||||
|
templates where possible.
|
||||||
|
|
||||||
|
### Content security policy
|
||||||
|
|
||||||
|
[Content Security Policy (CSP)](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is a defense-in-depth
|
||||||
|
technique to prevent XSS. To enable CSP, configure your web server to return an appropriate
|
||||||
|
`Content-Security-Policy` HTTP header.
|
||||||
|
|
||||||
|
<a id="offline-template-compiler"></a>
|
||||||
|
### Use the offline template compiler
|
||||||
|
|
||||||
|
The offline template compiler prevents a whole class of vulnerabilities called template injection,
|
||||||
|
and also greatly improves application performance. Use the offline template compiler in production
|
||||||
|
deployments; do not dynamically generate templates. Angular trusts template code, so generating
|
||||||
|
templates, in particular templates containing user data, circumvents Angular's built-in protections. For information about how to dynamically construct forms in a safe way, see
|
||||||
|
[Dynamic Forms Cookbook](../cookbook/dynamic-form.html).
|
||||||
|
|
||||||
|
### Server-side XSS protection
|
||||||
|
|
||||||
|
HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an
|
||||||
|
Angular application is the same as injecting executable code into the
|
||||||
|
application: it gives the attacker full control over the application. To prevent this,
|
||||||
|
use a templating language that automatically escapes values to prevent XSS vulnerabilities on
|
||||||
|
the server. Do not generate Angular templates on the server side using a templating language; doing this
|
||||||
|
carries a high risk of introducing template-injection vulnerabilities.
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='code-review'>
|
||||||
|
Auditing angular applications
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
Angular applications must follow the same security principles as regular web applications, and
|
||||||
|
must be audited as such. Angular-specific APIs that should be audited in a security review,
|
||||||
|
such as the [_bypassSecurityTrust_](#bypass-security-apis) methods, are marked in the documentation
|
||||||
|
as security sensitive.
|
|
@ -0,0 +1,390 @@
|
||||||
|
@title
|
||||||
|
HTTP Client
|
||||||
|
|
||||||
|
@intro
|
||||||
|
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.
|
||||||
|
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:
|
||||||
|
[XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and
|
||||||
|
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
|
||||||
|
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
||||||
|
|
||||||
|
The !{_Angular_http_library} simplifies application programming with the **XHR** and **JSONP** APIs.
|
||||||
|
This page covers:
|
||||||
|
|
||||||
|
- [The Tour of Heroes *HTTP* client demo](#http-client).
|
||||||
|
- [Fetch data with http.get](#fetch-data).
|
||||||
|
<li if-docs="ts"> [RxJS library](#rxjs).</li>
|
||||||
|
<li if-docs="ts"> [Enable RxJS operators](#enable-rxjs-operators).</li>
|
||||||
|
- [Process the response object](#extract-data).
|
||||||
|
- [Always handle errors](#error-handling).
|
||||||
|
- [Send data to the server](#update).
|
||||||
|
<li if-docs="ts"> [Fall back to promises](#promises).</li>
|
||||||
|
- [Cross-Origin Requests: Wikipedia example](#cors).
|
||||||
|
<ul if-docs="ts">
|
||||||
|
<li> [Search parameters](#search-parameters).</li>
|
||||||
|
<li> [More fun with observables](#more-observables).</li>
|
||||||
|
</ul>
|
||||||
|
- [Guarding against Cross-Site Request Forgery](#xsrf).
|
||||||
|
- [Override default request headers (and other request options)](#override-default-request-options).
|
||||||
|
- [Appendix: Tour of Heroes _in-memory web api_](#in-mem-web-api).
|
||||||
|
|
||||||
|
A <live-example>live example</live-example> illustrates these topics.
|
||||||
|
|
||||||
|
# Demos
|
||||||
|
|
||||||
|
This page describes server communication with the help of the following demos:
|
||||||
|
The root `AppComponent` orchestrates these demos:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/app.component.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
# Providing HTTP services
|
||||||
|
|
||||||
|
First, configure the application to use server communication facilities.
|
||||||
|
|
||||||
|
The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol.
|
||||||
|
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||||
|
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system.
|
||||||
|
|
||||||
|
Read about providers in the [Dependency Injection](dependency-injection.html) 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'}
|
||||||
|
|
||||||
|
|
||||||
|
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)`.
|
||||||
|
|
||||||
|
It works like this:
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/server-communication/http-toh.gif' alt="ToH mini app" width="250"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
This demo has a single component, the `HeroListComponent`. Here's its template:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero-list.component.html'}
|
||||||
|
|
||||||
|
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](template-syntax.html#ref-vars), `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
|
||||||
|
the event binding clears it to make it ready for a new hero name.
|
||||||
|
|
||||||
|
Below the button is an area for an error message.
|
||||||
|
|
||||||
|
|
||||||
|
{@a oninit}
|
||||||
|
|
||||||
|
|
||||||
|
{@a HeroListComponent}
|
||||||
|
### The *HeroListComponent* class
|
||||||
|
Here's the component class:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='component'}
|
||||||
|
|
||||||
|
Angular [injects](dependency-injection.html) a `HeroService` into the constructor
|
||||||
|
and the component calls that service to fetch and save data.
|
||||||
|
|
||||||
|
The component **does not talk directly to the !{_Angular_Http} client**.
|
||||||
|
The component doesn't know or care how it gets the data.
|
||||||
|
It delegates to the `HeroService`.
|
||||||
|
|
||||||
|
This is a golden rule: **always delegate data access to a supporting service class**.
|
||||||
|
|
||||||
|
Although _at runtime_ the component requests heroes immediately after creation,
|
||||||
|
you **don't** call the service's `get` method in the component's constructor.
|
||||||
|
Instead, call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html)
|
||||||
|
and rely on Angular to call `ngOnInit` when it instantiates this component.
|
||||||
|
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`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a HeroService}
|
||||||
|
|
||||||
|
## Fetch data with http.get
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Notice that the !{_Angular_Http} client service is
|
||||||
|
[injected](dependency-injection.html) into the `HeroService` constructor.
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='ctor'}
|
||||||
|
|
||||||
|
Look closely at how to call `!{_priv}http.get`:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='http-get'}
|
||||||
|
|
||||||
|
You pass the resource URL to `get` and it calls the server which returns heroes.
|
||||||
|
|
||||||
|
The server returns heroes once you've set up the [in-memory web api](#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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a extract-data}
|
||||||
|
## 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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Parse to JSON
|
||||||
|
Don't expect the decoded JSON to be the heroes !{_array} directly.
|
||||||
|
This server always wraps JSON results in an object with a `data`
|
||||||
|
property. You have to unwrap it to get the heroes.
|
||||||
|
This is conventional web API behavior, driven by
|
||||||
|
[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.
|
||||||
|
Not all servers return an object with a `data` property.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Do not return the response object
|
||||||
|
The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't
|
||||||
|
be a best practice.
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@a error-handling}
|
||||||
|
### Always handle errors
|
||||||
|
|
||||||
|
An important part of dealing with I/O is anticipating errors by preparing to catch them
|
||||||
|
and do something with them. One way to handle errors is to pass an error message
|
||||||
|
back to the component for presentation to the user,
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a subscribe}
|
||||||
|
|
||||||
|
|
||||||
|
{@a hero-list-component}
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<b> HeroListComponent </b> error handling
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='getHeroes'}
|
||||||
|
|
||||||
|
|
||||||
|
Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value. Afterward, remember to restore it.
|
||||||
|
|
||||||
|
<a id="update"></a>
|
||||||
|
<a id="post"></a>
|
||||||
|
## Send data to the server
|
||||||
|
|
||||||
|
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
|
||||||
|
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'}
|
||||||
|
|
||||||
|
To implement it, you must know the server's API for creating heroes.
|
||||||
|
|
||||||
|
[This sample's data server](#server) follows typical REST guidelines.
|
||||||
|
It expects a [`POST`](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) request
|
||||||
|
at the same endpoint as `GET` heroes.
|
||||||
|
It expects the new hero data to arrive in the body of the request,
|
||||||
|
structured like a `Hero` entity but without the `id` property.
|
||||||
|
The body of the request should look like this:
|
||||||
|
|
||||||
|
<code-example format="." language="javascript">
|
||||||
|
{ "name": "Windstorm" }
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='addhero'}
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
In the `headers` object, the `Content-Type` specifies that the body represents JSON.
|
||||||
|
### JSON results
|
||||||
|
|
||||||
|
As with `getHeroes()`, use the `!{_priv}extractData()` helper to [extract the data](#extract-data)
|
||||||
|
from the response.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero-list.component.ts' region='addHero'}
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='cors'>
|
||||||
|
Cross-Origin Requests: Wikipedia example
|
||||||
|
</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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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, you'll enable them in the [request headers](#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.
|
||||||
|
This [Stack Overflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP.### Search wikipedia
|
||||||
|
|
||||||
|
Here is a simple search that shows suggestions from Wikipedia as the user
|
||||||
|
types in a text box:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a xsrf}
|
||||||
|
|
||||||
|
## Guarding against Cross-Site Request Forgery
|
||||||
|
|
||||||
|
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
||||||
|
a different web page with malignant code that secretly sends a malicious request to your application's web server,
|
||||||
|
|
||||||
|
The server and client application must work together to thwart this attack.
|
||||||
|
Angular's `Http` client does its part by applying a default `CookieXSRFStrategy` automatically to all requests.
|
||||||
|
|
||||||
|
The `CookieXSRFStrategy` supports a common anti-XSRF technique in which the server sends a randomly
|
||||||
|
generated authentication token in a cookie named `XSRF-TOKEN`.
|
||||||
|
The HTTP client adds an `X-XSRF-TOKEN` header with that token value to subsequent requests.
|
||||||
|
The server receives both the cookie and the header, compares them, and processes the request only if the cookie and header match.
|
||||||
|
|
||||||
|
See the [XSRF topic on the Security page](security.html#xsrf) for more information about XSRF and Angular's `XSRFStrategy` counter measures.
|
||||||
|
|
||||||
|
|
||||||
|
{@a override-default-request-options}
|
||||||
|
|
||||||
|
## Override default request headers (and other request options)
|
||||||
|
|
||||||
|
Request options (such as headers) are merged into the
|
||||||
|
[default _RequestOptions_](https://angular.io/docs/ts/latest/api/http/index/BaseRequestOptions-class.html "API: BaseRequestOptions")
|
||||||
|
before the request is processed.
|
||||||
|
The `HttpModule` provides these default options via the `RequestOptions` token.
|
||||||
|
|
||||||
|
You can override these defaults to suit your application needs.
|
||||||
|
by creating a custom sub-class of `RequestOptions`
|
||||||
|
that sets the default options for the application.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Then it registers the provider in the root `AppModule`.
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/app.module.ts' region='provide-default-request-options'}
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='addhero'}
|
||||||
|
|
||||||
|
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_](#in-mem-web-api),
|
||||||
|
try commenting-out the `addHero` 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.
|
||||||
|
|
||||||
|
|
||||||
|
{@a in-mem-web-api}
|
||||||
|
|
||||||
|
## Appendix: Tour of Heroes _in-memory web api_
|
||||||
|
|
||||||
|
If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file:
|
||||||
|
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:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='endpoint-json'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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>
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Ensure that the `HeroService` endpoint refers to the web API:
|
||||||
|
|
||||||
|
{@example 'server-communication/ts/src/app/toh/hero.service.ts' region='endpoint'}
|
||||||
|
|
||||||
|
Here is the final, revised version of <span ngio-ex>src/app/app.module.ts</span>, demonstrating these steps.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that
|
||||||
|
the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
See the full source code in the <live-example></live-example>.
|
|
@ -0,0 +1,223 @@
|
||||||
|
@title
|
||||||
|
Setup for local development
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Install the Angular QuickStart seed for faster, more efficient development on your machine
|
||||||
|
|
||||||
|
@description
|
||||||
|
|
||||||
|
|
||||||
|
{@a develop-locally}
|
||||||
|
## Setup a local development environment
|
||||||
|
|
||||||
|
<span if-docs="ts">
|
||||||
|
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
|
||||||
|
It's not where you'd develop a real application.
|
||||||
|
You [should develop locally](#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
|
||||||
|
maintained [on github](!{_qsRepo} "Install the github QuickStart repo").
|
||||||
|
Make sure you have [!{_prereq} installed](#install-prerequisites "What if you don't have !{_prereq}?").
|
||||||
|
Then ...
|
||||||
|
1. Create a project folder (you can call it `quickstart` and rename it later).
|
||||||
|
1. [Clone](#clone "Clone it from github") or [download](#download "download it from github") the **QuickStart seed** into your project folder.
|
||||||
|
1. !{_Install} [!{_npm}](#install-prerequisites "What if you don't have !{_prereq}?") packages.
|
||||||
|
1. Run `!{_npm} !{_start}` to launch the sample application.
|
||||||
|
|
||||||
|
|
||||||
|
{@a clone}
|
||||||
|
### Clone
|
||||||
|
|
||||||
|
Perform the _clone-to-launch_ steps with these terminal commands.
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
git clone .git quickstart
|
||||||
|
cd quickstart
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
`npm start` fails in _Bash for Windows_ which does not support networking to servers as of January, 2017.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a download}
|
||||||
|
### Download
|
||||||
|
<a href="!{_qsRepoZip}" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
|
||||||
|
and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
cd quickstart
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
`npm start` fails in _Bash for Windows_ which does not support networking to servers as of January, 2017.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a non-essential}
|
||||||
|
|
||||||
|
## Delete _non-essential_ files (optional)
|
||||||
|
|
||||||
|
You can quickly delete the _non-essential_ files that concern testing and QuickStart repository maintenance
|
||||||
|
(***including all git-related artifacts*** such as the `.git` folder and `.gitignore`!).
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Do this only in the beginning to avoid accidentally deleting your own tests and git setup!
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Open a terminal window in the project folder and enter the following commands for your environment:
|
||||||
|
|
||||||
|
### OS/X (bash)
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
xargs rm -rf < non-essential-files.osx.txt
|
||||||
|
rm src/app/*.spec*.ts
|
||||||
|
rm non-essential-files.osx.txt
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
for /f %i in (non-essential-files.txt) do del %i /F /S /Q
|
||||||
|
rd .git /s /q
|
||||||
|
rd e2e /s /q
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a seed}
|
||||||
|
|
||||||
|
## What's in the QuickStart seed?
|
||||||
|
All guides and cookbooks have _at least these core files_.
|
||||||
|
Each file has a distinct purpose and evolves independently as the application grows.
|
||||||
|
|
||||||
|
Files outside `src/` concern building, deploying, and testing your app.
|
||||||
|
They include configuration files and external dependencies.
|
||||||
|
|
||||||
|
Files inside `src/` "belong" to your app.
|
||||||
|
Add new Typescript, HTML and CSS files inside the `src/` directory, most of them inside `src/app`,
|
||||||
|
unless told to do otherwise.
|
||||||
|
|
||||||
|
The following are all in `src/`
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td, th {vertical-align: top}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%">
|
||||||
|
|
||||||
|
<col width="20%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<col width="80%">
|
||||||
|
|
||||||
|
</col>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
File
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Purpose
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<ngio-ex>app/app.component.ts</ngio-ex>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Defines the same `AppComponent` as the one in the QuickStart !{_playground}.
|
||||||
|
It is the **root** component of what will become a tree of nested components
|
||||||
|
as the application evolves.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr if-docs="ts">
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<code>app/app.module.ts</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Defines `AppModule`, the [root module](appmodule.html "AppModule: the root module") that tells Angular how to assemble the application.
|
||||||
|
Right now it declares only the `AppComponent`.
|
||||||
|
Soon there will be more components to declare.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<ngio-ex>main.ts</ngio-ex>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
Compiles the application with the [JIT compiler](../glossary.html#jit) and
|
||||||
|
[bootstraps](appmodule.html#main "bootstrap the application")
|
||||||
|
the application's main module (`AppModule`) to run in the browser.
|
||||||
|
The JIT compiler is a reasonable choice during the development of most projects and
|
||||||
|
it's the only viable choice for a sample running in a _live-coding_ environment like Plunker.
|
||||||
|
You'll learn about alternative compiling and [deployment](deployment.html) options later in the documentation.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
### Next Step
|
||||||
|
|
||||||
|
If you're new to Angular, we recommend staying on the [learning path](learning-angular.html "Angular learning path").
|
||||||
|
<br></br><br></br>
|
||||||
|
|
||||||
|
{@a install-prerequisites}
|
||||||
|
|
||||||
|
## Appendix: !{_prereq}
|
|
@ -0,0 +1,614 @@
|
||||||
|
@title
|
||||||
|
Structural Directives
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements.
|
||||||
|
|
||||||
|
@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%; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
This guide looks at how Angular manipulates the DOM with **structural directives** and
|
||||||
|
how you can write your own structural directives to do the same thing.
|
||||||
|
|
||||||
|
### Table of contents
|
||||||
|
|
||||||
|
- [What are structural directives?](#definition)
|
||||||
|
- [*NgIf* case study](#ngIf)
|
||||||
|
- [Group sibling elements with <ng-container>](#ng-container)
|
||||||
|
- [The asterisk (\*) prefix](#asterisk)
|
||||||
|
- [Inside *NgFor*](#ngfor)
|
||||||
|
- [microsyntax](#microsyntax)
|
||||||
|
- [template input variables](#template-input-variable)
|
||||||
|
- [one structural directive per element](#one-per-element)
|
||||||
|
- [Inside the *NgSwitch* directives](#ngSwitch)
|
||||||
|
- [Prefer the (\*) prefix](#prefer-asterisk)
|
||||||
|
- [The <template> element](#template)
|
||||||
|
- [Write a structural directive](#unless)
|
||||||
|
|
||||||
|
Try the <live-example></live-example>.
|
||||||
|
|
||||||
|
|
||||||
|
{@a definition}
|
||||||
|
|
||||||
|
## What are structural directives?
|
||||||
|
|
||||||
|
Structural directives are responsible for HTML layout.
|
||||||
|
They shape or reshape the DOM's _structure_, typically by adding, removing, or manipulating
|
||||||
|
elements.
|
||||||
|
|
||||||
|
As with other directives, you apply a structural directive to a _host element_.
|
||||||
|
The directive then does whatever it's supposed to do with that host element and its descendents.
|
||||||
|
|
||||||
|
Structural directives are easy to recognize.
|
||||||
|
An asterisk (\*) precedes the directive attribute name as in this example.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif'}
|
||||||
|
|
||||||
|
No brackets. No parentheses. Just `*ngIf` set to a string.
|
||||||
|
|
||||||
|
You'll learn in this guide that the [asterisk (\*) is a convenience notation](#asterisk)
|
||||||
|
and the string is a [_microsyntax_](#microsyntax) rather than the usual [template expression](template-syntax.html#template-expressions).
|
||||||
|
Angular "de-sugars" this notation into a marked-up `<template>` that surrounds the
|
||||||
|
host element and its descendents.
|
||||||
|
Each structural directive does something different with that template.
|
||||||
|
|
||||||
|
Three of the common, built-in structural directives—[NgIf](template-syntax.html#ngIf),
|
||||||
|
[NgFor](template-syntax.html#ngFor), and [NgSwitch...](template-syntax.html#ngSwitch)—are
|
||||||
|
described in the [_Template Syntax_](template-syntax.html) guide and seen in samples throughout the Angular documentation.
|
||||||
|
Here's an example of them in a template:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='built-in'}
|
||||||
|
|
||||||
|
This guide won't repeat how to _use_ them. But it does explain _how they work_
|
||||||
|
and how to [write your own](#unless) structural directive.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-helpful}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
Directive spelling
|
||||||
|
</header>
|
||||||
|
|
||||||
|
Throughout this guide, you'll see a directive spelled in both _UpperCamelCase_ and _lowerCamelCase_.
|
||||||
|
Already you've seen `NgIf` and `ngIf`.
|
||||||
|
There's a reason. `NgIf` refers to the directive _class_;
|
||||||
|
`ngIf` refers to the directive's _attribute name_.
|
||||||
|
|
||||||
|
A directive _class_ is spelled in _UpperCamelCase_ (`NgIf`).
|
||||||
|
A directive's _attribute name_ is spelled in _lowerCamelCase_ (`ngIf`).
|
||||||
|
The guide refers to the directive _class_ when talking about its properties and what the directive does.
|
||||||
|
The guide refers to the _attribute name_ when describing how
|
||||||
|
you apply the directive to an element in the HTML template.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
There are two other kinds of Angular directives, described extensively elsewhere: (1) components and (2) attribute directives.
|
||||||
|
|
||||||
|
A *component* manages a region of HTML in the manner of a native HTML element.
|
||||||
|
Technically it's a directive with a template.
|
||||||
|
|
||||||
|
An [*attribute* directive](attribute-directives.html) changes the appearance or behavior
|
||||||
|
of an element, component, or another directive.
|
||||||
|
For example, the built-in [`NgStyle`](template-syntax.html#ngStyle) directive
|
||||||
|
changes several element styles at the same time.
|
||||||
|
|
||||||
|
You can apply many _attribute_ directives to one host element.
|
||||||
|
You can [only apply one](#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 value and makes an entire chunk of the DOM appear or disappear.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif-true'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/element-not-in-dom.png' alt="ngIf=false element not in DOM"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The top paragraph is in the DOM. The bottom, disused paragraph is not;
|
||||||
|
in its place is a comment about "template bindings" (more about that [later](#asterisk)).
|
||||||
|
|
||||||
|
When the condition is false, `NgIf` removes its host element from the DOM,
|
||||||
|
detaches it from DOM events (the attachments that it made),
|
||||||
|
detaches the component from Angular change detection, and destroys it.
|
||||||
|
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`.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='display-none'}
|
||||||
|
|
||||||
|
While invisible, the element remains in the DOM.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/element-display-in-dom.png' alt="hidden element still in DOM"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The difference between hiding and removing doesn't matter for a simple paragraph.
|
||||||
|
It does matter when the host element is attached to a resource intensive component.
|
||||||
|
Such a component's behavior continues even when hidden.
|
||||||
|
The component stays attached to its DOM element. It keeps listening to events.
|
||||||
|
Angular keeps checking for changes that could affect data bindings.
|
||||||
|
Whatever the component was doing, it keeps doing.
|
||||||
|
|
||||||
|
Although invisible, the component—and all of its descendant components—tie up resources.
|
||||||
|
The performance and memory burden can be substantial, responsiveness can degrade, and the user sees nothing.
|
||||||
|
|
||||||
|
On the positive side, showing the element again is quick.
|
||||||
|
The component's previous state is preserved and ready to display.
|
||||||
|
The component doesn't re-initialize—an operation that could be expensive.
|
||||||
|
So hiding and showing is sometimes the right thing to do.
|
||||||
|
|
||||||
|
But in the absence of a compelling reason to keep them around,
|
||||||
|
your preference should be to remove DOM elements that the user can't see
|
||||||
|
and recover the unused resources with a structural directive like `NgIf` .
|
||||||
|
|
||||||
|
**These same considerations apply to every structural directive, whether built-in or custom.**
|
||||||
|
Before applying a structural directive, you might want to pause for a moment
|
||||||
|
to consider the consequences of adding and removing elements and of creating and destroying components.
|
||||||
|
|
||||||
|
|
||||||
|
{@a ngcontainer}
|
||||||
|
|
||||||
|
|
||||||
|
{@a ng-container}
|
||||||
|
|
||||||
|
## Group sibling elements with <ng-container>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngfor-li'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif'}
|
||||||
|
|
||||||
|
Introducing another container element—typically a `<span>` or `<div>`—to
|
||||||
|
group the elements under a single _root_ is usually harmless.
|
||||||
|
_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.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif-span'}
|
||||||
|
|
||||||
|
You also have a CSS style rule that happens to apply to a `<span>` within a `<p>`aragraph.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.css' region='p-span'}
|
||||||
|
|
||||||
|
The constructed paragraph renders strangely.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/bad-paragraph.png' alt="spanned paragraph with bad style"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The `p span` style, intended for use elsewhere, was inadvertently applied here.
|
||||||
|
|
||||||
|
Another problem: some HTML elements require all immediate children to be of a specific type.
|
||||||
|
For example, the `<select>` tag requires `<option>` children.
|
||||||
|
You can't wrap the _options_ in a conditional `<div>` or a `<span>`.
|
||||||
|
|
||||||
|
When you try this,
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='select-span'}
|
||||||
|
|
||||||
|
the drop down is empty.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/bad-select.png' alt="spanned options don't work"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The browser won't display an `<option>` within a `<span>`.
|
||||||
|
|
||||||
|
### <ng-container> to the rescue
|
||||||
|
|
||||||
|
The Angular `<ng-container>` is a grouping element that doesn't interfere with styles or layout
|
||||||
|
because Angular _doesn't put it in the DOM_.
|
||||||
|
|
||||||
|
Here's the conditional paragraph again, this time using `<ng-container>`.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif-ngcontainer'}
|
||||||
|
|
||||||
|
It renders properly.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/good-paragraph.png' alt="ngcontainer paragraph with proper style"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Now conditionally exclude a _select_ `<option>` with `<ng-container>`.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='select-ngcontainer'}
|
||||||
|
|
||||||
|
The drop down works properly.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/select-ngcontainer-anim.gif' alt="ngcontainer options work properly"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
The `<ng-container>` is a syntax element recognized by the Angular parser.
|
||||||
|
It's not a directive, component, class, or interface.
|
||||||
|
It's more like the curly braces in a JavaScript `if`-block:
|
||||||
|
|
||||||
|
<code-example language="javascript">
|
||||||
|
if (someCondition) {
|
||||||
|
statement1;
|
||||||
|
statement2;
|
||||||
|
statement3;
|
||||||
|
}
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Without those braces JavaScript could only execute the first statement
|
||||||
|
when you intend to conditionally execute all of them as a single block.
|
||||||
|
The `<ng-container>` satisfies a similar need in Angular templates.
|
||||||
|
|
||||||
|
|
||||||
|
{@a asterisk}
|
||||||
|
|
||||||
|
## The asterisk (\*) prefix
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='asterisk'}
|
||||||
|
|
||||||
|
The asterisk is "syntactic sugar" for something a bit more complicated.
|
||||||
|
Internally, Angular "de-sugars" it in two stages.
|
||||||
|
First, it translates the `*ngIf="..."` into a template _attribute_, `template="ngIf ..."`, like this.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif-template-attr'}
|
||||||
|
|
||||||
|
Then it translates the template _attribute_ into a template _element_, wrapped around the host element, like this.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngif-template'}
|
||||||
|
|
||||||
|
* The `*ngIf` directive moved to the `<template>` tag where it became a property binding,`[ngIf]`.
|
||||||
|
* The rest of the `<div>`, including its class attribute, moved inside the `<template>` tag.
|
||||||
|
|
||||||
|
None of these forms are actually rendered.
|
||||||
|
Only the finished product ends up in the DOM.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/hero-div-in-dom.png' alt="hero div in DOM"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Angular consumed the `<template>` content during its actual rendering and
|
||||||
|
replaced the `<template>` with a diagnostic comment.
|
||||||
|
|
||||||
|
The [`NgFor`](#ngfor) and [`NgSwitch...`](#ngswitch) directives follow the same pattern.
|
||||||
|
|
||||||
|
|
||||||
|
{@a ngfor}
|
||||||
|
|
||||||
|
## Inside _*ngFor_
|
||||||
|
|
||||||
|
Angular transforms the `*ngFor` in similar fashion from asterisk (\*) syntax through
|
||||||
|
template _attribute_ to template _element_.
|
||||||
|
|
||||||
|
Here's a full-featured application of `NgFor`, written all three ways:
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='inside-ngfor'}
|
||||||
|
|
||||||
|
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`).
|
||||||
|
|
||||||
|
You enable these features in the string assigned to `ngFor`, which you write in Angular's [microsyntax](microsyntax).
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-helpful}
|
||||||
|
|
||||||
|
Everything _outside_ the `ngFor` string stays with the host element
|
||||||
|
(the `<div>`) as it moves inside the `<template>`.
|
||||||
|
In this example, the `[ngClass]="odd"` stays on the `<div>`.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a microsyntax}
|
||||||
|
### microsyntax
|
||||||
|
The Angular microsyntax lets you configure a directive in a compact, friendly string.
|
||||||
|
The microsyntax parser translates that string into attributes on the `<template>`:
|
||||||
|
|
||||||
|
* The `let` keyword declares a [_template input variable_](#template-input-variable)
|
||||||
|
that you reference within the template. The input variables in this example are `hero`, `i`, and `odd`.
|
||||||
|
The parser translates `let hero`, `let i`, and `let odd` into variables named,
|
||||||
|
`let-hero`, `let-i`, and `let-odd`.
|
||||||
|
|
||||||
|
* The microsyntax parser takes `of` and `trackby`, title-cases them (`of` -> `Of`, `trackBy` -> `TrackBy`),
|
||||||
|
and prefixes them with the directive's attribute name (`ngFor`), yielding the names `ngForOf` and `ngForTrackBy`.
|
||||||
|
Those are the names of two `NgFor` _input properties_ .
|
||||||
|
That's how the directive learns that the list is `heroes` and the track-by function is `trackById`.
|
||||||
|
|
||||||
|
* As the `NgFor` directive loops through the list, it sets and resets properties of its own _context_ object.
|
||||||
|
These properties include `index` and `odd` and a special property named `$implicit`.
|
||||||
|
|
||||||
|
* The `let-i` and `let-odd` variables were defined as `let i=index` and `let odd=odd`.
|
||||||
|
Angular sets them to the current value of the context's `index` and `odd` properties.
|
||||||
|
|
||||||
|
* The context property for `let-hero` wasn't specified.
|
||||||
|
It's intended source is implicit.
|
||||||
|
Angular sets `let-hero` to the value of the context's `$implicit` property
|
||||||
|
which `NgFor` has initialized with the hero for the current iteration.
|
||||||
|
|
||||||
|
* The [API guide](../api/common/index/NgFor-directive.html "API: NgFor")
|
||||||
|
describes additional `NgFor` directive properties and context properties.
|
||||||
|
|
||||||
|
These microsyntax mechanisms are available to you when you write your own structural directives.
|
||||||
|
Studying the source code for `NgIf` and `NgFor` is a great way to learn more.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a template-input-variable}
|
||||||
|
|
||||||
|
|
||||||
|
{@a template-input-variables}
|
||||||
|
### Template input variable
|
||||||
|
|
||||||
|
A _template input variable_ is a variable whose value you can reference _within_ a single instance of the template.
|
||||||
|
There are several such variables in this example: `hero`, `li`, and `odd`.
|
||||||
|
All are preceded by the keyword `let`.
|
||||||
|
|
||||||
|
A _template input variable_ is **_not_** the same as a
|
||||||
|
[template _reference_ variable](template-syntax.html#ref-vars),
|
||||||
|
neither _semantically_ nor _syntactically_.
|
||||||
|
|
||||||
|
You declare a template _input_ variable declaration with the `let` keyword (`let hero`).
|
||||||
|
The variable's scope is limited to a _single instance_ of the repeated template.
|
||||||
|
You can use the same variable name again in the definition of other structural directives.
|
||||||
|
|
||||||
|
You declare a template _reference_ variable declaration by prefixing the variable name with `#` (`#var`).
|
||||||
|
A _reference_ variable refers to its attached element, component or directive.
|
||||||
|
It can be accessed _anywhere_ in the _entire template_.
|
||||||
|
|
||||||
|
Template _input_ and _reference_ variable names have their own namespaces. The `hero` in `let hero` is never the same
|
||||||
|
variable as the `hero` declared as `#hero`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a one-per-element}
|
||||||
|
### One structural directive per host element
|
||||||
|
|
||||||
|
Someday you'll want to to repeat a block of HTML but only when a particular condition is true.
|
||||||
|
You'll _try_ to put both an `*ngFor` and an `*ngIf` on the same host element.
|
||||||
|
Angular won't let you. You may apply only one _structural_ directive to an element.
|
||||||
|
|
||||||
|
The reason is simplicity. Structural directives can do complex things with the host element and its descendents.
|
||||||
|
When two directives lay claim to the same host element, which one takes precedence?
|
||||||
|
Which should go first, the `NgIf` or the `NgFor`? Can the `NgIf` cancel the effect of the `NgFor`?
|
||||||
|
If so (and it seems like it should be so), how should Angular generalize the ability to cancel for other structural directives?
|
||||||
|
|
||||||
|
There are no easy answers to these questions. Prohibiting multiple structural directives makes them moot.
|
||||||
|
There's an easy solution for this use case: put the `*ngIf` on a container element that wraps the `*ngFor` element.
|
||||||
|
One or both elements can be an [`ng-container`](#ngcontainer) so you don't have to introduce extra levels of HTML.
|
||||||
|
|
||||||
|
|
||||||
|
{@a ngswitch}
|
||||||
|
|
||||||
|
## Inside the _NgSwitch_ directives
|
||||||
|
|
||||||
|
The Angular _NgSwitch_ is actually a set of cooperating directives: `NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault`.
|
||||||
|
|
||||||
|
Here's an example.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngswitch'}
|
||||||
|
|
||||||
|
The switch value assigned to `NgSwitch` (`hero.emotion`) determines which
|
||||||
|
(if any) of the switch cases are displayed.
|
||||||
|
|
||||||
|
`NgSwitch` itself is not a structural directive.
|
||||||
|
It's an _attribute_ directive that controls the behavior of the other two switch directives.
|
||||||
|
That's why you write `[ngSwitch]`, never `*ngSwitch`.
|
||||||
|
|
||||||
|
`NgSwitchCase` and `NgSwitchDefault` _are_ structural directives.
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 "de-sugared" into the template _attribute_ form.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngswitch-template-attr'}
|
||||||
|
|
||||||
|
That, in turn, can be "de-sugared" into the `<template>` element form.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='ngswitch-template'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a prefer-asterisk}
|
||||||
|
## Prefer the asterisk (\*) syntax.
|
||||||
|
|
||||||
|
The asterisk (\*) syntax is more clear than the other "de-sugared" forms.
|
||||||
|
Use [<ng-container>](#ng-container) when there's no single element
|
||||||
|
to host the directive.
|
||||||
|
|
||||||
|
While there's rarely a good reason to apply a structural directive in template _attribute_ or _element_ form,
|
||||||
|
it's still important to know that Angular creates a `<template>` and to understand how it works.
|
||||||
|
You'll refer to the `<template>` when you [write your own structural directive](#unless).
|
||||||
|
|
||||||
|
|
||||||
|
{@a template}
|
||||||
|
|
||||||
|
## The *<template>*
|
||||||
|
|
||||||
|
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" target="_blank" title="MDN: Template Tag">HTML 5 <template></a>
|
||||||
|
is a formula for rendering HTML.
|
||||||
|
It is never displayed directly.
|
||||||
|
In fact, before rendering the view, Angular _replaces_ the `<template>` and its contents with a comment.
|
||||||
|
|
||||||
|
If there is no structural directive, if you merely wrap some elements in a `<template>` and do nothing with it,
|
||||||
|
those elements disappear.
|
||||||
|
That's the fate of the middle "hip" in the phrase "Hip! Hip! Hooray!".
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='template-tag'}
|
||||||
|
|
||||||
|
Angular erases the middle "hip", leaving the cheer a bit less enthusiastic.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/template-rendering.png' width="350" alt="template tag rendering"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
A structural directive puts a `<template>` to work
|
||||||
|
as you'll see when you write your own structural directive.
|
||||||
|
|
||||||
|
|
||||||
|
{@a unless}
|
||||||
|
|
||||||
|
## Write a structural directive
|
||||||
|
In this section, you write a `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***.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='myUnless-1'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/unless.directive.ts' region='skeleton'}
|
||||||
|
|
||||||
|
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>.
|
||||||
|
|
||||||
|
The directive _attribute name_ should be spelled in _lowerCamelCase_ and begin with a prefix.
|
||||||
|
Don't use `ng`. That prefix belongs to Angular.
|
||||||
|
Pick something short that fits you or your company.
|
||||||
|
In this example, the prefix is `my`.
|
||||||
|
The directive _class_ name ends in `Directive` per the [style guide](style-guide.html#02-03 "Angular Style Guide").
|
||||||
|
Angular's own directives do not.
|
||||||
|
|
||||||
|
### _TemplateRef_ and _ViewContainerRef_
|
||||||
|
|
||||||
|
A simple structural directive like this one creates an
|
||||||
|
[_embedded view_](../api/core/index/EmbeddedViewRef-class.html "API: EmbeddedViewRef")
|
||||||
|
from the Angular-generated `<template>` and inserts that view in a
|
||||||
|
[_view container_](../api/core/index/ViewContainerRef-class.html "API: ViewContainerRef")
|
||||||
|
adjacent to the directive's original `<p>` host element.
|
||||||
|
|
||||||
|
You'll acquire the `<template>` contents with a
|
||||||
|
[`TemplateRef`](../api/core/index/TemplateRef-class.html "API: TemplateRef")
|
||||||
|
and access the _view container_ through a
|
||||||
|
[`ViewContainerRef`](../api/core/index/ViewContainerRef-class.html "API: ViewContainerRef").
|
||||||
|
|
||||||
|
You inject both in the directive constructor as private variables of the class.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/unless.directive.ts' region='ctor'}
|
||||||
|
|
||||||
|
### 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`
|
||||||
|
Read about `@Input` in the [_Template Syntax_](template-syntax.html#inputs-outputs) guide.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/unless.directive.ts' region='set'}
|
||||||
|
|
||||||
|
Angular sets the `myUnless` property whenever the value of the condition changes.
|
||||||
|
Because the `myUnless` property does work, it needs a setter.
|
||||||
|
|
||||||
|
* If the condition is falsy and the view hasn't been created previously,
|
||||||
|
tell the _view container_ to create the _embedded view_ from the template.
|
||||||
|
|
||||||
|
* If the condition is truthy and the view is currently displayed,
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/unless.directive.ts' region='no-docs'}
|
||||||
|
|
||||||
|
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
|
||||||
|
|
||||||
|
Then create some HTML to try it.
|
||||||
|
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html' region='myUnless'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/structural-directives/unless-anim.gif' alt="UnlessDirective in action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a summary}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
You can both try and download the source code for this guide in the <live-example></live-example>.
|
||||||
|
|
||||||
|
Here is the source from the `app/` folder.
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="app.component.ts">
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.component.html">
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.component.css">
|
||||||
|
{@example 'structural-directives/ts/src/app/app.component.css'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="app.module.ts">
|
||||||
|
{@example 'structural-directives/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="hero.ts">
|
||||||
|
{@example 'structural-directives/ts/src/app/hero.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="hero-switch.components.ts">
|
||||||
|
{@example 'structural-directives/ts/src/app/hero-switch.components.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="unless.directive.ts">
|
||||||
|
{@example 'structural-directives/ts/src/app/unless.directive.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
You learned
|
||||||
|
* that structural directives manipulate HTML layout.
|
||||||
|
* to use [`<ng-container>`](#ngcontainer) as a grouping element when there is no suitable host element.
|
||||||
|
* that the angular "de-sugars" [asterisk (\*) syntax](#asterisk) into a `<template>`.
|
||||||
|
* how that works for the `NgIf`, `NgFor` and `NgSwitch` built-in directives.
|
||||||
|
* about the [_microsyntax_](#microsyntax) that expands into a [`<template>`](#template).
|
||||||
|
* to write a [custom structural directive](#unless), `UnlessDirective`.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,256 @@
|
||||||
|
@title
|
||||||
|
User Input
|
||||||
|
|
||||||
|
@intro
|
||||||
|
User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models.
|
||||||
|
|
||||||
|
@description
|
||||||
|
User actions such as clicking a link, pushing a button, and entering
|
||||||
|
text raise DOM events.
|
||||||
|
This page explains how to bind those events to component event handlers using the Angular
|
||||||
|
event binding syntax.
|
||||||
|
|
||||||
|
Run the <live-example></live-example>.
|
||||||
|
## Binding to user input events
|
||||||
|
|
||||||
|
You can use [Angular event bindings](./template-syntax.html#event-binding)
|
||||||
|
to respond to any [DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
|
||||||
|
Many DOM events are triggered by user input. Binding to these events provides a way to
|
||||||
|
get input from the user.
|
||||||
|
|
||||||
|
To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted
|
||||||
|
[template statement](./template-syntax.html#template-statements) to it.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
<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
|
||||||
|
is the **template statement**, which responds
|
||||||
|
to the click event by calling the component's `onClickMe` method.
|
||||||
|
|
||||||
|
When writing a binding, be aware of a template statement's **execution context**.
|
||||||
|
The identifiers in a template statement belong to a specific context object,
|
||||||
|
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'}
|
||||||
|
|
||||||
|
When the user clicks the button, Angular calls the `onClickMe` method from `ClickMeComponent`.
|
||||||
|
|
||||||
|
## Get user input from the $event object
|
||||||
|
DOM events carry a payload of information that may be useful to the component.
|
||||||
|
This section shows how to bind to the `keyup` event of an input box to get the user's input after each keystroke.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
All [standard DOM event objects](https://developer.mozilla.org/en-US/docs/Web/API/Event)
|
||||||
|
have a `target` property, a reference to the element that raised the event.
|
||||||
|
In this case, `target` refers to the [`<input>` element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) and
|
||||||
|
`event.target.value` returns the current contents of that element.
|
||||||
|
|
||||||
|
After each call, the `onKey()` method appends the contents of the input box value to the list
|
||||||
|
in the component's `values` property, followed by a separator character (|).
|
||||||
|
The [interpolation](./template-syntax.html#interpolation)
|
||||||
|
displays the accumulating input box changes from the `values` property.
|
||||||
|
|
||||||
|
Suppose the user enters the letters "abc", and then backspaces to remove them one by one.
|
||||||
|
Here's what the UI displays:
|
||||||
|
<code-example>
|
||||||
|
a | ab | abc | ab | a | |
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
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>
|
||||||
|
a | b | c | backspace | backspace | backspace |
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a keyup1}
|
||||||
|
### Type the _$event_
|
||||||
|
|
||||||
|
The example above casts the `$event` as an `any` type.
|
||||||
|
That simplifies the code at a cost.
|
||||||
|
There is no type information
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The `$event` is now a specific `KeyboardEvent`.
|
||||||
|
Not all elements have a `value` property so it casts `target` to an input element.
|
||||||
|
The `OnKey` method more clearly expresses what it expects from the template and how it interprets the event.
|
||||||
|
|
||||||
|
### Passing _$event_ is a dubious practice
|
||||||
|
Typing the event object reveals a significant objection to passing the entire DOM event into the method:
|
||||||
|
the component has too much awareness of the template details.
|
||||||
|
It can't extract information without knowing more than it should about the HTML implementation.
|
||||||
|
That breaks the separation of concerns between the template (_what the user sees_)
|
||||||
|
and the component (_how the application processes user data_).
|
||||||
|
|
||||||
|
The next section shows how to use template reference variables to address this problem.
|
||||||
|
|
||||||
|
## Get user input from a template reference variable
|
||||||
|
There's another way to get the user data: use Angular
|
||||||
|
[**template reference variables**](./template-syntax.html#ref-vars).
|
||||||
|
These variables provide direct access to an element from within the template.
|
||||||
|
To declare a template reference variable, precede an identifier with a hash (or pound) character (#).
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
The template reference variable named `box`, declared on the `<input>` element,
|
||||||
|
refers to the `<input>` element itself.
|
||||||
|
The code uses the `box` variable to get the input element's `value` and display it
|
||||||
|
with interpolation between `<p>` tags.
|
||||||
|
|
||||||
|
The template is completely self contained. It doesn't bind to the component,
|
||||||
|
and the component does nothing.
|
||||||
|
|
||||||
|
Type something in the input box, and watch the display update with each keystroke.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
**This won't work at all unless you bind to an event**.
|
||||||
|
|
||||||
|
Angular updates the bindings (and therefore the screen)
|
||||||
|
only if the app does something in response to asynchronous events, such as keystrokes.
|
||||||
|
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
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
<a id="key-event"></a>
|
||||||
|
## Key event filtering (with `key.enter`)
|
||||||
|
The `(keyup)` event handler hears *every keystroke*.
|
||||||
|
Sometimes only the _Enter_ key matters, because it signals that the user has finished typing.
|
||||||
|
One way to reduce the noise would be to examine every `$event.keyCode` and take action only when the key is _Enter_.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Here's how it works.
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## On blur
|
||||||
|
|
||||||
|
In the previous example, the current state of the input box
|
||||||
|
is lost if the user mouses away and clicks elsewhere on the page
|
||||||
|
without first pressing _Enter_.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
## Put it all together
|
||||||
|
The previous page showed how to [display data](./displaying-data.html).
|
||||||
|
This page demonstrated event binding techniques.
|
||||||
|
|
||||||
|
Now, put it all together in a micro-app
|
||||||
|
that can display a list of heroes and add new heroes to the list.
|
||||||
|
The user can add a hero by typing the hero's name in the input box and
|
||||||
|
clicking **Add**.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Below is the "Little Tour of Heroes" component.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'user-input/ts/src/app/little-tour.component.ts' region='little-tour'}
|
||||||
|
|
||||||
|
### Observations
|
||||||
|
|
||||||
|
* **Use template variables to refer to elements** —
|
||||||
|
The `newHero` template variable refers to the `<input>` element.
|
||||||
|
You can reference `newHero` from any sibling or child of the `<input>` element.
|
||||||
|
|
||||||
|
* **Pass values, not elements** —
|
||||||
|
Instead of passing the `newHero` into the component's `addHero` method,
|
||||||
|
get the input box value and pass *that* to `addHero`.
|
||||||
|
|
||||||
|
* **Keep template statements simple** —
|
||||||
|
The `(blur)` event is bound to two JavaScript statements.
|
||||||
|
The first statement calls `addHero`. The second statement, `newHero.value=''`,
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="keyup.components.ts">
|
||||||
|
{@example 'user-input/ts/src/app/keyup.components.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="loop-back.component.ts">
|
||||||
|
{@example 'user-input/ts/src/app/loop-back.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="little-tour.component.ts">
|
||||||
|
{@example 'user-input/ts/src/app/little-tour.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
You have mastered the basic primitives for responding to user input and gestures.
|
||||||
|
|
||||||
|
These techniques are useful for small-scale demonstrations, but they
|
||||||
|
quickly become verbose and clumsy when handling large amounts of user input.
|
||||||
|
Two-way data binding is a more elegant and compact way to move
|
||||||
|
values between data entry fields and model properties.
|
||||||
|
The next page, `Forms`, explains how to write
|
||||||
|
two-way bindings with `NgModel`.
|
|
@ -0,0 +1,572 @@
|
||||||
|
@title
|
||||||
|
Webpack: an introduction
|
||||||
|
|
||||||
|
@intro
|
||||||
|
Create Angular applications with a Webpack based tooling
|
||||||
|
|
||||||
|
@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%; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
[**Webpack**](https://webpack.github.io/) is a popular module bundler,
|
||||||
|
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?](#what-is-webpack)
|
||||||
|
|
||||||
|
* [Entries and outputs](#entries-outputs)
|
||||||
|
* [Loaders](#loaders)
|
||||||
|
* [Plugins](#plugins)
|
||||||
|
|
||||||
|
[Configuring Webpack](#configure-webpack)
|
||||||
|
|
||||||
|
* [Common configuration](#common-configuration)
|
||||||
|
* [Development configuration](#development-configuration)
|
||||||
|
* [Production configuration](#production-configuration)
|
||||||
|
* [Test configuration](#test-configuration)
|
||||||
|
|
||||||
|
[Trying it out](#try)
|
||||||
|
|
||||||
|
[Conclusions](#conclusions)
|
||||||
|
|
||||||
|
<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
|
||||||
|
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.
|
||||||
|
|
||||||
|
You determine what Webpack does and how it does it with a JavaScript configuration file, `webpack.config.js`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a entries-outputs}
|
||||||
|
|
||||||
|
### 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`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='one-entry'}
|
||||||
|
|
||||||
|
Webpack inspects that file and traverses its `import` dependencies recursively.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='app-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 `app.ts` down.
|
||||||
|
|
||||||
|
Then it **outputs** these files to the `app.js` _bundle file_ designated in configuration:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='one-output'}
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
#### 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`:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='two-entries'}
|
||||||
|
|
||||||
|
Webpack constructs two separate dependency graphs
|
||||||
|
and emits *two* bundle files, one called `app.js` containing only the application code and
|
||||||
|
another called `vendor.js` with all the vendor dependencies.
|
||||||
|
|
||||||
|
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](#commons-chunk-plugin) in the guide.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a loaders}
|
||||||
|
|
||||||
|
### Loaders
|
||||||
|
|
||||||
|
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*.
|
||||||
|
Configure loaders for TypeScript and CSS as follows.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='loaders'}
|
||||||
|
|
||||||
|
As Webpack encounters `import` statements like these ...
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='imports'}
|
||||||
|
|
||||||
|
... it applies the `test` RegEx patterns. 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 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).
|
||||||
|
|
||||||
|
|
||||||
|
{@a plugins}
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
Webpack has a build pipeline with well-defined phases.
|
||||||
|
Tap into that pipeline with plugins such as the `uglify` minification plugin:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='plugins'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a configure-webpack}
|
||||||
|
|
||||||
|
## Configure Webpack
|
||||||
|
|
||||||
|
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**
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
mkdir angular-webpack
|
||||||
|
cd angular-webpack
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Add these files to the root directory:
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="package.json">
|
||||||
|
{@example 'webpack/ts/package.webpack.json'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="tsconfig.json">
|
||||||
|
{@example 'webpack/ts/tsconfig.1.json'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="webpack.config.js">
|
||||||
|
{@example 'webpack/ts/webpack.config.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="karma.conf.js">
|
||||||
|
{@example 'webpack/ts/karma.webpack.conf.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="config/helpers.js">
|
||||||
|
{@example 'webpack/ts/config/helpers.js'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
Many of these files should be familiar from other Angular documentation guides,
|
||||||
|
especially the [_Typescript configuration_](../guide/typescript-configuration.html) and
|
||||||
|
[_npm packages_](../guide/npm-packages.html) guides.
|
||||||
|
|
||||||
|
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
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm install
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a polyfills}
|
||||||
|
|
||||||
|
### Polyfills
|
||||||
|
|
||||||
|
You'll need polyfills to run an Angular application in most browsers as explained
|
||||||
|
in the [_Browser Support_](browser-support.html) 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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.callout.is-critical}
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
Loading polyfills
|
||||||
|
</header>
|
||||||
|
|
||||||
|
Load `zone.js` early within `polyfills.ts`, immediately after the other ES6 and metadata shims.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Developers typically have separate configurations for development, production, and test environments.
|
||||||
|
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'}
|
||||||
|
|
||||||
|
### Inside _webpack.common.js_
|
||||||
|
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`](#common-entries) - the entry-point files that define the bundles.
|
||||||
|
* [`resolve`](#common-resolve) - how to resolve file names when they lack extensions.
|
||||||
|
* [`module.rules`](#common-rules) - `module` is an object with `rules` for deciding how files are loaded.
|
||||||
|
* [`plugins`](#common-plugins) - creates instances of the plugins.
|
||||||
|
|
||||||
|
|
||||||
|
{@a common-entries}
|
||||||
|
#### _entries_
|
||||||
|
|
||||||
|
The first export is the *entries* object, described above:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/webpack.common.js' region='entries'}
|
||||||
|
|
||||||
|
This *entries* 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.
|
||||||
|
You could write `import` statements with explicit extensions like this example:
|
||||||
|
|
||||||
|
{@example 'webpack/ts-snippets/webpack.config.snippets.ts' region='single-import'}
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
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_):
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/webpack.common.js' region='loaders'}
|
||||||
|
|
||||||
|
* 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 excludes `.css` files within the `/src/app` directories where the component-scoped styles sit.
|
||||||
|
It includes only `.css` files located at or above `/src`; these are the application-wide styles.
|
||||||
|
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 —
|
||||||
|
which is what Angular expects to do with styles specified in a `styleUrls` metadata property.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a commons-chunk-plugin}
|
||||||
|
#### *CommonsChunkPlugin*
|
||||||
|
|
||||||
|
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`.
|
||||||
|
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).
|
||||||
|
|
||||||
|
|
||||||
|
{@a html-webpack-plugin}
|
||||||
|
#### *HtmlWebpackPlugin*
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
|
||||||
|
{@a environment-configuration}
|
||||||
|
|
||||||
|
### Environment-specific configuration
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
These files tend to be short and simple.
|
||||||
|
|
||||||
|
|
||||||
|
{@a development-configuration}
|
||||||
|
|
||||||
|
### Development Configuration
|
||||||
|
|
||||||
|
Here is the `webpack.dev.js` development configuration file.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/webpack.dev.js'}
|
||||||
|
|
||||||
|
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`).
|
||||||
|
|
||||||
|
|
||||||
|
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 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`.
|
||||||
|
|
||||||
|
Refer to the Webpack documentation for details on these and other configuration options in this file
|
||||||
|
|
||||||
|
Grab the app code at the end of this guide and try:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm start
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a production-configuration}
|
||||||
|
|
||||||
|
### Production Configuration
|
||||||
|
|
||||||
|
Configuration of a *production* build resembles *development* configuration ... with a few key changes.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/webpack.prod.js'}
|
||||||
|
|
||||||
|
You'll deploy the application and its dependencies to a real production server.
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
Grab the app code at the end of this guide and try:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a test-configuration}
|
||||||
|
|
||||||
|
### Test Configuration
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You could merge the test configuration into the `webpack.common` configuration and override the parts you don't want or need.
|
||||||
|
But it might be simpler to start over with a completely fresh configuration.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/webpack.test.js'}
|
||||||
|
|
||||||
|
Reconfigure karma to use webpack to run the tests:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'webpack/ts/config/karma.conf.js'}
|
||||||
|
|
||||||
|
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
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm test
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<a id="try"></a>## Trying it out
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/main.ts">
|
||||||
|
{@example 'webpack/ts/src/main.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="public/css/styles.css">
|
||||||
|
{@example 'webpack/ts/public/css/styles.css'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'webpack/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.html">
|
||||||
|
{@example 'webpack/ts/src/app/app.component.html'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.css">
|
||||||
|
{@example 'webpack/ts/src/app/app.component.css'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.spec.ts">
|
||||||
|
{@example 'webpack/ts/src/app/app.component.spec.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.module.ts">
|
||||||
|
{@example 'webpack/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>app.component.html</code> displays this downloadable Angular logo
|
||||||
|
<a href="https://raw.githubusercontent.com/angular/angular.io/master/public/resources/images/logos/angular2/angular.png" target="_blank">
|
||||||
|
<img src="/resources/images/logos/angular2/angular.png" height="40px" title="download Angular logo"></a>.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@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>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/vendor.ts">
|
||||||
|
{@example 'webpack/ts/src/vendor.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
<a id="highlights"></a>### Highlights:
|
||||||
|
|
||||||
|
* 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()`.
|
||||||
|
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.
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
[Back to top](#top)
|
|
@ -0,0 +1,82 @@
|
||||||
|
@title
|
||||||
|
Tutorial: Tour of Heroes
|
||||||
|
|
||||||
|
@intro
|
||||||
|
The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.
|
||||||
|
|
||||||
|
@description
|
||||||
|
Our grand plan for this tutorial is to build an app to help a staffing agency manage its stable of heroes.
|
||||||
|
Even heroes need to find work.
|
||||||
|
|
||||||
|
Of course we'll only make a little progress in this tutorial. What we do build will
|
||||||
|
have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying
|
||||||
|
a list of heroes, editing a selected hero's detail, and navigating among different
|
||||||
|
views of heroic data.
|
||||||
|
|
||||||
|
The Tour of Heroes covers the core fundamentals of Angular.
|
||||||
|
We’ll use built-in directives to show/hide elements and display lists of hero data.
|
||||||
|
We’ll create a component to display hero details and another to show an array of heroes.
|
||||||
|
We'll use one-way data binding for read-only data. We'll add editable fields to update a model
|
||||||
|
with two-way data binding. We'll bind component methods to user events like key strokes and clicks.
|
||||||
|
We’ll learn to select a hero from a master list and edit that hero in the details view. We'll
|
||||||
|
format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components.
|
||||||
|
|
||||||
|
We’ll learn enough core Angular to get started and gain confidence that
|
||||||
|
Angular can do whatever we need it to do.
|
||||||
|
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
||||||
|
to chapters with greater depth.
|
||||||
|
|
||||||
|
Run the <live-example name="toh-6"></live-example>.
|
||||||
|
|
||||||
|
## The End Game
|
||||||
|
|
||||||
|
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||||
|
view and our most heroic heroes:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Above the dashboard are two links ("Dashboard" and "Heroes").
|
||||||
|
We could click them to navigate between this Dashboard and a Heroes view.
|
||||||
|
|
||||||
|
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
||||||
|
of that hero where we can change the hero's name.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Clicking the "Back" button would return us to the "Dashboard".
|
||||||
|
Links at the top can take us to either of the main views.
|
||||||
|
We'll click "Heroes". The app takes to the "Heroes" master list view.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
||||||
|
|
||||||
|
We click the "View Details" button to drill into the
|
||||||
|
editable details of our selected hero.
|
||||||
|
|
||||||
|
The following diagram captures all of our navigation options.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Here's our app in action
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## Up Next
|
||||||
|
|
||||||
|
We’ll build this Tour of Heroes together, step by step.
|
||||||
|
We'll motivate each step with a requirement that we've
|
||||||
|
met in countless applications. Everything has a reason.
|
||||||
|
|
||||||
|
And we’ll meet many of the core fundamentals of Angular along the way.
|
|
@ -0,0 +1,229 @@
|
||||||
|
@title
|
||||||
|
The Hero Editor
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We build a simple hero editor
|
||||||
|
|
||||||
|
@description
|
||||||
|
## Setup to develop locally
|
||||||
|
Real application development takes place in a local development environment like your machine.
|
||||||
|
|
||||||
|
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
||||||
|
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>
|
||||||
|
after which the file structure should look like this:
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<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-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
When we're done with this first episode, the app runs like this <live-example></live-example>.
|
||||||
|
|
||||||
|
## Keep the app transpiling and running
|
||||||
|
We want to start the TypeScript compiler, have it watch for changes, and start our server.
|
||||||
|
Do this by entering the following command in the terminal window.
|
||||||
|
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm start
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
This command runs the compiler in watch mode, starts the server, launches the app in a browser,
|
||||||
|
and keeps the app running while we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
## Show our Hero
|
||||||
|
We want to display Hero data in our app
|
||||||
|
|
||||||
|
Update the `AppComponent` so it has two properties: a `title` property for the application name and a `hero` property
|
||||||
|
for a hero named "Windstorm".
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='app-component-1'}
|
||||||
|
|
||||||
|
Now update the template in the `@Component` decoration with data bindings to these new properties.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero'}
|
||||||
|
|
||||||
|
The browser should refresh and display our title and hero.
|
||||||
|
|
||||||
|
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
|
||||||
|
This is the "interpolation" form of one-way data binding.
|
||||||
|
Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html).### Hero object
|
||||||
|
|
||||||
|
At the moment, our hero is just a name. Our hero needs more properties.
|
||||||
|
Let's convert the `hero` from a literal string to a class.
|
||||||
|
|
||||||
|
Create a `Hero` class with `id` and `name` properties.
|
||||||
|
For now put this near the top of the `app.component.ts` file, just below the import statement.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts/src/app/app.component.ts' region='hero-class-1'}
|
||||||
|
|
||||||
|
Now that we have a `Hero` class, let’s refactor our component’s `hero` property to be of type `Hero`.
|
||||||
|
Then initialize it with an id of `1` and the name, "Windstorm".
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts/src/app/app.component.ts' region='hero-property-1'}
|
||||||
|
|
||||||
|
Because we changed the hero from a string to an object,
|
||||||
|
we update the binding in the template to refer to the hero’s `name` property.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero-2'}
|
||||||
|
|
||||||
|
The browser refreshes and continues to display our hero’s name.
|
||||||
|
|
||||||
|
### Adding more HTML
|
||||||
|
Displaying a name is good, but we want to see all of our hero’s properties.
|
||||||
|
We’ll add a `<div>` for our hero’s `id` property and another `<div>` for our hero’s `name`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero-properties'}
|
||||||
|
|
||||||
|
Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template.
|
||||||
|
|
||||||
|
### Multi-line template strings
|
||||||
|
|
||||||
|
We could make a more readable template with string concatenation
|
||||||
|
but that gets ugly fast, it is harder to read, and
|
||||||
|
it is easy to make a spelling error. Instead,
|
||||||
|
let’s take advantage of the template strings feature
|
||||||
|
in ES2015 and TypeScript to maintain our sanity.
|
||||||
|
|
||||||
|
Change the quotes around the template to back-ticks and
|
||||||
|
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='multi-line-strings'}
|
||||||
|
|
||||||
|
|
||||||
|
## Editing Our Hero
|
||||||
|
|
||||||
|
We want to be able to edit the hero name in a textbox.
|
||||||
|
|
||||||
|
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='editing-Hero'}
|
||||||
|
|
||||||
|
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
||||||
|
But something doesn’t feel right.
|
||||||
|
When we change the name, we notice that our change
|
||||||
|
is not reflected in the `<h2>`. We won't get the desired behavior
|
||||||
|
with a one-way binding to `<input>`.
|
||||||
|
|
||||||
|
### Two-Way Binding
|
||||||
|
|
||||||
|
We intend to display the name of the hero in the `<input>`, change it,
|
||||||
|
and see those changes wherever we bind to the hero’s name.
|
||||||
|
In short, we want two-way data binding.
|
||||||
|
|
||||||
|
Before we can use two-way data binding for **form inputs**, we need to import the `FormsModule`
|
||||||
|
package in our Angular module. We add it to the `NgModule` decorator's `imports` array. This array contains the list
|
||||||
|
of external modules used by our application.
|
||||||
|
Now we have included the forms package which includes `ngModel`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts/src/app/app.module.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
Learn more about the `FormsModule` and `ngModel` in the
|
||||||
|
[Forms](../guide/forms.html#ngModel) and
|
||||||
|
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
|
||||||
|
Let’s update the template to use the **`ngModel`** built-in directive for two-way binding.
|
||||||
|
|
||||||
|
Replace the `<input>` with the following HTML
|
||||||
|
|
||||||
|
<code-example language="html">
|
||||||
|
<input [(ngModel)]="hero.name" placeholder="name">
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The browser refreshes. We see our hero again. We can edit the hero’s name and
|
||||||
|
see the changes reflected immediately in the `<h2>`.
|
||||||
|
|
||||||
|
## The Road We’ve Travelled
|
||||||
|
Let’s take stock of what we’ve built.
|
||||||
|
|
||||||
|
* Our Tour of Heroes uses the double curly braces of interpolation (a kind of one-way data binding)
|
||||||
|
to display the application title and properties of a `Hero` object.
|
||||||
|
* We wrote a multi-line template using ES2015’s template strings to make our template readable.
|
||||||
|
* We can both display and change the hero’s name after adding a two-way data binding to the `<input>` element
|
||||||
|
using the built-in `ngModel` directive.
|
||||||
|
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
Here's the complete `app.component.ts` as it stands now:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-1/ts/src/app/app.component.ts' region='pt1'}
|
||||||
|
|
||||||
|
|
||||||
|
## The Road Ahead
|
||||||
|
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
|
||||||
|
We also want to allow the user to select a hero and display their details.
|
||||||
|
We’ll learn more about how to retrieve lists, bind them to the
|
||||||
|
template, and allow a user to select a hero in the
|
||||||
|
[next tutorial chapter](./toh-pt2.html).
|
|
@ -0,0 +1,356 @@
|
||||||
|
@title
|
||||||
|
Master/Detail
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We build a master/detail page with a list of heroes
|
||||||
|
|
||||||
|
@description
|
||||||
|
Our story needs more heroes.
|
||||||
|
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||||
|
allow the user to select a hero, and display the hero’s details.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
Let’s take stock of what we’ll need to display a list of heroes.
|
||||||
|
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||||
|
so we’ll need a way to do that.
|
||||||
|
|
||||||
|
## Where We Left Off
|
||||||
|
Before we continue with Part 2 of the Tour of Heroes,
|
||||||
|
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
||||||
|
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<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-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
### Keep the app transpiling and running
|
||||||
|
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm start
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
This will keep the application running while we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
## Displaying Our Heroes
|
||||||
|
### Creating heroes
|
||||||
|
Let’s create an array of ten heroes.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts/src/app/app.component.ts' region='hero-array'}
|
||||||
|
|
||||||
|
The `HEROES` array is of type `Hero`, the class defined in part one,
|
||||||
|
to create an array of heroes.
|
||||||
|
We aspire to fetch this list of heroes from a web service, but let’s take small steps
|
||||||
|
first and display mock heroes.
|
||||||
|
|
||||||
|
### Exposing heroes
|
||||||
|
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='hero-array-1'}
|
||||||
|
|
||||||
|
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||||
|
We could have defined the heroes list here in this component class.
|
||||||
|
But we know that ultimately we’ll get the heroes from a data service.
|
||||||
|
Because we know where we are heading, it makes sense to separate the hero data
|
||||||
|
from the class implementation from the start.### Displaying heroes in a template
|
||||||
|
Our component has `heroes`. Let’s create an unordered list in our template to display them.
|
||||||
|
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-template-1'}
|
||||||
|
|
||||||
|
Now we have a template that we can fill with our heroes.
|
||||||
|
|
||||||
|
### Listing heroes with ngFor
|
||||||
|
|
||||||
|
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
||||||
|
and display them individually.
|
||||||
|
We’ll need some help from Angular to do this. Let’s do this step by step.
|
||||||
|
|
||||||
|
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-ngfor-1'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-critical}
|
||||||
|
|
||||||
|
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
|
||||||
|
constitute a master template.
|
||||||
|
|
||||||
|
The `ngFor` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||||
|
and stamps out instances of this template.
|
||||||
|
|
||||||
|
The quoted text assigned to `ngFor` means
|
||||||
|
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
||||||
|
and make it available to the corresponding template instance*”.
|
||||||
|
|
||||||
|
The `let` keyword before "hero" identifies `hero` as a template input variable.
|
||||||
|
We can reference this variable within the template to access a hero’s properties.
|
||||||
|
|
||||||
|
Learn more about `ngFor` and template input variables in the
|
||||||
|
[Displaying Data](../guide/displaying-data.html#ngFor) and
|
||||||
|
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
|
||||||
|
Now we insert some content between the `<li>` tags
|
||||||
|
that uses the `hero` template variable to display the hero’s properties.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-for'}
|
||||||
|
|
||||||
|
When the browser refreshes, we see a list of heroes!
|
||||||
|
|
||||||
|
### Styling our heroes
|
||||||
|
Our list of heroes looks pretty bland.
|
||||||
|
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
|
||||||
|
|
||||||
|
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
||||||
|
to the following CSS classes:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts/src/app/app.component.ts' region='styles'}
|
||||||
|
|
||||||
|
Notice that we again use the back-tick notation for multi-line strings.
|
||||||
|
|
||||||
|
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
|
||||||
|
We'll do this in a later chapter. For now let's keep rolling.
|
||||||
|
|
||||||
|
When we assign styles to a component they are scoped to that specific component.
|
||||||
|
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
|
||||||
|
|
||||||
|
Our template for displaying the heroes should now look like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-styled'}
|
||||||
|
|
||||||
|
|
||||||
|
## Selecting a Hero
|
||||||
|
We have a list of heroes and we have a single hero displayed in our app.
|
||||||
|
The list and the single hero are not connected in any way.
|
||||||
|
We want the user to select a hero from our list, and have the selected hero appear in the details view.
|
||||||
|
This UI pattern is widely known as "master-detail".
|
||||||
|
In our case, the master is the heroes list and the detail is the selected hero.
|
||||||
|
|
||||||
|
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||||
|
|
||||||
|
### Click event
|
||||||
|
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-click'}
|
||||||
|
|
||||||
|
Focus on the event binding
|
||||||
|
<code-example>
|
||||||
|
(click)="onSelect(hero)"
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The parenthesis identify the `<li>` element’s `click` event as the target.
|
||||||
|
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||||
|
passing the template input variable `hero` as an argument.
|
||||||
|
That’s the same `hero` variable we defined previously in the `ngFor`.
|
||||||
|
Learn more about Event Binding in the
|
||||||
|
[User Input](../guide/user-input.html) and
|
||||||
|
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.### Add the click handler
|
||||||
|
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||||
|
We’ll add that method to our component now.
|
||||||
|
|
||||||
|
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
||||||
|
|
||||||
|
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||||
|
|
||||||
|
### Expose the selected hero
|
||||||
|
|
||||||
|
We no longer need the static `hero` property of the `AppComponent`.
|
||||||
|
**Replace** it with this simple `selectedHero` property:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts/src/app/app.component.ts' region='selected-hero'}
|
||||||
|
|
||||||
|
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||||
|
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
||||||
|
|
||||||
|
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||||
|
|
||||||
|
{@example 'toh-2/ts/src/app/app.component.ts' region='on-select'}
|
||||||
|
|
||||||
|
We will be showing the selected hero's details in our template.
|
||||||
|
At the moment, it is still referring to the old `hero` property.
|
||||||
|
Let’s fix the template to bind to the new `selectedHero` property.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-details'}
|
||||||
|
|
||||||
|
### Hide the empty detail with ngIf
|
||||||
|
|
||||||
|
When our app loads we see a list of heroes, but a hero is not selected.
|
||||||
|
The `selectedHero` is `undefined`.
|
||||||
|
That’s why we'll see the following error in the browser’s console:
|
||||||
|
|
||||||
|
<code-example format="nocode">
|
||||||
|
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Remember that we are displaying `selectedHero.name` in the template.
|
||||||
|
This name property does not exist because `selectedHero` itself is undefined.
|
||||||
|
|
||||||
|
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
|
||||||
|
|
||||||
|
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||||
|
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-if'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-critical}
|
||||||
|
|
||||||
|
Remember that the leading asterisk (`*`) in front of `ngIf` is
|
||||||
|
a critical part of this syntax.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||||
|
There will be no hero detail elements and no bindings to worry about.
|
||||||
|
|
||||||
|
When the user picks a hero, `selectedHero` becomes "truthy" and
|
||||||
|
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||||
|
`ngIf` and `ngFor` are called “structural directives” because they can change the
|
||||||
|
structure of portions of the DOM.
|
||||||
|
In other words, they give structure to the way Angular displays content in the DOM.
|
||||||
|
|
||||||
|
Learn more about `ngIf`, `ngFor` and other structural directives in the
|
||||||
|
[Structural Directives](../guide/structural-directives.html) and
|
||||||
|
[Template Syntax](../guide/template-syntax.html#directives) chapters.
|
||||||
|
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||||
|
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
|
||||||
|
When we click on a hero in the list, the selected hero displays in the hero details.
|
||||||
|
Everything is working as we expect.
|
||||||
|
|
||||||
|
### Styling the selection
|
||||||
|
|
||||||
|
We see the selected hero in the details area below but we can’t quickly locate that hero in the list above.
|
||||||
|
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
|
||||||
|
For example, when we select Magneta from the heroes list,
|
||||||
|
we can make it pop out visually by giving it a subtle background color as shown here.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We’ll add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
|
||||||
|
|
||||||
|
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||||
|
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-1'}
|
||||||
|
|
||||||
|
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
|
||||||
|
This is the syntax for a **property binding**, a binding in which data flows one way
|
||||||
|
from the data source (the expression `hero === selectedHero`) to a property of `class`.
|
||||||
|
|
||||||
|
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-2'}
|
||||||
|
|
||||||
|
|
||||||
|
Learn more about [property bindings](../guide/template-syntax.html#property-binding)
|
||||||
|
in the Template Syntax chapter.
|
||||||
|
The browser reloads our app.
|
||||||
|
We select the hero Magneta and the selection is clearly identified by the background color.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We select a different hero and the tell-tale color switches to that hero.
|
||||||
|
|
||||||
|
Here's the complete `app.component.ts` as it stands now:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-2/ts/src/app/app.component.ts'}
|
||||||
|
|
||||||
|
|
||||||
|
## The Road We’ve Travelled
|
||||||
|
Here’s what we achieved in this chapter:
|
||||||
|
|
||||||
|
* Our Tour of Heroes now displays a list of selectable heroes
|
||||||
|
* We added the ability to select a hero and show the hero’s details
|
||||||
|
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
### The Road Ahead
|
||||||
|
Our Tour of Heroes has grown, but it’s far from complete.
|
||||||
|
We can't put the entire app into a single component.
|
||||||
|
We need to break it up into sub-components and teach them to work together
|
||||||
|
as we learn in the [next chapter](toh-pt3.html).
|
|
@ -0,0 +1,365 @@
|
||||||
|
@title
|
||||||
|
Multiple Components
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We refactor the master/detail view into separate components
|
||||||
|
|
||||||
|
@description
|
||||||
|
Our app is growing.
|
||||||
|
Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
## Where We Left Off
|
||||||
|
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<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-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
### Keep the app transpiling and running
|
||||||
|
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm start
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
This will keep the application running while we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
## Making a Hero Detail Component
|
||||||
|
Our heroes list and our hero details are in the same component in the same file.
|
||||||
|
They're small now but each could grow.
|
||||||
|
We are sure to receive new requirements for one and not the other.
|
||||||
|
Yet every change puts both components at risk and doubles the testing burden without benefit.
|
||||||
|
If we had to reuse the hero details elsewhere in our app,
|
||||||
|
the heroes list would tag along for the ride.
|
||||||
|
|
||||||
|
Our current component violates the
|
||||||
|
[Single Responsibility Principle](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
|
||||||
|
It's only a tutorial but we can still do things right —
|
||||||
|
especially if doing them right is easy and we learn how to build Angular apps in the process.
|
||||||
|
|
||||||
|
Let’s break the hero details out into its own component.
|
||||||
|
|
||||||
|
### Separating the Hero Detail Component
|
||||||
|
Add a new file named `hero-detail.component.ts` to the `app` folder and create `HeroDetailComponent` as follows.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='v1'}
|
||||||
|
|
||||||
|
|
||||||
|
### Naming conventions
|
||||||
|
We like to identify at a glance which classes are components and which files contain components.
|
||||||
|
|
||||||
|
Notice that we have an `AppComponent` in a file named `app.component.ts` and our new
|
||||||
|
`HeroDetailComponent` is in a file named `hero-detail.component.ts`.
|
||||||
|
|
||||||
|
All of our component names end in "Component". All of our component file names end in ".component".
|
||||||
|
|
||||||
|
We spell our file names in lower **[dash case](../guide/glossary.html#dash-case)**
|
||||||
|
(AKA **[kebab-case](../guide/glossary.html#kebab-case)**) so we don't worry about
|
||||||
|
case sensitivity on the server or in source control.
|
||||||
|
|
||||||
|
<!-- TODO
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Learn more about naming conventions in the chapter [Naming Conventions]
|
||||||
|
:marked
|
||||||
|
-->We begin by importing the `Component` and `Input` decorators from Angular because we're going to need them soon.
|
||||||
|
|
||||||
|
We create metadata with the `@Component` decorator where we
|
||||||
|
specify the selector name that identifies this component's element.
|
||||||
|
Then we export the class to make it available to other components.
|
||||||
|
|
||||||
|
When we finish here, we'll import it into `AppComponent` and create a corresponding `<my-hero-detail>` element.#### Hero Detail Template
|
||||||
|
At the moment, the *Heroes* and *Hero Detail* views are combined in one template in `AppComponent`.
|
||||||
|
Let’s **cut** the *Hero Detail* content from `AppComponent` and **paste** it into the new template property of `HeroDetailComponent`.
|
||||||
|
|
||||||
|
We previously bound to the `selectedHero.name` property of the `AppComponent`.
|
||||||
|
Our `HeroDetailComponent` will have a `hero` property, not a `selectedHero` property.
|
||||||
|
So we replace `selectedHero` with `hero` everywhere in our new template. That's our only change.
|
||||||
|
The result looks like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='template'}
|
||||||
|
|
||||||
|
Now our hero detail layout exists only in the `HeroDetailComponent`.
|
||||||
|
|
||||||
|
#### Add the *hero* property
|
||||||
|
Let’s add that `hero` property we were talking about to the component class.
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero'}
|
||||||
|
|
||||||
|
Uh oh. We declared the `hero` property as type `Hero` but our `Hero` class is over in the `app.component.ts` file.
|
||||||
|
We have two components, each in their own file, that need to reference the `Hero` class.
|
||||||
|
|
||||||
|
We solve the problem by relocating the `Hero` class from `app.component.ts` to its own `hero.ts` file.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero.ts'}
|
||||||
|
|
||||||
|
We export the `Hero` class from `hero.ts` because we'll need to reference it in both component files.
|
||||||
|
Add the following import statement near the top of **both `app.component.ts` and `hero-detail.component.ts`**.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero-import'}
|
||||||
|
|
||||||
|
#### The *hero* property is an ***input***
|
||||||
|
|
||||||
|
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
|
||||||
|
|
||||||
|
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
|
||||||
|
The user's selection is in its `selectedHero` property.
|
||||||
|
|
||||||
|
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
|
||||||
|
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
|
||||||
|
<code-example language="html">
|
||||||
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Notice that the `hero` property is the ***target*** of a property binding — it's in square brackets to the left of the (=).
|
||||||
|
|
||||||
|
Angular insists that we declare a ***target*** property to be an ***input*** property.
|
||||||
|
If we don't, Angular rejects the binding and throws an error.
|
||||||
|
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
|
||||||
|
where we also explain why *target* properties require this special treatment and
|
||||||
|
*source* properties do not.There are a couple of ways we can declare that `hero` is an *input*.
|
||||||
|
We'll do it the way we *prefer*, by annotating the `hero` property with the `@Input` decorator that we imported earlier.
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero-input'}
|
||||||
|
|
||||||
|
|
||||||
|
Learn more about the `@Input()` decorator in the
|
||||||
|
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
|
||||||
|
|
||||||
|
## Refresh the AppModule
|
||||||
|
We return to the `AppModule`, the application's root module, and teach it to use the `HeroDetailComponent`.
|
||||||
|
|
||||||
|
We begin by importing the `HeroDetailComponent` so we can refer to it.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/app.module.ts' region='hero-detail-import'}
|
||||||
|
|
||||||
|
Then we add `HeroDetailComponent` to the `NgModule` decorator's `declarations` array.
|
||||||
|
This array contains the list of all components, pipes, and directives that we created
|
||||||
|
and that belong in our application's module.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/app.module.ts' region='declarations'}
|
||||||
|
|
||||||
|
|
||||||
|
## Refresh the AppComponentNow that the application knows about our `HeroDetailComponent`,
|
||||||
|
find the location in the `AppComponent` template where we removed the *Hero Detail* content
|
||||||
|
and add an element tag that represents the `HeroDetailComponent`.
|
||||||
|
<code-example language="html">
|
||||||
|
<my-hero-detail></my-hero-detail>
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
|
||||||
|
to the `HeroDetailComponent` element's `hero` property like this:
|
||||||
|
<code-example language="html">
|
||||||
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The `AppComponent`’s template should now look like this
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-3/ts/src/app/app.component.ts' region='hero-detail-template'}
|
||||||
|
|
||||||
|
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
|
||||||
|
The detail should update every time the user picks a new hero.
|
||||||
|
### It works!
|
||||||
|
When we view our app in the browser we see the list of heroes.
|
||||||
|
When we select a hero we can see the selected hero’s details.
|
||||||
|
|
||||||
|
What's fundamentally new is that we can use this `HeroDetailComponent`
|
||||||
|
to show hero details anywhere in the app.
|
||||||
|
|
||||||
|
We’ve created our first reusable component!
|
||||||
|
|
||||||
|
### Reviewing the App Structure
|
||||||
|
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<aio-folder>
|
||||||
|
src
|
||||||
|
<aio-folder>
|
||||||
|
app
|
||||||
|
<aio-file>
|
||||||
|
app.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
app.module.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero-detail.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
main.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
Here are the code files we discussed in this chapter.
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/hero-detail.component.ts">
|
||||||
|
{@example 'toh-3/ts/src/app/hero-detail.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'toh-3/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/hero.ts">
|
||||||
|
{@example 'toh-3/ts/src/app/hero.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.module.ts">
|
||||||
|
{@example 'toh-3/ts/src/app/app.module.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
|
||||||
|
## The Road We’ve Travelled
|
||||||
|
Let’s take stock of what we’ve built.
|
||||||
|
|
||||||
|
* We created a reusable component
|
||||||
|
* We learned how to make a component accept input
|
||||||
|
* We learned to declare the application directives we need in an Angular module. We
|
||||||
|
list the directives in the `NgModule` decorator's `declarations` array.
|
||||||
|
* We learned to bind a parent component to a child component.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
## The Road Ahead
|
||||||
|
Our Tour of Heroes has become more reusable with shared components.
|
||||||
|
|
||||||
|
We're still getting our (mock) data within the `AppComponent`.
|
||||||
|
That's not sustainable.
|
||||||
|
We should refactor data access to a separate service
|
||||||
|
and share it among the components that need data.
|
||||||
|
|
||||||
|
We’ll learn to create services in the [next tutorial](toh-pt4.html) chapter.
|
|
@ -0,0 +1,473 @@
|
||||||
|
@title
|
||||||
|
Services
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We create a reusable service to manage our hero data calls
|
||||||
|
|
||||||
|
@description
|
||||||
|
The Tour of Heroes is evolving and we anticipate adding more components in the near future.
|
||||||
|
|
||||||
|
Multiple components will need access to hero data and we don't want to copy and
|
||||||
|
paste the same code over and over.
|
||||||
|
Instead, we'll create a single reusable data service and learn to
|
||||||
|
inject it in the components that need it.
|
||||||
|
|
||||||
|
Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
|
||||||
|
It also makes it easier to unit test the component with a mock service.
|
||||||
|
|
||||||
|
Because data services are invariably asynchronous,
|
||||||
|
we'll finish the chapter with a **!{_Promise}**-based version of the data service.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
## Where We Left Off
|
||||||
|
Before we continue with our Tour of Heroes, let’s verify we have the following structure.
|
||||||
|
If not, we’ll need to go back and follow the previous chapters.
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<aio-folder>
|
||||||
|
src
|
||||||
|
<aio-folder>
|
||||||
|
app
|
||||||
|
<aio-file>
|
||||||
|
app.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
app.module.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero-detail.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
main.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
### Keep the app transpiling and running
|
||||||
|
Open a terminal/console window.
|
||||||
|
Start the TypeScript compiler, watch for changes, and start our server by entering the command:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm start
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
## Creating a Hero Service
|
||||||
|
Our stakeholders have shared their larger vision for our app.
|
||||||
|
They tell us they want to show the heroes in various ways on different pages.
|
||||||
|
We already can select a hero from a list.
|
||||||
|
Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
|
||||||
|
All three views need hero data.
|
||||||
|
|
||||||
|
At the moment the `AppComponent` defines mock heroes for display.
|
||||||
|
We have at least two objections.
|
||||||
|
First, defining heroes is not the component's job.
|
||||||
|
Second, we can't easily share that list of heroes with other components and views.
|
||||||
|
|
||||||
|
We can refactor this hero data acquisition business to a single service that provides heroes, and
|
||||||
|
share that service with all components that need heroes.
|
||||||
|
|
||||||
|
### Create the HeroService
|
||||||
|
Create a file in the `app` folder called `hero.service.ts`.
|
||||||
|
We've adopted a convention in which we spell the name of a service in lowercase followed by `.service`.
|
||||||
|
If the service name were multi-word, we'd spell the base filename in lower [dash-case](../guide/glossary.html#dash-case).
|
||||||
|
The `SpecialSuperHeroService` would be defined in the `special-super-hero.service.ts` file.We name the class `HeroService` and export it for others to import.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='empty-class'}
|
||||||
|
|
||||||
|
### Injectable Services
|
||||||
|
Notice that we imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
|
||||||
|
|
||||||
|
~~~ {.callout.is-helpful}
|
||||||
|
|
||||||
|
**Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
TypeScript sees the `@Injectable()` decorator and emits metadata about our service,
|
||||||
|
metadata that Angular may need to inject other dependencies into this service.
|
||||||
|
|
||||||
|
The `HeroService` doesn't have any dependencies *at the moment*. Add the decorator anyway.
|
||||||
|
It is a "best practice" to apply the `@Injectable()` decorator *from the start*
|
||||||
|
both for consistency and for future-proofing.
|
||||||
|
### Getting Heroes
|
||||||
|
Add a `getHeroes` method stub.
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='getHeroes-stub'}
|
||||||
|
|
||||||
|
We're holding back on the implementation for a moment to make an important point.
|
||||||
|
|
||||||
|
The consumer of our service doesn't know how the service gets the data.
|
||||||
|
Our `HeroService` could get `Hero` data from anywhere.
|
||||||
|
It could get the data from a web service or local storage
|
||||||
|
or from a mock data source.
|
||||||
|
|
||||||
|
That's the beauty of removing data access from the component.
|
||||||
|
We can change our minds about the implementation as often as we like,
|
||||||
|
for whatever reason, without touching any of the components that need heroes.
|
||||||
|
|
||||||
|
|
||||||
|
### Mock Heroes
|
||||||
|
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
|
||||||
|
We'll move the mock data to its own file.
|
||||||
|
|
||||||
|
Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`.
|
||||||
|
We copy the `import {Hero} ...` statement as well because the heroes array uses the `Hero` class.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/mock-heroes.ts'}
|
||||||
|
|
||||||
|
We export the `HEROES` constant so we can import it elsewhere — such as our `HeroService`.
|
||||||
|
|
||||||
|
Meanwhile, back in `app.component.ts` where we cut away the `HEROES` array,
|
||||||
|
we leave behind an uninitialized `heroes` property:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='heroes-prop'}
|
||||||
|
|
||||||
|
### Return Mocked Heroes
|
||||||
|
Back in the `HeroService` we import the mock `HEROES` and return it from the `getHeroes` method.
|
||||||
|
Our `HeroService` looks like this:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='full'}
|
||||||
|
|
||||||
|
### Use the Hero Service
|
||||||
|
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
|
||||||
|
|
||||||
|
We begin, as usual, by importing the thing we want to use, the `HeroService`.Importing the service allows us to *reference* it in our code.
|
||||||
|
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
|
||||||
|
|
||||||
|
### Do we *new* the *HeroService*? No way!
|
||||||
|
We could create a new instance of the `HeroService` with `new` like this:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='new-service'}
|
||||||
|
|
||||||
|
That's a bad idea for several reasons including
|
||||||
|
|
||||||
|
* Our component has to know how to create a `HeroService`.
|
||||||
|
If we ever change the `HeroService` constructor,
|
||||||
|
we'll have to find every place we create the service and fix it.
|
||||||
|
Running around patching code is error prone and adds to the test burden.
|
||||||
|
|
||||||
|
* We create a new service each time we use `new`.
|
||||||
|
What if the service should cache heroes and share that cache with others?
|
||||||
|
We couldn't do that.
|
||||||
|
|
||||||
|
* We're locking the `AppComponent` into a specific implementation of the `HeroService`.
|
||||||
|
It will be hard to switch implementations for different scenarios.
|
||||||
|
Can we operate offline?
|
||||||
|
Will we need different mocked versions under test?
|
||||||
|
Not easy.
|
||||||
|
|
||||||
|
*What if ... what if ... Hey, we've got work to do!*
|
||||||
|
|
||||||
|
We get it. Really we do.
|
||||||
|
But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
|
||||||
|
|
||||||
|
### Inject the *HeroService*
|
||||||
|
|
||||||
|
Two lines replace the one line that created with *new*:
|
||||||
|
1. We add a constructor that also defines a private property.
|
||||||
|
1. We add to the component's `providers` metadata.
|
||||||
|
|
||||||
|
Here's the constructor:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='ctor'}
|
||||||
|
|
||||||
|
The constructor itself does nothing. The parameter simultaneously
|
||||||
|
defines a private `heroService` property and identifies it as a `HeroService` injection site.Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
|
||||||
|
|
||||||
|
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.The *injector* does not know yet how to create a `HeroService`.
|
||||||
|
If we ran our code now, Angular would fail with an error:
|
||||||
|
<code-example format="nocode">
|
||||||
|
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
|
||||||
|
Do that by adding the following `providers` array property to the bottom of the component metadata
|
||||||
|
in the `@Component` call.
|
||||||
|
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
|
||||||
|
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
|
||||||
|
|
||||||
|
{@a child-component}
|
||||||
|
### *getHeroes* in the *AppComponent*
|
||||||
|
We've got the service in a `heroService` private variable. Let's use it.
|
||||||
|
|
||||||
|
We pause to think. We can call the service and get the data in one line.
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='get-heroes'}
|
||||||
|
|
||||||
|
We don't really need a dedicated method to wrap one line. We write it anyway:<a id="oninit"></a>### The *ngOnInit* Lifecycle Hook
|
||||||
|
`AppComponent` should fetch and display heroes without a fuss.
|
||||||
|
Where do we call the `getHeroes` method? In a constructor? We do *not*!
|
||||||
|
|
||||||
|
Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
|
||||||
|
especially anything that might call a server as a data access method is sure to do.
|
||||||
|
|
||||||
|
The constructor is for simple initializations like wiring constructor parameters to properties.
|
||||||
|
It's not for heavy lifting. We should be able to create a component in a test and not worry that it
|
||||||
|
might do real work — like calling a server! — before we tell it to do so.
|
||||||
|
|
||||||
|
If not the constructor, something has to call `getHeroes`.
|
||||||
|
|
||||||
|
Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
|
||||||
|
Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
|
||||||
|
at creation, after each change, and at its eventual destruction.
|
||||||
|
|
||||||
|
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
|
||||||
|
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.Here's the essential outline for the `OnInit` interface:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='on-init'}
|
||||||
|
|
||||||
|
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
|
||||||
|
at the right time. In our case, we initialize by calling `getHeroes`.Our application should be running as expected, showing a list of heroes and a hero detail view
|
||||||
|
when we click on a hero name.
|
||||||
|
|
||||||
|
We're getting closer. But something isn't quite right.
|
||||||
|
|
||||||
|
<a id="async"></a>
|
||||||
|
## Async Services and !{_Promise}s
|
||||||
|
Our `HeroService` returns a list of mock heroes immediately.
|
||||||
|
Its `getHeroes` signature is synchronous
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='get-heroes'}
|
||||||
|
|
||||||
|
Ask for heroes and they are there in the returned result.
|
||||||
|
|
||||||
|
Someday we're going to get heroes from a remote server. We don’t call http yet, but we aspire to in later chapters.
|
||||||
|
|
||||||
|
When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
|
||||||
|
even if we want to (which we shouldn't) because the browser won't block.
|
||||||
|
|
||||||
|
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
|
||||||
|
|
||||||
|
We'll use *!{_Promise}s*.
|
||||||
|
|
||||||
|
### The Hero Service makes a !{_Promise}
|
||||||
|
|
||||||
|
A **!{_Promise}** is ... well it's a promise to call us back later when the results are ready.
|
||||||
|
We ask an asynchronous service to do some work and give it a callback function.
|
||||||
|
It does that work (somewhere) and eventually it calls our function with the results of the work or an error.
|
||||||
|
We are simplifying. Learn about ES2015 Promises [here](http://exploringjs.com/es6/ch_promises.html) and elsewhere on the web.Update the `HeroService` with this !{_Promise}-returning `getHeroes` method:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.ts' region='get-heroes'}
|
||||||
|
|
||||||
|
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
|
||||||
|
by returning an **immediately resolved !{_Promise}** with our mock heroes as the result.
|
||||||
|
|
||||||
|
### Act on the !{_Promise}
|
||||||
|
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.1.ts' region='getHeroes'}
|
||||||
|
|
||||||
|
As a result of our change to `HeroService`, we're now setting `this.heroes` to a !{_Promise} rather than an array of heroes.
|
||||||
|
|
||||||
|
We have to change our implementation to *act on the !{_Promise} when it resolves*.
|
||||||
|
When the !{_Promise} resolves successfully, *then* we will have heroes to display.
|
||||||
|
|
||||||
|
We pass our callback function as an argument to the !{_Promise}'s **then** method:
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.ts' region='get-heroes'}
|
||||||
|
|
||||||
|
|
||||||
|
The [ES2015 arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
|
||||||
|
in the callback is more succinct than the equivalent function expression and gracefully handles *this*.Our callback sets the component's `heroes` property to the array of heroes returned by the service. That's all there is to it!
|
||||||
|
|
||||||
|
Our app should still be running, still showing a list of heroes, and still
|
||||||
|
responding to a name selection with a detail view.
|
||||||
|
Checkout the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.### Review the App Structure
|
||||||
|
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||||
|
|
||||||
|
<aio-filetree>
|
||||||
|
|
||||||
|
<aio-folder>
|
||||||
|
angular-tour-of-heroes
|
||||||
|
<aio-folder>
|
||||||
|
src
|
||||||
|
<aio-folder>
|
||||||
|
app
|
||||||
|
<aio-file>
|
||||||
|
app.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
app.module.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero-detail.component.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
hero.service.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
mock-heroes.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
main.ts
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
index.html
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
styles.css
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
systemjs.config.js
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
tsconfig.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
node_modules ...
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
<aio-file>
|
||||||
|
package.json
|
||||||
|
</aio-file>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-folder>
|
||||||
|
|
||||||
|
|
||||||
|
</aio-filetree>
|
||||||
|
|
||||||
|
Here are the code files we discussed in this chapter.
|
||||||
|
|
||||||
|
<md-tab-group>
|
||||||
|
|
||||||
|
<md-tab label="src/app/hero.service.ts">
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/app.component.ts">
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
<md-tab label="src/app/mock-heroes.ts">
|
||||||
|
{@example 'toh-4/ts/src/app/mock-heroes.ts'}
|
||||||
|
</md-tab>
|
||||||
|
|
||||||
|
|
||||||
|
</md-tab-group>
|
||||||
|
|
||||||
|
## The Road We’ve Travelled
|
||||||
|
Let’s take stock of what we’ve built.
|
||||||
|
|
||||||
|
* We created a service class that can be shared by many components.
|
||||||
|
* We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates.
|
||||||
|
* We defined our `HeroService` as a provider for our `AppComponent`.
|
||||||
|
* We created mock hero data and imported them into our service.
|
||||||
|
* We designed our service to return a !{_Promise} and our component to get our data from the !{_Promise}.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
### The Road Ahead
|
||||||
|
Our Tour of Heroes has become more reusable using shared components and services.
|
||||||
|
We want to create a dashboard, add menu links that route between the views, and format data in a template.
|
||||||
|
As our app evolves, we’ll learn how to design it to make it easier to grow and maintain.
|
||||||
|
|
||||||
|
We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
|
||||||
|
|
||||||
|
<a id="slow"></a>### Appendix: Take it slow
|
||||||
|
|
||||||
|
We can simulate a slow connection.
|
||||||
|
|
||||||
|
Import the `Hero` symbol and add the following `getHeroesSlowly` method to the `HeroService`
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero.service.ts' region='get-heroes-slowly'}
|
||||||
|
|
||||||
|
Like `getHeroes`, it also returns a !{_Promise}.
|
||||||
|
But this !{_Promise} waits 2 seconds before resolving the !{_Promise} with mock heroes.
|
||||||
|
|
||||||
|
Back in the `AppComponent`, replace `heroService.getHeroes` with `heroService.getHeroesSlowly`
|
||||||
|
and see how the app behaves.
|
|
@ -0,0 +1,510 @@
|
||||||
|
@title
|
||||||
|
Routing
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We add the Angular Router and learn to navigate among the views
|
||||||
|
|
||||||
|
@description
|
||||||
|
We received new requirements for our Tour of Heroes application:
|
||||||
|
|
||||||
|
* Add a *Dashboard* view.
|
||||||
|
* Navigate between the *Heroes* and *Dashboard* views.
|
||||||
|
* Clicking on a hero in either view navigates to a detail view of the selected hero.
|
||||||
|
* Clicking a *deep link* in an email opens the detail view for a particular hero.
|
||||||
|
|
||||||
|
When we’re done, users will be able to navigate the app like this:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We'll add Angular’s *Router* to our app to satisfy these requirements.
|
||||||
|
|
||||||
|
The [Routing and Navigation](../guide/router.html) chapter covers the router
|
||||||
|
in more detail than we will in this tutorial.
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
## Where We Left Off
|
||||||
|
|
||||||
|
Before we continue with our Tour of Heroes, let’s verify that
|
||||||
|
we have the following structure after adding our hero service
|
||||||
|
and hero detail component. If not, we’ll need to go back and follow the previous chapters.
|
||||||
|
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
## Action plan
|
||||||
|
|
||||||
|
Here's our plan:
|
||||||
|
|
||||||
|
* Turn `AppComponent` into an application shell that only handles navigation
|
||||||
|
* Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`
|
||||||
|
* Add routing
|
||||||
|
* Create a new `DashboardComponent`
|
||||||
|
* Tie the *Dashboard* into the navigation structure
|
||||||
|
|
||||||
|
*Routing* is another name for *navigation*. The *router* is the mechanism for navigating from view to view.
|
||||||
|
|
||||||
|
## Splitting the *AppComponent*
|
||||||
|
|
||||||
|
Our current app loads `AppComponent` and immediately displays the list of heroes.
|
||||||
|
|
||||||
|
Our revised app should present a shell with a choice of views (*Dashboard* and *Heroes*)
|
||||||
|
and then default to one of them.
|
||||||
|
|
||||||
|
The `AppComponent` should only handle navigation.
|
||||||
|
Let's move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`.
|
||||||
|
|
||||||
|
### *HeroesComponent*
|
||||||
|
|
||||||
|
`AppComponent` is already dedicated to *Heroes*.
|
||||||
|
Instead of moving anything out of `AppComponent`, we'll just rename it `HeroesComponent`
|
||||||
|
and create a new `AppComponent` shell separately.
|
||||||
|
|
||||||
|
The steps are to rename:
|
||||||
|
* <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>
|
||||||
|
* `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file)
|
||||||
|
* Selector `my-app` to `my-heroes`
|
||||||
|
## Create *AppComponent*
|
||||||
|
|
||||||
|
The new `AppComponent` will be the application shell.
|
||||||
|
It will have some navigation links at the top and a display area below for the pages we navigate to.
|
||||||
|
|
||||||
|
The initial steps are:
|
||||||
|
|
||||||
|
* Create the file <span ngio-ex>src/app/app.component.ts</span>.
|
||||||
|
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
|
||||||
|
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
|
||||||
|
* Move the following from `HeroesComponent` to `AppComponent`:
|
||||||
|
* `title` class property
|
||||||
|
* `@Component` template `<h1>` element, which contains a binding to `title`
|
||||||
|
* Add a `<my-heroes>` element to the app template just below the heading so we still see the heroes.
|
||||||
|
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
|
||||||
|
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because we'll need it in every other view.
|
||||||
|
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it has been promoted.
|
||||||
|
* Add the supporting `import` statements for `AppComponent`.
|
||||||
|
|
||||||
|
Our first draft looks like this:
|
||||||
|
The app still runs and still displays heroes.
|
||||||
|
Our refactoring of `AppComponent` into a new `AppComponent` and a `HeroesComponent` worked!
|
||||||
|
We have done no harm.
|
||||||
|
## Add Routing
|
||||||
|
|
||||||
|
We're ready to take the next step.
|
||||||
|
Instead of displaying heroes automatically, we'd like to show them *after* the user clicks a button.
|
||||||
|
In other words, we'd like to navigate to the list of heroes.
|
||||||
|
|
||||||
|
We'll need the Angular *Router*.
|
||||||
|
|
||||||
|
|
||||||
|
{@a configure-routes}
|
||||||
|
*Routes* tell the router which views to display when a user clicks a link or
|
||||||
|
pastes a URL into the browser address bar.
|
||||||
|
|
||||||
|
Let's define our first route as a route to the heroes component:
|
||||||
|
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
||||||
|
We have only one route definition at the moment but rest assured, we'll add more.
|
||||||
|
|
||||||
|
This *route definition* has the following parts:
|
||||||
|
|
||||||
|
- **path**: the router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}heroes`).
|
||||||
|
<li if-docs="dart"> **name**: the official name of the route;
|
||||||
|
it *must* begin with a capital letter to avoid confusion with the *path* (`Heroes`).</li>
|
||||||
|
- **component**: the component that the router should create when navigating to this route (`HeroesComponent`).
|
||||||
|
|
||||||
|
Learn more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing](../guide/router.html) chapter.
|
||||||
|
### Router Outlet
|
||||||
|
|
||||||
|
If we paste the path, `/heroes`, into the browser address bar,
|
||||||
|
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
||||||
|
But where?
|
||||||
|
|
||||||
|
We have to ***tell it where*** by adding a `<router-outlet>` element to the bottom of the template.
|
||||||
|
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
||||||
|
The router displays each component immediately below the `<router-outlet>` as we navigate through the application.
|
||||||
|
|
||||||
|
### Router Links
|
||||||
|
|
||||||
|
We don't really expect users to paste a route URL into the address bar.
|
||||||
|
We add an anchor tag to the template which, when clicked, triggers navigation to the `HeroesComponent`.
|
||||||
|
|
||||||
|
The revised template looks like this:
|
||||||
|
Refresh the browser. We see only the app title and heroes link. We don't see the heroes list.
|
||||||
|
|
||||||
|
The browser's address bar shows `/`.
|
||||||
|
The route path to `HeroesComponent` is `/heroes`, not `/`.
|
||||||
|
We don't have a route that matches the path `/`, so there is nothing to show.
|
||||||
|
That's something we'll want to fix.
|
||||||
|
We click the *Heroes* navigation link, the browser bar updates to `/heroes`,
|
||||||
|
and now we see the list of heroes. We are navigating at last!
|
||||||
|
|
||||||
|
At this stage, our `AppComponent` looks like this.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt5/ts/src/app/app.component.1.ts' region='v2'}
|
||||||
|
|
||||||
|
The *AppComponent* is now attached to a router and displaying routed views.
|
||||||
|
For this reason and to distinguish it from other kinds of components,
|
||||||
|
we call this type of component a *Router Component*.
|
||||||
|
## Add a *Dashboard*
|
||||||
|
|
||||||
|
Routing only makes sense when we have multiple views. We need another view.
|
||||||
|
|
||||||
|
Create a placeholder `DashboardComponent` that gives us something to navigate to and from.
|
||||||
|
We’ll come back and make it more useful later.
|
||||||
|
|
||||||
|
### Configure the dashboard route
|
||||||
|
|
||||||
|
Go back to `!{_appRoutingTsVsAppComp}` and teach it to navigate to the dashboard.
|
||||||
|
|
||||||
|
Import the dashboard component and
|
||||||
|
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
||||||
|
#### !{_redirectTo}
|
||||||
|
|
||||||
|
We want the app to show the dashboard when it starts and
|
||||||
|
we want to see a nice URL in the browser address bar that says `/dashboard`.
|
||||||
|
Remember that the browser launches with `/` in the address bar.
|
||||||
|
#### Add navigation to the template
|
||||||
|
|
||||||
|
Finally, add a dashboard navigation link to the template, just above the *Heroes* link.
|
||||||
|
|
||||||
|
We nested the two links within `<nav>` tags.
|
||||||
|
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
|
||||||
|
To see these changes in your browser, go to the application root (`/`) and reload.
|
||||||
|
The app displays the dashboard and we can navigate between the dashboard and the heroes.
|
||||||
|
|
||||||
|
## Dashboard Top Heroes
|
||||||
|
|
||||||
|
Let’s spice up the dashboard by displaying the top four heroes at a glance.
|
||||||
|
|
||||||
|
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||||||
|
template file.Create that file with this content:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt5/ts/src/app/dashboard.component.1.html'}
|
||||||
|
|
||||||
|
We use `*ngFor` once again to iterate over a list of heroes and display their names.
|
||||||
|
We added extra `<div>` elements to help with styling later in this chapter.
|
||||||
|
|
||||||
|
### Share the *HeroService*
|
||||||
|
|
||||||
|
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}.
|
||||||
|
|
||||||
|
Recall earlier in the chapter that we removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
||||||
|
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
||||||
|
|
||||||
|
That move created a singleton `HeroService` instance, available to *all* components of the application.
|
||||||
|
Angular will inject `HeroService` and we'll use it here in the `DashboardComponent`.
|
||||||
|
|
||||||
|
### Get heroes
|
||||||
|
|
||||||
|
Open <span ngio-ex>dashboard.component.ts</span> and add the requisite `import` statements.
|
||||||
|
Now implement the `DashboardComponent` class like this:
|
||||||
|
We've seen this kind of logic before in the `HeroesComponent`:
|
||||||
|
|
||||||
|
* Define a `heroes` !{_array} property.
|
||||||
|
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
||||||
|
* Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
|
||||||
|
|
||||||
|
In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice` method</span>.
|
||||||
|
|
||||||
|
Refresh the browser and see four heroes in the new dashboard.
|
||||||
|
|
||||||
|
## Navigate to Hero Details
|
||||||
|
|
||||||
|
Although we display the details of a selected hero at the bottom of the `HeroesComponent`,
|
||||||
|
we don't yet *navigate* to the `HeroDetailComponent` in the three ways specified in our requirements:
|
||||||
|
|
||||||
|
1. from the *Dashboard* to a selected hero.
|
||||||
|
1. from the *Heroes* list to a selected hero.
|
||||||
|
1. from a "deep link" URL pasted into the browser address bar.
|
||||||
|
|
||||||
|
Adding a hero-detail route seems like an obvious place to start.
|
||||||
|
|
||||||
|
### Routing to a hero detail
|
||||||
|
|
||||||
|
We'll add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}` where our other routes are configured.
|
||||||
|
|
||||||
|
The new route is a bit unusual in that we must tell the `HeroDetailComponent` *which hero to show*.
|
||||||
|
We didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||||||
|
|
||||||
|
At the moment the parent `HeroesComponent` sets the component's `hero` property to a
|
||||||
|
hero object with a binding like this.
|
||||||
|
|
||||||
|
<code-example language="html">
|
||||||
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
That clearly won't work in any of our routing scenarios.
|
||||||
|
Certainly not the last one; we can't embed an entire hero object in the URL! Nor would we want to.
|
||||||
|
|
||||||
|
### Parameterized route
|
||||||
|
|
||||||
|
We *can* add the hero's `id` to the URL. When routing to the hero whose `id` is 11,
|
||||||
|
we could expect to see a URL such as this:
|
||||||
|
|
||||||
|
<code-example format="nocode">
|
||||||
|
/detail/11
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The `/detail/` part of that URL is constant. The trailing numeric `id` part changes from hero to hero.
|
||||||
|
We need to represent that variable part of the route with a *parameter* (or *token*) that stands for the hero's `id`.
|
||||||
|
|
||||||
|
### Configure a Route with a Parameter
|
||||||
|
|
||||||
|
Here's the *route definition* we'll use.
|
||||||
|
The colon (:) in the path indicates that `:id` is a placeholder to be filled with a specific hero `id`
|
||||||
|
when navigating to the `HeroDetailComponent`.
|
||||||
|
We're finished with the application routes.
|
||||||
|
|
||||||
|
We won't add a `'Hero Detail'` link to the template because users
|
||||||
|
don't click a navigation *link* to view a particular hero.
|
||||||
|
They click a *hero* whether that hero is displayed on the dashboard or in the heroes list.
|
||||||
|
|
||||||
|
We'll get to those *hero* clicks later in the chapter.
|
||||||
|
There's no point in working on them until the `HeroDetailComponent`
|
||||||
|
is ready to be navigated *to*.
|
||||||
|
|
||||||
|
That will require an `HeroDetailComponent` overhaul.
|
||||||
|
|
||||||
|
## Revise the *HeroDetailComponent*
|
||||||
|
|
||||||
|
Before we rewrite the `HeroDetailComponent`, let's review what it looks like now:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/hero-detail.component.ts'}
|
||||||
|
|
||||||
|
The template won't change. We'll display a hero the same way.
|
||||||
|
The big changes are driven by how we get the hero.
|
||||||
|
First, add the requisite imports:
|
||||||
|
Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
|
||||||
|
into the constructor, saving their values in private fields:
|
||||||
|
We tell the class that we want to implement the `OnInit` interface.
|
||||||
|
The hero `id` is a number. Route parameters are *always strings*.
|
||||||
|
So we convert the route parameter value to a number with the !{_str2int}.
|
||||||
|
### Add *HeroService.getHero*
|
||||||
|
|
||||||
|
The problem with this bit of code is that `HeroService` doesn't have a `getHero` method!
|
||||||
|
We better fix that quickly before someone notices that we broke the app.
|
||||||
|
|
||||||
|
Open `HeroService` and add a `getHero` method that filters the heroes list from `getHeroes` by `id`:
|
||||||
|
Let's return to the `HeroDetailComponent` to clean up loose ends.
|
||||||
|
|
||||||
|
### Find our way back
|
||||||
|
|
||||||
|
We can navigate *to* the `HeroDetailComponent` in several ways.
|
||||||
|
How do we navigate somewhere else when we're done?
|
||||||
|
|
||||||
|
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
|
||||||
|
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
|
||||||
|
using the `Location` service we injected previously.
|
||||||
|
|
||||||
|
Going back too far could take us out of the application.
|
||||||
|
That's acceptable in a demo. We'd guard against it in a real application,
|
||||||
|
perhaps with the [!{_CanDeactivateGuard}](../api/!{_CanDeactivateGuardUri}.html).
|
||||||
|
Then we wire this method with an event binding to a *Back* button that we
|
||||||
|
add to the bottom of the component template.
|
||||||
|
Modifying the template to add this button spurs us to take one more
|
||||||
|
incremental improvement and migrate the template to its own file,
|
||||||
|
called <span ngio-ex>hero-detail.component.html</span>:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt5/ts/src/app/hero-detail.component.html'}
|
||||||
|
|
||||||
|
We update the component metadata with a <span if-docs="ts">`moduleId` and a </span>`templateUrl` pointing to the template file that we just created.
|
||||||
|
Refresh the browser and see the results.
|
||||||
|
|
||||||
|
## Select a *Dashboard* Hero
|
||||||
|
|
||||||
|
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
|
||||||
|
|
||||||
|
Although the dashboard heroes are presented as button-like blocks, they should behave like anchor tags.
|
||||||
|
When hovering over a hero block, the target URL should display in the browser status bar
|
||||||
|
and the user should be able to copy the link or open the hero detail view in a new tab.
|
||||||
|
|
||||||
|
To achieve this effect, reopen the `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
||||||
|
with `<a>` tags. The opening `<a>` tag looks like this:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt5/ts/src/app/dashboard.component.html' region='click'}
|
||||||
|
|
||||||
|
Notice the `[routerLink]` binding.
|
||||||
|
|
||||||
|
Top level navigation in the [`AppComponent`
|
||||||
|
template](#router-links) has router links set to fixed !{_pathVsName}s of the
|
||||||
|
destination routes, "/dashboard" and "/heroes".
|
||||||
|
|
||||||
|
This time, we're binding to an expression containing a **link parameters !{_array}**.
|
||||||
|
The !{_array} has two elements, the ***!{_pathVsName}*** of
|
||||||
|
the destination route and a ***route parameter*** set to the value of the current hero's `id`.
|
||||||
|
|
||||||
|
The two !{_array} items align with the ***!{_pathVsName}*** and ***:id***
|
||||||
|
token in the parameterized hero detail route definition we added to
|
||||||
|
`!{_appRoutingTsVsAppComp}` earlier in the chapter:
|
||||||
|
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’s details.
|
||||||
|
|
||||||
|
## Select a Hero in the *HeroesComponent*
|
||||||
|
|
||||||
|
Earlier we added the ability to select a hero from the dashboard.
|
||||||
|
We'll do something similar in the `HeroesComponent`.
|
||||||
|
|
||||||
|
The `HeroesComponent` template exhibits a "master/detail" style with the list of heroes
|
||||||
|
at the top and details of the selected hero below.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-4/ts/src/app/app.component.ts' region='template'}
|
||||||
|
|
||||||
|
Our goal is to move the detail to its own view and navigate to it when the user decides to edit a selected hero.
|
||||||
|
|
||||||
|
Delete the `<h1>` at the top (we forgot about it during the `AppComponent`-to-`HeroesComponent` conversion).
|
||||||
|
|
||||||
|
Delete the last line of the template with the `<my-hero-detail>` tags.
|
||||||
|
|
||||||
|
We'll no longer show the full `HeroDetailComponent` here.
|
||||||
|
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
|
||||||
|
|
||||||
|
We'll throw in a small twist for variety.
|
||||||
|
We are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
|
||||||
|
When the user selects a hero from the list, we *don't* go to the detail page.
|
||||||
|
We show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
|
||||||
|
|
||||||
|
### Add the *mini-detail*
|
||||||
|
|
||||||
|
Add the following HTML fragment at the bottom of the template where the `<my-hero-detail>` used to be:
|
||||||
|
After clicking a hero, the user should see something like this below the hero list:
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/mini-hero-detail.png' alt="Mini Hero Detail" height="70"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Format with the *uppercase* pipe
|
||||||
|
|
||||||
|
Notice that the hero's name is displayed in CAPITAL LETTERS. That's the effect of the `uppercase` pipe
|
||||||
|
that we slipped into the interpolation binding. Look for it right after the pipe operator ( | ).
|
||||||
|
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||||||
|
Angular ships with several pipes and we can write our own.
|
||||||
|
|
||||||
|
Learn about pipes in the [Pipes](../guide/pipes.html) chapter.
|
||||||
|
### Move content out of the component file
|
||||||
|
|
||||||
|
We are not done. We still have to update the component class to support navigation to the
|
||||||
|
`HeroDetailComponent` when the user clicks the *View Details* button.
|
||||||
|
|
||||||
|
This component file is really big. Most of it is either template or CSS styles.
|
||||||
|
It's difficult to find the component logic amidst the noise of HTML and CSS.
|
||||||
|
|
||||||
|
Let's migrate the template and the styles to their own files before we make any more changes:
|
||||||
|
|
||||||
|
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
|
||||||
|
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
||||||
|
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
|
||||||
|
<li if-docs="ts">. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component.</li>
|
||||||
|
|
||||||
|
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
||||||
|
We could list multiple style files from different locations if we needed them.
|
||||||
|
### Update the _HeroesComponent_ class.
|
||||||
|
|
||||||
|
The `HeroesComponent` navigates to the `HeroDetailComponent` in response to a button click.
|
||||||
|
The button's _click_ event is bound to a `gotoDetail` method that navigates _imperatively_
|
||||||
|
by telling the router where to go.
|
||||||
|
|
||||||
|
This approach requires some changes to the component class:
|
||||||
|
|
||||||
|
1. Import the `router` from the Angular router library
|
||||||
|
1. Inject the `router` in the constructor (along with the `HeroService`)
|
||||||
|
1. Implement `gotoDetail` by calling the `router.navigate` method
|
||||||
|
Note that we're passing a two-element **link parameters !{_array}**
|
||||||
|
— a path and the route parameter — to
|
||||||
|
the `router.navigate` method just as we did in the `[routerLink]` binding
|
||||||
|
back in the `DashboardComponent`.
|
||||||
|
Here's the fully revised `HeroesComponent` class:
|
||||||
|
Refresh the browser and start clicking.
|
||||||
|
We can navigate around the app, from the dashboard to hero details and back,
|
||||||
|
for heroes list to the mini-detail to the hero details and back to the heroes again.
|
||||||
|
We can jump back and forth between the dashboard and the heroes.
|
||||||
|
|
||||||
|
We've met all of the navigational requirements that propelled this chapter.
|
||||||
|
|
||||||
|
## Styling the App
|
||||||
|
|
||||||
|
The app is functional but pretty ugly.
|
||||||
|
Our creative designer team provided some CSS files to make it look better.
|
||||||
|
|
||||||
|
### A Dashboard with Style
|
||||||
|
|
||||||
|
The designers think we should display the dashboard heroes in a row of rectangles.
|
||||||
|
They've given us ~60 lines of CSS for this purpose including some simple media queries for responsive design.
|
||||||
|
|
||||||
|
If we paste these ~60 lines into the component `styles` metadata,
|
||||||
|
they'll completely obscure the component logic.
|
||||||
|
Let's not do that. It's easier to edit CSS in a separate `*.css` file anyway.
|
||||||
|
|
||||||
|
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference
|
||||||
|
that file in the component metadata's `styleUrls` !{_array} property like this:
|
||||||
|
### Stylish Hero Details
|
||||||
|
|
||||||
|
The designers also gave us CSS styles specifically for the `HeroDetailComponent`.
|
||||||
|
|
||||||
|
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
||||||
|
folder and refer to that file inside
|
||||||
|
the `styleUrls` !{_array} as we did for `DashboardComponent`.
|
||||||
|
Let's also remove the `hero` property `@Input` !{_decorator}
|
||||||
|
<span if-docs="ts">and its import</span>
|
||||||
|
while we are at it.
|
||||||
|
|
||||||
|
Here's the content for the aforementioned component CSS files.
|
||||||
|
### Style the Navigation Links
|
||||||
|
|
||||||
|
The designers gave us CSS to make the navigation links in our `AppComponent` look more like selectable buttons.
|
||||||
|
We cooperated by surrounding those links in `<nav>` tags.
|
||||||
|
|
||||||
|
Add a <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
||||||
|
|
||||||
|
### Global application styles
|
||||||
|
|
||||||
|
When we add styles to a component, we're keeping everything a component needs
|
||||||
|
— HTML, the CSS, the code — together in one convenient place.
|
||||||
|
It's pretty easy to package it all up and re-use the component somewhere else.
|
||||||
|
|
||||||
|
We can also create styles at the *application level* outside of any component.
|
||||||
|
|
||||||
|
Our designers provided some basic styles to apply to elements across the entire app.
|
||||||
|
These correspond to the full set of master styles that we installed earlier during [setup](../guide/setup.html).
|
||||||
|
Here is an excerpt:
|
||||||
|
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already.
|
||||||
|
Ensure that it contains the [master styles given here](!{styles_css}).
|
||||||
|
|
||||||
|
If necessary, also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
||||||
|
Look at the app now. Our dashboard, heroes, and navigation links are styling!
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## Application structure and code
|
||||||
|
|
||||||
|
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||||
|
Verify that we have the following structure:
|
||||||
|
|
||||||
|
## Recap
|
||||||
|
|
||||||
|
### The Road Behind
|
||||||
|
|
||||||
|
We travelled a great distance in this chapter
|
||||||
|
|
||||||
|
- We added the Angular *Router* to navigate among different components.
|
||||||
|
- We learned how to create router links to represent navigation menu items.
|
||||||
|
- We used router link parameters to navigate to the details of user selected hero.
|
||||||
|
- We shared the `HeroService` among multiple components.
|
||||||
|
- We moved HTML and CSS out of the component file and into their own files.
|
||||||
|
- We added the `uppercase` pipe to format data.
|
||||||
|
<li if-docs="ts"> We refactored routes into a `Routing Module` that we import.</li>
|
||||||
|
|
||||||
|
### The Road Ahead
|
||||||
|
|
||||||
|
We have much of the foundation we need to build an application.
|
||||||
|
We're still missing a key piece: remote data access.
|
||||||
|
|
||||||
|
In the next chapter,
|
||||||
|
we’ll replace our mock data with data retrieved from a server using http.
|
|
@ -0,0 +1,293 @@
|
||||||
|
@title
|
||||||
|
HTTP
|
||||||
|
|
||||||
|
@intro
|
||||||
|
We convert our service and components to use Angular's HTTP service
|
||||||
|
|
||||||
|
@description
|
||||||
|
Our stakeholders appreciate our progress.
|
||||||
|
Now they want to get the hero data from a server, let users add, edit, and delete heroes,
|
||||||
|
and save these changes back to the server.
|
||||||
|
|
||||||
|
In this chapter we teach our application to make the corresponding HTTP calls to a remote server's web API.
|
||||||
|
|
||||||
|
Run the <live-example></live-example> for this part.
|
||||||
|
|
||||||
|
## Where We Left Off
|
||||||
|
|
||||||
|
In the [previous chapter](toh-pt5.html), we learned to navigate between the dashboard and the fixed heroes list, editing a selected hero along the way.
|
||||||
|
That's our starting point for this chapter.
|
||||||
|
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Providing HTTP Services
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
### Register for HTTP services
|
||||||
|
|
||||||
|
## Simulating the web API
|
||||||
|
|
||||||
|
We recommend registering application-wide services in the root
|
||||||
|
`!{_AppModuleVsAppComp}` *providers*. <span if-docs="dart">Here we're
|
||||||
|
registering in `main` for a special reason.</span>
|
||||||
|
|
||||||
|
Our application is in the early stages of development and far from ready for production.
|
||||||
|
We don't even have a web server that can handle requests for heroes.
|
||||||
|
Until we do, *we'll have to fake it*.
|
||||||
|
|
||||||
|
We're going to *trick* the HTTP client into fetching and saving data from
|
||||||
|
a mock service, the *in-memory web API*.
|
||||||
|
<span if-docs="dart"> The application itself doesn't need to know and
|
||||||
|
shouldn't know about this. So we'll slip the in-memory web API into the
|
||||||
|
configuration *above* the `AppComponent`.</span>
|
||||||
|
|
||||||
|
Here is a version of <span ngio-ex>!{_appModuleTsVsMainTs}</span> that performs this trick:
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/in-memory-data.service.ts' region='init'}
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This file replaces the <code> </code> which is now safe to delete.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
## Heroes and HTTP
|
||||||
|
|
||||||
|
Look at our current `HeroService` implementation
|
||||||
|
We returned a !{_Promise} resolved with mock heroes.
|
||||||
|
It may have seemed like overkill at the time, but we were anticipating the
|
||||||
|
day when we fetched heroes with an HTTP client and we knew that would have to be an asynchronous operation.
|
||||||
|
|
||||||
|
That day has arrived! Let's convert `getHeroes()` to use HTTP.
|
||||||
|
Our updated import statements are now:
|
||||||
|
Refresh the browser, and the hero data should be successfully loaded from the
|
||||||
|
mock server.
|
||||||
|
|
||||||
|
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
|
||||||
|
|
||||||
|
We're still returning a !{_Promise} but we're creating it differently.
|
||||||
|
That response JSON has a single `data` property.
|
||||||
|
The `data` property holds the !{_array} of *heroes* that the caller really wants.
|
||||||
|
So we grab that !{_array} and return it as the resolved !{_Promise} value.
|
||||||
|
|
||||||
|
|
||||||
|
~~~ {.alert.is-important}
|
||||||
|
|
||||||
|
Pay close attention to the shape of the data returned by the server.
|
||||||
|
This particular *in-memory web API* example happens to return an object with a `data` property.
|
||||||
|
Your API might return something else. Adjust the code to match *your web API*.
|
||||||
|
|
||||||
|
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The caller is unaware of these machinations. It receives a !{_Promise} of *heroes* just as it did before.
|
||||||
|
It has no idea that we fetched the heroes from the (mock) server.
|
||||||
|
It knows nothing of the twists and turns required to convert the HTTP response into heroes.
|
||||||
|
Such is the beauty and purpose of delegating data access to a service like this `HeroService`.
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
At the end of `getHeroes()` we `catch` server failures and pass them to an error handler:
|
||||||
|
This is a critical step!
|
||||||
|
We must anticipate HTTP failures as they happen frequently for reasons beyond our control.
|
||||||
|
In this demo service we log the error to the console; we would do better in real life.
|
||||||
|
|
||||||
|
We've also decided to return a user friendly form of the error to
|
||||||
|
the caller in a !{rejected_promise} so that the caller can display a proper error message to the user.
|
||||||
|
|
||||||
|
### Get hero by id
|
||||||
|
The `HeroDetailComponent` asks the `HeroService` to fetch a single hero to edit.
|
||||||
|
|
||||||
|
The `HeroService` currently fetches all heroes and then finds the desired hero
|
||||||
|
by filtering for the one with the matching `id`.
|
||||||
|
That's fine in a simulation. It's wasteful to ask a real server for _all_ heroes when we only want one.
|
||||||
|
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (e.g., `api/hero/11`).
|
||||||
|
|
||||||
|
Update the `HeroService.getHero` method to make a _get-by-id_ request,
|
||||||
|
applying what we just learned to write `getHeroes`:It's almost the same as `getHeroes`.
|
||||||
|
The URL identifies _which_ hero the server should update by encoding the hero id into the URL
|
||||||
|
to match the `api/hero/:id` pattern.
|
||||||
|
|
||||||
|
We also adjust to the fact that the `data` in the response is a single hero object rather than !{_an} !{_array}.
|
||||||
|
|
||||||
|
### Unchanged _getHeroes_ API
|
||||||
|
|
||||||
|
Although we made significant *internal* changes to `getHeroes()` and `getHero()`,
|
||||||
|
the public signatures did not change.
|
||||||
|
We still return a !{_Promise} from both methods.
|
||||||
|
We won't have to update any of the components that call them.
|
||||||
|
|
||||||
|
Our stakeholders are thrilled with the web API integration so far.
|
||||||
|
Now they want the ability to create and delete heroes.
|
||||||
|
|
||||||
|
Let's see first what happens when we try to update a hero's details.
|
||||||
|
|
||||||
|
## Update hero details
|
||||||
|
|
||||||
|
We can edit a hero's name already in the hero detail view. Go ahead and try
|
||||||
|
it. As we type, the hero name is updated in the view heading.
|
||||||
|
But when we hit the `Back` button, the changes are lost!
|
||||||
|
|
||||||
|
Updates weren't lost before. What changed?
|
||||||
|
When the app used a list of mock heroes, updates were applied directly to the
|
||||||
|
hero objects within the single, app-wide, shared list. Now that we are fetching data
|
||||||
|
from a server, if we want changes to persist, we'll need to write them back to
|
||||||
|
the server.
|
||||||
|
|
||||||
|
### Save hero details
|
||||||
|
|
||||||
|
Let's ensure that edits to a hero's name aren't lost. Start by adding,
|
||||||
|
to the end of the hero detail template, a save button with a `click` event
|
||||||
|
binding that invokes a new component method named `save`:
|
||||||
|
The `save` method persists hero name changes using the hero service
|
||||||
|
`update` method and then navigates back to the previous view:
|
||||||
|
### Hero service `update` method
|
||||||
|
|
||||||
|
The overall structure of the `update` method is similar to that of
|
||||||
|
`getHeroes`, although we'll use an HTTP _put_ to persist changes
|
||||||
|
server-side:
|
||||||
|
We identify _which_ hero the server should update by encoding the hero id in
|
||||||
|
the URL. The put body is the JSON string encoding of the hero, obtained by
|
||||||
|
calling `!{_JSON_stringify}`. We identify the body content type
|
||||||
|
(`application/json`) in the request header.
|
||||||
|
|
||||||
|
Refresh the browser and give it a try. Changes to hero names should now persist.
|
||||||
|
|
||||||
|
## Add a hero
|
||||||
|
|
||||||
|
To add a new hero we need to know the hero's name. Let's use an input
|
||||||
|
element for that, paired with an add button.
|
||||||
|
|
||||||
|
Insert the following into the heroes component HTML, first thing after
|
||||||
|
the heading:
|
||||||
|
In response to a click event, we call the component's click handler and then
|
||||||
|
clear the input field so that it will be ready to use for another name.
|
||||||
|
When the given name is non-blank, the handler delegates creation of the
|
||||||
|
named hero to the hero service, and then adds the new hero to our !{_array}.
|
||||||
|
|
||||||
|
Finally, we implement the `create` method in the `HeroService` class.Refresh the browser and create some new heroes!
|
||||||
|
|
||||||
|
## Delete a hero
|
||||||
|
|
||||||
|
Too many heroes?
|
||||||
|
Let's add a delete button to each hero in the heroes view.
|
||||||
|
|
||||||
|
Add this button element to the heroes component HTML, right after the hero
|
||||||
|
name in the repeated `<li>` tag:
|
||||||
|
The `<li>` element should now look like this:
|
||||||
|
In addition to calling the component's `delete` method, the delete button
|
||||||
|
click handling code stops the propagation of the click event — we
|
||||||
|
don't want the `<li>` click handler to be triggered because that would
|
||||||
|
select the hero that we are going to delete!
|
||||||
|
|
||||||
|
The logic of the `delete` handler is a bit trickier:
|
||||||
|
Of course, we delegate hero deletion to the hero service, but the component
|
||||||
|
is still responsible for updating the display: it removes the deleted hero
|
||||||
|
from the !{_array} and resets the selected hero if necessary.
|
||||||
|
We want our delete button to be placed at the far right of the hero entry.
|
||||||
|
This extra CSS accomplishes that:
|
||||||
|
### Hero service `delete` method
|
||||||
|
|
||||||
|
The hero service's `delete` method uses the _delete_ HTTP method to remove the hero from the server:
|
||||||
|
Refresh the browser and try the new delete functionality.
|
||||||
|
|
||||||
|
<div id='observables'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## !{_Observable}s
|
||||||
|
But requests aren't always "one and done". We may start one request,
|
||||||
|
then cancel it, and make a different request before the server has responded to the first request.
|
||||||
|
Such a _request-cancel-new-request_ sequence is difficult to implement with *!{_Promise}s*.
|
||||||
|
It's easy with *!{_Observable}s* as we'll see.
|
||||||
|
|
||||||
|
### Search-by-name
|
||||||
|
|
||||||
|
We're going to add a *hero search* feature to the Tour of Heroes.
|
||||||
|
As the user types a name into a search box, we'll make repeated HTTP requests for heroes filtered by that name.
|
||||||
|
|
||||||
|
We start by creating `HeroSearchService` that sends search queries to our server's web api.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/hero-search.service.ts'}
|
||||||
|
|
||||||
|
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
|
||||||
|
in the `HeroService`, although the URL now has a query string.
|
||||||
|
### HeroSearchComponent
|
||||||
|
|
||||||
|
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
|
||||||
|
|
||||||
|
The component template is simple — just a text box and a list of matching search results.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/hero-search.component.html'}
|
||||||
|
|
||||||
|
We'll also want to add styles for the new component.
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/hero-search.component.css'}
|
||||||
|
|
||||||
|
As the user types in the search box, a *keyup* event binding calls the component's `search` method with the new search box value.
|
||||||
|
|
||||||
|
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
|
||||||
|
|
||||||
|
But, as we'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
|
||||||
|
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until we flow it through the `async` pipe (`AsyncPipe`).
|
||||||
|
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
|
||||||
|
|
||||||
|
Time to create the `HeroSearchComponent` class and metadata.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/hero-search.component.ts'}
|
||||||
|
|
||||||
|
#### Search terms
|
||||||
|
|
||||||
|
Let's focus on the `!{_priv}searchTerms`:
|
||||||
|
<a id="ngoninit"></a>
|
||||||
|
#### Initialize the _**heroes**_ property (_**ngOnInit**_)
|
||||||
|
|
||||||
|
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
|
||||||
|
We're going to turn the stream
|
||||||
|
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
|
||||||
|
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of HTTP requests.
|
||||||
|
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
|
||||||
|
### Add the search component to the dashboard
|
||||||
|
|
||||||
|
We add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
||||||
|
|
||||||
|
|
||||||
|
{@example 'toh-pt6/ts/src/app/dashboard.component.html'}
|
||||||
|
|
||||||
|
Finally, we import `HeroSearchComponent` from
|
||||||
|
<span ngio-ex>hero-search.component.ts</span>
|
||||||
|
and add it to the `!{_declarations}` !{_array}:
|
||||||
|
Run the app again, go to the *Dashboard*, and enter some text in the search box.
|
||||||
|
At some point it might look like this.
|
||||||
|
|
||||||
|
<figure class='image-display'>
|
||||||
|
<img src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component"> </img>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## Application structure and code
|
||||||
|
|
||||||
|
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||||
|
Verify that we have the following structure:
|
||||||
|
|
||||||
|
## Home Stretch
|
||||||
|
|
||||||
|
We are at the end of our journey for now, but we have accomplished a lot.
|
||||||
|
- We added the necessary dependencies to use HTTP in our application.
|
||||||
|
- We refactored `HeroService` to load heroes from a web API.
|
||||||
|
- We extended `HeroService` to support post, put and delete methods.
|
||||||
|
- We updated our components to allow adding, editing and deleting of heroes.
|
||||||
|
- We configured an in-memory web API.
|
||||||
|
- We learned how to use !{_Observable}s.
|
||||||
|
|
||||||
|
Here are the files we _added or changed_ in this chapter.
|
||||||
|
|
||||||
|
### Next Step
|
||||||
|
|
||||||
|
Return to the [learning path](../guide/learning-angular.html#architecture) where
|
||||||
|
you can read about the concepts and practices you discovered in this tutorial.
|
Loading…
Reference in New Issue