Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
8de2ace80a | |||
c977994864 | |||
12b8e1af55 | |||
9a188485f5 | |||
45a10419bc | |||
2245748c14 | |||
bcea196530 | |||
b9e32c833a | |||
be49e0ee93 | |||
bf95655a1a | |||
6bf5b84fa4 | |||
4836565ca7 | |||
750e4e8156 | |||
a0846194b7 | |||
bcf6b90c95 | |||
3ca2a0aa37 | |||
b4be96c65d | |||
434ff5fecb | |||
a1bb9c2d42 | |||
7e626bef0a | |||
a1e83a8ed2 | |||
cbeb197aa5 | |||
0330fa6b82 | |||
97135e8fd5 | |||
35bd07fc7b | |||
a8ac77b645 | |||
9ecd377a51 | |||
76171bd8b4 | |||
1f106d75bc | |||
a4fae8c405 | |||
33c07b3394 | |||
c9d06e676f | |||
c7c65d9fda | |||
257a9e3e6f | |||
c7c0a1688e | |||
7e95e2b0ba | |||
ddc286f4b5 | |||
3d17a3672e | |||
61d253f5fd | |||
54be25a7a1 | |||
b1757037fb | |||
f0476fcff0 | |||
a5c4bb5b96 | |||
4c1f32b0db | |||
383d8969ab | |||
333ffd8d32 | |||
d4679a0bc2 | |||
4ce29f3a5b | |||
17b7bc3e06 | |||
f19bd5f4f3 | |||
d503d25f29 | |||
5d275e994a | |||
d8c8b13bb8 | |||
4671168635 | |||
1ac78bfd5d | |||
4340beacea | |||
ec89f378fc | |||
4dd6863bc2 | |||
37c626e673 | |||
f0a110928b | |||
c39e7d1eb2 | |||
799bffb431 | |||
fda607cc2f | |||
cc3aa68123 | |||
306621d2d6 | |||
d204f7aa2a | |||
a94f5e8cbb | |||
1390afef23 | |||
b0346a6e45 | |||
e5da059994 | |||
ac92c3bb26 | |||
87157d7089 | |||
611dd12f0f | |||
969ce9dc2b | |||
34834a9e79 | |||
6e2ddccc2c | |||
55742e4737 | |||
0091b1e8db |
@ -41,7 +41,7 @@ jobs:
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
||||
- run: bazel run @io_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel run @build_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
@ -256,6 +256,8 @@ groups:
|
||||
files:
|
||||
include:
|
||||
- "aio/*"
|
||||
exclude:
|
||||
- "aio/content/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- IgorMinar
|
||||
@ -276,6 +278,8 @@ groups:
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
@ -289,5 +293,7 @@ groups:
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
@ -1,9 +1,12 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
# force trusty as Google Chrome addon is not supported on Precise
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
# firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
@ -53,6 +56,7 @@ env:
|
||||
- CI_MODE=docs_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_e2e
|
||||
- CI_MODE=bazel
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
@ -11,8 +11,15 @@ filegroup(
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
"node_modules/typescript/**",
|
||||
"node_modules/zone.js/**/*.d.ts",
|
||||
"node_modules/zone.js/**",
|
||||
"node_modules/rxjs/**/*.d.ts",
|
||||
"node_modules/rxjs/**/*.js",
|
||||
"node_modules/@types/**/*.d.ts",
|
||||
"node_modules/tsickle/**",
|
||||
"node_modules/hammerjs/**/*.d.ts",
|
||||
"node_modules/protobufjs/**",
|
||||
"node_modules/bytebuffer/**",
|
||||
"node_modules/reflect-metadata/**",
|
||||
"node_modules/minimist/**/*.js",
|
||||
]),
|
||||
)
|
38
CHANGELOG.md
@ -1,3 +1,41 @@
|
||||
<a name="4.3.2"></a>
|
||||
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([cbeb197](https://github.com/angular/angular/commit/cbeb197))
|
||||
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([a084619](https://github.com/angular/angular/commit/a084619)), closes [#17344](https://github.com/angular/angular/issues/17344)
|
||||
* **compiler:** allow numbers for ICU message cases in lexer ([#18095](https://github.com/angular/angular/issues/18095)) ([a8ac77b](https://github.com/angular/angular/commit/a8ac77b)), closes [#17799](https://github.com/angular/angular/issues/17799)
|
||||
* **core:** invoke error handler outside of the Angular Zone ([#18269](https://github.com/angular/angular/issues/18269)) ([a1bb9c2](https://github.com/angular/angular/commit/a1bb9c2)), closes [#17073](https://github.com/angular/angular/issues/17073) [#7774](https://github.com/angular/angular/issues/7774)
|
||||
* **platform-server:** don't clobber parse5 properties when setting ([#18237](https://github.com/angular/angular/issues/18237)) ([97135e8](https://github.com/angular/angular/commit/97135e8)), closes [#17050](https://github.com/angular/angular/issues/17050)
|
||||
* **router:** child CanActivate guard should wait for parent to complete ([#18110](https://github.com/angular/angular/issues/18110)) ([b9e32c8](https://github.com/angular/angular/commit/b9e32c8)), closes [#15670](https://github.com/angular/angular/issues/15670)
|
||||
* **router:** should throw when lazy loaded module doesn't define any routes ([#15001](https://github.com/angular/angular/issues/15001)) ([be49e0e](https://github.com/angular/angular/commit/be49e0e)), closes [#14596](https://github.com/angular/angular/issues/14596)
|
||||
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([1f106d7](https://github.com/angular/angular/commit/1f106d7))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.1"></a>
|
||||
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always camelcase style property names that contain auto styles ([383d896](https://github.com/angular/angular/commit/383d896)), closes [#17938](https://github.com/angular/angular/issues/17938)
|
||||
* **animations:** capture cancelled animation styles within grouped animations ([333ffd8](https://github.com/angular/angular/commit/333ffd8)), closes [#17170](https://github.com/angular/angular/issues/17170)
|
||||
* **animations:** do not crash animations if a nested component fires CD during CD ([4c1f32b](https://github.com/angular/angular/commit/4c1f32b)), closes [#18193](https://github.com/angular/angular/issues/18193)
|
||||
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
|
||||
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
|
||||
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
|
||||
* **compiler-cli:** don't generate empty `<target/>` when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
|
||||
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
|
||||
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
|
||||
* **router:** should run resolvers for the same route concurrently ([ec89f37](https://github.com/angular/angular/commit/ec89f37)), closes [#14279](https://github.com/angular/angular/issues/14279)
|
||||
* **router:** terminal route in custom matcher ([5d275e9](https://github.com/angular/angular/commit/5d275e9))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.0"></a>
|
||||
# [4.3.0](https://github.com/angular/angular/compare/4.3.0-rc.0...4.3.0) (2017-07-14)
|
||||
|
||||
|
12
WORKSPACE
@ -1,11 +1,17 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "io_bazel_rules_typescript",
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
commit = "3a8404d",
|
||||
tag = "0.0.5",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
|
||||
node_repositories(package_json = "//:package.json")
|
||||
|
||||
git_repository(
|
||||
name = "build_bazel_rules_angular",
|
||||
remote = "https://github.com/bazelbuild/rules_angular.git",
|
||||
tag = "0.0.1",
|
||||
)
|
@ -124,7 +124,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||
|
||||
You also import NgModules_ from Angular _libraries_ using JavaScript import statements:
|
||||
You also import NgModules from Angular _libraries_ using JavaScript import statements:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Bootstrapping
|
||||
|
||||
An NgModule class describes how the application parts fit together.
|
||||
Every application has at least one NgModule, the _root_ module
|
||||
that you [bootstrap](guide/appmodule#main) to launch the application.
|
||||
Every application has at least one NgModule, 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](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
||||
@ -25,7 +25,7 @@ The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
|
||||
* **_bootstrap_** — the _root_ component that Angular creates and inserts into the `index.html` host web page.
|
||||
|
||||
The [NgModules](guide/ngmodule) guide dives deeply into the details of NgModules.
|
||||
All you need to know at the moment is a few basics about these three properties.
|
||||
All you need to know at the moment is a few basics about these three properties.
|
||||
|
||||
|
||||
{@a imports}
|
||||
@ -34,7 +34,7 @@ All you need to know at the moment is a few basics about these three properties.
|
||||
### The _imports_ array
|
||||
|
||||
NgModules are a way to consolidate features that belong together into discrete units.
|
||||
Many features of Angular itself are organized as NgModules.
|
||||
Many features of Angular itself are organized as NgModules.
|
||||
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
|
||||
Eventually you may create a feature module.
|
||||
|
||||
@ -70,7 +70,7 @@ 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_ NgModules—all of them classes decorated
|
||||
It tells Angular about specific _other_ NgModules—all of them classes decorated
|
||||
with `@NgModule`—that the application needs to function properly.
|
||||
|
||||
</div>
|
||||
@ -110,7 +110,7 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
|
||||
|
||||
### The _bootstrap_ array
|
||||
|
||||
You launch the application by [_bootstrapping_](guide/bootstrapping#main) the root `AppModule`.
|
||||
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.
|
||||
|
||||
@ -127,13 +127,6 @@ 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.
|
||||
|
@ -193,8 +193,8 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
|
||||
|
||||
|
||||
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/">SauceLabs</a> and
|
||||
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/">SauceLabs</a> and
|
||||
<a href="https://www.browserstack.com">Browserstack</a>.
|
||||
|
||||
|
||||
@ -215,7 +215,7 @@ that implement missing features in JavaScript.
|
||||
|
||||
|
||||
|
||||
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||
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 can help you determine which polyfills to load, depending on the browsers you target and the features you use.
|
||||
@ -241,7 +241,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<th>
|
||||
Browsers (Desktop & Mobile)
|
||||
</th>
|
||||
@ -253,7 +253,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
Chrome, Firefox, Edge, Safari 9+
|
||||
</td>
|
||||
@ -265,7 +265,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
Safari 7 & 8, IE10 & 11, Android 4.1+
|
||||
</td>
|
||||
@ -279,7 +279,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
IE9
|
||||
</td>
|
||||
@ -309,7 +309,7 @@ Here are the features which may require additional polyfills:
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<th>
|
||||
Feature
|
||||
</th>
|
||||
@ -325,7 +325,7 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
[Animations](guide/animations)
|
||||
@ -363,14 +363,14 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
[NgClass](api/common/NgClass) on SVG elements
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
|
||||
[classList](guide/browser-support#classlist)
|
||||
</td>
|
||||
@ -382,16 +382,17 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Http](guide/http) when sending and receiving binary data
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Typed Array](guide/browser-support#typedarray) <br>[Blob](guide/browser-support#blob)<br>[FormData](guide/browser-support#formdata)
|
||||
[Typed Array](guide/browser-support#typedarray)<br>
|
||||
[Blob](guide/browser-support#blob)<br>
|
||||
[FormData](guide/browser-support#formdata)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -405,7 +406,7 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
|
||||
### Suggested polyfills ##
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
|
||||
|
||||
<table>
|
||||
@ -542,5 +543,5 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
|
||||
|
||||
|
||||
\* Figures are for minified and gzipped code,
|
||||
\* Figures are for minified and gzipped code,
|
||||
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.
|
||||
|
@ -27,12 +27,12 @@ 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 the sample in the Angular CLI.
|
||||
But it's also good in its own right.
|
||||
But it's also good in its own right.
|
||||
It helps clearly separate app code from setup and configuration files.
|
||||
|
||||
All samples now have a `src/` folder at the project root.
|
||||
The former `app/` folder moves under `src/`.
|
||||
Read about moving your existing project to this structure in
|
||||
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="Migrating samples/quickstart app to the src folder">
|
||||
the QuickStart repo update instructions</a>.
|
||||
|
||||
@ -48,8 +48,8 @@ Notably:
|
||||
The new [**Reactive Forms**](guide/reactive-forms) 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](guide/forms) 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,
|
||||
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)
|
||||
@ -65,25 +65,25 @@ Revised samples are clearer and cover all topics discussed.
|
||||
|
||||
## Miscellaneous (2017-01-05)
|
||||
|
||||
* [Setup](guide/setup) guide:
|
||||
added (optional) instructions on how to remove _non-essential_ files.
|
||||
* [Setup](guide/setup) 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 with `./` as a best practice.
|
||||
* [Style Guide](guide/styleguide): copy edits and revised rules.
|
||||
|
||||
## Router: more detail (2016-12-21)
|
||||
|
||||
Added more information to the [Router](guide/router) guide
|
||||
Added more information to the [Router](guide/router) 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](guide/http#override-default-request-options) guide.
|
||||
Added section on how to set default request headers (and other request options) to
|
||||
HTTP 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`.
|
||||
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](guide/testing#live-examples) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
|
||||
|
||||
## Internationalization: pluralization and _select_ (2016-11-30)
|
||||
@ -112,15 +112,15 @@ 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](guide/upgrade) guide covers the
|
||||
new AOT friendly `upgrade/static` module
|
||||
The updated [NgUpgrade Guide](guide/upgrade) 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](guide/ts-to-js) guide
|
||||
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
|
||||
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.
|
||||
@ -156,7 +156,7 @@ in the `in-memory-web-api` repo.
|
||||
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.
|
||||
New `:enter` and `:leave` aliases make animation more natural.
|
||||
|
||||
## Sync with Angular v.2.1.0 (2016-10-12)
|
||||
|
||||
@ -176,11 +176,11 @@ 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](guide/router) guide now locates route configuration
|
||||
in a _Routing Module_.
|
||||
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
|
||||
most conspicuously in the
|
||||
[NgModule](guide/ngmodule) guide and [NgModule FAQ](guide/ngmodule-faq) guide.
|
||||
|
||||
## New "Internationalization" guide (2016-09-30)
|
||||
@ -194,7 +194,7 @@ 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.
|
||||
The new "angular-in-memory-web-api" has new features.
|
||||
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md">Read about them on github</a>.
|
||||
|
||||
## "Style Guide" with _NgModules_ (2016-09-27)
|
||||
@ -215,5 +215,5 @@ modules with SystemJS as the samples currently do.
|
||||
|
||||
## "Lifecycle Hooks" guide simplified (2016-09-24)
|
||||
|
||||
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
|
||||
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
|
||||
draws more attention to the order in which Angular calls the hooks.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Cheat Sheet
|
||||
<h1 class="no-toc">Cheat Sheet</h1>
|
||||
|
||||
<div id="cheatsheet">
|
||||
<table class="is-full-width is-fixed-layout">
|
||||
@ -23,28 +23,28 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes that belong to this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||
<td><p>List of modules to import into this module. Everything from the imported modules
|
||||
is available to <code>declarations</code> of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -56,61 +56,61 @@ is available to <code>declarations</code> of this module.</p>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
|
||||
<code><div [title]="'Hello ' + ponyName"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||
<td><p>Sets up two-way data binding. Equivalent to: <code><my-cmp [title]="name" (titleChange)="name=$event"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
|
||||
<code><ng-template [myUnless]="myExpression"><p>...</p></ng-template></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||
<td><p>An <code><svg></code> root element is detected as an SVG element automatically, without the prefix.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -124,19 +124,19 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
||||
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"></code></td>
|
||||
<td><code><div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"></code></td>
|
||||
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -150,7 +150,7 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -164,19 +164,19 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||
<td><p>Declares that a class is a component and provides metadata about the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
||||
</p>
|
||||
</td>
|
||||
@ -191,13 +191,13 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
|
||||
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
|
||||
<p>Does not support parent-child relationship selectors.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers for this directive and its children.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -212,19 +212,19 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>moduleId:</b> module.id</code></td>
|
||||
<td><code><b>moduleId:</b> module.id</code></td>
|
||||
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers scoped to this component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||
<td><p>Inline template or external template URL of the component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||
<td><p>List of inline CSS styles or external stylesheet URLs for styling the component’s view.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -238,36 +238,36 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||
<td><p>Declares an input property that you can update via property binding (example:
|
||||
<code><my-cmp [myProperty]="someExpression"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code><my-cmp (myEvent)="doSomething()"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -281,39 +281,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||
<td><p>Called after every change to input properties and before processing content or child views.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's or directive's content.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's view. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||
<td><p>Called once, before the instance is destroyed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -325,15 +325,15 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -347,39 +347,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||
<td><p>Marks the location to load the component of the active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -29,7 +29,7 @@ import {HttpClientModule} from '@angular/common/http';
|
||||
export class MyAppModule {}
|
||||
```
|
||||
|
||||
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
|
||||
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
|
||||
into your components and services.
|
||||
|
||||
## Making a request for JSON data
|
||||
@ -50,10 +50,13 @@ The `get()` method on `HttpClient` makes accessing this data straightforward.
|
||||
|
||||
```javascript
|
||||
@Component(...)
|
||||
export class MyComponent implements NgOnInit {
|
||||
export class MyComponent implements OnInit {
|
||||
|
||||
results: string[];
|
||||
|
||||
// Inject HttpClient into your component or service.
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
// Make the HTTP request:
|
||||
this.http.get('/api/items').subscribe(data => {
|
||||
@ -69,7 +72,7 @@ export class MyComponent implements NgOnInit {
|
||||
|
||||
In the above example, the `data['results']` field access stands out because you use bracket notation to access the results field. If you tried to write `data.results`, TypeScript would correctly complain that the `Object` coming back from HTTP does not have a `results` property. That's because while `HttpClient` parsed the JSON response into an `Object`, it doesn't know what shape that object is.
|
||||
|
||||
You can, however, tell `HttpClient` what type the response will be, which is recommended.
|
||||
You can, however, tell `HttpClient` what type the response will be, which is recommended.
|
||||
To do so, first you define an interface with the correct shape:
|
||||
|
||||
```javascript
|
||||
@ -160,7 +163,7 @@ RxJS has a useful operator called `.retry()`, which automatically resubscribes t
|
||||
|
||||
First, import it:
|
||||
|
||||
```js
|
||||
```js
|
||||
import 'rxjs/add/operator/retry';
|
||||
```
|
||||
|
||||
@ -194,7 +197,7 @@ In addition to fetching data from the server, `HttpClient` supports mutating req
|
||||
|
||||
### Making a POST request
|
||||
|
||||
One common operation is to POST data to a server; for example when submitting a form. The code for
|
||||
One common operation is to POST data to a server; for example when submitting a form. The code for
|
||||
sending a POST request is very similar to the code for GET:
|
||||
|
||||
```javascript
|
||||
@ -258,22 +261,22 @@ The above sections detail how to use the basic HTTP functionality in `@angular/c
|
||||
|
||||
### Intercepting all requests or responses
|
||||
|
||||
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
|
||||
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
|
||||
before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.
|
||||
|
||||
#### Writing an interceptor
|
||||
|
||||
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
|
||||
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
|
||||
has a single `intercept()` method. Here is a simple interceptor which does nothing but forward the request through without altering it:
|
||||
|
||||
```javascript
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class NoopInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(Req);
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -316,7 +319,7 @@ An interceptor must pass through all events that it does not understand or inten
|
||||
|
||||
##### Ordering
|
||||
|
||||
When you provide multiple interceptors in an application, Angular applies them in the order that you
|
||||
When you provide multiple interceptors in an application, Angular applies them in the order that you
|
||||
provided them.
|
||||
|
||||
##### Immutability
|
||||
@ -332,10 +335,10 @@ If you have a need to mutate the request body, you need to copy the request body
|
||||
Since requests are immutable, they cannot be modified directly. To mutate them, use `clone()`:
|
||||
|
||||
```javascript
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpError<any>> {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// This is a duplicate. It is exactly the same as the original.
|
||||
const dupReq = req.clone();
|
||||
|
||||
|
||||
// Change the URL and replace 'http://' with 'https://'
|
||||
const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
|
||||
}
|
||||
@ -349,15 +352,15 @@ A common use of interceptors is to set default headers on outgoing responses. Fo
|
||||
|
||||
```javascript
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Get the auth header from the service.
|
||||
const authHeader: this.auth.getAuthorizationHeader();
|
||||
const authHeader = this.auth.getAuthorizationHeader();
|
||||
// Clone the request to add the new header.
|
||||
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
|
||||
// Pass on the cloned request instead of the original request.
|
||||
@ -387,15 +390,15 @@ import 'rxjs/add/operator/do';
|
||||
|
||||
export class TimingInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const elapsed = Date.now();
|
||||
const started = Date.now();
|
||||
return next
|
||||
.handle(req)
|
||||
.do(event => {
|
||||
if (event instanceof HttpResponse) {
|
||||
const time = Date.now() - started;
|
||||
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
|
||||
const elapsed = Date.now() - started;
|
||||
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -413,7 +416,7 @@ abstract class HttpCache {
|
||||
* Returns a cached response, if any, or null if not present.
|
||||
*/
|
||||
abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
|
||||
|
||||
|
||||
/**
|
||||
* Adds or updates the response in the cache.
|
||||
*/
|
||||
@ -427,14 +430,14 @@ An interceptor can apply this cache to outgoing requests.
|
||||
@Injectable()
|
||||
export class CachingInterceptor implements HttpInterceptor {
|
||||
constructor(private cache: HttpCache) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Before doing anything, it's important to only cache GET requests.
|
||||
// Skip this interceptor if the request method isn't GET.
|
||||
if (req.method !== 'GET') {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
|
||||
// First, check the cache to see if this request exists.
|
||||
const cachedResponse = this.cache.get(req);
|
||||
if (cachedResponse) {
|
||||
@ -442,7 +445,7 @@ export class CachingInterceptor implements HttpInterceptor {
|
||||
// the request to the next handler.
|
||||
return Observable.of(cachedResponse);
|
||||
}
|
||||
|
||||
|
||||
// No cached response exists. Go to the network, and cache
|
||||
// the response when it arrives.
|
||||
return next.handle(req).do(event => {
|
||||
@ -466,17 +469,17 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
||||
if (req.method !== 'GET') {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
|
||||
// This will be an Observable of the cached value if there is one,
|
||||
// or an empty Observable otherwise. It starts out empty.
|
||||
let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();
|
||||
|
||||
|
||||
// Check the cache.
|
||||
const cachedResponse = this.cache.get(req);
|
||||
if (cachedResponse) {
|
||||
maybeCachedResponse = Observable.of(cachedResponse);
|
||||
}
|
||||
|
||||
|
||||
// Create an Observable (but don't subscribe) that represents making
|
||||
// the network request and caching the value.
|
||||
const networkResponse = next.handle(req).do(event => {
|
||||
@ -485,7 +488,7 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
||||
this.cache.put(req, event);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Now, combine the two and send the cached response first (if there is
|
||||
// one), and the network response second.
|
||||
return Observable.concat(maybeCachedResponse, networkResponse);
|
||||
@ -506,7 +509,7 @@ const req = new HttpRequest('POST', '/upload/file', file, {
|
||||
});
|
||||
```
|
||||
|
||||
This option enables tracking of progress events. Remember, every progress event triggers
|
||||
This option enables tracking of progress events. Remember, every progress event triggers
|
||||
change detection, so only turn them on if you intend to actually update the UI on each event.
|
||||
|
||||
Next, make the request through the `request()` method of `HttpClient`. The result will be an Observable of events, just like with interceptors:
|
||||
@ -529,11 +532,11 @@ http.request(req).subscribe(event => {
|
||||
|
||||
[Cross-Site Request Forgery (XSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website. `HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
|
||||
|
||||
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
|
||||
to relative URLs but not on GET/HEAD requests or
|
||||
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
|
||||
to relative URLs but not on GET/HEAD requests or
|
||||
on requests with an absolute URL.
|
||||
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
|
||||
cookie with a salt for added security.
|
||||
|
||||
In order to prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.
|
||||
@ -595,23 +598,23 @@ it('expects a GET request', inject([HttpClient, HttpTestingController], (http: H
|
||||
http
|
||||
.get('/data')
|
||||
.subscribe(data => expect(data['name']).toEqual('Test Data'));
|
||||
|
||||
|
||||
// At this point, the request is pending, and no response has been
|
||||
// sent. The next step is to expect that the request happened.
|
||||
const req = httpMock.expectOne('/test');
|
||||
|
||||
const req = httpMock.expectOne('/data');
|
||||
|
||||
// If no request with that URL was made, or if multiple requests match,
|
||||
// expectOne() would throw. However this test makes only one request to
|
||||
// this URL, so it will match and return a mock request. The mock request
|
||||
// can be used to deliver a response or make assertions against the
|
||||
// request. In this case, the test asserts that the request is a GET.
|
||||
expect(req.request.method).toEqual('GET');
|
||||
|
||||
|
||||
// Next, fulfill the request by transmitting a response.
|
||||
req.flush({name: 'Test Data'});
|
||||
|
||||
|
||||
// Finally, assert that there are no outstanding requests.
|
||||
mockHttp.verify();
|
||||
httpMock.verify();
|
||||
}));
|
||||
```
|
||||
|
||||
@ -619,7 +622,7 @@ The last step, verifying that no requests remain outstanding, is common enough f
|
||||
|
||||
```javascript
|
||||
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
|
||||
mockHttp.verify();
|
||||
httpMock.verify();
|
||||
}));
|
||||
```
|
||||
|
||||
@ -628,7 +631,7 @@ afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
|
||||
If matching by URL isn't sufficient, it's possible to implement your own matching function. For example, you could look for an outgoing request that has an Authorization header:
|
||||
|
||||
```javascript
|
||||
const req = mockHttp.expectOne((req) => req.headers.has('Authorization'));
|
||||
const req = httpMock.expectOne((req) => req.headers.has('Authorization'));
|
||||
```
|
||||
|
||||
Just as with the `expectOne()` by URL in the test above, if 0 or 2+ requests match this expectation, it will throw.
|
||||
@ -639,7 +642,7 @@ If you need to respond to duplicate requests in your test, use the `match()` API
|
||||
|
||||
```javascript
|
||||
// Expect that 5 pings have been made and flush them.
|
||||
const reqs = mockHttp.match('/ping');
|
||||
const reqs = httpMock.match('/ping');
|
||||
expect(reqs.length).toBe(5);
|
||||
reqs.forEach(req => req.flush());
|
||||
```
|
||||
|
@ -397,8 +397,7 @@ 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
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) and [HTTP Client](guide/http#oninit)
|
||||
guides show how.
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) guide shows how.
|
||||
|
||||
|
||||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||
|
@ -615,7 +615,7 @@ Once the application begins, the app root injector is closed to new providers.
|
||||
|
||||
Time passes and application logic triggers lazy loading of a module.
|
||||
Angular must add the lazy-loaded module's providers to an injector somewhere.
|
||||
It can't added them to the app root injector because that injector is closed to new providers.
|
||||
It can't add them to the app root injector because that injector is closed to new providers.
|
||||
So Angular creates a new child injector for the lazy-loaded module context.
|
||||
|
||||
|
||||
|
@ -163,7 +163,6 @@ without waiting for Angular updates.
|
||||
***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 you even have a server).
|
||||
Read about it in the [HTTP Client](guide/http#in-mem-web-api) 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*.
|
||||
|
@ -255,11 +255,11 @@ During each navigation, the `Router` emits navigation events through the `Router
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<code>RouteConfigLoadStart</code>
|
||||
<code>RouteConfigLoadEnd</code>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An [event](api/router/RouteConfigLoadStart) triggered after a route has been lazy loaded.
|
||||
An [event](api/router/RouteConfigLoadEnd) triggered after a route has been lazy loaded.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -178,7 +178,7 @@ For a discussion of the unit testing setup files, [see below](guide/testing#setu
|
||||
{@a isolated-v-testing-utilities}
|
||||
|
||||
|
||||
### Isolated unit tests vs. the Angular testing utilites
|
||||
### Isolated unit tests vs. the Angular testing utilities
|
||||
|
||||
[Isolated unit tests](guide/testing#isolated-unit-tests "Unit testing without the Angular testing utilities")
|
||||
examine an instance of a class all by itself without any dependence on Angular or any injected values.
|
||||
|
@ -247,12 +247,10 @@ next to the original _ES5_ version for comparison:
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a name-constructor}
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
{@a name-constructor}
|
||||
|
||||
### Name the constructor
|
||||
|
||||
A **named** constructor displays clearly in the console log
|
||||
if the component throws a runtime error.
|
||||
An **unnamed** constructor displays as an anonymous function, for example, `class0`,
|
||||
|
BIN
aio/content/images/bios/gerardsans.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 4.1 KiB |
@ -573,5 +573,14 @@
|
||||
"website": "http://www.methotic.com",
|
||||
"bio": "Thierry is a senior consultant and trainer, specialized on Angular, and a Google Developer Expert.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"gerardsans": {
|
||||
"name": "Gerard Sans",
|
||||
"picture": "gerardsans.jpg",
|
||||
"twitter": "gerardsans",
|
||||
"website": "https://medium.com/@gerard.sans",
|
||||
"bio": "Gerard is very excited about the future of the Web and JavaScript. Always happy Computer Science Engineer and humble Google Developer Expert. He loves to share his learnings by giving talks, trainings and writing about cool technologies. He loves running AngularZone and GraphQL London, mentoring students and giving back to the community.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Cross Platform</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Speed and Performance</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Productivity</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Full Development Story</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
|
@ -110,7 +110,7 @@
|
||||
|
||||
<a href="guide/quickstart">
|
||||
<div class="card">
|
||||
<img src="../assets/images/icons/code-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/code-icon.svg" height="70px">
|
||||
<div class="card-text-container">
|
||||
<div class="text-headline">Get Started</div>
|
||||
<p>Start building your Angular application.</p>
|
||||
|
@ -1,116 +0,0 @@
|
||||
<header class="hero background-sky">
|
||||
<h1 class="hero-title no-toc">News</h1>
|
||||
<div class="clear"></div>
|
||||
</header>
|
||||
<artice>
|
||||
<div class="grid-fluid l-space-bottom-2">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Core Team</h3></div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 12, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/10/angular-210-now-available.html">Angular
|
||||
2.1.0 Now Available</a></div>
|
||||
<p>Angular version 2.1.0 - incremental-metamorphosis - is a minor release following our
|
||||
announced adoption of Semantic Versioning...</p>
|
||||
<div class="author"><img src="generated/images/bios/stephenfluin.jpg">
|
||||
<div class="posted">Posted by <b>Stephen Fluin</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 7, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/10/versioning-and-releasing-angular.html">Versioning
|
||||
and Releasing Angular</a></div>
|
||||
<p>In order for the ecosystem around Angular to thrive, developers need stability from the
|
||||
Angular framework so that reusable components and libraries, tools and learned practices
|
||||
don’t go obsolete unexpectedly...</p>
|
||||
<div class="author"><img src="generated/images/bios/igor-minar.jpg">
|
||||
<div class="posted">Posted by <b>Igor Minar</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Developer Community</h3>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 30, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="https://www.thepolyglotdeveloper.com/2016/10/use-pre-populated-sqlite-database-nativescript-angular-2/">Use
|
||||
A Pre-Populated SQLite Database With NativeScript And Angular 2</a></div>
|
||||
<p>I figured it would be a good idea to demonstrate how to ship a NativeScript Angular 2
|
||||
application with a pre-filled SQLite database rather than populating it on-the-fly....</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Nic Raboy</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 13, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html">Two-way
|
||||
Data Binding in Angular 2</a></div>
|
||||
<p>If there was one feature in Angular that made us go “Wow”, then it was probably its
|
||||
two-way data binding system. Changes in the application state have been automagically
|
||||
reflected into the view...</p>
|
||||
<div class="author"><img src="generated/images/bios/angular-gde-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Pascal Precht</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 10, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://www.creativebloq.com/how-to/build-a-material-design-app-with-angular-2">Build
|
||||
a Material Design app with Angular 2</a></div>
|
||||
<p>This walkthrough reveals how to create a DialogComponent and to-do app with Angular
|
||||
Material and the Angular CLI...</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Daniel Zen</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Sept 30, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://www.simb.co/angular-cli-using-docker/?platform=hootsuite">Using
|
||||
Angular CLI to create Angular 2 applications in Docker</a></div>
|
||||
<p>Angular CLI is a great tool for developing Angular 2 applications. I thought it would be
|
||||
fun to do a quick demo...</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Simeon Bateman</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase">Twitter</h3></div>
|
||||
<div class="clear"></div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c3"><p></p></div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="title"><a href="http://twitter.com/angularjs" data-show-count="false"
|
||||
class="twitter-follow-button">Follow @angularjs</a></div>
|
||||
<p><a class="twitter-timeline" data-chrome="nofooter noborders noheader"
|
||||
href="http://twitter.com/angularjs" data-widget-id="700150278465523713"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
@ -17,7 +17,7 @@
|
||||
"title": "Events"
|
||||
},
|
||||
{
|
||||
"url": "https://blog.angularjs.org/",
|
||||
"url": "https://blog.angular.io/",
|
||||
"title": "Blog"
|
||||
}
|
||||
],
|
||||
@ -39,7 +39,7 @@
|
||||
"title": "Events"
|
||||
},
|
||||
{
|
||||
"url": "https://blog.angularjs.org/",
|
||||
"url": "https://blog.angular.io/",
|
||||
"title": "Blog"
|
||||
}
|
||||
]
|
||||
|
@ -137,7 +137,7 @@ Create a file in the `app` folder called `hero.service.ts`.
|
||||
|
||||
|
||||
The naming convention for service files is the service name in lowercase followed by `.service`.
|
||||
For a multi-word service name, use lower [dash-case](guide/glossary).
|
||||
For a multi-word service name, use lower [dash-case](guide/glossary#dash-case).
|
||||
For example, the filename for `SpecialSuperHeroService` is `special-super-hero.service.ts`.
|
||||
|
||||
</div>
|
||||
|
@ -83,10 +83,6 @@ Added hero "Zero" to confirm that the data service can handle a hero with `id==0
|
||||
Don't worry about the details of this backend substitution; you can
|
||||
skip it when you have a real web API server.
|
||||
|
||||
Read more about the in-memory web API in the
|
||||
[Appendix: Tour of Heroes in-memory web api](guide/http#in-mem-web-api)
|
||||
section of the [HTTP Client](guide/http#in-mem-web-api) page.
|
||||
|
||||
</div>
|
||||
|
||||
## Heroes and HTTP
|
||||
|
@ -81,7 +81,7 @@
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.20.0-rc.5",
|
||||
"dgeni-packages": "^0.20.0-rc.6",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -4,12 +4,8 @@ set -eu -o pipefail
|
||||
|
||||
readonly thisDir=$(cd $(dirname $0); pwd)
|
||||
readonly parentDir=$(dirname $thisDir)
|
||||
readonly TOKEN=${ANGULAR_PAYLOAD_FIREBASE_TOKEN:-}
|
||||
readonly PROJECT_NAME="angular-payload-size"
|
||||
|
||||
# temporarily turn on debugging - we disable it later in the script to prevent token leak
|
||||
set -x
|
||||
|
||||
source ${thisDir}/_payload-limits.sh
|
||||
|
||||
failed=false
|
||||
@ -45,15 +41,21 @@ timestamp=$(date +%s)
|
||||
payloadData="$payloadData\"timestamp\": $timestamp, "
|
||||
|
||||
# Add change source: application, dependencies, or 'application+dependencies'
|
||||
applicationChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | grep -v ${parentDir}/content | grep -v ${parentDir}/yarn.lock | wc -l)
|
||||
dependencyChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE ${parentDir}/yarn.lock | wc -l)
|
||||
applicationChanged=false
|
||||
dependenciesChanged=false
|
||||
if [[ $(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | grep -v aio/yarn.lock | grep -v content) ]]; then
|
||||
applicationChanged=true
|
||||
fi
|
||||
if [[ $(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir/yarn.lock) ]]; then
|
||||
dependenciesChanged=true
|
||||
fi
|
||||
|
||||
if [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -eq 0 ]]; then
|
||||
if $dependenciesChanged && $applicationChanged; then
|
||||
change='application+dependencies'
|
||||
elif $dependenciesChanged; then
|
||||
# only yarn.lock changed
|
||||
change='dependencies'
|
||||
elif [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -gt 0 ]]; then
|
||||
change='application+dependencies'
|
||||
elif [[ $applicationChanges -gt 0 ]]; then
|
||||
elif $applicationChanged; then
|
||||
change='application'
|
||||
else
|
||||
# Nothing changed in aio/
|
||||
@ -71,7 +73,7 @@ if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
|
||||
|
||||
# WARNING: FIREBASE_TOKEN should NOT be printed.
|
||||
set +x
|
||||
firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$TOKEN" $dbPath
|
||||
firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$ANGULAR_PAYLOAD_FIREBASE_TOKEN" $dbPath
|
||||
fi
|
||||
|
||||
if [[ $failed = true ]]; then
|
||||
|
@ -19,11 +19,6 @@ const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
// Constants
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
// Specify the path to Chrome on Travis
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
|
@ -632,7 +632,6 @@ describe('AppComponent', () => {
|
||||
it('should initialize the search worker', inject([SearchService], (searchService: SearchService) => {
|
||||
fixture.detectChanges(); // triggers ngOnInit
|
||||
expect(searchService.initWorker).toHaveBeenCalled();
|
||||
expect(searchService.loadIndex).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -48,7 +48,7 @@ export class AppComponent implements OnInit {
|
||||
* the styling of individual pages.
|
||||
* You will get three classes:
|
||||
*
|
||||
* * `page-...`: computed from the current document id (e.g. news, guide-security, tutorial-toh-pt2)
|
||||
* * `page-...`: computed from the current document id (e.g. events, guide-security, tutorial-toh-pt2)
|
||||
* * `folder-...`: computed from the top level folder for an id (e.g. guide, tutorial, etc)
|
||||
* * `view-...`: computef from the navigation view (e.g. SideNav, TopBar, etc)
|
||||
*/
|
||||
@ -111,8 +111,8 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
// Do not initialize the search on browsers that lack web worker support
|
||||
if ('Worker' in window) {
|
||||
this.searchService.initWorker('app/search/search-worker.js');
|
||||
this.searchService.loadIndex();
|
||||
// Delay initialization by up to 2 seconds
|
||||
this.searchService.initWorker('app/search/search-worker.js', 2000);
|
||||
}
|
||||
|
||||
this.onResize(window.innerWidth);
|
||||
|
@ -65,6 +65,13 @@ describe('CodeExampleComponent', () => {
|
||||
expect(actual).toBe('Great Example');
|
||||
});
|
||||
|
||||
it('should remove the `title` attribute after initialisation', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example title="Great Example"></code-example>'}});
|
||||
createComponent(oneLineCode);
|
||||
expect(codeExampleDe.nativeElement.getAttribute('title')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should pass hideCopy to CodeComonent', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example hideCopy="true"></code-example>'}});
|
||||
|
@ -45,6 +45,8 @@ export class CodeExampleComponent implements OnInit {
|
||||
this.path = element.getAttribute('path') || '';
|
||||
this.region = element.getAttribute('region') || '';
|
||||
this.title = element.getAttribute('title') || '';
|
||||
// Now remove the title attribute to prevent unwanted tooltip popups when hovering over the code.
|
||||
element.removeAttribute('title');
|
||||
|
||||
this.isAvoid = this.path.indexOf('.avoid.') !== -1;
|
||||
this.hideCopy = this.isAvoid || getBoolFromAttribute(element, ['hidecopy', 'hide-copy']);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchBoxComponent } from './search-box.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
@ -36,30 +36,67 @@ describe('SearchBoxComponent', () => {
|
||||
});
|
||||
|
||||
describe('initialisation', () => {
|
||||
it('should get the current search query from the location service', inject([LocationService], (location: MockLocationService) => {
|
||||
it('should get the current search query from the location service',
|
||||
inject([LocationService], (location: MockLocationService) => fakeAsync(() => {
|
||||
location.search.and.returnValue({ search: 'initial search' });
|
||||
component.ngOnInit();
|
||||
expect(location.search).toHaveBeenCalled();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('initial search');
|
||||
expect(component.searchBox.nativeElement.value).toEqual('initial search');
|
||||
})));
|
||||
});
|
||||
|
||||
describe('onSearch', () => {
|
||||
it('should debounce by 300ms', fakeAsync(() => {
|
||||
component.doSearch();
|
||||
expect(host.searchHandler).not.toHaveBeenCalled();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should pass through the value of the input box', fakeAsync(() => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (input)';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (input)');
|
||||
}));
|
||||
|
||||
it('should only send events if the search value has changed', fakeAsync(() => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = 'some query';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.nativeElement.value = 'some other query';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('on input', () => {
|
||||
it('should trigger the onSearch event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (input)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (input)');
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('on keyup', () => {
|
||||
it('should trigger the onSearch event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (keyup)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('keyup', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (keyup)');
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -73,28 +110,11 @@ describe('SearchBoxComponent', () => {
|
||||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('should trigger the search event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (click)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('click', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (click)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('event filtering', () => {
|
||||
it('should only send events if the search value has changed', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = 'some query';
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.nativeElement.value = 'some other query';
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(2);
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -26,10 +26,11 @@ import 'rxjs/add/operator/distinctUntilChanged';
|
||||
})
|
||||
export class SearchBoxComponent implements OnInit {
|
||||
|
||||
private searchDebounce = 300;
|
||||
private searchSubject = new Subject<string>();
|
||||
|
||||
@ViewChild('searchBox') searchBox: ElementRef;
|
||||
@Output() onSearch = this.searchSubject.distinctUntilChanged();
|
||||
@Output() onSearch = this.searchSubject.distinctUntilChanged().debounceTime(this.searchDebounce);
|
||||
@Output() onFocus = new EventEmitter<string>();
|
||||
|
||||
constructor(private locationService: LocationService) { }
|
||||
|
@ -1,24 +1,62 @@
|
||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SearchService } from './search.service';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
describe('SearchService', () => {
|
||||
|
||||
let injector: ReflectiveInjector;
|
||||
let service: SearchService;
|
||||
let sendMessageSpy: jasmine.Spy;
|
||||
let mockWorker: WebWorkerClient;
|
||||
|
||||
beforeEach(() => {
|
||||
sendMessageSpy = jasmine.createSpy('sendMessage').and.returnValue(Observable.of({}));
|
||||
mockWorker = { sendMessage: sendMessageSpy } as any;
|
||||
spyOn(WebWorkerClient, 'create').and.returnValue(mockWorker);
|
||||
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
SearchService,
|
||||
{ provide: NgZone, useFactory: () => new NgZone({ enableLongStackTrace: false }) }
|
||||
]);
|
||||
service = injector.get(SearchService);
|
||||
});
|
||||
|
||||
describe('loadIndex', () => {
|
||||
it('should send a "load-index" message to the worker');
|
||||
it('should connect the `ready` property to the response to the "load-index" message');
|
||||
describe('initWorker', () => {
|
||||
it('should create the worker and load the index after the specified delay', fakeAsync(() => {
|
||||
service.initWorker('some/url', 100);
|
||||
expect(WebWorkerClient.create).not.toHaveBeenCalled();
|
||||
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(WebWorkerClient.create).toHaveBeenCalledWith('some/url', jasmine.any(NgZone));
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should send a "query-index" message to the worker');
|
||||
it('should push the response to the `searchResults` observable');
|
||||
beforeEach(() => {
|
||||
// We must initialize the service before calling search
|
||||
service.initWorker('some/url', 100);
|
||||
});
|
||||
|
||||
it('should trigger a `loadIndex` synchronously', () => {
|
||||
service.search('some query');
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
});
|
||||
|
||||
it('should send a "query-index" message to the worker', () => {
|
||||
service.search('some query');
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('query-index', 'some query');
|
||||
});
|
||||
|
||||
it('should push the response to the `searchResults` observable', () => {
|
||||
const mockSearchResults = { results: ['a', 'b'] };
|
||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
||||
let searchResults: any;
|
||||
service.searchResults.subscribe(results => searchResults = results);
|
||||
service.search('some query');
|
||||
expect(searchResults).toEqual(mockSearchResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,8 +7,10 @@ can be found in the LICENSE file at http://angular.io/license
|
||||
import { NgZone, Injectable, Type } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/operator/publishLast';
|
||||
import 'rxjs/add/observable/race';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
export interface SearchResults {
|
||||
@ -27,26 +29,50 @@ export interface SearchResult {
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
private worker: WebWorkerClient;
|
||||
private ready: Observable<boolean>;
|
||||
private resultsSubject = new ReplaySubject<SearchResults>(1);
|
||||
readonly searchResults = this.resultsSubject.asObservable();
|
||||
private searchesSubject = new ReplaySubject<string>(1);
|
||||
searchResults: Observable<SearchResults>;
|
||||
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
initWorker(workerUrl) {
|
||||
this.worker = new WebWorkerClient(new Worker(workerUrl), this.zone);
|
||||
}
|
||||
|
||||
loadIndex() {
|
||||
const ready = this.ready = this.worker.sendMessage<boolean>('load-index').publishLast();
|
||||
// trigger the index to be loaded immediately
|
||||
ready.connect();
|
||||
/**
|
||||
* Initialize the search engine. We offer an `initDelay` to prevent the search initialisation from delaying the
|
||||
* initial rendering of the web page. Triggering a search will override this delay and cause the index to be
|
||||
* loaded immediately.
|
||||
*
|
||||
* @param workerUrl the url of the WebWorker script that runs the searches
|
||||
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
|
||||
*/
|
||||
initWorker(workerUrl: string, initDelay: number) {
|
||||
const searchResults = Observable
|
||||
// Wait for the initDelay or the first search
|
||||
.race(
|
||||
Observable.timer(initDelay),
|
||||
this.searchesSubject.first()
|
||||
)
|
||||
.concatMap(() => {
|
||||
// Create the worker and load the index
|
||||
const worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return worker.sendMessage('load-index').concatMap(() =>
|
||||
// Once the index has loaded, switch to listening to the searches coming in
|
||||
this.searchesSubject.switchMap((query) =>
|
||||
// Each search gets switched to a web worker message, whose results are returned via an observable
|
||||
worker.sendMessage<SearchResults>('query-index', query)
|
||||
)
|
||||
);
|
||||
}).publish();
|
||||
|
||||
// Connect to the observable to kick off the timer
|
||||
searchResults.connect();
|
||||
|
||||
// Expose the connected observable to the rest of the world
|
||||
this.searchResults = searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search query to the index.
|
||||
* The results will appear on the `searchResults` observable.
|
||||
*/
|
||||
search(query: string) {
|
||||
this.ready.concatMap(ready => {
|
||||
return this.worker.sendMessage('query-index', query) as Observable<SearchResults>;
|
||||
}).subscribe(results => this.resultsSubject.next(results));
|
||||
this.searchesSubject.next(query);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,11 @@ export interface WebWorkerMessage {
|
||||
export class WebWorkerClient {
|
||||
private nextId = 0;
|
||||
|
||||
constructor(private worker: Worker, private zone: NgZone) {
|
||||
static create(workerUrl: string, zone: NgZone) {
|
||||
return new WebWorkerClient(new Worker(workerUrl), zone);
|
||||
}
|
||||
|
||||
private constructor(private worker: Worker, private zone: NgZone) {
|
||||
}
|
||||
|
||||
sendMessage<T>(type: string, payload?: any): Observable<T> {
|
||||
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
@ -17,6 +17,7 @@ aio-shell.page-docs {
|
||||
|
||||
.sidenav-content {
|
||||
min-height: 450px;
|
||||
padding: 80px 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,11 @@ aio-code pre {
|
||||
}
|
||||
}
|
||||
|
||||
.code-tab-group div.mat-tab-body-content {
|
||||
.code-tab-group .mat-tab-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-tab-group .mat-tab-body-content {
|
||||
height: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ table {
|
||||
background-color: $lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
th {
|
||||
@ -80,6 +80,35 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
#cheatsheet table tbody td {
|
||||
overflow: auto;
|
||||
}
|
||||
#cheatsheet {
|
||||
|
||||
table tbody td {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 990px) {
|
||||
|
||||
/* Force table to not be like tables anymore */
|
||||
table, thead, tbody, tfoot, tr, th, td {
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
|
||||
code {
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
th, td {
|
||||
&:not(:last-child) {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
aio/tools/RELEASE.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Docs releases
|
||||
|
||||
This document explains how to update the documentation examples after an Angular release. This is only needed for major and minor versions.
|
||||
|
||||
All the packages for the docs' examples are specified in `/aio/tools/examples/shared/package.json`
|
||||
|
||||
**1)** So within the `shared` folder, you need to issue the following command:
|
||||
|
||||
```
|
||||
$ yarn upgrade-interactive --tilde
|
||||
```
|
||||
|
||||
There, select all the packages that are updated on the new Angular release.
|
||||
|
||||
**2)** Changes to the tsconfig.json? There is one to update at `/aio/tools/examples/shared/boilerplate/src/tsconfig.json`
|
||||
|
||||
**3)** The file `/aio/tools/examples/shared/boilerplate/src/systemjs.config.web.js` contains the configuration for plunkers. It has some hardcoded versions that could be updated.
|
||||
|
||||
**4)** As in step 3, more hardcoded versions at `/aio/tools/plunker-builder/translator/rules/indexHtml.js`
|
@ -12,19 +12,19 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "~4.2.0",
|
||||
"@angular/common": "~4.2.0",
|
||||
"@angular/compiler": "~4.2.0",
|
||||
"@angular/compiler-cli": "~4.2.0",
|
||||
"@angular/core": "~4.2.0",
|
||||
"@angular/forms": "~4.2.0",
|
||||
"@angular/http": "~4.2.0",
|
||||
"@angular/platform-browser": "~4.2.0",
|
||||
"@angular/platform-browser-dynamic": "~4.2.0",
|
||||
"@angular/platform-server": "~4.2.0",
|
||||
"@angular/router": "~4.2.0",
|
||||
"@angular/tsc-wrapped": "~4.2.0",
|
||||
"@angular/upgrade": "~4.2.0",
|
||||
"@angular/animations": "~4.3.1",
|
||||
"@angular/common": "~4.3.1",
|
||||
"@angular/compiler": "~4.3.1",
|
||||
"@angular/compiler-cli": "~4.3.1",
|
||||
"@angular/core": "~4.3.1",
|
||||
"@angular/forms": "~4.3.1",
|
||||
"@angular/http": "~4.3.1",
|
||||
"@angular/platform-browser": "~4.3.1",
|
||||
"@angular/platform-browser-dynamic": "~4.3.1",
|
||||
"@angular/platform-server": "~4.3.1",
|
||||
"@angular/router": "~4.3.1",
|
||||
"@angular/tsc-wrapped": "~4.3.1",
|
||||
"@angular/upgrade": "~4.3.1",
|
||||
"angular-in-memory-web-api": "~0.3.2",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^5.1.0",
|
||||
|
@ -20,11 +20,7 @@ exports.config = {
|
||||
|
||||
// Capabilities to be passed to the webdriver instance.
|
||||
capabilities: {
|
||||
'browserName': 'chrome',
|
||||
// For Travis
|
||||
chromeOptions: {
|
||||
binary: process.env.CHROME_BIN
|
||||
}
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
|
||||
// Framework to use. Jasmine is recommended.
|
||||
|
@ -1,10 +1,12 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
# yarn v0.25.3
|
||||
# node v7.8.0
|
||||
|
||||
|
||||
"@angular/animations@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.2.0.tgz#e964fc56c9621f28679f24d5e69026e2d1571425"
|
||||
"@angular/animations@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.1.tgz#1f7e0bb803efc21c608246e6765a1c647f3d1a5f"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
@ -74,79 +76,79 @@
|
||||
optionalDependencies:
|
||||
node-sass "^4.3.0"
|
||||
|
||||
"@angular/common@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.2.0.tgz#5df34718bcefc49918bfcb2683f6c19720b66a61"
|
||||
"@angular/common@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.1.tgz#260f487a7cdca326c436bd3ea9515c797de2ff72"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/compiler-cli@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.2.0.tgz#bd6f6b71f003df48a8f86184a8c16533afdf23ec"
|
||||
"@angular/compiler-cli@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.3.1.tgz#00b41afb6faeb4aef561b8427804ac8880aff63c"
|
||||
dependencies:
|
||||
"@angular/tsc-wrapped" "4.2.0"
|
||||
"@angular/tsc-wrapped" "4.3.1"
|
||||
minimist "^1.2.0"
|
||||
reflect-metadata "^0.1.2"
|
||||
|
||||
"@angular/compiler@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.2.0.tgz#a21df81995b210f822ffd70b57247d474876fbed"
|
||||
"@angular/compiler@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.1.tgz#3a24d49ecf01ac2b6e07f63e378b8ff8e257fe09"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/core@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.2.0.tgz#8bf57d01379c2a9e29476ad569dec9e20d5b17dc"
|
||||
"@angular/core@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.1.tgz#a9d0a7d644b96260674269b689a04feea632a8d3"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/forms@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.2.0.tgz#cb3ae69172e254452fa77578605ebc1bb72138c9"
|
||||
"@angular/forms@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.1.tgz#33914da2cb146430ff901471e682c76654622dfe"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/http@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.2.0.tgz#484af53639e04a68834c5167a1955d2d0cde8e1c"
|
||||
"@angular/http@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.3.1.tgz#e4f661f746711e88ecbea76a3c905babf97d315a"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/platform-browser-dynamic@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.2.0.tgz#b84c05616bd824e15b52b2b85c47b58b25d0158a"
|
||||
"@angular/platform-browser-dynamic@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.1.tgz#84034da60a82ef36e7effda7b3ade6e645b330b3"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/platform-browser@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.2.0.tgz#dfd782e7ebacba1bbe2ae0556d5d7fb012f2a6cd"
|
||||
"@angular/platform-browser@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.1.tgz#db727b06eed64bda5defec71815db26a4da2f690"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/platform-server@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.2.0.tgz#90add7fcd9c4f568a31058c9d93a9aca09eb4c58"
|
||||
"@angular/platform-server@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.3.1.tgz#3b915fc4013c6a947a8c147b4db0279b025936eb"
|
||||
dependencies:
|
||||
parse5 "^3.0.1"
|
||||
tslib "^1.7.1"
|
||||
xhr2 "^0.1.4"
|
||||
|
||||
"@angular/router@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.2.0.tgz#7cda9a23621ee41b466eced8bb4cbb62237ba6b9"
|
||||
"@angular/router@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.3.1.tgz#5219d44526156d816065841127610165a015b450"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@angular/tsc-wrapped@4.2.0", "@angular/tsc-wrapped@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.2.0.tgz#e62ce9953c27ba96e4c6daf117f10e31169ccea2"
|
||||
"@angular/tsc-wrapped@4.3.1", "@angular/tsc-wrapped@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.3.1.tgz#f6616a4d2a3bbec1cded664fd1f526edce99ef41"
|
||||
dependencies:
|
||||
tsickle "^0.21.0"
|
||||
|
||||
"@angular/upgrade@~4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-4.2.0.tgz#2766c8a8bb507146b6d38bed2c379f77bbc70335"
|
||||
"@angular/upgrade@~4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-4.3.1.tgz#db80f9d428e4801b0a41618df71bf899882f39c8"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
|
@ -20,14 +20,16 @@ module.exports = function addImageDimensions(getImageDimensions) {
|
||||
const src = props.src;
|
||||
if (!src) {
|
||||
file.message('Missing src in image tag `' + source(node, file) + '`');
|
||||
} else if (props.width === undefined && props.height === undefined) {
|
||||
} else {
|
||||
try {
|
||||
const dimensions = getImageDimensions(addImageDimensionsImpl.basePath, src);
|
||||
props.width = '' + dimensions.width;
|
||||
props.height = '' + dimensions.height;
|
||||
if (props.width === undefined && props.height === undefined) {
|
||||
props.width = '' + dimensions.width;
|
||||
props.height = '' + dimensions.height;
|
||||
}
|
||||
} catch(e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
file.message('Unable to load src in image tag `' + source(node, file) + '`');
|
||||
file.fail('Unable to load src in image tag `' + source(node, file) + '`');
|
||||
} else {
|
||||
file.fail(e.message);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ describe('addImageDimensions post-processor', () => {
|
||||
expect(log.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log a warning for images whose source cannot be loaded', () => {
|
||||
it('should fail for images whose source cannot be loaded', () => {
|
||||
getImageDimensionsSpy.and.callFake(() => {
|
||||
const error = new Error('no such file or directory');
|
||||
error.code = 'ENOENT';
|
||||
@ -68,13 +68,8 @@ describe('addImageDimensions post-processor', () => {
|
||||
docType: 'a',
|
||||
renderedContent: '<img src="missing">'
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(() => processor.$process(docs)).toThrowError('Unable to load src in image tag `<img src="missing">` - doc (a) ');
|
||||
expect(getImageDimensionsSpy).toHaveBeenCalled();
|
||||
expect(docs).toEqual([jasmine.objectContaining({
|
||||
docType: 'a',
|
||||
renderedContent: '<img src="missing">'
|
||||
})]);
|
||||
expect(log.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore images with width or height attributes', () => {
|
||||
@ -87,7 +82,6 @@ describe('addImageDimensions post-processor', () => {
|
||||
`
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(getImageDimensionsSpy).not.toHaveBeenCalled();
|
||||
expect(docs).toEqual([jasmine.objectContaining({
|
||||
docType: 'a',
|
||||
renderedContent: `
|
||||
|