Compare commits
265 Commits
6.0.0-beta
...
5.2x
Author | SHA1 | Date | |
---|---|---|---|
5c89d6bffa | |||
3e6a86fb0a | |||
a7ebf5aadd | |||
b42921bbd2 | |||
722dec11b0 | |||
9e6268ba59 | |||
435f6eecd2 | |||
1c1cbba04b | |||
3b692a55a7 | |||
69a0578e00 | |||
b5ca275590 | |||
519f022b02 | |||
236a9320df | |||
28ac24444f | |||
99909bbf2c | |||
ee60bb5b36 | |||
f6120c09e7 | |||
e2bdef4cf6 | |||
8115edc82f | |||
a8b5465e24 | |||
9ce495b3d8 | |||
d40263447d | |||
31c5c1060a | |||
c9ebd60435 | |||
5a14e2238f | |||
3ceee99e22 | |||
28b23f954c | |||
ad17e5e791 | |||
c30d329faa | |||
991300b86c | |||
7c45db3a19 | |||
67cf11d071 | |||
49082d7ab2 | |||
6b627f67db | |||
5c320b4c2a | |||
ac2b04a5ab | |||
a63b764b54 | |||
2654357c72 | |||
4ec40c6ab2 | |||
80d424798e | |||
7fa2d4b503 | |||
f4845fae12 | |||
f693be3996 | |||
a73d5308e0 | |||
e1bf067090 | |||
884de18cba | |||
dfa2fb95d5 | |||
2639b4bffb | |||
978f97cc59 | |||
f1a063298e | |||
d241532488 | |||
f755db78dc | |||
5dd2b5135d | |||
7ac34e42a0 | |||
029dbf0e18 | |||
bba65e0f41 | |||
a069e08354 | |||
03d93c96a3 | |||
020338230f | |||
a1d86daa71 | |||
7078fbffb4 | |||
0aa9b46b79 | |||
831592c381 | |||
f628797d91 | |||
47f51c2ead | |||
ba9cd5bbc4 | |||
b54ad053f9 | |||
5b8eb9c5c7 | |||
0b683123d2 | |||
363498b6b4 | |||
a1bb56f739 | |||
5bb9fcad3e | |||
f4697f351e | |||
1d571b299d | |||
3a0b5a928c | |||
265ac8a106 | |||
fa7d8907d0 | |||
0220ce7002 | |||
3bd0b2ab28 | |||
a589ca0adb | |||
72f8abd7b3 | |||
20a900b648 | |||
6435ecd3c6 | |||
16d1700a8e | |||
b75cf3f70b | |||
4f19491fec | |||
8f36fd1374 | |||
2de0d4c1db | |||
5e4af7c550 | |||
8ec21fc325 | |||
eb48750705 | |||
be59c3a98c | |||
b333919722 | |||
235a235fab | |||
2d5e7d1b52 | |||
647b8595d0 | |||
0a1a397cd7 | |||
7f9b1b78f6 | |||
1e9484673d | |||
88bec238ac | |||
62e7b9da1e | |||
61341b2791 | |||
92a5876f51 | |||
a57df4ee20 | |||
92d7060cb0 | |||
7e9b120452 | |||
b081dfe705 | |||
4a4d749710 | |||
c878d55397 | |||
263a2eca88 | |||
44154e71fd | |||
0b2f7d13d0 | |||
420cc7afc6 | |||
5fc77c90cb | |||
c3484450b8 | |||
fbef94a8ee | |||
aa456edafc | |||
7007f51c35 | |||
bc1e22922a | |||
cf8d512e43 | |||
0b1f5d2127 | |||
dcf64a0d01 | |||
a9545aba4d | |||
d9ae70c699 | |||
a751649c8d | |||
3f5a3d6ea1 | |||
10a014d89e | |||
8feb8e5408 | |||
16dada28f5 | |||
67cf7128ae | |||
16e5b866d2 | |||
83d43ac850 | |||
cd25939be9 | |||
b58c3527e9 | |||
efc67ee5ef | |||
7a406a3896 | |||
98001a065d | |||
e442881ead | |||
b37cee36f9 | |||
e56de1025a | |||
64ae6d206e | |||
54a14312d1 | |||
7e95802cc1 | |||
e3e7044d06 | |||
eb3bfc25be | |||
94d769de71 | |||
66191e8a37 | |||
bec188506c | |||
4f869ff755 | |||
8f6047340e | |||
9744a1c966 | |||
0bcfae7cac | |||
140e7c00d1 | |||
941e88ff79 | |||
71ea931df5 | |||
545fdf10e2 | |||
7e928db204 | |||
cd4c0eab94 | |||
5b06069fd9 | |||
d0f3162e84 | |||
81537cb161 | |||
370ab66c4f | |||
2707012181 | |||
4d62be69c5 | |||
7e51e52f55 | |||
e81606c97a | |||
f791e9f081 | |||
3aa7e0228a | |||
9d3326caa7 | |||
1940b18124 | |||
0846784b98 | |||
0d10b9002e | |||
0c9ec37e26 | |||
9a0700f5bd | |||
ae7bc2238d | |||
5df626bbe1 | |||
5a624fa1be | |||
3a86940ca5 | |||
7b120b5f73 | |||
de25d1886e | |||
d77444b88a | |||
240aed29e0 | |||
bf29936af9 | |||
339ca83f9d | |||
447783e575 | |||
743d8bc845 | |||
f816666ede | |||
d3c2aa5f95 | |||
3cc1d76ee7 | |||
124283982b | |||
65cf1add97 | |||
8b14488827 | |||
f9fa157a09 | |||
eb8ddd2983 | |||
1aa2947f70 | |||
61abba4bed | |||
5da72cc465 | |||
3db75b3f64 | |||
db3e65fb17 | |||
7a20691f13 | |||
ccd0298ec9 | |||
eeab433c8d | |||
c83c4168ca | |||
06d6c76192 | |||
9dca5f2743 | |||
81306c1f61 | |||
0365592119 | |||
407b5cf408 | |||
4c1743cce3 | |||
7305e8b45e | |||
285dd6be34 | |||
b10540a0b5 | |||
b62739a989 | |||
bb577c624b | |||
02483a01ad | |||
120bdeecdc | |||
8dff9d84ed | |||
b4cd27979b | |||
11b12670b2 | |||
dd48df105b | |||
18174e5564 | |||
72265f796f | |||
6e8bc310f0 | |||
c40ae7f7cf | |||
5bd93b1f0f | |||
a1cc02f0bd | |||
5778bb820a | |||
87754ad5ec | |||
d364117aa8 | |||
6245637e81 | |||
ab69f12e2c | |||
1278cca883 | |||
ede9cb7c2f | |||
c3fb820473 | |||
2af19c96f2 | |||
dcca799dbb | |||
d3d3f7191a | |||
e0b31dbfef | |||
8df56fe93a | |||
676d9c2c4b | |||
2b68e8d98a | |||
d964491f2a | |||
e6080527c6 | |||
3b7bab7d22 | |||
108fa15792 | |||
1b2271a3b1 | |||
f9381e42de | |||
bbb8f386f1 | |||
08aa54e1d9 | |||
fac4d8d42a | |||
170885c51b | |||
5713faa667 | |||
23596b3f30 | |||
f8fa20d71a | |||
d6d8fe829a | |||
5269ce287e | |||
3e03dbe576 | |||
33b338120c | |||
811679a583 | |||
2c33d17609 | |||
9c99e6a838 | |||
98174758ad | |||
97b928053d | |||
1fe55e252c | |||
53ed4b4648 |
25
.circleci/bazel.rc
Normal file
25
.circleci/bazel.rc
Normal file
@ -0,0 +1,25 @@
|
||||
# These options are enabled when running on CI
|
||||
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
|
||||
# Don't be spammy in the logs
|
||||
build --noshow_progress
|
||||
|
||||
# Don't run manual tests
|
||||
test --test_tag_filters=-manual
|
||||
|
||||
# Enable experimental CircleCI bazel remote cache proxy
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
build --experimental_remote_spawn_cache --remote_rest_cache=http://localhost:7643
|
||||
|
||||
# Prevent unstable environment variables from tainting cache keys
|
||||
build --experimental_strict_action_env
|
||||
|
||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||
# Limit Bazel to consuming resources that fit in CircleCI "medium" class which is the default:
|
||||
# https://circleci.com/docs/2.0/configuration-reference/#resource_class
|
||||
build --local_resources=3072,2.0,1.0
|
||||
|
||||
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
||||
test --flaky_test_attempts=2
|
@ -15,6 +15,13 @@
|
||||
var_1: &docker_image angular/ngcontainer:0.1.0
|
||||
var_2: &cache_key angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.1.0
|
||||
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
var_3: &setup-bazel-remote-cache
|
||||
run:
|
||||
name: Start up bazel remote cache proxy
|
||||
command: ~/bazel-remote-proxy -backend circleci://
|
||||
background: true
|
||||
|
||||
# Settings common to each job
|
||||
anchor_1: &job_defaults
|
||||
working_directory: ~/ng
|
||||
@ -34,14 +41,16 @@ jobs:
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# Check BUILD.bazel formatting before we have a node_modules directory
|
||||
# Then we don't need any exclude pattern to avoid checking those files
|
||||
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
|
||||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
||||
# Run the skylark linter to check our Bazel rules
|
||||
- run: 'find . -type f -name "*.bzl" |
|
||||
xargs java -jar /usr/local/bin/Skylint_deploy.jar ||
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: 'yarn buildifier -mode=check ||
|
||||
(echo -e "\nBUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
||||
- run: 'yarn skylint ||
|
||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)'
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
@ -54,6 +63,11 @@ jobs:
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
@ -62,7 +76,16 @@ jobs:
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for a build command to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
- run: bazel query --output=label '//modules/... union //packages/... union //tools/...' | xargs bazel test --config=ci
|
||||
- run: bazel query --output=label '//modules/... union //packages/... union //tools/...' | xargs bazel test
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
||||
destination: packages/core/test/bundling/hello_world/bundle.min.js
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.brotli
|
||||
destination: packages/core/test/bundling/hello_world/bundle.min.js.brotli
|
||||
|
||||
- save_cache:
|
||||
key: *cache_key
|
||||
|
11
.circleci/setup_cache.sh
Executable file
11
.circleci/setup_cache.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Install bazel remote cache proxy
|
||||
# This is temporary until the feature is no longer experimental on CircleCI.
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
|
||||
set -u -e
|
||||
|
||||
readonly DOWNLOAD_URL="https://5-116431813-gh.circle-artifacts.com/0/pkg/bazel-remote-proxy-$(uname -s)_$(uname -m)"
|
||||
|
||||
curl --fail -o ~/bazel-remote-proxy "$DOWNLOAD_URL"
|
||||
chmod +x ~/bazel-remote-proxy
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -25,7 +25,7 @@ ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
||||
## Minimal reproduction of the problem with instructions
|
||||
<!--
|
||||
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
|
||||
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
|
||||
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
|
||||
-->
|
||||
|
||||
## What is the motivation / use case for changing the behavior?
|
||||
|
42
.github/angular-robot.yml
vendored
42
.github/angular-robot.yml
vendored
@ -13,6 +13,30 @@ merge:
|
||||
# text to show when some checks are failing
|
||||
failureText: "The following checks are failing:"
|
||||
|
||||
# the g3 status will be added to your pull requests if they include files that match the patterns
|
||||
g3Status:
|
||||
# set to true to disable
|
||||
disabled: false
|
||||
# the name of the status
|
||||
context: "google3"
|
||||
# text to show when the status is pending
|
||||
pendingDesc: "Googler: test this change in google3 http://go/angular-g3sync"
|
||||
# text to show when the status is success
|
||||
successDesc: "Does not affect google3"
|
||||
# list of patterns to check for the files changed by the PR
|
||||
# this list must be manually kept in sync with google3/third_party/javascript/angular2/copy.bara.sky
|
||||
include:
|
||||
- "BUILD.bazel"
|
||||
- "LICENSE"
|
||||
- "WORKSPACE"
|
||||
- "modules/**"
|
||||
- "packages/**"
|
||||
# list of patterns to ignore for the files changed by the PR
|
||||
exclude:
|
||||
- "packages/language-service/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
|
||||
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
|
||||
mergeConflictComment: "Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges.
|
||||
\nPlease help to unblock it by resolving these conflicts. Thanks!"
|
||||
@ -26,13 +50,15 @@ merge:
|
||||
noConflict: true
|
||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
|
||||
requiredLabels:
|
||||
- "PR target:"
|
||||
- "PR target: *"
|
||||
- "cla: yes"
|
||||
|
||||
# list of labels that a PR shouldn't have, checked after the required labels with a regexp
|
||||
forbiddenLabels:
|
||||
- "PR target: TBD"
|
||||
- "PR action: cleanup"
|
||||
- "PR action: review"
|
||||
- "PR state: blocked"
|
||||
- "cla: no"
|
||||
|
||||
# list of PR statuses that need to be successful
|
||||
@ -60,9 +86,15 @@ triage:
|
||||
triagedLabels:
|
||||
-
|
||||
- "type: bug"
|
||||
- "severity"
|
||||
- "freq"
|
||||
- "comp:"
|
||||
- "severity*"
|
||||
- "freq*"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: feature"
|
||||
- "comp:"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: refactor"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: RFC / Discussion / question"
|
||||
- "comp: *"
|
||||
|
@ -44,6 +44,7 @@ groups:
|
||||
all:
|
||||
users: all
|
||||
required: 1
|
||||
rejection_value: -999
|
||||
# In this group, your self-approval does not count
|
||||
author_approval:
|
||||
auto: false
|
||||
|
@ -56,7 +56,6 @@ env:
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
@ -24,9 +24,7 @@ filegroup(
|
||||
"typescript",
|
||||
"zone.js",
|
||||
"tsutils",
|
||||
"@types/jasmine",
|
||||
"@types/node",
|
||||
"@types/source-map",
|
||||
"@types",
|
||||
"tsickle",
|
||||
"hammerjs",
|
||||
"protobufjs",
|
||||
@ -34,6 +32,7 @@ filegroup(
|
||||
"reflect-metadata",
|
||||
"source-map-support",
|
||||
"minimist",
|
||||
"tslib",
|
||||
] for ext in [
|
||||
"*.js",
|
||||
"*.json",
|
||||
|
124
CHANGELOG.md
124
CHANGELOG.md
@ -1,3 +1,127 @@
|
||||
<a name="6.0.0-beta.4"></a>
|
||||
# [6.0.0-beta.4](https://github.com/angular/angular/compare/6.0.0-beta.3...6.0.0-beta.4) (2018-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([b081dfe](https://github.com/angular/angular/commit/b081dfe)), closes [#21872](https://github.com/angular/angular/issues/21872)
|
||||
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([dcf64a0](https://github.com/angular/angular/commit/dcf64a0)), closes [#22095](https://github.com/angular/angular/issues/22095)
|
||||
* **common:** add locale currency values ([#21783](https://github.com/angular/angular/issues/21783)) ([420cc7a](https://github.com/angular/angular/commit/420cc7a)), closes [#20385](https://github.com/angular/angular/issues/20385)
|
||||
* **common:** round currencies based on decimal digits in `CurrencyPipe` ([#21783](https://github.com/angular/angular/issues/21783)) ([44154e7](https://github.com/angular/angular/commit/44154e7)), closes [#10189](https://github.com/angular/angular/issues/10189)
|
||||
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([be59c3a](https://github.com/angular/angular/commit/be59c3a))
|
||||
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([72f8abd](https://github.com/angular/angular/commit/72f8abd)), closes [#22089](https://github.com/angular/angular/issues/22089)
|
||||
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([16d1700](https://github.com/angular/angular/commit/16d1700))
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [#21980](https://github.com/angular/angular/issues/21980)
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([a751649](https://github.com/angular/angular/commit/a751649))
|
||||
* **forms:** make Validators.email support optional controls ([#20869](https://github.com/angular/angular/issues/20869)) ([140e7c0](https://github.com/angular/angular/commit/140e7c0))
|
||||
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([0bcfae7](https://github.com/angular/angular/commit/0bcfae7))
|
||||
* **forms:** set state before emitting a value from ngModelChange ([#21514](https://github.com/angular/angular/issues/21514)) ([9744a1c](https://github.com/angular/angular/commit/9744a1c)), closes [#21513](https://github.com/angular/angular/issues/21513)
|
||||
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([b37cee3](https://github.com/angular/angular/commit/b37cee3))
|
||||
* **platform-browser:** add @Injectable where it was missing ([#22005](https://github.com/angular/angular/issues/22005)) ([0a1a397](https://github.com/angular/angular/commit/0a1a397))
|
||||
* **platform-browser:** support 0/false/null values in transfer_state ([#22179](https://github.com/angular/angular/issues/22179)) ([6435ecd](https://github.com/angular/angular/commit/6435ecd))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** allow explicit specification of factories ([#22003](https://github.com/angular/angular/issues/22003)) ([e442881](https://github.com/angular/angular/commit/e442881))
|
||||
* **compiler:** mark @NgModules in provider lists for identification at runtime ([#22005](https://github.com/angular/angular/issues/22005)) ([2d5e7d1](https://github.com/angular/angular/commit/2d5e7d1))
|
||||
* **forms:** multiple validators for array method ([#20766](https://github.com/angular/angular/issues/20766)) ([941e88f](https://github.com/angular/angular/commit/941e88f)), closes [#20665](https://github.com/angular/angular/issues/20665)
|
||||
* change @Injectable() to support tree-shakeable tokens ([#22005](https://github.com/angular/angular/issues/22005)) ([235a235](https://github.com/angular/angular/commit/235a235))
|
||||
|
||||
|
||||
|
||||
<a name="5.2.5"></a>
|
||||
## [5.2.5](https://github.com/angular/angular/compare/5.2.4...5.2.5) (2018-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **aio:** update Firebase redirects and SW routes ([#21763](https://github.com/angular/angular/pull/21763)) ([#22104](https://github.com/angular/angular/pull/22104)) ([15ff7ba](https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
|
||||
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([d57fd0b](https://github.com/angular/angular/commit/d57fd0b)), closes [#21872](https://github.com/angular/angular/issues/21872)
|
||||
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([c5ec8d9](https://github.com/angular/angular/commit/c5ec8d9)), closes [#22095](https://github.com/angular/angular/issues/22095)
|
||||
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([c6bdc83](https://github.com/angular/angular/commit/c6bdc83))
|
||||
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([1b8ea10](https://github.com/angular/angular/commit/1b8ea10)), closes [#22089](https://github.com/angular/angular/issues/22089)
|
||||
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([c4f841f](https://github.com/angular/angular/commit/c4f841f))
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [#21980](https://github.com/angular/angular/issues/21980)
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([47b71d9](https://github.com/angular/angular/commit/47b71d9))
|
||||
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([56b9591](https://github.com/angular/angular/commit/56b9591))
|
||||
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([0b23573](https://github.com/angular/angular/commit/0b23573))
|
||||
* **platform-browser:** support 0/false/null values in transfer_state ([#22179](https://github.com/angular/angular/issues/22179)) ([da6ab91](https://github.com/angular/angular/commit/da6ab91))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.0-beta.3"></a>
|
||||
# [6.0.0-beta.3](https://github.com/angular/angular/compare/6.0.0-beta.2...6.0.0-beta.3) (2018-02-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** don't convert null to a string when flushing a mock request ([#21417](https://github.com/angular/angular/issues/21417)) ([8b14488](https://github.com/angular/angular/commit/8b14488)), closes [#20744](https://github.com/angular/angular/issues/20744)
|
||||
* **core:** fix [#20582](https://github.com/angular/angular/issues/20582), don't need to wrap zone in location change listener ([#20640](https://github.com/angular/angular/issues/20640)) ([f791e9f](https://github.com/angular/angular/commit/f791e9f))
|
||||
* **core:** fix proper propagation of subscriptions in EventEmitter ([#22016](https://github.com/angular/angular/issues/22016)) ([e81606c](https://github.com/angular/angular/commit/e81606c)), closes [#21999](https://github.com/angular/angular/issues/21999)
|
||||
* **core:** should check Zone existance when scheduleMicroTask ([#20656](https://github.com/angular/angular/issues/20656)) ([3a86940](https://github.com/angular/angular/commit/3a86940))
|
||||
* **forms:** publish missing types ([#19941](https://github.com/angular/angular/issues/19941)) ([2707012](https://github.com/angular/angular/commit/2707012))
|
||||
* **ivy:** generate correct interpolations ([#21946](https://github.com/angular/angular/issues/21946)) ([3cc1d76](https://github.com/angular/angular/commit/3cc1d76))
|
||||
* **ivy:** generate lifecycle pattern ([#21865](https://github.com/angular/angular/issues/21865)) ([f816666](https://github.com/angular/angular/commit/f816666))
|
||||
* **ivy:** improve `bindV` perf and memory usage ([#21881](https://github.com/angular/angular/issues/21881)) ([0846784](https://github.com/angular/angular/commit/0846784))
|
||||
* **ivy:** remove unnecessary parameter of NgOnChangesFeature ([#21879](https://github.com/angular/angular/issues/21879)) ([65cf1ad](https://github.com/angular/angular/commit/65cf1ad))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** reflect static methods added to classes in metadata ([#21926](https://github.com/angular/angular/issues/21926)) ([eb8ddd2](https://github.com/angular/angular/commit/eb8ddd2))
|
||||
* **ivy:** add canonical example of a pipe. ([#21834](https://github.com/angular/angular/issues/21834)) ([743d8bc](https://github.com/angular/angular/commit/743d8bc))
|
||||
* **ivy:** add support for attributes on ng-content nodes ([#21935](https://github.com/angular/angular/issues/21935)) ([1aa2947](https://github.com/angular/angular/commit/1aa2947))
|
||||
* **ivy:** memoize array literals in render3 ([#21973](https://github.com/angular/angular/issues/21973)) ([4d62be6](https://github.com/angular/angular/commit/4d62be6))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ivy:** improve Uglify configuration in hello world integration test ([#21985](https://github.com/angular/angular/issues/21985)) ([7e51e52](https://github.com/angular/angular/commit/7e51e52))
|
||||
|
||||
|
||||
|
||||
<a name="5.2.4"></a>
|
||||
## [5.2.4](https://github.com/angular/angular/compare/5.2.3...5.2.4) (2018-02-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** don't convert null to a string when flushing a mock request ([#21417](https://github.com/angular/angular/issues/21417)) ([c4fb696](https://github.com/angular/angular/commit/c4fb696)), closes [#20744](https://github.com/angular/angular/issues/20744)
|
||||
* **core:** fix [#20582](https://github.com/angular/angular/issues/20582), don't need to wrap zone in location change listener ([#22007](https://github.com/angular/angular/issues/22007)) ([ce51ea9](https://github.com/angular/angular/commit/ce51ea9))
|
||||
* **core:** fix proper propagation of subscriptions in EventEmitter ([#22016](https://github.com/angular/angular/issues/22016)) ([c6645e7](https://github.com/angular/angular/commit/c6645e7)), closes [#21999](https://github.com/angular/angular/issues/21999)
|
||||
* **core:** should check Zone existance when scheduleMicroTask ([#20656](https://github.com/angular/angular/issues/20656)) ([aa9ba7f](https://github.com/angular/angular/commit/aa9ba7f))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.0-beta.2"></a>
|
||||
# [6.0.0-beta.2](https://github.com/angular/angular/compare/6.0.0-beta.1...6.0.0-beta.2) (2018-01-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **router:** add navigationSource and restoredState to NavigationStart event ([#21728](https://github.com/angular/angular/issues/21728)) ([c40ae7f](https://github.com/angular/angular/commit/c40ae7f))
|
||||
* **service-worker:** add helper script which will uninstall SW ([#21863](https://github.com/angular/angular/issues/21863)) ([b10540a](https://github.com/angular/angular/commit/b10540a))
|
||||
|
||||
|
||||
|
||||
<a name="5.2.3"></a>
|
||||
## [5.2.3](https://github.com/angular/angular/compare/5.2.2...5.2.3) (2018-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** allow HttpInterceptors to inject HttpClient ([#19809](https://github.com/angular/angular/issues/19809)) ([ed2b717](https://github.com/angular/angular/commit/ed2b717)), closes [#18224](https://github.com/angular/angular/issues/18224)
|
||||
* **common:** generate closure-locale data file with exported plural functions ([#21873](https://github.com/angular/angular/issues/21873)) ([c2f5ed5](https://github.com/angular/angular/commit/c2f5ed5)), closes [#21870](https://github.com/angular/angular/issues/21870)
|
||||
* **core:** fix retrieving the binding name when an expression changes ([#21814](https://github.com/angular/angular/issues/21814)) ([81d64d6](https://github.com/angular/angular/commit/81d64d6)), closes [#21735](https://github.com/angular/angular/issues/21735) [#21788](https://github.com/angular/angular/issues/21788)
|
||||
* **forms:** allow FormBuilder to create controls with any formState type ([#20917](https://github.com/angular/angular/issues/20917)) ([56f3e18](https://github.com/angular/angular/commit/56f3e18)), closes [#20368](https://github.com/angular/angular/issues/20368)
|
||||
* **forms:** inserting and removing controls should work in re-bound form arrays ([#21822](https://github.com/angular/angular/issues/21822)) ([fad99cc](https://github.com/angular/angular/commit/fad99cc)), closes [#21501](https://github.com/angular/angular/issues/21501)
|
||||
* **language-service:** ensure correct paths are passed to TypeScript ([#21812](https://github.com/angular/angular/issues/21812)) ([250c8da](https://github.com/angular/angular/commit/250c8da))
|
||||
* **language-service:** spell diagnostics correctly ([#21812](https://github.com/angular/angular/issues/21812)) ([778e6e7](https://github.com/angular/angular/commit/778e6e7))
|
||||
* **router:** remove [@internal](https://github.com/internal) tag on ParamInheritanceType ([#21773](https://github.com/angular/angular/issues/21773)) ([35a0721](https://github.com/angular/angular/commit/35a0721)), closes [#21456](https://github.com/angular/angular/issues/21456)
|
||||
|
||||
|
||||
|
||||
<a name="6.0.0-beta.1"></a>
|
||||
# [6.0.0-beta.1](https://github.com/angular/angular/compare/6.0.0-beta.0...6.0.0-beta.1) (2018-01-25)
|
||||
|
||||
|
@ -51,7 +51,7 @@ and help you to craft the change so that it is successfully accepted into the pr
|
||||
|
||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like:
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
|
||||
|
||||
- version of Angular used
|
||||
- 3rd-party libraries and their versions
|
||||
@ -61,7 +61,7 @@ A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm
|
||||
|
||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||
|
||||
Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
|
||||
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
||||
|
||||
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
|
||||
|
||||
@ -72,7 +72,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||
We cannot accept code without this.
|
||||
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
||||
1. Fork the angular/angular repo.
|
||||
1. Make your changes in a new git branch:
|
||||
|
||||
@ -173,12 +173,12 @@ The **header** is mandatory and the **scope** of the header is optional.
|
||||
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||
to read on GitHub as well as in various git tools.
|
||||
|
||||
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
|
||||
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
|
||||
|
||||
```
|
||||
docs(changelog): update change log to beta.5
|
||||
docs(changelog): update changelog to beta.5
|
||||
```
|
||||
```
|
||||
fix(release): need to depend on latest rxjs and zone.js
|
||||
@ -203,7 +203,7 @@ Must be one of the following:
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
### Scope
|
||||
The scope should be the name of the npm package affected (as perceived by person reading changelog generated from commit messages.
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
@ -232,10 +232,10 @@ There are currently a few exceptions to the "use package name" rule:
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
|
||||
|
||||
### Subject
|
||||
The subject contains succinct description of the change:
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
### Body
|
||||
@ -259,6 +259,19 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
* For corporations we'll need you to
|
||||
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
|
||||
|
||||
<hr>
|
||||
|
||||
If you have more than one Git identity, you must make sure that you sign the CLA using the primary email address associated with the ID that has been granted access to the Angular repository. Git identities can be associated with more than one email address, and only one is primary. Here are some links to help you sort out multiple Git identities and email addresses:
|
||||
|
||||
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
|
||||
* https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
|
||||
* https://help.github.com/articles/about-commit-email-addresses/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
[angular-group]: https://groups.google.com/forum/#!forum/angular
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
|
||||
|
40
WORKSPACE
40
WORKSPACE
@ -1,11 +1,14 @@
|
||||
workspace(name = "angular")
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary
|
||||
# programs produce source-mapped stack traces.
|
||||
RULES_NODEJS_VERSION = "926349cea4cd360afcd5647ccdd09d2d2fb471aa"
|
||||
|
||||
git_repository(
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
remote = "https://github.com/bazelbuild/rules_nodejs.git",
|
||||
tag = "0.3.1",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION,
|
||||
strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION,
|
||||
sha256 = "5ba3c8c209078c2e3f0c6aa4abd01a1a561f92a5bfda04e25604af5f4734d69d",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||
@ -13,10 +16,13 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi
|
||||
check_bazel_version("0.9.0")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
git_repository(
|
||||
RULES_TYPESCRIPT_VERSION = "0.10.1"
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
commit = "c4ea003acd7d42269b81a2d25eb832972cd24912"
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION,
|
||||
strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION,
|
||||
sha256 = "a2c81776a4a492ff9f878f9705639f5647bef345f7f3e1da09c9eeb8dec80485",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
@ -56,3 +62,23 @@ http_archive(
|
||||
strip_prefix = "bazel-9755c72b48866ed034bd28aa033e9abd27431b1e",
|
||||
sha256 = "5b8443fc3481b5fcd9e7f348e1dd93c1397f78b223623c39eb56494c55f41962",
|
||||
)
|
||||
|
||||
# We have a source dependency on the Devkit repository, because it's built with
|
||||
# Bazel.
|
||||
# This allows us to edit sources and have the effect appear immediately without
|
||||
# re-packaging or "npm link"ing.
|
||||
# Even better, things like aspects will visit the entire graph including
|
||||
# ts_library rules in the devkit repository.
|
||||
http_archive(
|
||||
name = "angular_devkit",
|
||||
url = "https://github.com/angular/devkit/archive/v0.3.1.zip",
|
||||
strip_prefix = "devkit-0.3.1",
|
||||
sha256 = "31d4b597fe9336650acf13df053c1c84dcbe9c29c6a833bcac3819cd3fd8cad3",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/v1.0.2.zip",
|
||||
strip_prefix = "brotli-1.0.2",
|
||||
sha256 = "b43d5d6bc40f2fa6c785b738d86c6bbe022732fe25196ebbe43b9653a025920d",
|
||||
)
|
||||
|
@ -39,7 +39,7 @@
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
"config": "tests/e2e/protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
@ -50,12 +50,12 @@
|
||||
"project": "src/tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json"
|
||||
"project": "tests/e2e/tsconfig.e2e.json"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
"config": "src/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
|
@ -105,8 +105,7 @@ The general setup is as follows:
|
||||
* Open a terminal, ensure the dependencies are installed; run an initial doc generation; then start the doc-viewer:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
yarn docs
|
||||
yarn setup
|
||||
yarn start
|
||||
```
|
||||
|
||||
|
BIN
aio/content/examples/.DS_Store
vendored
Normal file
BIN
aio/content/examples/.DS_Store
vendored
Normal file
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
/* #docregion import */
|
||||
@import 'hero-details-box.css';
|
||||
/* The AOT compiler needs the `./` to show that this is local */
|
||||
@import './hero-details-box.css';
|
||||
/* #enddocregion import */
|
||||
|
||||
/* #docregion host */
|
||||
|
@ -5,7 +5,8 @@ import { Hero } from './hero';
|
||||
@Component({
|
||||
selector: 'app-hero-team',
|
||||
template: `
|
||||
<link rel="stylesheet" href="assets/hero-team.component.css">
|
||||
<!-- We must use a relative URL so that the AOT compiler can find the stylesheet -->
|
||||
<link rel="stylesheet" href="../assets/hero-team.component.css">
|
||||
<h3>Team</h3>
|
||||
<ul>
|
||||
<li *ngFor="let member of hero.team">
|
||||
|
@ -15,6 +15,7 @@ describe('Form Validation Tests', function () {
|
||||
});
|
||||
|
||||
tests('Template-Driven Form');
|
||||
bobTests();
|
||||
});
|
||||
|
||||
describe('Reactive form', () => {
|
||||
|
@ -20,7 +20,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
// #enddocregion directive-providers
|
||||
})
|
||||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@Input() forbiddenName: string;
|
||||
@Input('appForbiddenName') forbiddenName: string;
|
||||
|
||||
validate(control: AbstractControl): {[key: string]: any} {
|
||||
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
|
||||
|
@ -12,7 +12,7 @@
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<!-- #docregion name-input -->
|
||||
<input id="name" name="name" class="form-control"
|
||||
required minlength="4" forbiddenName="bob"
|
||||
required minlength="4" appForbiddenName="bob"
|
||||
[(ngModel)]="hero.name" #name="ngModel" >
|
||||
<!-- #enddocregion name-input -->
|
||||
|
||||
|
@ -1,138 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Server Communication', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('Tour of Heroes (Observable)', function () {
|
||||
|
||||
let initialHeroCount = 4;
|
||||
let newHeroName = 'Mr. IQ';
|
||||
let heroCountAfterAdd = 5;
|
||||
|
||||
let heroListComp = element(by.tagName('hero-list'));
|
||||
let addButton = heroListComp.element(by.tagName('button'));
|
||||
let heroTags = heroListComp.all(by.tagName('li'));
|
||||
let heroNameInput = heroListComp.element(by.tagName('input'));
|
||||
|
||||
it('should exist', function() {
|
||||
expect(heroListComp).toBeDefined('<hero-list> must exist');
|
||||
});
|
||||
|
||||
it('should display ' + initialHeroCount + ' heroes after init', function () {
|
||||
expect(heroTags.count()).toBe(initialHeroCount);
|
||||
});
|
||||
|
||||
it('should not add hero with empty name', function () {
|
||||
expect(addButton).toBeDefined('"Add Hero" button must be defined');
|
||||
addButton.click().then(function() {
|
||||
expect(heroTags.count()).toBe(initialHeroCount, 'No new hero should be added');
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a new hero to the list', function () {
|
||||
expect(heroNameInput).toBeDefined('<input> for hero name must exist');
|
||||
expect(addButton).toBeDefined('"Add Hero" button must be defined');
|
||||
heroNameInput.sendKeys(newHeroName);
|
||||
addButton.click().then(function() {
|
||||
expect(heroTags.count()).toBe(heroCountAfterAdd, 'A new hero should be added');
|
||||
let newHeroInList = heroTags.get(heroCountAfterAdd - 1).getText();
|
||||
expect(newHeroInList).toBe(newHeroName, 'The hero should be added to the end of the list');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Wikipedia Demo', function () {
|
||||
|
||||
it('should initialize the demo with empty result list', function () {
|
||||
let myWikiComp = element(by.tagName('my-wiki'));
|
||||
expect(myWikiComp).toBeDefined('<my-wiki> must exist');
|
||||
let resultList = myWikiComp.all(by.tagName('li'));
|
||||
expect(resultList.count()).toBe(0, 'result list must be empty');
|
||||
});
|
||||
|
||||
describe('Fetches after each keystroke', function () {
|
||||
it('should fetch results after "B"', function(done: any) {
|
||||
testForRefreshedResult('B', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "Ba"', function(done: any) {
|
||||
testForRefreshedResult('a', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "Bas"', function(done: any) {
|
||||
testForRefreshedResult('s', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "Basic"', function(done: any) {
|
||||
testForRefreshedResult('ic', done);
|
||||
});
|
||||
});
|
||||
|
||||
function testForRefreshedResult(keyPressed: string, done: () => void) {
|
||||
testForResult('my-wiki', keyPressed, false, done);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Smarter Wikipedia Demo', function () {
|
||||
|
||||
it('should initialize the demo with empty result list', function () {
|
||||
let myWikiSmartComp = element(by.tagName('my-wiki-smart'));
|
||||
expect(myWikiSmartComp).toBeDefined('<my-wiki-smart> must exist');
|
||||
let resultList = myWikiSmartComp.all(by.tagName('li'));
|
||||
expect(resultList.count()).toBe(0, 'result list must be empty');
|
||||
});
|
||||
|
||||
it('should fetch results after "Java"', function(done: any) {
|
||||
testForNewResult('Java', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "JavaS"', function(done: any) {
|
||||
testForStaleResult('S', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "JavaSc"', function(done: any) {
|
||||
testForStaleResult('c', done);
|
||||
});
|
||||
|
||||
it('should fetch results after "JavaScript"', function(done: any) {
|
||||
testForStaleResult('ript', done);
|
||||
});
|
||||
|
||||
|
||||
function testForNewResult(keyPressed: string, done: () => void) {
|
||||
testForResult('my-wiki-smart', keyPressed, false, done);
|
||||
}
|
||||
|
||||
function testForStaleResult(keyPressed: string, done: () => void) {
|
||||
testForResult('my-wiki-smart', keyPressed, true, done);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function testForResult(componentTagName: string, keyPressed: string, hasListBeforeSearch: boolean, done: () => void) {
|
||||
let searchWait = 1000; // Wait for wikipedia but not so long that tests timeout
|
||||
let wikiComponent = element(by.tagName(componentTagName));
|
||||
expect(wikiComponent).toBeDefined('<' + componentTagName + '> must exist');
|
||||
let searchBox = wikiComponent.element(by.tagName('input'));
|
||||
expect(searchBox).toBeDefined('<input> for search must exist');
|
||||
|
||||
searchBox.sendKeys(keyPressed).then(function () {
|
||||
let resultList = wikiComponent.all(by.tagName('li'));
|
||||
|
||||
if (hasListBeforeSearch) {
|
||||
expect(resultList.count()).toBeGreaterThan(0, 'result list should not be empty before search');
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
expect(resultList.count()).toBeGreaterThan(0, 'result list should not be empty after search');
|
||||
done();
|
||||
}, searchWait);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
139
aio/content/examples/http/e2e/app.e2e-spec.ts
Normal file
139
aio/content/examples/http/e2e/app.e2e-spec.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { browser, element, by, ElementFinder } from 'protractor';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const page = {
|
||||
configClearButton: element.all(by.css('app-config > div button')).get(2),
|
||||
configErrorButton: element.all(by.css('app-config > div button')).get(3),
|
||||
configErrorMessage: element(by.css('app-config p')),
|
||||
configGetButton: element.all(by.css('app-config > div button')).get(0),
|
||||
configGetResponseButton: element.all(by.css('app-config > div button')).get(1),
|
||||
configSpan: element(by.css('app-config span')),
|
||||
downloadButton: element.all(by.css('app-downloader button')).get(0),
|
||||
downloadClearButton: element.all(by.css('app-downloader button')).get(1),
|
||||
downloadMessage: element(by.css('app-downloader p')),
|
||||
heroesListAddButton: element.all(by.css('app-heroes > div button')).get(0),
|
||||
heroesListInput: element(by.css('app-heroes > div input')),
|
||||
heroesListSearchButton: element.all(by.css('app-heroes > div button')).get(1),
|
||||
heroesListItems: element.all(by.css('app-heroes ul li')),
|
||||
logClearButton: element(by.css('app-messages button')),
|
||||
logList: element(by.css('app-messages ol')),
|
||||
logListItems: element.all(by.css('app-messages ol li')),
|
||||
searchInput: element(by.css('app-package-search input#name')),
|
||||
searchListItems: element.all(by.css('app-package-search li')),
|
||||
uploadInput: element(by.css('app-uploader input')),
|
||||
uploadMessage: element(by.css('app-uploader p'))
|
||||
};
|
||||
|
||||
let checkLogForMessage = (message: string) => {
|
||||
expect(page.logList.getText()).toContain(message);
|
||||
};
|
||||
|
||||
describe('Http Tests', function() {
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('Heroes', () => {
|
||||
it('retrieves the list of heroes at startup', () => {
|
||||
expect(page.heroesListItems.count()).toBe(4);
|
||||
expect(page.heroesListItems.get(0).getText()).toContain('Mr. Nice');
|
||||
checkLogForMessage('GET "api/heroes"');
|
||||
});
|
||||
|
||||
it('makes a POST to add a new hero', () => {
|
||||
page.heroesListInput.sendKeys('Magneta');
|
||||
page.heroesListAddButton.click();
|
||||
expect(page.heroesListItems.count()).toBe(5);
|
||||
checkLogForMessage('POST "api/heroes"');
|
||||
});
|
||||
|
||||
it('makes a GET to search for a hero', () => {
|
||||
page.heroesListInput.sendKeys('Celeritas');
|
||||
page.heroesListSearchButton.click();
|
||||
checkLogForMessage('GET "api/heroes?name=Celeritas"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Messages', () => {
|
||||
it('can clear the logs', () => {
|
||||
expect(page.logListItems.count()).toBe(1);
|
||||
page.logClearButton.click();
|
||||
expect(page.logListItems.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration', () => {
|
||||
it('can fetch the configuration JSON file', () => {
|
||||
page.configGetButton.click();
|
||||
checkLogForMessage('GET "assets/config.json"');
|
||||
expect(page.configSpan.getText()).toContain('Heroes API URL is "api/heroes"');
|
||||
expect(page.configSpan.getText()).toContain('Textfile URL is "assets/textfile.txt"');
|
||||
});
|
||||
|
||||
it('can fetch the configuration JSON file with headers', () => {
|
||||
page.configGetResponseButton.click();
|
||||
checkLogForMessage('GET "assets/config.json"');
|
||||
expect(page.configSpan.getText()).toContain('Response headers:');
|
||||
expect(page.configSpan.getText()).toContain('content-type: application/json; charset=UTF-8');
|
||||
});
|
||||
|
||||
it('can clear the configuration log', () => {
|
||||
page.configGetResponseButton.click();
|
||||
expect(page.configSpan.getText()).toContain('Response headers:');
|
||||
page.configClearButton.click();
|
||||
expect(page.configSpan.isPresent()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('throws an error for a non valid url', () => {
|
||||
page.configErrorButton.click();
|
||||
checkLogForMessage('GET "not/a/real/url"');
|
||||
expect(page.configErrorMessage.getText()).toContain('"Something bad happened; please try again later."');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Download', () => {
|
||||
it('can download a txt file and show it', () => {
|
||||
page.downloadButton.click();
|
||||
checkLogForMessage('DownloaderService downloaded "assets/textfile.txt"');
|
||||
checkLogForMessage('GET "assets/textfile.txt"');
|
||||
expect(page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
});
|
||||
|
||||
it('can clear the log of the download', () => {
|
||||
page.downloadButton.click();
|
||||
expect(page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
page.downloadClearButton.click();
|
||||
expect(page.downloadMessage.isPresent()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Upload', () => {
|
||||
it('can upload a file', () => {
|
||||
const filename = 'app.po.ts';
|
||||
const url = resolve(__dirname, filename);
|
||||
page.uploadInput.sendKeys(url);
|
||||
checkLogForMessage('POST "/upload/file" succeeded in');
|
||||
expect(page.uploadMessage.getText()).toContain(
|
||||
`File "${filename}" was completely uploaded!`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PackageSearch', () => {
|
||||
it('can search for npm package and find in cache', () => {
|
||||
const packageName = 'angular';
|
||||
page.searchInput.sendKeys(packageName);
|
||||
checkLogForMessage(
|
||||
'Caching response from "https://npmsearch.com/query?q=angular"');
|
||||
expect(page.searchListItems.count()).toBeGreaterThan(1, 'angular items');
|
||||
|
||||
page.searchInput.clear();
|
||||
page.searchInput.sendKeys(' ');
|
||||
expect(page.searchListItems.count()).toBe(0, 'search empty');
|
||||
|
||||
page.searchInput.clear();
|
||||
page.searchInput.sendKeys(packageName);
|
||||
checkLogForMessage(
|
||||
'Found cached response for "https://npmsearch.com/query?q=angular"');
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"projectType": "testing"
|
||||
}
|
||||
|
18
aio/content/examples/http/specs.stackblitz.json
Normal file
18
aio/content/examples/http/specs.stackblitz.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"description": "Http Guide Testing",
|
||||
"files":[
|
||||
"src/app/heroes/heroes.service.ts",
|
||||
"src/app/heroes/heroes.service.spec.ts",
|
||||
|
||||
"src/app/http-error-handler.service.ts",
|
||||
"src/app/message.service.ts",
|
||||
"src/testing/*.ts",
|
||||
|
||||
"src/styles.css",
|
||||
"src/test.css",
|
||||
"src/main-specs.ts",
|
||||
"src/index-specs.html"
|
||||
],
|
||||
"main": "src/index-specs.html",
|
||||
"tags": ["http", "testing"]
|
||||
}
|
24
aio/content/examples/http/src/app/app.component.html
Normal file
24
aio/content/examples/http/src/app/app.component.html
Normal file
@ -0,0 +1,24 @@
|
||||
<h1>HTTP Sample</h1>
|
||||
<div>
|
||||
<input type="checkbox" id="heroes" [checked]="toggleHeroes" (click)="toggleHeroes()">
|
||||
<label for="heroes">Heroes</label>
|
||||
|
||||
<input type="checkbox" id="config" [checked]="showConfig" (click)="toggleConfig()">
|
||||
<label for="config">Config</label>
|
||||
|
||||
<input type="checkbox" id="downloader" [checked]="showDownloader" (click)="toggleDownloader()">
|
||||
<label for="downloader">Downloader</label>
|
||||
|
||||
<input type="checkbox" id="uploader" [checked]="showUploader" (click)="toggleUploader()">
|
||||
<label for="uploader">Uploader</label>
|
||||
|
||||
<input type="checkbox" id="search" [checked]="showSearch" (click)="toggleSearch()">
|
||||
<label for="search">Search</label>
|
||||
</div>
|
||||
|
||||
<app-heroes *ngIf="showHeroes"></app-heroes>
|
||||
<app-messages></app-messages>
|
||||
<app-config *ngIf="showConfig"></app-config>
|
||||
<app-downloader *ngIf="showDownloader"></app-downloader>
|
||||
<app-uploader *ngIf="showUploader"></app-uploader>
|
||||
<app-package-search *ngIf="showSearch"></app-package-search>
|
@ -1,13 +1,19 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<hero-list></hero-list>
|
||||
<hero-list-promise></hero-list-promise>
|
||||
<my-wiki></my-wiki>
|
||||
<my-wiki-smart></my-wiki-smart>
|
||||
`
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
export class AppComponent {
|
||||
showHeroes = true;
|
||||
showConfig = true;
|
||||
showDownloader = true;
|
||||
showUploader = true;
|
||||
showSearch = true;
|
||||
|
||||
toggleHeroes() { this.showHeroes = !this.showHeroes; }
|
||||
toggleConfig() { this.showConfig = !this.showConfig; }
|
||||
toggleDownloader() { this.showDownloader = !this.showDownloader; }
|
||||
toggleUploader() { this.showUploader = !this.showUploader; }
|
||||
toggleSearch() { this.showSearch = !this.showSearch; }
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule, JsonpModule } from '@angular/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
JsonpModule
|
||||
],
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,46 +1,89 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule, JsonpModule } from '@angular/http';
|
||||
// #docregion sketch
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
// #enddocregion sketch
|
||||
import { FormsModule } from '@angular/forms';
|
||||
// #docregion sketch
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
// #enddocregion sketch
|
||||
import { HttpClientXsrfModule } from '@angular/common/http';
|
||||
|
||||
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
|
||||
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
import { HeroData } from './hero-data';
|
||||
import { requestOptionsProvider } from './default-request-options.service';
|
||||
import { RequestCache, RequestCacheWithMap } from './request-cache.service';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AuthService } from './auth.service';
|
||||
import { ConfigComponent } from './config/config.component';
|
||||
import { DownloaderComponent } from './downloader/downloader.component';
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
import { HttpErrorHandler } from './http-error-handler.service';
|
||||
import { MessageService } from './message.service';
|
||||
import { MessagesComponent } from './messages/messages.component';
|
||||
import { PackageSearchComponent } from './package-search/package-search.component';
|
||||
import { UploaderComponent } from './uploader/uploader.component';
|
||||
|
||||
import { HeroListComponent } from './toh/hero-list.component';
|
||||
import { HeroListPromiseComponent } from './toh/hero-list.component.promise';
|
||||
|
||||
import { WikiComponent } from './wiki/wiki.component';
|
||||
import { WikiSmartComponent } from './wiki/wiki-smart.component';
|
||||
import { httpInterceptorProviders } from './http-interceptors/index';
|
||||
// #docregion sketch
|
||||
|
||||
@NgModule({
|
||||
// #docregion xsrf
|
||||
imports: [
|
||||
// #enddocregion xsrf
|
||||
BrowserModule,
|
||||
// #enddocregion sketch
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
JsonpModule,
|
||||
// #docregion in-mem-web-api
|
||||
InMemoryWebApiModule.forRoot(HeroData)
|
||||
// #enddocregion in-mem-web-api
|
||||
// #docregion sketch
|
||||
// import HttpClientModule after BrowserModule.
|
||||
// #docregion xsrf
|
||||
HttpClientModule,
|
||||
// #enddocregion sketch
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'My-Xsrf-Cookie',
|
||||
headerName: 'My-Xsrf-Header',
|
||||
}),
|
||||
// #enddocregion xsrf
|
||||
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
// Remove it when a real server is ready to receive requests.
|
||||
HttpClientInMemoryWebApiModule.forRoot(
|
||||
InMemoryDataService, {
|
||||
dataEncapsulation: false,
|
||||
passThruUnknownUrl: true,
|
||||
put204: false // return entity after PUT/update
|
||||
}
|
||||
)
|
||||
// #docregion sketch, xsrf
|
||||
],
|
||||
// #enddocregion xsrf
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeroListComponent,
|
||||
HeroListPromiseComponent,
|
||||
WikiComponent,
|
||||
WikiSmartComponent
|
||||
// #enddocregion sketch
|
||||
ConfigComponent,
|
||||
DownloaderComponent,
|
||||
HeroesComponent,
|
||||
MessagesComponent,
|
||||
UploaderComponent,
|
||||
PackageSearchComponent,
|
||||
// #docregion sketch
|
||||
],
|
||||
// #docregion provide-default-request-options
|
||||
providers: [ requestOptionsProvider ],
|
||||
// #enddocregion provide-default-request-options
|
||||
// #enddocregion sketch
|
||||
// #docregion interceptor-providers
|
||||
providers: [
|
||||
// #enddocregion interceptor-providers
|
||||
AuthService,
|
||||
HttpErrorHandler,
|
||||
MessageService,
|
||||
{ provide: RequestCache, useClass: RequestCacheWithMap },
|
||||
// #docregion interceptor-providers
|
||||
httpInterceptorProviders
|
||||
],
|
||||
// #enddocregion interceptor-providers
|
||||
// #docregion sketch
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
|
||||
|
||||
// #enddocregion sketch
|
||||
|
9
aio/content/examples/http/src/app/auth.service.ts
Normal file
9
aio/content/examples/http/src/app/auth.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
/** Mock client-side authentication/authorization service */
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
getAuthorizationToken() {
|
||||
return 'some-auth-token';
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<h3>Get configuration from JSON file</h3>
|
||||
<div>
|
||||
<button (click)="clear(); showConfig()">get</button>
|
||||
<button (click)="clear(); showConfigResponse()">getResponse</button>
|
||||
<button (click)="clear()">clear</button>
|
||||
<button (click)="clear(); makeError()">error</button>
|
||||
<span *ngIf="config">
|
||||
<p>Heroes API URL is "{{config.heroesUrl}}"</p>
|
||||
<p>Textfile URL is "{{config.textfile}}"</p>
|
||||
<div *ngIf="headers">
|
||||
Response headers:
|
||||
<ul>
|
||||
<li *ngFor="let header of headers">{{header}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<p *ngIf="error" class="error">{{error | json}}</p>
|
78
aio/content/examples/http/src/app/config/config.component.ts
Normal file
78
aio/content/examples/http/src/app/config/config.component.ts
Normal file
@ -0,0 +1,78 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Config, ConfigService } from './config.service';
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-config',
|
||||
templateUrl: './config.component.html',
|
||||
providers: [ ConfigService ],
|
||||
styles: ['.error {color: red;}']
|
||||
})
|
||||
export class ConfigComponent {
|
||||
error: any;
|
||||
headers: string[];
|
||||
// #docregion v2
|
||||
config: Config;
|
||||
|
||||
// #enddocregion v2
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
clear() {
|
||||
this.config = undefined;
|
||||
this.error = undefined;
|
||||
this.headers = undefined;
|
||||
}
|
||||
|
||||
// #docregion v1, v2, v3
|
||||
showConfig() {
|
||||
this.configService.getConfig()
|
||||
// #enddocregion v1, v2
|
||||
.subscribe(
|
||||
data => this.config = { ...data }, // success path
|
||||
error => this.error = error // error path
|
||||
);
|
||||
}
|
||||
// #enddocregion v3
|
||||
|
||||
showConfig_v1() {
|
||||
this.configService.getConfig_1()
|
||||
// #docregion v1, v1_callback
|
||||
.subscribe(data => this.config = {
|
||||
heroesUrl: data['heroesUrl'],
|
||||
textfile: data['textfile']
|
||||
});
|
||||
// #enddocregion v1_callback
|
||||
}
|
||||
// #enddocregion v1
|
||||
|
||||
showConfig_v2() {
|
||||
this.configService.getConfig()
|
||||
// #docregion v2, v2_callback
|
||||
// clone the data object, using its known Config shape
|
||||
.subscribe(data => this.config = { ...data });
|
||||
// #enddocregion v2_callback
|
||||
}
|
||||
// #enddocregion v2
|
||||
|
||||
// #docregion showConfigResponse
|
||||
showConfigResponse() {
|
||||
this.configService.getConfigResponse()
|
||||
// resp is of type `HttpResponse<Config>`
|
||||
.subscribe(resp => {
|
||||
// display its headers
|
||||
const keys = resp.headers.keys();
|
||||
this.headers = keys.map(key =>
|
||||
`${key}: ${resp.headers.get(key)}`);
|
||||
|
||||
// access the body directly, which is typed as `Config`.
|
||||
this.config = { ... resp.body };
|
||||
});
|
||||
}
|
||||
// #enddocregion showConfigResponse
|
||||
makeError() {
|
||||
this.configService.makeIntentionalError().subscribe(null, error => this.error = error );
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
100
aio/content/examples/http/src/app/config/config.service.ts
Normal file
100
aio/content/examples/http/src/app/config/config.service.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// #docplaster
|
||||
// #docregion , proto
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
// #enddocregion proto
|
||||
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
|
||||
import { catchError, retry } from 'rxjs/operators';
|
||||
// #enddocregion rxjs-imports
|
||||
|
||||
// #docregion config-interface
|
||||
export interface Config {
|
||||
heroesUrl: string;
|
||||
textfile: string;
|
||||
}
|
||||
// #enddocregion config-interface
|
||||
// #docregion proto
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
// #enddocregion proto
|
||||
// #docregion getConfig_1
|
||||
configUrl = 'assets/config.json';
|
||||
|
||||
// #enddocregion getConfig_1
|
||||
// #docregion proto
|
||||
constructor(private http: HttpClient) { }
|
||||
// #enddocregion proto
|
||||
|
||||
// #docregion getConfig, getConfig_1, getConfig_2, getConfig_3
|
||||
getConfig() {
|
||||
// #enddocregion getConfig_1, getConfig_2, getConfig_3
|
||||
return this.http.get<Config>(this.configUrl)
|
||||
.pipe(
|
||||
retry(3), // retry a failed request up to 3 times
|
||||
catchError(this.handleError) // then handle the error
|
||||
);
|
||||
}
|
||||
// #enddocregion getConfig
|
||||
|
||||
getConfig_1() {
|
||||
// #docregion getConfig_1
|
||||
return this.http.get(this.configUrl);
|
||||
}
|
||||
// #enddocregion getConfig_1
|
||||
|
||||
getConfig_2() {
|
||||
// #docregion getConfig_2
|
||||
// now returns an Observable of Config
|
||||
return this.http.get<Config>(this.configUrl);
|
||||
}
|
||||
// #enddocregion getConfig_2
|
||||
|
||||
getConfig_3() {
|
||||
// #docregion getConfig_3
|
||||
return this.http.get<Config>(this.configUrl)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
// #enddocregion getConfig_3
|
||||
|
||||
// #docregion getConfigResponse
|
||||
getConfigResponse(): Observable<HttpResponse<Config>> {
|
||||
return this.http.get<Config>(
|
||||
this.configUrl, { observe: 'response' });
|
||||
}
|
||||
// #enddocregion getConfigResponse
|
||||
|
||||
// #docregion handleError
|
||||
private handleError(error: HttpErrorResponse) {
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
console.error('An error occurred:', error.error.message);
|
||||
} else {
|
||||
// The backend returned an unsuccessful response code.
|
||||
// The response body may contain clues as to what went wrong,
|
||||
console.error(
|
||||
`Backend returned code ${error.status}, ` +
|
||||
`body was: ${error.error}`);
|
||||
}
|
||||
// return an ErrorObservable with a user-facing error message
|
||||
return new ErrorObservable(
|
||||
'Something bad happened; please try again later.');
|
||||
};
|
||||
// #enddocregion handleError
|
||||
|
||||
makeIntentionalError() {
|
||||
return this.http.get('not/a/real/url')
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// #docregion proto
|
||||
}
|
||||
// #enddocregion proto
|
@ -1,16 +0,0 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BaseRequestOptions, RequestOptions } from '@angular/http';
|
||||
|
||||
@Injectable()
|
||||
export class DefaultRequestOptions extends BaseRequestOptions {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Set the default 'Content-Type' header
|
||||
this.headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
||||
|
||||
export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
|
@ -0,0 +1,4 @@
|
||||
<h3>Download the textfile</h3>
|
||||
<button (click)="download()">Download</button>
|
||||
<button (click)="clear()">clear</button>
|
||||
<p *ngIf="contents">Contents: "{{contents}}"</p>
|
@ -0,0 +1,23 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { DownloaderService } from './downloader.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-downloader',
|
||||
templateUrl: './downloader.component.html',
|
||||
providers: [ DownloaderService ]
|
||||
})
|
||||
export class DownloaderComponent {
|
||||
contents: string;
|
||||
constructor(private downloaderService: DownloaderService) {}
|
||||
|
||||
clear() {
|
||||
this.contents = undefined;
|
||||
}
|
||||
|
||||
// #docregion download
|
||||
download() {
|
||||
this.downloaderService.getTextFile('assets/textfile.txt')
|
||||
.subscribe(results => this.contents = results);
|
||||
}
|
||||
// #enddocregion download
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
@Injectable()
|
||||
export class DownloaderService {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private messageService: MessageService) { }
|
||||
|
||||
// #docregion getTextFile
|
||||
getTextFile(filename: string) {
|
||||
// The Observable returned by get() is of type Observable<string>
|
||||
// because a text response was specified.
|
||||
// There's no need to pass a <string> type parameter to get().
|
||||
return this.http.get(filename, {responseType: 'text'})
|
||||
.pipe(
|
||||
tap( // Log the result or error
|
||||
data => this.log(filename, data),
|
||||
error => this.logError(filename, error)
|
||||
)
|
||||
);
|
||||
}
|
||||
// #enddocregion getTextFile
|
||||
|
||||
private log(filename: string, data: string) {
|
||||
const message = `DownloaderService downloaded "${filename}" and got "${data}".`;
|
||||
this.messageService.add(message);
|
||||
}
|
||||
|
||||
private logError(filename: string, error: any) {
|
||||
const message = `DownloaderService failed to download "${filename}"; got error "${error.message}".`;
|
||||
console.error(message);
|
||||
this.messageService.add(message);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
// #docregion
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
export class HeroData implements InMemoryDbService {
|
||||
createDb() {
|
||||
let heroes = [
|
||||
{ id: 1, name: 'Windstorm' },
|
||||
{ id: 2, name: 'Bombasto' },
|
||||
{ id: 3, name: 'Magneta' },
|
||||
{ id: 4, name: 'Tornado' }
|
||||
];
|
||||
return {heroes};
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{ "id": 1, "name": "Windstorm" },
|
||||
{ "id": 2, "name": "Bombasto" },
|
||||
{ "id": 3, "name": "Magneta" },
|
||||
{ "id": 4, "name": "Tornado" }
|
||||
]
|
||||
}
|
4
aio/content/examples/http/src/app/heroes/hero.ts
Normal file
4
aio/content/examples/http/src/app/heroes/hero.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/* HeroesComponent's private CSS styles */
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
.heroes li {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
width: 19em;
|
||||
}
|
||||
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
|
||||
.heroes a {
|
||||
color: #888;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.heroes a:hover {
|
||||
color:#607D8B;
|
||||
}
|
||||
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
position: relative;
|
||||
left: 24em;
|
||||
top: -32px;
|
||||
background-color: gray !important;
|
||||
color: white;
|
||||
display: inherit;
|
||||
padding: 5px 8px;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 100%;
|
||||
margin-bottom: 2px;
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
.heroes input {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
width: 12em;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<h3>Heroes</h3>
|
||||
<!-- #docregion add -->
|
||||
<div>
|
||||
<label>Hero name:
|
||||
<input #heroName />
|
||||
</label>
|
||||
<!-- (click) passes input value to add() and then clears the input -->
|
||||
<button (click)="add(heroName.value); heroName.value=''">
|
||||
add
|
||||
</button>
|
||||
<button (click)="search(heroName.value)">
|
||||
search
|
||||
</button>
|
||||
</div>
|
||||
<!-- #enddocregion add -->
|
||||
|
||||
<!-- #docregion list -->
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes">
|
||||
<a (click)="edit(hero)">
|
||||
<span class="badge">{{ hero.id || -1 }}</span>
|
||||
<span *ngIf="hero!==editHero">{{hero.name}}</span>
|
||||
<input *ngIf="hero===editHero" [(ngModel)]="hero.name"
|
||||
(blur)="update()" (keyup.enter)="update()">
|
||||
</a>
|
||||
<!-- #docregion delete -->
|
||||
<button class="delete" title="delete hero"
|
||||
(click)="delete(hero)">x</button>
|
||||
<!-- #enddocregion delete -->
|
||||
</li>
|
||||
</ul>
|
||||
<!-- #enddocregion list -->
|
76
aio/content/examples/http/src/app/heroes/heroes.component.ts
Normal file
76
aio/content/examples/http/src/app/heroes/heroes.component.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroesService } from './heroes.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
templateUrl: './heroes.component.html',
|
||||
providers: [ HeroesService ],
|
||||
styleUrls: ['./heroes.component.css']
|
||||
})
|
||||
export class HeroesComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
editHero: Hero; // the hero currently being edited
|
||||
|
||||
constructor(private heroesService: HeroesService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getHeroes();
|
||||
}
|
||||
|
||||
getHeroes(): void {
|
||||
this.heroesService.getHeroes()
|
||||
.subscribe(heroes => this.heroes = heroes);
|
||||
}
|
||||
|
||||
add(name: string): void {
|
||||
this.editHero = undefined;
|
||||
name = name.trim();
|
||||
if (!name) { return; }
|
||||
|
||||
// The server will generate the id for this new hero
|
||||
const newHero: Hero = { name } as Hero;
|
||||
// #docregion add-hero-subscribe
|
||||
this.heroesService.addHero(newHero)
|
||||
.subscribe(hero => this.heroes.push(hero));
|
||||
// #enddocregion add-hero-subscribe
|
||||
}
|
||||
|
||||
delete(hero: Hero): void {
|
||||
this.heroes = this.heroes.filter(h => h !== hero);
|
||||
// #docregion delete-hero-subscribe
|
||||
this.heroesService.deleteHero(hero.id).subscribe();
|
||||
// #enddocregion delete-hero-subscribe
|
||||
/*
|
||||
// #docregion delete-hero-no-subscribe
|
||||
// oops ... subscribe() is missing so nothing happens
|
||||
this.heroesService.deleteHero(hero.id);
|
||||
// #enddocregion delete-hero-no-subscribe
|
||||
*/
|
||||
}
|
||||
|
||||
edit(hero) {
|
||||
this.editHero = hero;
|
||||
}
|
||||
|
||||
search(searchTerm: string) {
|
||||
this.editHero = undefined;
|
||||
if (searchTerm) {
|
||||
this.heroesService.searchHeroes(searchTerm)
|
||||
.subscribe(heroes => this.heroes = heroes);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.editHero) {
|
||||
this.heroesService.updateHero(this.editHero)
|
||||
.subscribe(hero => {
|
||||
// replace the hero in the heroes list with update from server
|
||||
const ix = hero ? this.heroes.findIndex(h => h.id === hero.id) : -1;
|
||||
if (ix > -1) { this.heroes[ix] = hero; }
|
||||
});
|
||||
this.editHero = undefined;
|
||||
}
|
||||
}
|
||||
}
|
156
aio/content/examples/http/src/app/heroes/heroes.service.spec.ts
Normal file
156
aio/content/examples/http/src/app/heroes/heroes.service.spec.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
|
||||
// Other imports
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpClient, HttpResponse } from '@angular/common/http';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroesService } from './heroes.service';
|
||||
import { HttpErrorHandler } from '../http-error-handler.service';
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
describe('HeroesService', () => {
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
let heroService: HeroesService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
// Import the HttpClient mocking services
|
||||
imports: [ HttpClientTestingModule ],
|
||||
// Provide the service-under-test and its dependencies
|
||||
providers: [
|
||||
HeroesService,
|
||||
HttpErrorHandler,
|
||||
MessageService
|
||||
]
|
||||
});
|
||||
|
||||
// Inject the http, test controller, and service-under-test
|
||||
// as they will be referenced by each test.
|
||||
httpClient = TestBed.get(HttpClient);
|
||||
httpTestingController = TestBed.get(HttpTestingController);
|
||||
heroService = TestBed.get(HeroesService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// After every test, assert that there are no more pending requests.
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
/// HeroService method tests begin ///
|
||||
|
||||
describe('#getHeroes', () => {
|
||||
let expectedHeroes: Hero[];
|
||||
|
||||
beforeEach(() => {
|
||||
heroService = TestBed.get(HeroesService);
|
||||
expectedHeroes = [
|
||||
{ id: 1, name: 'A' },
|
||||
{ id: 2, name: 'B' },
|
||||
] as Hero[];
|
||||
});
|
||||
|
||||
it('should return expected heroes (called once)', () => {
|
||||
|
||||
heroService.getHeroes().subscribe(
|
||||
heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
|
||||
fail
|
||||
);
|
||||
|
||||
// HeroService should have made one request to GET heroes from expected URL
|
||||
const req = httpTestingController.expectOne(heroService.heroesUrl);
|
||||
expect(req.request.method).toEqual('GET');
|
||||
|
||||
// Respond with the mock heroes
|
||||
req.flush(expectedHeroes);
|
||||
});
|
||||
|
||||
it('should be OK returning no heroes', () => {
|
||||
|
||||
heroService.getHeroes().subscribe(
|
||||
heroes => expect(heroes.length).toEqual(0, 'should have empty heroes array'),
|
||||
fail
|
||||
);
|
||||
|
||||
const req = httpTestingController.expectOne(heroService.heroesUrl);
|
||||
req.flush([]); // Respond with no heroes
|
||||
});
|
||||
|
||||
// This service reports the error but finds a way to let the app keep going.
|
||||
it('should turn 404 into an empty heroes result', () => {
|
||||
|
||||
heroService.getHeroes().subscribe(
|
||||
heroes => expect(heroes.length).toEqual(0, 'should return empty heroes array'),
|
||||
fail
|
||||
);
|
||||
|
||||
const req = httpTestingController.expectOne(heroService.heroesUrl);
|
||||
|
||||
// respond with a 404 and the error message in the body
|
||||
const msg = 'deliberate 404 error';
|
||||
req.flush(msg, {status: 404, statusText: 'Not Found'});
|
||||
});
|
||||
|
||||
it('should return expected heroes (called multiple times)', () => {
|
||||
|
||||
heroService.getHeroes().subscribe();
|
||||
heroService.getHeroes().subscribe();
|
||||
heroService.getHeroes().subscribe(
|
||||
heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
|
||||
fail
|
||||
);
|
||||
|
||||
const requests = httpTestingController.match(heroService.heroesUrl);
|
||||
expect(requests.length).toEqual(3, 'calls to getHeroes()');
|
||||
|
||||
// Respond to each request with different mock hero results
|
||||
requests[0].flush([]);
|
||||
requests[1].flush([{id: 1, name: 'bob'}]);
|
||||
requests[2].flush(expectedHeroes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateHero', () => {
|
||||
// Expecting the query form of URL so should not 404 when id not found
|
||||
const makeUrl = (id: number) => `${heroService.heroesUrl}/?id=${id}`;
|
||||
|
||||
it('should update a hero and return it', () => {
|
||||
|
||||
const updateHero: Hero = { id: 1, name: 'A' };
|
||||
|
||||
heroService.updateHero(updateHero).subscribe(
|
||||
data => expect(data).toEqual(updateHero, 'should return the hero'),
|
||||
fail
|
||||
);
|
||||
|
||||
// HeroService should have made one request to PUT hero
|
||||
const req = httpTestingController.expectOne(heroService.heroesUrl);
|
||||
expect(req.request.method).toEqual('PUT');
|
||||
expect(req.request.body).toEqual(updateHero);
|
||||
|
||||
// Expect server to return the hero after PUT
|
||||
const expectedResponse = new HttpResponse(
|
||||
{ status: 200, statusText: 'OK', body: updateHero });
|
||||
req.event(expectedResponse);
|
||||
});
|
||||
|
||||
// This service reports the error but finds a way to let the app keep going.
|
||||
it('should turn 404 error into return of the update hero', () => {
|
||||
const updateHero: Hero = { id: 1, name: 'A' };
|
||||
|
||||
heroService.updateHero(updateHero).subscribe(
|
||||
data => expect(data).toEqual(updateHero, 'should return the update hero'),
|
||||
fail
|
||||
);
|
||||
|
||||
const req = httpTestingController.expectOne(heroService.heroesUrl);
|
||||
|
||||
// respond with a 404 and the error message in the body
|
||||
const msg = 'deliberate 404 error';
|
||||
req.flush(msg, {status: 404, statusText: 'Not Found'});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: test other HeroService methods
|
||||
});
|
99
aio/content/examples/http/src/app/heroes/heroes.service.ts
Normal file
99
aio/content/examples/http/src/app/heroes/heroes.service.ts
Normal file
@ -0,0 +1,99 @@
|
||||
// #docplaster
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
// #docregion http-options
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
// #enddocregion http-options
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HttpErrorHandler, HandleError } from '../http-error-handler.service';
|
||||
|
||||
// #docregion http-options
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'my-auth-token'
|
||||
})
|
||||
};
|
||||
// #enddocregion http-options
|
||||
|
||||
@Injectable()
|
||||
export class HeroesService {
|
||||
heroesUrl = 'api/heroes'; // URL to web api
|
||||
private handleError: HandleError;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
httpErrorHandler: HttpErrorHandler) {
|
||||
this.handleError = httpErrorHandler.createHandleError('HeroesService');
|
||||
}
|
||||
|
||||
/** GET heroes from the server */
|
||||
getHeroes (): Observable<Hero[]> {
|
||||
return this.http.get<Hero[]>(this.heroesUrl)
|
||||
.pipe(
|
||||
catchError(this.handleError('getHeroes', []))
|
||||
);
|
||||
}
|
||||
|
||||
// #docregion searchHeroes
|
||||
/* GET heroes whose name contains search term */
|
||||
searchHeroes(term: string): Observable<Hero[]> {
|
||||
term = term.trim();
|
||||
|
||||
// Add safe, URL encoded search parameter if there is a search term
|
||||
const options = term ?
|
||||
{ params: new HttpParams().set('name', term) } : {};
|
||||
|
||||
return this.http.get<Hero[]>(this.heroesUrl, options)
|
||||
.pipe(
|
||||
catchError(this.handleError<Hero[]>('searchHeroes', []))
|
||||
);
|
||||
}
|
||||
// #enddocregion searchHeroes
|
||||
|
||||
//////// Save methods //////////
|
||||
|
||||
// #docregion addHero
|
||||
/** POST: add a new hero to the database */
|
||||
addHero (hero: Hero): Observable<Hero> {
|
||||
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
|
||||
.pipe(
|
||||
catchError(this.handleError('addHero', hero))
|
||||
);
|
||||
}
|
||||
// #enddocregion addHero
|
||||
|
||||
// #docregion deleteHero
|
||||
/** DELETE: delete the hero from the server */
|
||||
deleteHero (id: number): Observable<{}> {
|
||||
const url = `${this.heroesUrl}/${id}`; // DELETE api/heroes/42
|
||||
return this.http.delete(url, httpOptions)
|
||||
.pipe(
|
||||
catchError(this.handleError('deleteHero'))
|
||||
);
|
||||
}
|
||||
// #enddocregion deleteHero
|
||||
|
||||
// #docregion updateHero
|
||||
/** PUT: update the hero on the server. Returns the updated hero upon success. */
|
||||
updateHero (hero: Hero): Observable<Hero> {
|
||||
// #enddocregion updateHero
|
||||
// #docregion update-headers
|
||||
httpOptions.headers =
|
||||
httpOptions.headers.set('Authorization', 'my-new-auth-token');
|
||||
// #enddocregion update-headers
|
||||
|
||||
// #docregion updateHero
|
||||
return this.http.put<Hero>(this.heroesUrl, hero, httpOptions)
|
||||
.pipe(
|
||||
catchError(this.handleError('updateHero', hero))
|
||||
);
|
||||
}
|
||||
// #enddocregion updateHero
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
/** Type of the handleError function returned by HttpErrorHandler.createHandleError */
|
||||
export type HandleError =
|
||||
<T> (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable<T>;
|
||||
|
||||
/** Handles HttpClient errors */
|
||||
@Injectable()
|
||||
export class HttpErrorHandler {
|
||||
constructor(private messageService: MessageService) { }
|
||||
|
||||
/** Create curried handleError function that already knows the service name */
|
||||
createHandleError = (serviceName = '') => <T>
|
||||
(operation = 'operation', result = {} as T) => this.handleError(serviceName, operation, result);
|
||||
|
||||
/**
|
||||
* Returns a function that handles Http operation failures.
|
||||
* This error handler lets the app continue to run as if no error occurred.
|
||||
* @param serviceName = name of the data service that attempted the operation
|
||||
* @param operation - name of the operation that failed
|
||||
* @param result - optional value to return as the observable result
|
||||
*/
|
||||
handleError<T> (serviceName = '', operation = 'operation', result = {} as T) {
|
||||
|
||||
return (error: HttpErrorResponse): Observable<T> => {
|
||||
// TODO: send the error to remote logging infrastructure
|
||||
console.error(error); // log to console instead
|
||||
|
||||
const message = (error.error instanceof ErrorEvent) ?
|
||||
error.error.message :
|
||||
`server returned code ${error.status} with body "${error.error}"`;
|
||||
|
||||
// TODO: better job of transforming error for user consumption
|
||||
this.messageService.add(`${serviceName}: ${operation} failed: ${message}`);
|
||||
|
||||
// Let the app keep running by returning a safe result.
|
||||
return of( result );
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// #docplaster
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
// #docregion
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||
// Get the auth token from the service.
|
||||
const authToken = this.auth.getAuthorizationToken();
|
||||
|
||||
// #enddocregion
|
||||
/*
|
||||
* The verbose way:
|
||||
// #docregion
|
||||
// Clone the request and replace the original headers with
|
||||
// cloned headers, updated with the authorization.
|
||||
const authReq = req.clone({
|
||||
headers: req.headers.set('Authorization', authToken)
|
||||
});
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion set-header-shortcut
|
||||
// Clone the request and set the new header in one step.
|
||||
const authReq = req.clone({ setHeaders: { Authorization: authToken } });
|
||||
// #enddocregion set-header-shortcut
|
||||
// #docregion
|
||||
|
||||
// send cloned request with header to the next handler.
|
||||
return next.handle(authReq);
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,86 @@
|
||||
// #docplaster
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpHeaders, HttpRequest, HttpResponse,
|
||||
HttpInterceptor, HttpHandler
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { startWith, tap } from 'rxjs/operators';
|
||||
|
||||
import { RequestCache } from '../request-cache.service';
|
||||
import { searchUrl } from '../package-search/package-search.service';
|
||||
|
||||
|
||||
/**
|
||||
* If request is cachable (e.g., package search) and
|
||||
* response is in cache return the cached response as observable.
|
||||
* If has 'x-refresh' header that is true,
|
||||
* then also re-run the package search, using response from next(),
|
||||
* returning an observable that emits the cached response first.
|
||||
*
|
||||
* If not in cache or not cachable,
|
||||
* pass request through to next()
|
||||
*/
|
||||
// #docregion v1
|
||||
@Injectable()
|
||||
export class CachingInterceptor implements HttpInterceptor {
|
||||
constructor(private cache: RequestCache) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||
// continue if not cachable.
|
||||
if (!isCachable(req)) { return next.handle(req); }
|
||||
|
||||
const cachedResponse = this.cache.get(req);
|
||||
// #enddocregion v1
|
||||
// #docregion intercept-refresh
|
||||
// cache-then-refresh
|
||||
if (req.headers.get('x-refresh')) {
|
||||
const results$ = sendRequest(req, next, this.cache);
|
||||
return cachedResponse ?
|
||||
results$.pipe( startWith(cachedResponse) ) :
|
||||
results$;
|
||||
}
|
||||
// cache-or-fetch
|
||||
// #docregion v1
|
||||
return cachedResponse ?
|
||||
of(cachedResponse) : sendRequest(req, next, this.cache);
|
||||
// #enddocregion intercept-refresh
|
||||
}
|
||||
}
|
||||
// #enddocregion v1
|
||||
|
||||
|
||||
/** Is this request cachable? */
|
||||
function isCachable(req: HttpRequest<any>) {
|
||||
// Only GET requests are cachable
|
||||
return req.method === 'GET' &&
|
||||
// Only npm package search is cachable in this app
|
||||
-1 < req.url.indexOf(searchUrl);
|
||||
}
|
||||
|
||||
// #docregion send-request
|
||||
/**
|
||||
* Get server response observable by sending request to `next()`.
|
||||
* Will add the response to the cache on the way out.
|
||||
*/
|
||||
function sendRequest(
|
||||
req: HttpRequest<any>,
|
||||
next: HttpHandler,
|
||||
cache: RequestCache): Observable<HttpEvent<any>> {
|
||||
|
||||
// No headers allowed in npm search request
|
||||
const noHeaderReq = req.clone({ headers: new HttpHeaders() });
|
||||
|
||||
return next.handle(noHeaderReq).pipe(
|
||||
tap(event => {
|
||||
// There may be other events besides the response.
|
||||
if (event instanceof HttpResponse) {
|
||||
cache.put(req, event); // Update the cache.
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
// #enddocregion send-request
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Injectable()
|
||||
export class EnsureHttpsInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// #docregion excerpt
|
||||
// clone request and replace 'http://' with 'https://' at the same time
|
||||
const secureReq = req.clone({
|
||||
url: req.url.replace('http://', 'https://')
|
||||
});
|
||||
// send the cloned, "secure" request to the next handler.
|
||||
return next.handle(secureReq);
|
||||
// #enddocregion excerpt
|
||||
}
|
||||
}
|
34
aio/content/examples/http/src/app/http-interceptors/index.ts
Normal file
34
aio/content/examples/http/src/app/http-interceptors/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// #docplaster
|
||||
// #docregion interceptor-providers
|
||||
/* "Barrel" of Http Interceptors */
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
||||
// #enddocregion interceptor-providers
|
||||
import { AuthInterceptor } from './auth-interceptor';
|
||||
import { CachingInterceptor } from './caching-interceptor';
|
||||
import { EnsureHttpsInterceptor } from './ensure-https-interceptor';
|
||||
import { LoggingInterceptor } from './logging-interceptor';
|
||||
// #docregion interceptor-providers
|
||||
import { NoopInterceptor } from './noop-interceptor';
|
||||
// #enddocregion interceptor-providers
|
||||
import { TrimNameInterceptor } from './trim-name-interceptor';
|
||||
import { UploadInterceptor } from './upload-interceptor';
|
||||
|
||||
// #docregion interceptor-providers
|
||||
|
||||
/** Http interceptor providers in outside-in order */
|
||||
export const httpInterceptorProviders = [
|
||||
// #docregion noop-provider
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
|
||||
// #enddocregion noop-provider, interceptor-providers
|
||||
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: EnsureHttpsInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: TrimNameInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: UploadInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true },
|
||||
|
||||
// #docregion interceptor-providers
|
||||
];
|
||||
// #enddocregion interceptor-providers
|
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler,
|
||||
HttpRequest, HttpResponse
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
// #docregion excerpt
|
||||
import { finalize, tap } from 'rxjs/operators';
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements HttpInterceptor {
|
||||
constructor(private messenger: MessageService) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||
const started = Date.now();
|
||||
let ok: string;
|
||||
|
||||
// extend server response observable with logging
|
||||
return next.handle(req)
|
||||
.pipe(
|
||||
tap(
|
||||
// Succeeds when there is a response; ignore other events
|
||||
event => ok = event instanceof HttpResponse ? 'succeeded' : '',
|
||||
// Operation failed; error is an HttpErrorResponse
|
||||
error => ok = 'failed'
|
||||
),
|
||||
// Log when response observable either completes or errors
|
||||
finalize(() => {
|
||||
const elapsed = Date.now() - started;
|
||||
const msg = `${req.method} "${req.urlWithParams}"
|
||||
${ok} in ${elapsed} ms.`;
|
||||
this.messenger.add(msg);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// #enddocregion excerpt
|
@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/** Pass untouched request through to the next request handler. */
|
||||
@Injectable()
|
||||
export class NoopInterceptor implements HttpInterceptor {
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler):
|
||||
Observable<HttpEvent<any>> {
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Injectable()
|
||||
export class TrimNameInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const body = req.body;
|
||||
if (!body || !body.name ) {
|
||||
return next.handle(req);
|
||||
}
|
||||
// #docregion excerpt
|
||||
// copy the body and trim whitespace from the name property
|
||||
const newBody = { ...body, name: body.name.trim() };
|
||||
// clone request and set its body
|
||||
const newReq = req.clone({ body: newBody });
|
||||
// send the cloned request to the next handler.
|
||||
return next.handle(newReq);
|
||||
// #enddocregion excerpt
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent, HttpInterceptor, HttpHandler,
|
||||
HttpRequest, HttpResponse,
|
||||
HttpEventType, HttpProgressEvent
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
/** Simulate server replying to file upload request */
|
||||
@Injectable()
|
||||
export class UploadInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (req.url.indexOf('/upload/file') === -1) {
|
||||
return next.handle(req);
|
||||
}
|
||||
const delay = 300; // Todo: inject delay?
|
||||
return createUploadEvents(delay);
|
||||
}
|
||||
}
|
||||
|
||||
/** Create simulation of upload event stream */
|
||||
function createUploadEvents(delay: number) {
|
||||
// Simulate XHR behavior which would provide this information in a ProgressEvent
|
||||
const chunks = 5;
|
||||
const total = 12345678;
|
||||
const chunkSize = Math.ceil(total / chunks);
|
||||
|
||||
return new Observable<HttpEvent<any>>(observer => {
|
||||
// notify the event stream that the request was sent.
|
||||
observer.next({type: HttpEventType.Sent});
|
||||
|
||||
uploadLoop(0);
|
||||
|
||||
function uploadLoop(loaded: number) {
|
||||
// N.B.: Cannot use setInterval or rxjs delay (which uses setInterval)
|
||||
// because e2e test won't complete. A zone thing?
|
||||
// Use setTimeout and tail recursion instead.
|
||||
setTimeout(() => {
|
||||
loaded += chunkSize;
|
||||
|
||||
if (loaded >= total) {
|
||||
const doneResponse = new HttpResponse({
|
||||
status: 201, // OK but no body;
|
||||
});
|
||||
observer.next(doneResponse);
|
||||
observer.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
const progressEvent: HttpProgressEvent = {
|
||||
type: HttpEventType.UploadProgress,
|
||||
loaded,
|
||||
total
|
||||
};
|
||||
observer.next(progressEvent);
|
||||
uploadLoop(loaded);
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
}
|
13
aio/content/examples/http/src/app/in-memory-data.service.ts
Normal file
13
aio/content/examples/http/src/app/in-memory-data.service.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Mr. Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
];
|
||||
return {heroes};
|
||||
}
|
||||
}
|
14
aio/content/examples/http/src/app/message.service.ts
Normal file
14
aio/content/examples/http/src/app/message.service.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class MessageService {
|
||||
messages: string[] = [];
|
||||
|
||||
add(message: string) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<div *ngIf="messageService.messages.length">
|
||||
<h3>Messages</h3>
|
||||
<button class="clear" (click)="messageService.clear()">clear</button>
|
||||
<br>
|
||||
<ol>
|
||||
<li *ngFor='let message of messageService.messages'> {{message}} </li>
|
||||
</ol>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-messages',
|
||||
templateUrl: './messages.component.html'
|
||||
})
|
||||
export class MessagesComponent {
|
||||
constructor(public messageService: MessageService) {}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<!-- #docplaster -->
|
||||
<h3>Search Npm Packages</h3>
|
||||
<p><i>Searches when typing stops. Caches for 30 seconds.</i></p>
|
||||
<!-- #docregion search -->
|
||||
<input (keyup)="search($event.target.value)" id="name" placeholder="Search"/>
|
||||
<!-- #enddocregion search -->
|
||||
<input type="checkbox" id="refresh" [checked]="withRefresh" (click)="toggleRefresh()">
|
||||
<label for="refresh">with refresh</label>
|
||||
<!-- #docregion search -->
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let package of packages$ | async">
|
||||
<b>{{package.name}} v.{{package.version}}</b> -
|
||||
<i>{{package.description}}</i>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- #enddocregion search -->
|
@ -0,0 +1,39 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { NpmPackageInfo, PackageSearchService } from './package-search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-package-search',
|
||||
templateUrl: './package-search.component.html',
|
||||
providers: [ PackageSearchService ]
|
||||
})
|
||||
export class PackageSearchComponent implements OnInit {
|
||||
// #docregion debounce
|
||||
withRefresh = false;
|
||||
packages$: Observable<NpmPackageInfo[]>;
|
||||
private searchText$ = new Subject<string>();
|
||||
|
||||
search(packageName: string) {
|
||||
this.searchText$.next(packageName);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.packages$ = this.searchText$.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
switchMap(packageName =>
|
||||
this.searchService.search(packageName, this.withRefresh))
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private searchService: PackageSearchService) { }
|
||||
|
||||
// #enddocregion debounce
|
||||
|
||||
toggleRefresh() { this.withRefresh = ! this.withRefresh; }
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
import { HttpErrorHandler, HandleError } from '../http-error-handler.service';
|
||||
|
||||
export interface NpmPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const searchUrl = 'https://npmsearch.com/query';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'x-refresh': 'true'
|
||||
})
|
||||
};
|
||||
|
||||
function createHttpOptions(packageName: string, refresh = false) {
|
||||
// npm package name search api
|
||||
// e.g., http://npmsearch.com/query?q=dom'
|
||||
const params = new HttpParams({ fromObject: { q: packageName } });
|
||||
const headerMap = refresh ? {'x-refresh': 'true'} : {};
|
||||
const headers = new HttpHeaders(headerMap) ;
|
||||
return { headers, params };
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PackageSearchService {
|
||||
private handleError: HandleError;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
httpErrorHandler: HttpErrorHandler) {
|
||||
this.handleError = httpErrorHandler.createHandleError('HeroesService');
|
||||
}
|
||||
|
||||
search (packageName: string, refresh = false): Observable<NpmPackageInfo[]> {
|
||||
// clear if no pkg name
|
||||
if (!packageName.trim()) { return of([]); }
|
||||
|
||||
const options = createHttpOptions(packageName, refresh);
|
||||
|
||||
// TODO: Add error handling
|
||||
return this.http.get(searchUrl, options).pipe(
|
||||
map((data: any) => {
|
||||
return data.results.map(entry => ({
|
||||
name: entry.name[0],
|
||||
version: entry.version[0],
|
||||
description: entry.description[0]
|
||||
} as NpmPackageInfo )
|
||||
);
|
||||
}),
|
||||
catchError(this.handleError('search', []))
|
||||
);
|
||||
}
|
||||
}
|
60
aio/content/examples/http/src/app/request-cache.service.ts
Normal file
60
aio/content/examples/http/src/app/request-cache.service.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpResponse } from '@angular/common/http';
|
||||
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
export interface RequestCacheEntry {
|
||||
url: string;
|
||||
response: HttpResponse<any>;
|
||||
lastRead: number;
|
||||
}
|
||||
|
||||
// #docregion request-cache
|
||||
export abstract class RequestCache {
|
||||
abstract get(req: HttpRequest<any>): HttpResponse<any> | undefined;
|
||||
abstract put(req: HttpRequest<any>, response: HttpResponse<any>): void
|
||||
}
|
||||
// #enddocregion request-cache
|
||||
|
||||
const maxAge = 30000; // maximum cache age (ms)
|
||||
|
||||
@Injectable()
|
||||
export class RequestCacheWithMap implements RequestCache {
|
||||
|
||||
cache = new Map<string, RequestCacheEntry>();
|
||||
|
||||
constructor(private messenger: MessageService) { }
|
||||
|
||||
get(req: HttpRequest<any>): HttpResponse<any> | undefined {
|
||||
const url = req.urlWithParams;
|
||||
const cached = this.cache.get(url);
|
||||
|
||||
if (!cached) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isExpired = cached.lastRead < (Date.now() - maxAge);
|
||||
const expired = isExpired ? 'expired ' : '';
|
||||
this.messenger.add(
|
||||
`Found ${expired}cached response for "${url}".`);
|
||||
return isExpired ? undefined : cached.response;
|
||||
}
|
||||
|
||||
put(req: HttpRequest<any>, response: HttpResponse<any>): void {
|
||||
const url = req.urlWithParams;
|
||||
this.messenger.add(`Caching response from "${url}".`);
|
||||
|
||||
const entry = { url, response, lastRead: Date.now() };
|
||||
this.cache.set(url, entry);
|
||||
|
||||
// remove expired cache entries
|
||||
const expired = Date.now() - maxAge;
|
||||
this.cache.forEach(entry => {
|
||||
if (entry.lastRead < expired) {
|
||||
this.cache.delete(entry.url);
|
||||
}
|
||||
});
|
||||
|
||||
this.messenger.add(`Request cache size: ${this.cache.size}.`);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<h1>Tour of Heroes ({{mode}})</h1>
|
||||
<h3>Heroes:</h3>
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes">{{hero.name}}</li>
|
||||
</ul>
|
||||
|
||||
<label>New hero name: <input #newHeroName /></label>
|
||||
<button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button>
|
||||
|
||||
<p class="error" *ngIf="errorMessage">{{errorMessage}}</p>
|
@ -1,40 +0,0 @@
|
||||
// #docregion
|
||||
// Promise Version
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service.promise';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-promise',
|
||||
templateUrl: './hero-list.component.html',
|
||||
providers: [ HeroService ],
|
||||
styles: ['.error {color:red;}']
|
||||
})
|
||||
// #docregion component
|
||||
export class HeroListPromiseComponent implements OnInit {
|
||||
errorMessage: string;
|
||||
heroes: Hero[];
|
||||
mode = 'Promise';
|
||||
|
||||
constructor (private heroService: HeroService) {}
|
||||
|
||||
ngOnInit() { this.getHeroes(); }
|
||||
|
||||
// #docregion methods
|
||||
getHeroes() {
|
||||
this.heroService.getHeroes()
|
||||
.then(
|
||||
heroes => this.heroes = heroes,
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
|
||||
addHero (name: string) {
|
||||
if (!name) { return; }
|
||||
this.heroService.addHero(name)
|
||||
.then(
|
||||
hero => this.heroes.push(hero),
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
// #enddocregion methods
|
||||
}
|
||||
// #enddocregion component
|
@ -1,44 +0,0 @@
|
||||
// #docregion
|
||||
// Observable Version
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list',
|
||||
templateUrl: './hero-list.component.html',
|
||||
providers: [ HeroService ],
|
||||
styles: ['.error {color:red;}']
|
||||
})
|
||||
// #docregion component
|
||||
export class HeroListComponent implements OnInit {
|
||||
errorMessage: string;
|
||||
heroes: Hero[];
|
||||
mode = 'Observable';
|
||||
|
||||
constructor (private heroService: HeroService) {}
|
||||
|
||||
ngOnInit() { this.getHeroes(); }
|
||||
|
||||
// #docregion methods
|
||||
// #docregion getHeroes
|
||||
getHeroes() {
|
||||
this.heroService.getHeroes()
|
||||
.subscribe(
|
||||
heroes => this.heroes = heroes,
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
// #enddocregion getHeroes
|
||||
|
||||
// #docregion addHero
|
||||
addHero(name: string) {
|
||||
if (!name) { return; }
|
||||
this.heroService.create(name)
|
||||
.subscribe(
|
||||
hero => this.heroes.push(hero),
|
||||
error => this.errorMessage = <any>error);
|
||||
}
|
||||
// #enddocregion addHero
|
||||
// #enddocregion methods
|
||||
}
|
||||
// #enddocregion component
|
@ -1,60 +0,0 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// Promise Version
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Headers, RequestOptions } from '@angular/http';
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
// #enddocregion rxjs-imports
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
// URL to web api
|
||||
private heroesUrl = 'app/heroes';
|
||||
|
||||
constructor (private http: Http) {}
|
||||
|
||||
// #docregion methods
|
||||
getHeroes (): Promise<Hero[]> {
|
||||
return this.http.get(this.heroesUrl)
|
||||
.toPromise()
|
||||
.then(this.extractData)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
addHero (name: string): Promise<Hero> {
|
||||
let headers = new Headers({ 'Content-Type': 'application/json' });
|
||||
let options = new RequestOptions({ headers: headers });
|
||||
|
||||
return this.http.post(this.heroesUrl, { name }, options)
|
||||
.toPromise()
|
||||
.then(this.extractData)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
private extractData(res: Response) {
|
||||
let body = res.json();
|
||||
return body.data || { };
|
||||
}
|
||||
|
||||
private handleError (error: Response | any) {
|
||||
// In a real world app, we might use a remote logging infrastructure
|
||||
let errMsg: string;
|
||||
if (error instanceof Response) {
|
||||
const body = error.json() || '';
|
||||
const err = body.error || JSON.stringify(body);
|
||||
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
|
||||
} else {
|
||||
errMsg = error.message ? error.message : error.toString();
|
||||
}
|
||||
console.error(errMsg);
|
||||
return Promise.reject(errMsg);
|
||||
}
|
||||
|
||||
// #enddocregion methods
|
||||
}
|
||||
// #enddocregion
|
@ -1,80 +0,0 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// Observable Version
|
||||
// #docregion v1
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
// #enddocregion v1
|
||||
// #docregion import-request-options
|
||||
import { Headers, RequestOptions } from '@angular/http';
|
||||
// #enddocregion import-request-options
|
||||
// #docregion v1
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
// #enddocregion rxjs-imports
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
// #docregion endpoint
|
||||
private heroesUrl = 'api/heroes'; // URL to web API
|
||||
// #enddocregion endpoint
|
||||
|
||||
// #docregion ctor
|
||||
constructor (private http: Http) {}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion methods, error-handling, http-get
|
||||
getHeroes(): Observable<Hero[]> {
|
||||
return this.http.get(this.heroesUrl)
|
||||
.map(this.extractData)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion error-handling, http-get, v1
|
||||
|
||||
// #docregion create, create-sig
|
||||
create(name: string): Observable<Hero> {
|
||||
// #enddocregion create-sig
|
||||
let headers = new Headers({ 'Content-Type': 'application/json' });
|
||||
let options = new RequestOptions({ headers: headers });
|
||||
|
||||
return this.http.post(this.heroesUrl, { name }, options)
|
||||
.map(this.extractData)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion create
|
||||
|
||||
// #docregion v1, extract-data
|
||||
private extractData(res: Response) {
|
||||
let body = res.json();
|
||||
return body.data || { };
|
||||
}
|
||||
// #enddocregion extract-data
|
||||
// #docregion error-handling
|
||||
|
||||
private handleError (error: Response | any) {
|
||||
// In a real world app, you might use a remote logging infrastructure
|
||||
let errMsg: string;
|
||||
if (error instanceof Response) {
|
||||
const body = error.json() || '';
|
||||
const err = body.error || JSON.stringify(body);
|
||||
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
|
||||
} else {
|
||||
errMsg = error.message ? error.message : error.toString();
|
||||
}
|
||||
console.error(errMsg);
|
||||
return Observable.throw(errMsg);
|
||||
}
|
||||
// #enddocregion error-handling, methods
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
/*
|
||||
// #docregion endpoint-json
|
||||
private heroesUrl = 'app/heroes.json'; // URL to JSON file
|
||||
// #enddocregion endpoint-json
|
||||
*/
|
@ -1,6 +0,0 @@
|
||||
// #docregion
|
||||
export class Hero {
|
||||
constructor(
|
||||
public id: number,
|
||||
public name: string) { }
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<h3>Upload file</h3>
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
<div>
|
||||
<label for="picked">Choose file to upload</label>
|
||||
<div>
|
||||
<input type="file" id="picked" #picked
|
||||
(click)="message=''"
|
||||
(change)="onPicked(picked)">
|
||||
</div>
|
||||
</div>
|
||||
<p *ngIf="message">{{message}}</p>
|
||||
</form>
|
@ -0,0 +1,25 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { UploaderService } from './uploader.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-uploader',
|
||||
templateUrl: './uploader.component.html',
|
||||
providers: [ UploaderService ]
|
||||
})
|
||||
export class UploaderComponent {
|
||||
message: string;
|
||||
|
||||
constructor(private uploaderService: UploaderService) {}
|
||||
|
||||
onPicked(input: HTMLInputElement) {
|
||||
const file = input.files[0];
|
||||
if (file) {
|
||||
this.uploaderService.upload(file).subscribe(
|
||||
msg => {
|
||||
input.value = null;
|
||||
this.message = msg;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
105
aio/content/examples/http/src/app/uploader/uploader.service.ts
Normal file
105
aio/content/examples/http/src/app/uploader/uploader.service.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpClient, HttpEvent, HttpEventType, HttpProgressEvent,
|
||||
HttpRequest, HttpResponse, HttpErrorResponse
|
||||
} from '@angular/common/http';
|
||||
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { catchError, last, map, tap } from 'rxjs/operators';
|
||||
|
||||
import { MessageService } from '../message.service';
|
||||
|
||||
@Injectable()
|
||||
export class UploaderService {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private messenger: MessageService) {}
|
||||
|
||||
// If uploading multiple files, change to:
|
||||
// upload(files: FileList) {
|
||||
// const formData = new FormData();
|
||||
// files.forEach(f => formData.append(f.name, f));
|
||||
// new HttpRequest('POST', '/upload/file', formData, {reportProgress: true});
|
||||
// ...
|
||||
// }
|
||||
|
||||
upload(file: File) {
|
||||
if (!file) { return; }
|
||||
|
||||
// COULD HAVE WRITTEN:
|
||||
// return this.http.post('/upload/file', file, {
|
||||
// reportProgress: true,
|
||||
// observe: 'events'
|
||||
// }).pipe(
|
||||
|
||||
// Create the request object that POSTs the file to an upload endpoint.
|
||||
// The `reportProgress` option tells HttpClient to listen and return
|
||||
// XHR progress events.
|
||||
// #docregion upload-request
|
||||
const req = new HttpRequest('POST', '/upload/file', file, {
|
||||
reportProgress: true
|
||||
});
|
||||
// #enddocregion upload-request
|
||||
|
||||
// #docregion upload-body
|
||||
// The `HttpClient.request` API produces a raw event stream
|
||||
// which includes start (sent), progress, and response events.
|
||||
return this.http.request(req).pipe(
|
||||
map(event => this.getEventMessage(event, file)),
|
||||
tap(message => this.showProgress(message)),
|
||||
last(), // return last (completed) message to caller
|
||||
catchError(this.handleError(file))
|
||||
);
|
||||
// #enddocregion upload-body
|
||||
}
|
||||
|
||||
// #docregion getEventMessage
|
||||
/** Return distinct message for sent, upload progress, & response events */
|
||||
private getEventMessage(event: HttpEvent<any>, file: File) {
|
||||
switch (event.type) {
|
||||
case HttpEventType.Sent:
|
||||
return `Uploading file "${file.name}" of size ${file.size}.`;
|
||||
|
||||
case HttpEventType.UploadProgress:
|
||||
// Compute and show the % done:
|
||||
const percentDone = Math.round(100 * event.loaded / event.total);
|
||||
return `File "${file.name}" is ${percentDone}% uploaded.`;
|
||||
|
||||
case HttpEventType.Response:
|
||||
return `File "${file.name}" was completely uploaded!`;
|
||||
|
||||
default:
|
||||
return `File "${file.name}" surprising upload event: ${event.type}.`;
|
||||
}
|
||||
}
|
||||
// #enddocregion getEventMessage
|
||||
|
||||
/**
|
||||
* Returns a function that handles Http upload failures.
|
||||
* @param file - File object for file being uploaded
|
||||
*
|
||||
* When no `UploadInterceptor` and no server,
|
||||
* you'll end up here in the error handler.
|
||||
*/
|
||||
private handleError(file: File) {
|
||||
const userMessage = `${file.name} upload failed.`;
|
||||
|
||||
return (error: HttpErrorResponse) => {
|
||||
// TODO: send the error to remote logging infrastructure
|
||||
console.error(error); // log to console instead
|
||||
|
||||
const message = (error.error instanceof Error) ?
|
||||
error.error.message :
|
||||
`server returned code ${error.status} with body "${error.error}"`;
|
||||
|
||||
this.messenger.add(`${userMessage} ${message}`);
|
||||
|
||||
// Let app keep running but indicate failure.
|
||||
return of(userMessage);
|
||||
};
|
||||
}
|
||||
|
||||
private showProgress(message: string) {
|
||||
this.messenger.add(message);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
|
||||
// #docregion import-subject
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
// #enddocregion import-subject
|
||||
|
||||
import { WikipediaService } from './wikipedia.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-wiki-smart',
|
||||
template: `
|
||||
<h1>Smarter Wikipedia Demo</h1>
|
||||
<p>Search when typing stops</p>
|
||||
<input #term (keyup)="search(term.value)"/>
|
||||
<ul>
|
||||
<li *ngFor="let item of items | async">{{item}}</li>
|
||||
</ul>`,
|
||||
providers: [ WikipediaService ]
|
||||
})
|
||||
export class WikiSmartComponent implements OnInit {
|
||||
items: Observable<string[]>;
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) {}
|
||||
|
||||
// #docregion subject
|
||||
private searchTermStream = new Subject<string>();
|
||||
search(term: string) { this.searchTermStream.next(term); }
|
||||
// #enddocregion subject
|
||||
|
||||
ngOnInit() {
|
||||
// #docregion observable-operators
|
||||
this.items = this.searchTermStream
|
||||
.debounceTime(300)
|
||||
.distinctUntilChanged()
|
||||
.switchMap((term: string) => this.wikipediaService.search(term));
|
||||
// #enddocregion observable-operators
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { WikipediaService } from './wikipedia.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-wiki',
|
||||
template: `
|
||||
<h1>Wikipedia Demo</h1>
|
||||
<p>Search after each keystroke</p>
|
||||
<input #term (keyup)="search(term.value)"/>
|
||||
<ul>
|
||||
<li *ngFor="let item of items | async">{{item}}</li>
|
||||
</ul>`,
|
||||
providers: [ WikipediaService ]
|
||||
})
|
||||
export class WikiComponent {
|
||||
items: Observable<string[]>;
|
||||
|
||||
constructor (private wikipediaService: WikipediaService) { }
|
||||
|
||||
search (term: string) {
|
||||
this.items = this.wikipediaService.search(term);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Create the query string by hand
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Jsonp } from '@angular/http';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
@Injectable()
|
||||
export class WikipediaService {
|
||||
constructor(private jsonp: Jsonp) { }
|
||||
|
||||
// TODO: Add error handling
|
||||
search(term: string) {
|
||||
|
||||
let wikiUrl = 'http://en.wikipedia.org/w/api.php';
|
||||
|
||||
// #docregion query-string
|
||||
let queryString =
|
||||
`?search=${term}&action=opensearch&format=json&callback=JSONP_CALLBACK`;
|
||||
|
||||
return this.jsonp
|
||||
.get(wikiUrl + queryString)
|
||||
.map(response => <string[]> response.json()[1]);
|
||||
// #enddocregion query-string
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Jsonp, URLSearchParams } from '@angular/http';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
@Injectable()
|
||||
export class WikipediaService {
|
||||
constructor(private jsonp: Jsonp) {}
|
||||
|
||||
search (term: string) {
|
||||
|
||||
let wikiUrl = 'http://en.wikipedia.org/w/api.php';
|
||||
|
||||
// #docregion search-parameters
|
||||
let params = new URLSearchParams();
|
||||
params.set('search', term); // the user's search value
|
||||
params.set('action', 'opensearch');
|
||||
params.set('format', 'json');
|
||||
params.set('callback', 'JSONP_CALLBACK');
|
||||
// #enddocregion search-parameters
|
||||
|
||||
// #docregion call-jsonp
|
||||
// TODO: Add error handling
|
||||
return this.jsonp
|
||||
.get(wikiUrl, { search: params })
|
||||
.map(response => <string[]> response.json()[1]);
|
||||
// #enddocregion call-jsonp
|
||||
}
|
||||
}
|
4
aio/content/examples/http/src/assets/config.json
Normal file
4
aio/content/examples/http/src/assets/config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"heroesUrl": "api/heroes",
|
||||
"textfile": "assets/textfile.txt"
|
||||
}
|
1
aio/content/examples/http/src/assets/textfile.txt
Normal file
1
aio/content/examples/http/src/assets/textfile.txt
Normal file
@ -0,0 +1 @@
|
||||
This is the downloaded text file
|
88
aio/content/examples/http/src/browser-test-shim.js
Normal file
88
aio/content/examples/http/src/browser-test-shim.js
Normal file
@ -0,0 +1,88 @@
|
||||
// BROWSER TESTING SHIM
|
||||
// Keep it in-sync with what karma-test-shim does
|
||||
// #docregion
|
||||
/*global jasmine, __karma__, window*/
|
||||
(function () {
|
||||
|
||||
Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.
|
||||
|
||||
// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
|
||||
// Error.stackTraceLimit = Infinity; //
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
|
||||
|
||||
var baseURL = document.baseURI;
|
||||
baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/';
|
||||
|
||||
System.config({
|
||||
baseURL: baseURL,
|
||||
// Extend usual application package list with test folder
|
||||
packages: { 'testing': { main: 'index.js', defaultExtension: 'js' } },
|
||||
|
||||
// Assume npm: is set in `paths` in systemjs.config
|
||||
// Map the angular testing umd bundles
|
||||
map: {
|
||||
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
|
||||
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
|
||||
'@angular/common/http/testing': 'npm:@angular/common/bundles/common-http-testing.umd.js',
|
||||
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
|
||||
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
|
||||
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
|
||||
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
|
||||
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
|
||||
'@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
|
||||
},
|
||||
});
|
||||
|
||||
System.import('systemjs.config.js')
|
||||
// .then(importSystemJsExtras) // not in this project
|
||||
.then(initTestBed)
|
||||
.then(initTesting);
|
||||
|
||||
/** Optional SystemJS configuration extras. Keep going w/o it */
|
||||
function importSystemJsExtras(){
|
||||
return System.import('systemjs.config.extras.js')
|
||||
.catch(function(reason) {
|
||||
console.log(
|
||||
'Note: System.import could not load "systemjs.config.extras.js" where you might have added more configuration. It is an optional file so we will continue without it.'
|
||||
);
|
||||
console.log(reason);
|
||||
});
|
||||
}
|
||||
|
||||
function initTestBed(){
|
||||
return Promise.all([
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
])
|
||||
|
||||
.then(function (providers) {
|
||||
var coreTesting = providers[0];
|
||||
var browserTesting = providers[1];
|
||||
|
||||
coreTesting.TestBed.initTestEnvironment(
|
||||
browserTesting.BrowserDynamicTestingModule,
|
||||
browserTesting.platformBrowserDynamicTesting());
|
||||
})
|
||||
}
|
||||
|
||||
// Import all spec files defined in the html (__spec_files__)
|
||||
// and start Jasmine testrunner
|
||||
function initTesting () {
|
||||
console.log('loading spec files: '+__spec_files__.join(', '));
|
||||
return Promise.all(
|
||||
__spec_files__.map(function(spec) {
|
||||
return System.import(spec);
|
||||
})
|
||||
)
|
||||
// After all imports load, re-execute `window.onload` which
|
||||
// triggers the Jasmine test-runner start or explain what went wrong
|
||||
.then(success, console.error.bind(console));
|
||||
|
||||
function success () {
|
||||
console.log('Spec files loaded; starting Jasmine testrunner');
|
||||
window.onload();
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
4
aio/content/examples/http/src/index-specs.html
Normal file
4
aio/content/examples/http/src/index-specs.html
Normal file
@ -0,0 +1,4 @@
|
||||
<!--
|
||||
Intentionally empty placeholder for Stackblitz.
|
||||
Do not need index.html in zip-download either as you should run tests with `npm test`
|
||||
-->
|
@ -1,27 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Angular Http Demo</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app></my-app>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>HttpClient Demo</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
44
aio/content/examples/http/src/main-specs.ts
Normal file
44
aio/content/examples/http/src/main-specs.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import './testing/global-jasmine';
|
||||
import 'jasmine-core/lib/jasmine-core/jasmine-html.js';
|
||||
import 'jasmine-core/lib/jasmine-core/boot.js';
|
||||
|
||||
declare var jasmine;
|
||||
|
||||
import './polyfills';
|
||||
|
||||
import 'zone.js/dist/async-test';
|
||||
import 'zone.js/dist/fake-async-test';
|
||||
import 'zone.js/dist/long-stack-trace-zone';
|
||||
import 'zone.js/dist/proxy.js';
|
||||
import 'zone.js/dist/sync-test';
|
||||
import 'zone.js/dist/jasmine-patch';
|
||||
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// Import spec files individually for Stackblitz
|
||||
import './app/heroes/heroes.service.spec.ts';
|
||||
import './testing/http-client.spec.ts';
|
||||
|
||||
//
|
||||
bootstrap();
|
||||
|
||||
//
|
||||
function bootstrap () {
|
||||
if (window['jasmineRef']) {
|
||||
location.reload();
|
||||
return;
|
||||
} else {
|
||||
window.onload(undefined);
|
||||
window['jasmineRef'] = jasmine.getEnv();
|
||||
}
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
}
|
1
aio/content/examples/http/src/test.css
Normal file
1
aio/content/examples/http/src/test.css
Normal file
@ -0,0 +1 @@
|
||||
@import "~jasmine-core/lib/jasmine-core/jasmine.css"
|
3
aio/content/examples/http/src/testing/global-jasmine.ts
Normal file
3
aio/content/examples/http/src/testing/global-jasmine.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import jasmineRequire from 'jasmine-core/lib/jasmine-core/jasmine.js';
|
||||
|
||||
window['jasmineRequire'] = jasmineRequire;
|
192
aio/content/examples/http/src/testing/http-client.spec.ts
Normal file
192
aio/content/examples/http/src/testing/http-client.spec.ts
Normal file
@ -0,0 +1,192 @@
|
||||
// #docplaster
|
||||
// #docregion imports
|
||||
// Http testing module and mocking controller
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
|
||||
// Other imports
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
// #enddocregion imports
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
interface Data {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const testUrl = '/data';
|
||||
|
||||
// #docregion setup
|
||||
describe('HttpClient testing', () => {
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ HttpClientTestingModule ]
|
||||
});
|
||||
|
||||
// Inject the http service and test controller for each test
|
||||
httpClient = TestBed.get(HttpClient);
|
||||
httpTestingController = TestBed.get(HttpTestingController);
|
||||
});
|
||||
// #enddocregion setup
|
||||
// #docregion afterEach
|
||||
afterEach(() => {
|
||||
// After every test, assert that there are no more pending requests.
|
||||
httpTestingController.verify();
|
||||
});
|
||||
// #enddocregion afterEach
|
||||
// #docregion setup
|
||||
/// Tests begin ///
|
||||
// #enddocregion setup
|
||||
// #docregion get-test
|
||||
it('can test HttpClient.get', () => {
|
||||
const testData: Data = {name: 'Test Data'};
|
||||
|
||||
// Make an HTTP GET request
|
||||
httpClient.get<Data>(testUrl)
|
||||
.subscribe(data =>
|
||||
// When observable resolves, result should match test data
|
||||
expect(data).toEqual(testData)
|
||||
);
|
||||
|
||||
// The following `expectOne()` will match the request's URL.
|
||||
// If no requests or multiple requests matched that URL
|
||||
// `expectOne()` would throw.
|
||||
const req = httpTestingController.expectOne('/data');
|
||||
|
||||
// Assert that the request is a GET.
|
||||
expect(req.request.method).toEqual('GET');
|
||||
|
||||
// Respond with mock data, causing Observable to resolve.
|
||||
// Subscribe callback asserts that correct data was returned.
|
||||
req.flush(testData);
|
||||
|
||||
// Finally, assert that there are no outstanding requests.
|
||||
httpTestingController.verify();
|
||||
});
|
||||
// #enddocregion get-test
|
||||
it('can test HttpClient.get with matching header', () => {
|
||||
const testData: Data = {name: 'Test Data'};
|
||||
|
||||
// Make an HTTP GET request with specific header
|
||||
httpClient.get<Data>(testUrl, {
|
||||
headers: new HttpHeaders({'Authorization': 'my-auth-token'})
|
||||
})
|
||||
.subscribe(data =>
|
||||
expect(data).toEqual(testData)
|
||||
);
|
||||
|
||||
// Find request with a predicate function.
|
||||
// #docregion predicate
|
||||
// Expect one request with an authorization header
|
||||
const req = httpTestingController.expectOne(
|
||||
req => req.headers.has('Authorization')
|
||||
);
|
||||
// #enddocregion predicate
|
||||
req.flush(testData);
|
||||
});
|
||||
|
||||
it('can test multiple requests', () => {
|
||||
let testData: Data[] = [
|
||||
{ name: 'bob' }, { name: 'carol' },
|
||||
{ name: 'ted' }, { name: 'alice' }
|
||||
];
|
||||
|
||||
// Make three requests in a row
|
||||
httpClient.get<Data[]>(testUrl)
|
||||
.subscribe(d => expect(d.length).toEqual(0, 'should have no data'));
|
||||
|
||||
httpClient.get<Data[]>(testUrl)
|
||||
.subscribe(d => expect(d).toEqual([testData[0]], 'should be one element array'));
|
||||
|
||||
httpClient.get<Data[]>(testUrl)
|
||||
.subscribe(d => expect(d).toEqual(testData, 'should be expected data'));
|
||||
|
||||
// #docregion multi-request
|
||||
// get all pending requests that match the given URL
|
||||
const requests = httpTestingController.match(testUrl);
|
||||
expect(requests.length).toEqual(3);
|
||||
|
||||
// Respond to each request with different results
|
||||
requests[0].flush([]);
|
||||
requests[1].flush([testData[0]]);
|
||||
requests[2].flush(testData);
|
||||
// #enddocregion multi-request
|
||||
});
|
||||
|
||||
// #docregion 404
|
||||
it('can test for 404 error', () => {
|
||||
const emsg = 'deliberate 404 error';
|
||||
|
||||
httpClient.get<Data[]>(testUrl).subscribe(
|
||||
data => fail('should have failed with the 404 error'),
|
||||
(error: HttpErrorResponse) => {
|
||||
expect(error.status).toEqual(404, 'status');
|
||||
expect(error.error).toEqual(emsg, 'message');
|
||||
}
|
||||
);
|
||||
|
||||
const req = httpTestingController.expectOne(testUrl);
|
||||
|
||||
// Respond with mock error
|
||||
req.flush(emsg, { status: 404, statusText: 'Not Found' });
|
||||
});
|
||||
// #enddocregion 404
|
||||
|
||||
// #docregion network-error
|
||||
it('can test for network error', () => {
|
||||
const emsg = 'simulated network error';
|
||||
|
||||
httpClient.get<Data[]>(testUrl).subscribe(
|
||||
data => fail('should have failed with the network error'),
|
||||
(error: HttpErrorResponse) => {
|
||||
expect(error.error.message).toEqual(emsg, 'message');
|
||||
}
|
||||
);
|
||||
|
||||
const req = httpTestingController.expectOne(testUrl);
|
||||
|
||||
// Create mock ErrorEvent, raised when something goes wrong at the network level.
|
||||
// Connection timeout, DNS error, offline, etc
|
||||
const errorEvent = new ErrorEvent('so sad', {
|
||||
message: emsg,
|
||||
// #enddocregion network-error
|
||||
// The rest of this is optional and not used.
|
||||
// Just showing that you could provide this too.
|
||||
filename: 'HeroService.ts',
|
||||
lineno: 42,
|
||||
colno: 21
|
||||
// #docregion network-error
|
||||
});
|
||||
|
||||
// Respond with mock error
|
||||
req.error(errorEvent);
|
||||
});
|
||||
// #enddocregion network-error
|
||||
|
||||
it('httpTestingController.verify should fail if HTTP response not simulated', () => {
|
||||
// Sends request
|
||||
httpClient.get('some/api').subscribe();
|
||||
|
||||
// verify() should fail because haven't handled the pending request.
|
||||
expect(() => httpTestingController.verify()).toThrow();
|
||||
|
||||
// Now get and flush the request so that afterEach() doesn't fail
|
||||
const req = httpTestingController.expectOne('some/api');
|
||||
req.flush(null);
|
||||
});
|
||||
|
||||
// Proves that verify in afterEach() really would catch error
|
||||
// if test doesn't simulate the HTTP response.
|
||||
//
|
||||
// Must disable this test because can't catch an error in an afterEach().
|
||||
// Uncomment if you want to confirm that afterEach() does the job.
|
||||
// it('afterEach() should fail when HTTP response not simulated',() => {
|
||||
// // Sends request which is never handled by this test
|
||||
// httpClient.get('some/api').subscribe();
|
||||
// });
|
||||
// #docregion setup
|
||||
});
|
||||
// #enddocregion setup
|
@ -3,7 +3,9 @@
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
|
||||
"!src/testing/*.*",
|
||||
"!src/index-specs.html"
|
||||
],
|
||||
"tags": ["http", "jsonp"]
|
||||
"tags": ["http"]
|
||||
}
|
||||
|
@ -21,7 +21,14 @@ import { MessagesComponent } from './messages/messages.component';
|
||||
FormsModule
|
||||
],
|
||||
// #docregion providers
|
||||
providers: [ HeroService, MessageService ],
|
||||
// #docregion providers-heroservice
|
||||
providers: [
|
||||
HeroService,
|
||||
// #enddocregion providers-heroservice
|
||||
MessageService
|
||||
// #docregion providers-heroservice
|
||||
],
|
||||
// #enddocregion providers-heroservice
|
||||
// #enddocregion providers
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
|
@ -3,6 +3,6 @@
|
||||
<h2>Messages</h2>
|
||||
<button class="clear"
|
||||
(click)="messageService.clear()">clear</button>
|
||||
<div *ngFor='let message of messageService.messages'> {{message}} </div>
|
||||
<div *ngFor="let message of messageService.messages"> {{message}} </div>
|
||||
|
||||
</div>
|
||||
|
@ -190,6 +190,29 @@ describe('Tutorial part 6', () => {
|
||||
const maxId = heroesBefore[heroesBefore.length - 1].id;
|
||||
expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName});
|
||||
});
|
||||
|
||||
it('displays correctly styled buttons', async () => {
|
||||
element.all(by.buttonText('x')).then(buttons => {
|
||||
for (const button of buttons) {
|
||||
// Inherited styles from styles.css
|
||||
expect(button.getCssValue('font-family')).toBe('Arial');
|
||||
expect(button.getCssValue('border')).toContain('none');
|
||||
expect(button.getCssValue('padding')).toBe('5px 10px');
|
||||
expect(button.getCssValue('border-radius')).toBe('4px');
|
||||
// Styles defined in heroes.component.css
|
||||
expect(button.getCssValue('left')).toBe('194px');
|
||||
expect(button.getCssValue('top')).toBe('-32px');
|
||||
}
|
||||
});
|
||||
|
||||
const addButton = element(by.buttonText('add'));
|
||||
// Inherited styles from styles.css
|
||||
expect(addButton.getCssValue('font-family')).toBe('Arial');
|
||||
expect(addButton.getCssValue('border')).toContain('none');
|
||||
expect(addButton.getCssValue('padding')).toBe('5px 10px');
|
||||
expect(addButton.getCssValue('border-radius')).toBe('4px');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Progressive hero search', () => {
|
||||
|
@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
/* #docregion additions */
|
||||
.button {
|
||||
button {
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
|
@ -53,7 +53,7 @@ export class AppModule {
|
||||
@Inject(PLATFORM_ID) private platformId: Object,
|
||||
@Inject(APP_ID) private appId: string) {
|
||||
const platform = isPlatformBrowser(platformId) ?
|
||||
'on the server' : 'in the browser';
|
||||
'in the browser' : 'on the server';
|
||||
console.log(`Running ${platform} with appId=${appId}`);
|
||||
}
|
||||
// #enddocregion platform-detection
|
||||
|
@ -1254,7 +1254,7 @@ also encapsulate a style sheet within a specific component.
|
||||
|
||||
|
||||
### Styles configuration
|
||||
<code-example hideCopy path="ajs-quick-reference/.angular-cli.1.json" region="styles" linenums="false">
|
||||
<code-example hideCopy path="ajs-quick-reference/.angular-cli.1.json" region="styles" linenums="false"></code-example>
|
||||
|
||||
With the Angular CLI, you can configure your global styles in the `.angular-cli.json` file.
|
||||
You can rename the extension to `.scss` to use sass.
|
||||
|
@ -92,7 +92,7 @@ You can control your app compilation by providing template compiler options in t
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhiteSpace": false,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
@ -234,9 +234,7 @@ done manually.
|
||||
### *preserveWhitespaces*
|
||||
|
||||
This option tells the compiler whether to remove blank text nodes from compiled templates.
|
||||
This option is `true` by default.
|
||||
|
||||
*Note*: It is recommended to set this explicitly to `false` as it emits smaller template factory modules and might be set to `false` by default in the future.
|
||||
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
|
||||
|
||||
### *allowEmptyCodegenFiles*
|
||||
|
||||
|
175
aio/content/guide/api-page-class.md
Normal file
175
aio/content/guide/api-page-class.md
Normal file
@ -0,0 +1,175 @@
|
||||
<div class="breadcrumb">
|
||||
<a href="#">API<a> / <a href="#">@core<a>
|
||||
</div>
|
||||
<header class="api-header">
|
||||
<h1><label class="api-status-label experimental">experimental</label><label class="api-type-label class">class</label>Class Name</h1>
|
||||
</header>
|
||||
<div class="page-actions">
|
||||
<a href="#"><label class="raised page-label"><i class="material-icons">mode_edit</i>suggest edits</label></a>
|
||||
<a href="#"><label class="raised page-label"><i class="material-icons">code</i>view source</label></a>
|
||||
</div>
|
||||
<p>Class description goes here. This is a short and to the point one or two sentence description that easily introduces the reader to the class.</p>
|
||||
<div class="api-body">
|
||||
<section>
|
||||
<h2>Overview</h2>
|
||||
<code-example language="ts" hidecopy="true" ng-version="5.2.0"><aio-code class="simple-code" ng-reflect-ng-class="[object Object]" ng-reflect-code="
|
||||
class <a href="api/core/Compi" ng-reflect-hide-copy="true" ng-reflect-language="ts" ng-reflect-linenums="" ng-reflect-path="" ng-reflect-region="" ng-reflect-title=""><pre class="prettyprint lang-ts">
|
||||
<code class="animated fadeIn"><span class="kwd">class</span><span class="pln"> </span><a href="api/core/Compiler" class="code-anchor"><span class="typ">Compiler</span></a><span class="pln"> </span><span class="pun">{</span><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#compileModuleSync"><span class="pln">compileModuleSync</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>):</span><span class="pln"> </span><span class="typ">NgModuleFactory</span><span class="pun"><</span><span class="pln">T</span><span class="pun">></span></a><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAsync"><span class="pln">compileModuleAsync</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun"><</span><span class="typ">NgModuleFactory</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>></span></a><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAndAllComponentsSync"><span class="pln">compileModuleAndAllComponentsSync</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>):</span><span class="pln"> </span><span class="typ">ModuleWithComponentFactories</span><span class="pun"><</span><span class="pln">T</span><span class="pun">></span></a><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAndAllComponentsAsync"><span class="pln">compileModuleAndAllComponentsAsync</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun"><</span><span class="typ">ModuleWithComponentFactories</span><span class="pun"><</span><span class="pln">T</span><span class="pun">>></span></a><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#clearCache"><span class="pln">clearCache</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span></a><span class="pln">
|
||||
</span><a class="code-anchor" href="api/core/Compiler#clearCacheFor"><span class="pln">clearCacheFor</span><span class="pun">(</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun"><</span><span class="pln">any</span><span class="pun">>)</span></a><span class="pln">
|
||||
</span><span class="pun">}</span></code>
|
||||
</pre></aio-code></code-example>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Description</h2>
|
||||
<p>The longer class description goes here which can include multiple paragraphs.</p>
|
||||
</p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
|
||||
<h3>Subclasses</h3>
|
||||
<ul>
|
||||
<li><a href="#">Subclass1</a></li>
|
||||
<li><a href="#">Subclass2</a></li>
|
||||
<li><a href="#">Subclass3</a></li>
|
||||
</ul>
|
||||
<h3>See Also</h3>
|
||||
<ul>
|
||||
<li><a href="#">Link1</a></li>
|
||||
<li><a href="#">Link2</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Constructor</h2>
|
||||
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
|
||||
<aio-code class="simple-code"><pre class="prettyprint lang-">
|
||||
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
|
||||
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
|
||||
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
|
||||
</pre></aio-code></code-example>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Properties</h2>
|
||||
<table class="is-full-width list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code><strong>Property1</strong></code>
|
||||
</td>
|
||||
<td><label class="property-type-label type">Type</label></td>
|
||||
<td>Description goes here</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code><strong>Property2</strong></code>
|
||||
</td>
|
||||
<td>Type</td>
|
||||
<td>Description goes here</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code><strong>Property3</strong></code>
|
||||
</td>
|
||||
<td>Type</td>
|
||||
<td>Description goes here</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section class="api-method">
|
||||
<h2>Methods</h2>
|
||||
<table class="is-full-width item-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method1Name( )</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<p>Description goes here</p>
|
||||
<br>
|
||||
<p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="is-full-width api-method item-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method2Name( )</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<p>Description goes here</p>
|
||||
<hr>
|
||||
<h5>Declaration</h5>
|
||||
<code-example language="ts" hidecopy="true" ng-version="5.2.0">
|
||||
<aio-code class="simple-code"><pre class="prettyprint lang-ts">
|
||||
<code class="animated fadeIn"><span class="kwd">class</span><span class="pln"> </span><a href="api/animations/AnimationBuilder" class="code-anchor"><span class="typ">AnimationBuilder</span></a><span class="pln"> </span><span class="pun">{</span><span class="pln"></span><a class="code-anchor" href="api/animations/AnimationBuilder#build"><span class="pln">build</span><span class="pun">(</span><span class="pln">animation</span><span class="pun">:</span><span class="pln"> </span><span class="typ">AnimationMetadata</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="typ">AnimationMetadata</span><span class="pun">[]):</span><span class="pln"> </span><span class="typ">AnimationFactory</span></a><span class="pln"></span><span class="pun">}</span></code></pre>
|
||||
</aio-code>
|
||||
</code-example>
|
||||
<h6>Parameters</h6>
|
||||
<h6>Returns</h6>
|
||||
<p>Returns information and results goes here.</p>
|
||||
<h6>Errors</h6>
|
||||
<p>Error information goes here</p>
|
||||
<hr>
|
||||
<p>Further details provided as needed. Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball.</p><hr>
|
||||
<h6>Overloads</h6>
|
||||
<table class="is-full-width">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
|
||||
<aio-code class="simple-code"><pre class="prettyprint lang-">
|
||||
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
|
||||
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
|
||||
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
|
||||
</pre></aio-code></code-example>
|
||||
</td>
|
||||
<td>Description goes here</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
|
||||
<aio-code class="simple-code"><pre class="prettyprint lang-">
|
||||
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
|
||||
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
|
||||
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
|
||||
</pre></aio-code></code-example>
|
||||
</td>
|
||||
<td>Description goes here</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<h5>Example: Descriptive Title of Method Example</h5>
|
||||
<p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Example: Descriptive Title of Combined Example Goes Here</h2>
|
||||
<p>Intro description text about what the example is and how it can be used.</p>
|
||||
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
|
||||
<aio-code class="simple-code"><pre class="prettyprint lang-">
|
||||
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
|
||||
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
|
||||
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
|
||||
</pre></aio-code></code-example>
|
||||
<p>Further explanation provided as needed. Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball.</p>
|
||||
</section>
|
||||
</div>
|
@ -186,9 +186,9 @@ template for our `HeroListComponent`:
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.html" title="src/app/hero-list.component.html"></code-example>
|
||||
|
||||
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<hero-detail>` uses Angular's [template syntax](guide/template-syntax).
|
||||
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<app-hero-detail>` uses Angular's [template syntax](guide/template-syntax).
|
||||
|
||||
In the last line of the template, the `<hero-detail>` tag is a custom element that represents a new component, `HeroDetailComponent`.
|
||||
In the last line of the template, the `<app-hero-detail>` tag is a custom element that represents a new component, `HeroDetailComponent`.
|
||||
|
||||
The `HeroDetailComponent` is a *different* component than the `HeroListComponent` you've been reviewing.
|
||||
The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the
|
||||
@ -197,7 +197,7 @@ The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
|
||||
|
||||
<img src="generated/images/guide/architecture/component-tree.png" alt="Metadata" class="left">
|
||||
|
||||
Notice how `<hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
|
||||
Notice how `<app-hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
|
||||
|
||||
<hr class="clear"/>
|
||||
|
||||
@ -230,8 +230,8 @@ information Angular needs to create and present the component and its view.
|
||||
Here are a few of the most useful `@Component` configuration options:
|
||||
|
||||
* `selector`: CSS selector that tells Angular to create and insert an instance of this component
|
||||
where it finds a `<hero-list>` tag in *parent* HTML.
|
||||
For example, if an app's HTML contains `<hero-list></hero-list>`, then
|
||||
where it finds a `<app-hero-list>` tag in *parent* HTML.
|
||||
For example, if an app's HTML contains `<app-hero-list></app-hero-list>`, then
|
||||
Angular inserts an instance of the `HeroListComponent` view between those tags.
|
||||
|
||||
* `templateUrl`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates).
|
||||
|
@ -2,188 +2,96 @@
|
||||
|
||||
Angular supports most recent browsers. This includes the following specific versions:
|
||||
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
|
||||
<th>
|
||||
<th>
|
||||
Browser
|
||||
</th>
|
||||
|
||||
<th>
|
||||
Supported versions
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
Chrome
|
||||
</th>
|
||||
</td>
|
||||
|
||||
<th>
|
||||
<td>
|
||||
latest
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
Firefox
|
||||
</th>
|
||||
</td>
|
||||
|
||||
<th>
|
||||
<td>
|
||||
latest
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
Edge
|
||||
</th>
|
||||
</td>
|
||||
|
||||
<th>
|
||||
<td>
|
||||
2 most recent major versions
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
IE
|
||||
</th>
|
||||
|
||||
<th>
|
||||
Safari
|
||||
</th>
|
||||
|
||||
<th>
|
||||
iOS
|
||||
</th>
|
||||
|
||||
<th>
|
||||
Android
|
||||
</th>
|
||||
|
||||
<th>
|
||||
</td>
|
||||
<td>
|
||||
11<br>10<br>9
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>
|
||||
IE Mobile
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
latest
|
||||
</td>
|
||||
|
||||
<td>
|
||||
latest
|
||||
</td>
|
||||
|
||||
<td>
|
||||
14
|
||||
</td>
|
||||
|
||||
<td>
|
||||
11
|
||||
</td>
|
||||
|
||||
<td>
|
||||
10
|
||||
</td>
|
||||
|
||||
<td>
|
||||
10
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Nougat (7.0)<br>Marshmallow (6.0)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
11
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Safari
|
||||
</td>
|
||||
|
||||
<td>
|
||||
2 most recent major versions
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
iOS
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
2 most recent major versions
|
||||
</td>
|
||||
|
||||
<td>
|
||||
13
|
||||
</td>
|
||||
|
||||
<td>
|
||||
10
|
||||
</td>
|
||||
|
||||
<td>
|
||||
9
|
||||
</td>
|
||||
|
||||
<td>
|
||||
9
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Lollipop<br>(5.0, 5.1)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
Android
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
Nougat (7.0)<br>Marshmallow (6.0)<br>Lollipop (5.0, 5.1)<br>KitKat (4.4)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
9
|
||||
</td>
|
||||
|
||||
<td>
|
||||
8
|
||||
</td>
|
||||
|
||||
<td>
|
||||
8
|
||||
</td>
|
||||
|
||||
<td>
|
||||
KitKat<br>(4.4)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
7
|
||||
</td>
|
||||
|
||||
<td>
|
||||
7
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Jelly Bean<br>(4.1, 4.2, 4.3)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
@ -219,21 +127,16 @@ the CLI created with your project.
|
||||
|
||||
This file incorporates the mandatory and many of the optional polyfills as JavaScript `import` statements.
|
||||
|
||||
The npm packages for the _mandatory_ polyfills (such as `zone.js`) were installed automatically for you when you created your project and
|
||||
their corresponding `import` statements are ready to go.
|
||||
You probably won't touch these.
|
||||
The npm packages for the _mandatory_ polyfills (such as `zone.js`) were installed automatically for you when you created your project and their corresponding `import` statements are ready to go. You probably won't touch these.
|
||||
|
||||
But if you need an optional polyfill, you'll have to install its npm package with `npm` or `yarn`.
|
||||
For example, [if you need the web animations polyfill](http://caniuse.com/#feat=web-animation),
|
||||
you could install it with either of the following commands:
|
||||
But if you need an optional polyfill, you'll have to install its npm package.
|
||||
For example, [if you need the web animations polyfill](http://caniuse.com/#feat=web-animation), you could install it with `npm`, using the following command (or the `yarn` equivalent):
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm install --save web-animations-js
|
||||
yarn add web-animations-js
|
||||
</code-example>
|
||||
|
||||
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement
|
||||
as in the following example:
|
||||
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement as in the following example:
|
||||
|
||||
<code-example title="src/polyfills.ts">
|
||||
/**
|
||||
@ -243,7 +146,7 @@ as in the following example:
|
||||
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
</code-example>
|
||||
|
||||
If you can't find the polyfill you want in `polyfills.ts`,
|
||||
If you can't find the polyfill you want in `polyfills.ts`,
|
||||
add it yourself, following the same pattern:
|
||||
|
||||
1. install the npm package
|
||||
@ -283,6 +186,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
<td>
|
||||
|
||||
[ES7/reflect](guide/browser-support#core-es7-reflect) (JIT only)
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -295,8 +199,8 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[ES6](guide/browser-support#core-es6)
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -309,7 +213,6 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[ES6<br>classList](guide/browser-support#classlist)
|
||||
|
||||
</td>
|
||||
@ -323,8 +226,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
Some features of Angular may require additional polyfills.
|
||||
|
||||
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today.
|
||||
You'll need a polyfill to use animations in other browsers.
|
||||
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today. You'll need a polyfill to use animations in other browsers.
|
||||
|
||||
Here are the features which may require additional polyfills:
|
||||
|
||||
@ -351,19 +253,20 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
<td>
|
||||
|
||||
[JIT compilation](guide/aot-compiler).
|
||||
[JIT compilation](guide/aot-compiler).
|
||||
|
||||
Required to reflect for metadata.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
[ES7/reflect](guide/browser-support#core-es7-reflect)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
All current browsers.
|
||||
Enabled by default.
|
||||
Can remove If you always use AOT and only use Angular decorators.
|
||||
All current browsers. Enabled by default.
|
||||
Can remove if you always use AOT and only use Angular decorators.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -373,12 +276,13 @@ Here are the features which may require additional polyfills:
|
||||
<td>
|
||||
|
||||
[Animations](guide/animations)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Web Animations](guide/browser-support#web-animations)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -391,13 +295,23 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
<td>
|
||||
|
||||
If you use the following deprecated i18n pipes: [date](api/common/DeprecatedDatePipe), [currency](api/common/DeprecatedCurrencyPipe), [decimal](api/common/DeprecatedDecimalPipe) and [percent](api/common/DeprecatedPercentPipe)
|
||||
If you use the following deprecated i18n pipes:
|
||||
|
||||
|
||||
[date](api/common/DeprecatedDatePipe),
|
||||
|
||||
[currency](api/common/DeprecatedCurrencyPipe),
|
||||
|
||||
[decimal](api/common/DeprecatedDecimalPipe),
|
||||
|
||||
[percent](api/common/DeprecatedPercentPipe)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Intl API](guide/browser-support#intl)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -410,13 +324,15 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
<td>
|
||||
|
||||
[NgClass](api/common/NgClass) on SVG elements
|
||||
[NgClass](api/common/NgClass)
|
||||
|
||||
on SVG elements
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[classList](guide/browser-support#classlist)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -429,14 +345,19 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
<td>
|
||||
|
||||
[Http](guide/http) when sending and receiving binary data
|
||||
[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)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -476,6 +397,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<td>
|
||||
|
||||
<a id='core-es7-reflect' href="https://github.com/zloirock/core-js/blob/master/es7/reflect.js">ES7/reflect</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -491,7 +413,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='core-es6' href="https://github.com/zloirock/core-js">ES6</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -507,7 +431,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='classlist' href="https://github.com/eligrey/classList.js">classList</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -523,7 +449,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='intl' href="https://github.com/andyearnshaw/Intl.js">Intl</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -539,7 +467,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='web-animations' href="https://github.com/web-animations/web-animations-js">Web Animations</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -555,7 +485,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='typedarray' href="https://github.com/inexorabletash/polyfill/blob/master/typedarray.js">Typed Array</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -571,7 +503,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='blob' href="https://github.com/eligrey/Blob.js">Blob</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -587,7 +521,9 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='formdata' href="https://github.com/francois2metz/html5-formdata">FormData</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -603,14 +539,13 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
\* Figures are for minified and gzipped code,
|
||||
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.
|
||||
|
||||
{@a non-cli}
|
||||
## Polyfills for non-CLI users
|
||||
|
||||
If you aren't using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
|
||||
If you are not using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
|
||||
|
||||
<code-example title="src/index.html">
|
||||
<!-- pre-zone polyfills -->
|
||||
@ -625,8 +560,13 @@ If you aren't using the CLI, you should add your polyfill scripts directly to th
|
||||
// __Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
// __Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
// __zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
</script>
|
||||
|
||||
/*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*/
|
||||
// __Zone_enable_cross_context_check = true;
|
||||
</script>
|
||||
<!-- zone.js required by Angular -->
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
|
||||
|
@ -120,11 +120,13 @@ 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 (removed August 2017, PR #18694)
|
||||
explains how to write apps in ES6/7
|
||||
The updated TypeScript to JavaScript guide 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.
|
||||
|
||||
This was [removed in August 2017](https://github.com/angular/angular/pull/18694) but can still be
|
||||
viewed in the [v2 documentation](https://v2.angular.io/docs/ts/latest/cookbook/ts-to-js.html).
|
||||
|
||||
## Sync with Angular v.2.1.1 (2016-10-21)
|
||||
|
||||
Docs and code samples updated and tested with Angular v.2.1.1.
|
||||
|
@ -140,6 +140,11 @@ is available to <code>declarations</code> of this module.</p>
|
||||
<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>
|
||||
<tr>
|
||||
<td><code><div <b>[ngStyle]</b>="{'property': 'value'}"></code><br><code><div <b>[ngStyle]</b>="dynamicStyles()"></code></td>
|
||||
<td><p>Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table class="is-full-width is-fixed-layout">
|
||||
|
@ -14,7 +14,7 @@ You can run the <live-example></live-example> in Stackblitz and download the cod
|
||||
## Using component styles
|
||||
|
||||
For every Angular component you write, you may define not only an HTML template,
|
||||
but also the CSS styles that go with that template,
|
||||
but also the CSS styles that go with that template,
|
||||
specifying any selectors, rules, and media queries that you need.
|
||||
|
||||
One way to do this is to set the `styles` property in the component metadata.
|
||||
@ -42,7 +42,7 @@ This scoping restriction is a ***styling modularity feature***.
|
||||
* You can use the CSS class names and selectors that make the most sense in the context of each component.
|
||||
|
||||
|
||||
* Class names and selectors are local to the component and don't collide with
|
||||
* Class names and selectors are local to the component and don't collide with
|
||||
classes and selectors used elsewhere in the application.
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ This scoping restriction is a ***styling modularity feature***.
|
||||
## Special selectors
|
||||
|
||||
Component styles have a few special *selectors* from the world of shadow DOM style scoping
|
||||
(described in the [CSS Scoping Module Level 1](https://www.w3.org/TR/css-scoping-1) page on the
|
||||
(described in the [CSS Scoping Module Level 1](https://www.w3.org/TR/css-scoping-1) page on the
|
||||
[W3C](https://www.w3.org) site).
|
||||
The following sections describe these selectors.
|
||||
|
||||
@ -78,7 +78,7 @@ The `:host` selector is the only way to target the host element. You can't reach
|
||||
the host element from inside the component with other selectors because it's not part of the
|
||||
component's own template. The host element is in a parent component's template.
|
||||
|
||||
Use the *function form* to apply host styles conditionally by
|
||||
Use the *function form* to apply host styles conditionally by
|
||||
including another selector inside parentheses after `:host`.
|
||||
|
||||
The next example targets the host element again, but only when it also has the `active` CSS class.
|
||||
@ -104,15 +104,15 @@ if some ancestor element has the CSS class `theme-light`.
|
||||
|
||||
### (deprecated) `/deep/`, `>>>`, and `::ng-deep`
|
||||
|
||||
Component styles normally apply only to the HTML in the component's own template.
|
||||
Component styles normally apply only to the HTML in the component's own template.
|
||||
|
||||
Use the `/deep/` shadow-piercing descendant combinator to force a style down through the child
|
||||
component tree into all the child component views.
|
||||
The `/deep/` combinator works to any depth of nested components, and it applies to both the view
|
||||
children and content children of the component.
|
||||
children and content children of the component.
|
||||
|
||||
The following example targets all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM.
|
||||
The following example targets all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" title="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
@ -140,7 +140,7 @@ Until then `::ng-deep` should be preferred for a broader compatibility with the
|
||||
|
||||
## Loading component styles
|
||||
|
||||
There are several ways to add styles to a component:
|
||||
There are several ways to add styles to a component:
|
||||
|
||||
* By setting `styles` or `styleUrls` metadata.
|
||||
* Inline in the template HTML.
|
||||
@ -177,8 +177,8 @@ to a component's `@Component` decorator:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="src/app/hero-app.component.ts (CSS in file)" path="component-styles/src/app/hero-app.component.1.ts"></code-pane>
|
||||
<code-pane title="src/app/hero-app.component.css" path="component-styles/src/app/hero-app.component.1.css"></code-pane>
|
||||
</code-tabs>
|
||||
<code-pane title="src/app/hero-app.component.css" path="component-styles/src/app/hero-app.component.css"></code-pane>
|
||||
</code-tabs>
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
@ -209,14 +209,14 @@ inside `<style>` tags.
|
||||
|
||||
### Template link tags
|
||||
|
||||
You can also write `<link>` tags into the component's HTML template.
|
||||
You can also write `<link>` tags into the component's HTML template.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink" title="src/app/hero-team.component.ts">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
The link tag's `href` URL must be relative to the
|
||||
The link tag's `href` URL must be relative to the
|
||||
_**application root**_, not relative to the component file.
|
||||
|
||||
When building with the CLI, be sure to include the linked style file among the assets to be copied to the server as described in the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-asset-configuration).
|
||||
@ -244,7 +244,7 @@ See the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-
|
||||
|
||||
### Non-CSS style files
|
||||
|
||||
If you're building with the CLI,
|
||||
If you're building with the CLI,
|
||||
you can write style files in [sass](http://sass-lang.com/), [less](http://lesscss.org/), or [stylus](http://stylus-lang.com/) and specify those files in the `@Component.styleUrls` metadata with the appropriate extensions (`.scss`, `.less`, `.styl`) as in the following example:
|
||||
|
||||
<code-example>
|
||||
@ -259,7 +259,7 @@ you can write style files in [sass](http://sass-lang.com/), [less](http://lesscs
|
||||
The CLI build process runs the pertinent CSS preprocessor.
|
||||
|
||||
When generating a component file with `ng generate component`, the CLI emits an empty CSS styles file (`.css`) by default.
|
||||
You can configure the CLI to default to your preferred CSS preprocessor
|
||||
You can configure the CLI to default to your preferred CSS preprocessor
|
||||
as explained in the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-css-preprocessors
|
||||
"CSS Preprocessor integration").
|
||||
|
||||
@ -281,7 +281,7 @@ component* basis, you can set the *view encapsulation mode* in the component met
|
||||
Choose from the following modes:
|
||||
|
||||
* `Native` view encapsulation uses the browser's native shadow DOM implementation (see
|
||||
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||
on the [MDN](https://developer.mozilla.org) site)
|
||||
to attach a shadow DOM to the component's host element, and then puts the component
|
||||
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
||||
@ -290,18 +290,18 @@ Choose from the following modes:
|
||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
|
||||
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||
This is essentially the same as pasting the component's styles into the HTML.
|
||||
|
||||
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
`Native` view encapsulation only works on browsers that have native support
|
||||
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
||||
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
||||
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||
in most cases.
|
||||
@ -331,7 +331,7 @@ There are two kinds of generated attributes:
|
||||
|
||||
* An element that would be a shadow DOM host in native encapsulation has a
|
||||
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
that identifies to which host's emulated shadow DOM this element belongs.
|
||||
|
||||
The exact values of these attributes aren't important. They are automatically
|
||||
@ -351,5 +351,5 @@ by the generated component styles, which are in the `<head>` section of the DOM:
|
||||
</code-example>
|
||||
|
||||
These styles are post-processed so that each selector is augmented
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
These extra selectors enable the scoping rules described in this page.
|
||||
|
@ -101,7 +101,7 @@ Begin the title with the markdown `#` character. Alternatively, you can write th
|
||||
|
||||
**Only one title (`<h1>`) per document!**
|
||||
|
||||
Title text should be in "Title Case", which means that you use capital letters to start the first words and all _principal_ words. Use lower case letters for _secondary words such as "in", "of", and "the".
|
||||
Title text should be in "Title Case", which means that you use capital letters to start the first words and all _principal_ words. Use lower case letters for _secondary_ words such as "in", "of", and "the".
|
||||
|
||||
```html
|
||||
# The Meat of the Matter
|
||||
|
@ -97,7 +97,7 @@ In fact, many libraries declare and export components you'll never use.
|
||||
For example, a material design library will export all components because it doesn’t know which ones you will use. However, it is unlikely that you will use them all.
|
||||
For the ones you don't reference, the tree shaker drops these components from the final code package.
|
||||
|
||||
If a component isn't an _entry component_ or isn't found in a template,
|
||||
If a component isn't an _entry component_ and isn't found in a template,
|
||||
the tree shaker will throw it away. So, it's best to add only the components that are truly entry components to help keep your app
|
||||
as trim as possible.
|
||||
|
||||
|
@ -98,7 +98,7 @@ When the CLI generated the `CustomerDashboardComponent` for the feature module,
|
||||
</code-example>
|
||||
|
||||
|
||||
To see this HTML in the `AppComponent`, you first have to export the `CustomerDashboardComponent` in the `CustomerDashboardModule`. In `customer-dashboard.module.ts`, just beneath the declarations array, add an exports array containing `CustomerDashboardModule`:
|
||||
To see this HTML in the `AppComponent`, you first have to export the `CustomerDashboardComponent` in the `CustomerDashboardModule`. In `customer-dashboard.module.ts`, just beneath the `declarations` array, add an `exports` array containing `CustomerDashboardModule`:
|
||||
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="component-exports" title="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user