Compare commits
342 Commits
Author | SHA1 | Date | |
---|---|---|---|
54be25a7a1 | |||
b1757037fb | |||
f0476fcff0 | |||
a5c4bb5b96 | |||
4c1f32b0db | |||
383d8969ab | |||
333ffd8d32 | |||
d4679a0bc2 | |||
4ce29f3a5b | |||
17b7bc3e06 | |||
f19bd5f4f3 | |||
d503d25f29 | |||
5d275e994a | |||
d8c8b13bb8 | |||
4671168635 | |||
1ac78bfd5d | |||
4340beacea | |||
ec89f378fc | |||
4dd6863bc2 | |||
37c626e673 | |||
f0a110928b | |||
c39e7d1eb2 | |||
799bffb431 | |||
fda607cc2f | |||
cc3aa68123 | |||
306621d2d6 | |||
d204f7aa2a | |||
a94f5e8cbb | |||
1390afef23 | |||
b0346a6e45 | |||
e5da059994 | |||
ac92c3bb26 | |||
87157d7089 | |||
611dd12f0f | |||
969ce9dc2b | |||
34834a9e79 | |||
6e2ddccc2c | |||
55742e4737 | |||
0091b1e8db | |||
a0b06befb6 | |||
4fbb5b29ea | |||
e0fa727594 | |||
3ecc5e5398 | |||
f7686d4124 | |||
01a2688848 | |||
8e56c3cb30 | |||
7955cacec4 | |||
dd04f09483 | |||
3d85f72652 | |||
9f28e838d3 | |||
ddb766e456 | |||
72143e80da | |||
bc1ea8c54b | |||
45ffe54ae4 | |||
1bf7ba87a0 | |||
db96c963a8 | |||
18559897a0 | |||
ce0f4f0d7c | |||
4f1e4ffa4e | |||
f0beb4d750 | |||
bc3b2ac251 | |||
ffda3e41e0 | |||
a301dba68f | |||
04f3a4a7a5 | |||
f06ce9adc8 | |||
660eec4a23 | |||
be3352a084 | |||
998049ec9b | |||
a7ea0086ee | |||
edb8375a5f | |||
26b9492315 | |||
e110a80caf | |||
20127c1456 | |||
a50d935a48 | |||
7c479f073e | |||
bbf2133fa9 | |||
4300439ab2 | |||
ec14679668 | |||
df06e8b7a4 | |||
1b1a6ba0bb | |||
876ca9ee3a | |||
d9b03be08f | |||
b6aad07634 | |||
3d0406c247 | |||
db3bcc939e | |||
4d45fe6fb5 | |||
076ea2281f | |||
aec39c28d8 | |||
b9525ece77 | |||
719101338a | |||
e131f6bbe8 | |||
a9757ec674 | |||
9003770f02 | |||
e8bbf86e66 | |||
3a0886dc12 | |||
062a7aa2cf | |||
e28f097fc2 | |||
b30c5fc874 | |||
d52ab8e2c9 | |||
df7b875f6c | |||
0e71836cd5 | |||
470a7c6bcd | |||
3abf208235 | |||
92c18d167e | |||
99b38f52cb | |||
633ec30291 | |||
57cfcb0830 | |||
433d479a1e | |||
7c4ac68e66 | |||
20556346a3 | |||
5a417b8514 | |||
8cfc2e2ec0 | |||
11647e4c78 | |||
9e1b61326c | |||
cb7609109d | |||
b3d90365b6 | |||
cc611c93b6 | |||
8928a58796 | |||
05a33d5035 | |||
09f1609f81 | |||
c723d42d0a | |||
9dd550fa1e | |||
abbac4bc69 | |||
671a175dfb | |||
c1474f33be | |||
0ede642cb9 | |||
9c1f6fd06f | |||
798947efa4 | |||
7ae8ad6aab | |||
9c3386b1b7 | |||
2ba3ada27f | |||
8e28382e4a | |||
3203639d7d | |||
f85b543cc1 | |||
c81ad9d19d | |||
37797e2b4e | |||
2a7ebbe982 | |||
72747e5213 | |||
504500de50 | |||
e1174f3774 | |||
6bae73c076 | |||
11db3bd85e | |||
0193be7c9b | |||
2ea73513ea | |||
67e9c62013 | |||
227dbbcfba | |||
cb16e9c747 | |||
3b2d2c467a | |||
ae27af7399 | |||
c69fff15c9 | |||
dd7c1134e3 | |||
b116901400 | |||
70981c601e | |||
f2f61c9cf0 | |||
1bb2476804 | |||
ec58246a1b | |||
6fc5174a13 | |||
105e920b69 | |||
858dea98e5 | |||
71ee0c5b03 | |||
578bdeb522 | |||
6282a86135 | |||
e9b67243ed | |||
fa1c187abc | |||
b51697c197 | |||
c6b75b0823 | |||
0d7e1a9b4e | |||
9d15d85391 | |||
92d7ecf627 | |||
9263da570f | |||
dc88e0a881 | |||
fa34ed8bf3 | |||
f54a901b8d | |||
8a1a989a1c | |||
b479ed9407 | |||
d5dc53ead8 | |||
8e00161601 | |||
01d4eae984 | |||
154154dde2 | |||
4459e0c1c8 | |||
b052ef5f1e | |||
40921bb927 | |||
dfcca66fdc | |||
1ac9085b0a | |||
1cfe67dac4 | |||
8d01db4638 | |||
4268c82898 | |||
3c4eef99be | |||
96b17034e1 | |||
e47a77f941 | |||
af14b1e384 | |||
40f77cb563 | |||
6c1a8daafc | |||
d699c354db | |||
34f3832af9 | |||
f1626574dd | |||
68fc65dbcb | |||
693f79e88a | |||
448d9f9f46 | |||
8786ba95fb | |||
97bb374218 | |||
233044e337 | |||
f365a0f45c | |||
263c1a1d7e | |||
3097083277 | |||
43c187b624 | |||
3165fd3dc9 | |||
e80851d98b | |||
b754e600e3 | |||
81734cf7b6 | |||
30f4fe26e0 | |||
d6265dfcbe | |||
d51f86291f | |||
97ace57d39 | |||
86949e0c20 | |||
6924780ae9 | |||
1b0b69eeec | |||
fa85389f62 | |||
2e55857c82 | |||
ca970f5ee5 | |||
204a2cf942 | |||
0440251919 | |||
08ecfd891d | |||
7395a64668 | |||
979bfd07e1 | |||
b6ce814279 | |||
66088fef1a | |||
808bd4af41 | |||
f90b35a85e | |||
8ae0eec230 | |||
0fe685102f | |||
a98440bb85 | |||
3112311134 | |||
1b13bdea4b | |||
3ce9d51a9c | |||
14d2de13bb | |||
5713e7c9b6 | |||
87206e1986 | |||
414c7e956b | |||
6191d53a78 | |||
7d30ccc4a9 | |||
494a0d064a | |||
849200b576 | |||
60273a941f | |||
7ba720a62a | |||
eacc36bbd5 | |||
8b4acbbcbf | |||
b1fe63d081 | |||
f2ee1dcdb7 | |||
21018af2bf | |||
67ffbae6f9 | |||
5dd5bfde72 | |||
400486ced7 | |||
1a947e4b75 | |||
92bcfefc35 | |||
133b5e6e36 | |||
5c576d3b9d | |||
68b64a261a | |||
68f939ea8c | |||
8c129d73b8 | |||
1fc0d05565 | |||
20a04f9076 | |||
02a38d3ea5 | |||
37cdc4f759 | |||
b1dab181e0 | |||
fd6c4e371b | |||
4352dd27c4 | |||
34cc3f2982 | |||
97fd2480e7 | |||
1d93cf2e85 | |||
3fb98fe4ea | |||
bb804dd3e9 | |||
0034bb28e5 | |||
ca51e020cb | |||
d16852898f | |||
46ddf501a9 | |||
8c89cc4fc5 | |||
00874c27f4 | |||
c59c390cdc | |||
009651e14f | |||
f194f18dbd | |||
4e6be15069 | |||
3e685f98c6 | |||
6c8e7dd63e | |||
2447bd1bac | |||
1c6a252596 | |||
d3c92a307a | |||
8f5836cb14 | |||
319ce182db | |||
aa92f3a721 | |||
afbb6bb797 | |||
4f37f86433 | |||
3093c55e9e | |||
5ac3919259 | |||
f58211d9d8 | |||
fe126cb737 | |||
adc1b129e4 | |||
9315ab88d7 | |||
1ddbddb0db | |||
232bd9395d | |||
956a7e95d7 | |||
ce00fa3627 | |||
3515860b15 | |||
77e717e872 | |||
b46cc744b3 | |||
2cc931ed2a | |||
e096a85874 | |||
2c3e948e61 | |||
53f57d74b8 | |||
2b1de07f02 | |||
fb877696bf | |||
01173b9441 | |||
0564dd25e2 | |||
ba0f6decc3 | |||
06a0cf2e31 | |||
a8afa65a54 | |||
f73a4c229c | |||
d378a29565 | |||
2bdf2feea7 | |||
607fb1fff8 | |||
38fc2a0055 | |||
0c07f8c099 | |||
d8d21c77d5 | |||
b4cd20cbbc | |||
f840afb983 | |||
93d1b4ed9d | |||
fa81c8eeb3 | |||
98308cd79c | |||
052331fabc | |||
541c9a94bf | |||
12452d4ab4 | |||
709a3f6de7 | |||
7caa0a8aa4 | |||
0f56296c24 | |||
0a846a2fce | |||
bffccf4622 | |||
fc774a1871 | |||
d647db222c | |||
0c7eb93889 | |||
0658e1da7f | |||
d2d8e5d40f | |||
5e794492c1 |
@ -1,18 +1,31 @@
|
|||||||
defaults: &defaults
|
# Configuration file for https://circleci.com/gh/angular/angular
|
||||||
|
|
||||||
|
# Note: YAML anchors allow an object to be re-used, reducing duplication.
|
||||||
|
# The ampersand declares an alias for an object, then later the `<<: *name`
|
||||||
|
# syntax dereferences it.
|
||||||
|
# See http://blog.daemonl.com/2016/02/yaml.html
|
||||||
|
# To validate changes, use an online parser, eg.
|
||||||
|
# http://yaml-online-parser.appspot.com/
|
||||||
|
|
||||||
|
# Settings common to each job
|
||||||
|
anchor_1: &job_defaults
|
||||||
working_directory: ~/ng
|
working_directory: ~/ng
|
||||||
docker:
|
docker:
|
||||||
- image: angular/ngcontainer
|
- image: angular/ngcontainer
|
||||||
|
|
||||||
|
# After checkout, rebase on top of master.
|
||||||
|
# Similar to travis behavior, but not quite the same.
|
||||||
|
# See https://discuss.circleci.com/t/1662
|
||||||
|
anchor_2: &post_checkout
|
||||||
|
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
<<: *defaults
|
<<: *job_defaults
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
# After checkout, rebase on top of master.
|
<<: *post_checkout
|
||||||
# Similar to travis behavior, but not quite the same.
|
|
||||||
# See https://discuss.circleci.com/t/1662
|
|
||||||
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||||
|
|
||||||
@ -21,9 +34,10 @@ jobs:
|
|||||||
- run: ./node_modules/.bin/gulp lint
|
- run: ./node_modules/.bin/gulp lint
|
||||||
|
|
||||||
build:
|
build:
|
||||||
<<: *defaults
|
<<: *job_defaults
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||||
|
|
||||||
|
12
.github/ISSUE_TEMPLATE.md
vendored
12
.github/ISSUE_TEMPLATE.md
vendored
@ -1,14 +1,14 @@
|
|||||||
<!--
|
<!--
|
||||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||||
|
|
||||||
ISSUES MISSING IMPORTANT INFORMATION MIGHT BE CLOSED WITHOUT INVESTIGATION.
|
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## I'm submitting a ...
|
## I'm submitting a...
|
||||||
<!-- Check one of the following options with "x" -->
|
<!-- Check one of the following options with "x" -->
|
||||||
<pre><code>
|
<pre><code>
|
||||||
[ ] Regression (behavior that used to work and stopped working in a new release)
|
[ ] Regression (a behavior that used to work and stopped working in a new release)
|
||||||
[ ] Bug report <!-- Please search github for a similar issue or PR before submitting -->
|
[ ] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
|
||||||
[ ] Feature request
|
[ ] Feature request
|
||||||
[ ] Documentation issue or request
|
[ ] Documentation issue or request
|
||||||
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
||||||
@ -32,7 +32,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
|
|||||||
<!-- Describe the motivation or the concrete use case. -->
|
<!-- Describe the motivation or the concrete use case. -->
|
||||||
|
|
||||||
|
|
||||||
## Please tell us about your environment
|
## Environment
|
||||||
|
|
||||||
<pre><code>
|
<pre><code>
|
||||||
Angular version: X.Y.Z
|
Angular version: X.Y.Z
|
||||||
@ -49,7 +49,7 @@ Browser:
|
|||||||
- [ ] Edge version XX
|
- [ ] Edge version XX
|
||||||
|
|
||||||
For Tooling issues:
|
For Tooling issues:
|
||||||
- Node version: XX <!-- use `node --version` -->
|
- Node version: XX <!-- run `node --version` -->
|
||||||
- Platform: <!-- Mac, Linux, Windows -->
|
- Platform: <!-- Mac, Linux, Windows -->
|
||||||
|
|
||||||
Others:
|
Others:
|
||||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,5 +1,5 @@
|
|||||||
## PR Checklist
|
## PR Checklist
|
||||||
Does please check if your PR fulfills the following requirements:
|
Please check if your PR fulfills the following requirements:
|
||||||
|
|
||||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
|
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
|
||||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
bazel-*
|
bazel-*
|
||||||
|
e2e_test.*
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
|
|
||||||
|
@ -8,9 +8,11 @@
|
|||||||
# alexeagle - Alex Eagle
|
# alexeagle - Alex Eagle
|
||||||
# alxhub - Alex Rickabaugh
|
# alxhub - Alex Rickabaugh
|
||||||
# chuckjaz - Chuck Jazdzewski
|
# chuckjaz - Chuck Jazdzewski
|
||||||
|
# Foxandxss - Jesús Rodríguez
|
||||||
# gkalpak - George Kalpakas
|
# gkalpak - George Kalpakas
|
||||||
# IgorMinar - Igor Minar
|
# IgorMinar - Igor Minar
|
||||||
# jasonaden - Jason Aden
|
# jasonaden - Jason Aden
|
||||||
|
# juleskremer - Jules Kremer
|
||||||
# kara - Kara Erickson
|
# kara - Kara Erickson
|
||||||
# matsko - Matias Niemelä
|
# matsko - Matias Niemelä
|
||||||
# mhevery - Misko Hevery
|
# mhevery - Misko Hevery
|
||||||
@ -18,10 +20,11 @@
|
|||||||
# pkozlowski-opensource - Pawel Kozlowski
|
# pkozlowski-opensource - Pawel Kozlowski
|
||||||
# robwormald - Rob Wormald
|
# robwormald - Rob Wormald
|
||||||
# tbosch - Tobias Bosch
|
# tbosch - Tobias Bosch
|
||||||
|
# tinayuangao - Tina Gao
|
||||||
# vicb - Victor Berchet
|
# vicb - Victor Berchet
|
||||||
# vikerman - Vikram Subramanian
|
# vikerman - Vikram Subramanian
|
||||||
# wardbell - Ward Bell
|
# wardbell - Ward Bell
|
||||||
# tinayuangao - Tina Gao
|
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
@ -93,6 +96,7 @@ groups:
|
|||||||
- "packages/core/*"
|
- "packages/core/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- tbosch #primary
|
||||||
|
- chuckjaz
|
||||||
- mhevery
|
- mhevery
|
||||||
- vicb
|
- vicb
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
@ -104,6 +108,7 @@ groups:
|
|||||||
- "packages/platform-browser/animations/*"
|
- "packages/platform-browser/animations/*"
|
||||||
users:
|
users:
|
||||||
- matsko #primary
|
- matsko #primary
|
||||||
|
- chuckjaz #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
|
||||||
@ -249,10 +254,46 @@ groups:
|
|||||||
angular.io:
|
angular.io:
|
||||||
conditions:
|
conditions:
|
||||||
files:
|
files:
|
||||||
|
include:
|
||||||
- "aio/*"
|
- "aio/*"
|
||||||
|
exclude:
|
||||||
|
- "aio/content/*"
|
||||||
users:
|
users:
|
||||||
- IgorMinar #primary
|
- petebacondarwin #primary
|
||||||
- petebacondarwin #secondary
|
- IgorMinar
|
||||||
- gkalpak
|
- gkalpak
|
||||||
- wardbell
|
- mhevery #fallback
|
||||||
|
|
||||||
|
angular.io-guide-and-tutorial:
|
||||||
|
conditions:
|
||||||
|
files:
|
||||||
|
include:
|
||||||
|
- "aio/content/*"
|
||||||
|
exclude:
|
||||||
|
- "aio/content/marketing/*"
|
||||||
|
- "aio/content/navigation.json"
|
||||||
|
- "aio/content/license.md"
|
||||||
|
users:
|
||||||
|
- juleskremer #primary
|
||||||
|
- Foxandxss
|
||||||
|
- stephenfluin
|
||||||
|
- wardbell
|
||||||
|
- petebacondarwin
|
||||||
|
- gkalpak
|
||||||
|
- IgorMinar #fallback
|
||||||
|
- mhevery #fallback
|
||||||
|
|
||||||
|
angular.io-marketing:
|
||||||
|
conditions:
|
||||||
|
files:
|
||||||
|
include:
|
||||||
|
- "aio/content/marketing/*"
|
||||||
|
- "aio/content/navigation.json"
|
||||||
|
- "aio/content/license.md"
|
||||||
|
users:
|
||||||
|
- juleskremer #primary
|
||||||
|
- stephenfluin
|
||||||
|
- petebacondarwin
|
||||||
|
- gkalpak
|
||||||
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
@ -76,5 +76,4 @@ script:
|
|||||||
- ./scripts/ci/angular.sh
|
- ./scripts/ci/angular.sh
|
||||||
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
||||||
- ./scripts/ci/cleanup.sh
|
- ./scripts/ci/cleanup.sh
|
||||||
# Disabled so we can debug Travis build failures on Master seeming to coming from printing logs
|
- ./scripts/ci/print-logs.sh
|
||||||
# - ./scripts/ci/print-logs.sh
|
|
||||||
|
4224
CHANGELOG.md
4224
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
|||||||
|
|
||||||
## <a name="question"></a> Got a Question or Problem?
|
## <a name="question"></a> Got a Question or Problem?
|
||||||
|
|
||||||
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
|
Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
|
||||||
|
|
||||||
Stack Overflow is a much better place to ask questions since:
|
Stack Overflow is a much better place to ask questions since:
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Stack Overflow is a much better place to ask questions since:
|
|||||||
- questions and answers stay available for public viewing so your question / answer might help someone else
|
- questions and answers stay available for public viewing so your question / answer might help someone else
|
||||||
- Stack Overflow's voting system assures that the best answers are prominently visible.
|
- Stack Overflow's voting system assures that the best answers are prominently visible.
|
||||||
|
|
||||||
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to Stack Overflow.
|
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
|
||||||
|
|
||||||
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
|
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
|
||||||
|
|
||||||
@ -206,6 +206,7 @@ The scope should be the name of the npm package affected (as perceived by person
|
|||||||
|
|
||||||
The following is the list of supported scopes:
|
The following is the list of supported scopes:
|
||||||
|
|
||||||
|
* **animations**
|
||||||
* **common**
|
* **common**
|
||||||
* **compiler**
|
* **compiler**
|
||||||
* **compiler-cli**
|
* **compiler-cli**
|
||||||
|
11
README.md
11
README.md
@ -3,22 +3,21 @@
|
|||||||
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[](http://issuestats.com/github/angular/angular)
|
[](http://issuestats.com/github/angular/angular)
|
||||||
[](http://issuestats.com/github/angular/angular)
|
[](http://issuestats.com/github/angular/angular)
|
||||||
[](https://badge.fury.io/js/%40angular%2Fcore)
|
[](https://www.npmjs.com/@angular/core)
|
||||||
|
|
||||||
|
|
||||||
[](https://saucelabs.com/u/angular2-ci)
|
[](https://saucelabs.com/u/angular2-ci)
|
||||||
|
|
||||||
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
||||||
|
|
||||||
Angular
|
# Angular
|
||||||
=========
|
|
||||||
|
|
||||||
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript (JS) and other languages.
|
|
||||||
|
|
||||||
|
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript and other languages.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
[Get started in 5 minutes][quickstart].
|
[Get started in 5 minutes][quickstart].
|
||||||
|
|
||||||
|
|
||||||
## Want to help?
|
## Want to help?
|
||||||
|
|
||||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
||||||
|
@ -16,6 +16,7 @@ Here are the most important tasks you might need to use:
|
|||||||
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
||||||
|
|
||||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||||
|
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||||
* `yarn lint` - check that the doc-viewer code follows our style rules.
|
* `yarn lint` - check that the doc-viewer code follows our style rules.
|
||||||
* `yarn test` - watch all the source files, for the doc-viewer, and run all the unit tests when any change.
|
* `yarn test` - watch all the source files, for the doc-viewer, and run all the unit tests when any change.
|
||||||
* `yarn e2e` - run all the e2e tests for the doc-viewer.
|
* `yarn e2e` - run all the e2e tests for the doc-viewer.
|
||||||
@ -30,6 +31,10 @@ Here are the most important tasks you might need to use:
|
|||||||
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
|
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
|
||||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||||
|
|
||||||
|
* `yarn example-e2e` - run all e2e tests for examples
|
||||||
|
- `yarn example-e2e -- --setup` - force webdriver update & other setup, then run tests
|
||||||
|
- `yarn example-e2e -- --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||||
|
|
||||||
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
||||||
|
|
||||||
## Using ServiceWorker locally
|
## Using ServiceWorker locally
|
||||||
|
@ -88,6 +88,21 @@ server {
|
|||||||
resolver 127.0.0.1;
|
resolver 127.0.0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Notify about PR changes
|
||||||
|
location "~^/pr-updated/?$" {
|
||||||
|
if ($request_method != "POST") {
|
||||||
|
add_header Allow "POST";
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_pass_request_headers on;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_method POST;
|
||||||
|
proxy_pass http://{{$AIO_UPLOAD_HOSTNAME}}:{{$AIO_UPLOAD_PORT}}$request_uri;
|
||||||
|
|
||||||
|
resolver 127.0.0.1;
|
||||||
|
}
|
||||||
|
|
||||||
# Everything else
|
# Everything else
|
||||||
location / {
|
location / {
|
||||||
return 404;
|
return 404;
|
||||||
|
@ -18,45 +18,17 @@ export class BuildCreator extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Methods - Public
|
// Methods - Public
|
||||||
public changePrVisibility(pr: string, makePublic: boolean): Promise<void> {
|
|
||||||
const {oldPrDir, newPrDir} = this.getCandidatePrDirs(pr, makePublic);
|
|
||||||
|
|
||||||
return Promise.
|
|
||||||
all([this.exists(oldPrDir), this.exists(newPrDir)]).
|
|
||||||
then(([oldPrDirExisted, newPrDirExisted]) => {
|
|
||||||
if (!oldPrDirExisted) {
|
|
||||||
throw new UploadError(404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
|
|
||||||
} else if (newPrDirExisted) {
|
|
||||||
throw new UploadError(409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve().
|
|
||||||
then(() => shell.mv(oldPrDir, newPrDir)).
|
|
||||||
then(() => this.listShasByDate(newPrDir)).
|
|
||||||
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
|
|
||||||
then(() => undefined);
|
|
||||||
}).
|
|
||||||
catch(err => {
|
|
||||||
if (!(err instanceof UploadError)) {
|
|
||||||
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public create(pr: string, sha: string, archivePath: string, isPublic: boolean): Promise<void> {
|
public create(pr: string, sha: string, archivePath: string, isPublic: boolean): Promise<void> {
|
||||||
// Use only part of the SHA for more readable URLs.
|
// Use only part of the SHA for more readable URLs.
|
||||||
sha = sha.substr(0, SHORT_SHA_LEN);
|
sha = sha.substr(0, SHORT_SHA_LEN);
|
||||||
|
|
||||||
const {oldPrDir: otherVisPrDir, newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
|
const {newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
|
||||||
const shaDir = path.join(prDir, sha);
|
const shaDir = path.join(prDir, sha);
|
||||||
let dirToRemoveOnError: string;
|
let dirToRemoveOnError: string;
|
||||||
|
|
||||||
return Promise.resolve().
|
return Promise.resolve().
|
||||||
then(() => this.exists(otherVisPrDir)).
|
|
||||||
// If the same PR exists with different visibility, update the visibility first.
|
// If the same PR exists with different visibility, update the visibility first.
|
||||||
then(otherVisPrDirExisted => (otherVisPrDirExisted && this.changePrVisibility(pr, isPublic)) as any).
|
then(() => this.updatePrVisibility(pr, isPublic)).
|
||||||
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
|
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
|
||||||
then(([prDirExisted, shaDirExisted]) => {
|
then(([prDirExisted, shaDirExisted]) => {
|
||||||
if (shaDirExisted) {
|
if (shaDirExisted) {
|
||||||
@ -84,6 +56,36 @@ export class BuildCreator extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updatePrVisibility(pr: string, makePublic: boolean): Promise<boolean> {
|
||||||
|
const {oldPrDir: otherVisPrDir, newPrDir: targetVisPrDir} = this.getCandidatePrDirs(pr, makePublic);
|
||||||
|
|
||||||
|
return Promise.
|
||||||
|
all([this.exists(otherVisPrDir), this.exists(targetVisPrDir)]).
|
||||||
|
then(([otherVisPrDirExisted, targetVisPrDirExisted]) => {
|
||||||
|
if (!otherVisPrDirExisted) {
|
||||||
|
// No visibility change: Either the visibility is up-to-date or the PR does not exist.
|
||||||
|
return false;
|
||||||
|
} else if (targetVisPrDirExisted) {
|
||||||
|
// Error: Directories for both visibilities exist.
|
||||||
|
throw new UploadError(409, `Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`.
|
||||||
|
return Promise.resolve().
|
||||||
|
then(() => shell.mv(otherVisPrDir, targetVisPrDir)).
|
||||||
|
then(() => this.listShasByDate(targetVisPrDir)).
|
||||||
|
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
|
||||||
|
then(() => true);
|
||||||
|
}).
|
||||||
|
catch(err => {
|
||||||
|
if (!(err instanceof UploadError)) {
|
||||||
|
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Methods - Protected
|
// Methods - Protected
|
||||||
protected exists(fileOrDir: string): Promise<boolean> {
|
protected exists(fileOrDir: string): Promise<boolean> {
|
||||||
return new Promise(resolve => fs.access(fileOrDir, err => resolve(!err)));
|
return new Promise(resolve => fs.access(fileOrDir, err => resolve(!err)));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Imports
|
// Imports
|
||||||
|
import * as bodyParser from 'body-parser';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
@ -84,6 +85,7 @@ class UploadServerFactory {
|
|||||||
|
|
||||||
protected createMiddleware(buildVerifier: BuildVerifier, buildCreator: BuildCreator): express.Express {
|
protected createMiddleware(buildVerifier: BuildVerifier, buildCreator: BuildCreator): express.Express {
|
||||||
const middleware = express();
|
const middleware = express();
|
||||||
|
const jsonParser = bodyParser.json();
|
||||||
|
|
||||||
middleware.get(/^\/create-build\/([1-9][0-9]*)\/([0-9a-f]{40})\/?$/, (req, res) => {
|
middleware.get(/^\/create-build\/([1-9][0-9]*)\/([0-9a-f]{40})\/?$/, (req, res) => {
|
||||||
const pr = req.params[0];
|
const pr = req.params[0];
|
||||||
@ -96,8 +98,8 @@ class UploadServerFactory {
|
|||||||
} else if (!archive) {
|
} else if (!archive) {
|
||||||
this.throwRequestError(400, `Missing or empty '${X_FILE_HEADER}' header`, req);
|
this.throwRequestError(400, `Missing or empty '${X_FILE_HEADER}' header`, req);
|
||||||
} else {
|
} else {
|
||||||
buildVerifier.
|
Promise.resolve().
|
||||||
verify(+pr, authHeader).
|
then(() => buildVerifier.verify(+pr, authHeader)).
|
||||||
then(verStatus => verStatus === BUILD_VERIFICATION_STATUS.verifiedAndTrusted).
|
then(verStatus => verStatus === BUILD_VERIFICATION_STATUS.verifiedAndTrusted).
|
||||||
then(isPublic => buildCreator.create(pr, sha, archive, isPublic).
|
then(isPublic => buildCreator.create(pr, sha, archive, isPublic).
|
||||||
then(() => res.sendStatus(isPublic ? 201 : 202))).
|
then(() => res.sendStatus(isPublic ? 201 : 202))).
|
||||||
@ -105,8 +107,23 @@ class UploadServerFactory {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
||||||
middleware.get('*', req => this.throwRequestError(404, 'Unknown resource', req));
|
middleware.post(/^\/pr-updated\/?$/, jsonParser, (req, res) => {
|
||||||
middleware.all('*', req => this.throwRequestError(405, 'Unsupported method', req));
|
const {action, number: prNo}: {action?: string, number?: number} = req.body;
|
||||||
|
const visMayHaveChanged = !action || (action === 'labeled') || (action === 'unlabeled');
|
||||||
|
|
||||||
|
if (!visMayHaveChanged) {
|
||||||
|
res.sendStatus(200);
|
||||||
|
} else if (!prNo) {
|
||||||
|
this.throwRequestError(400, `Missing or empty 'number' field`, req);
|
||||||
|
} else {
|
||||||
|
Promise.resolve().
|
||||||
|
then(() => buildVerifier.getPrIsTrusted(prNo)).
|
||||||
|
then(isPublic => buildCreator.updatePrVisibility(String(prNo), isPublic)).
|
||||||
|
then(() => res.sendStatus(200)).
|
||||||
|
catch(err => this.respondWithError(res, err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
middleware.all('*', req => this.throwRequestError(404, 'Unknown resource', req));
|
||||||
middleware.use((err: any, _req: any, res: express.Response, _next: any) => this.respondWithError(res, err));
|
middleware.use((err: any, _req: any, res: express.Response, _next: any) => this.respondWithError(res, err));
|
||||||
|
|
||||||
return middleware;
|
return middleware;
|
||||||
@ -125,7 +142,10 @@ class UploadServerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected throwRequestError(status: number, error: string, req: express.Request) {
|
protected throwRequestError(status: number, error: string, req: express.Request) {
|
||||||
throw new UploadError(status, `${error} in request: ${req.method} ${req.originalUrl}`);
|
const message = `${error} in request: ${req.method} ${req.originalUrl}` +
|
||||||
|
(!req.body ? '' : ` ${JSON.stringify(req.body)}`);
|
||||||
|
|
||||||
|
throw new UploadError(status, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
// Using the values below, we can fake the response of the corresponding methods in tests. This is
|
||||||
|
// necessary, because the test upload-server will be running as a separate node process, so we will
|
||||||
|
// not have direct access to the code (e.g. for mocking).
|
||||||
|
// (See also 'lib/verify-setup/start-test-upload-server.ts'.)
|
||||||
|
|
||||||
|
/* tslint:disable: variable-name */
|
||||||
|
|
||||||
|
// Special values to be used as `authHeader` in `BuildVerifier#verify()`.
|
||||||
|
export const BV_verify_error = 'FAKE_VERIFICATION_ERROR';
|
||||||
|
export const BV_verify_verifiedNotTrusted = 'FAKE_VERIFIED_NOT_TRUSTED';
|
||||||
|
|
||||||
|
// Special values to be used as `pr` in `BuildVerifier#getPrIsTrusted()`.
|
||||||
|
export const BV_getPrIsTrusted_error = 32203;
|
||||||
|
export const BV_getPrIsTrusted_notTrusted = 72457;
|
||||||
|
|
||||||
|
/* tslint:enable: variable-name */
|
@ -317,6 +317,51 @@ describe(`nginx`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe(`${host}/pr-updated`, () => {
|
||||||
|
const url = `${scheme}://${host}/pr-updated`;
|
||||||
|
|
||||||
|
|
||||||
|
it('should disallow non-POST requests', done => {
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass requests through to the upload server', done => {
|
||||||
|
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`;
|
||||||
|
|
||||||
|
const cmd1 = `${cmdPrefix} ${url}`;
|
||||||
|
const cmd2 = `${cmdPrefix} --data '{"number":${pr}}' ${url}`;
|
||||||
|
const cmd3 = `${cmdPrefix} --data '{"number":${pr},"action":"foo"}' ${url}`;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(cmd1).then(h.verifyResponse(400, /Missing or empty 'number' field/)),
|
||||||
|
h.runCmd(cmd2).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(cmd3).then(h.verifyResponse(200)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for unknown paths', done => {
|
||||||
|
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/*`, () => {
|
describe(`${host}/*`, () => {
|
||||||
|
|
||||||
it('should respond with 404 for unknown URLs (even if the resource exists)', done => {
|
it('should respond with 404 for unknown URLs (even if the resource exists)', done => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// Imports
|
// Imports
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as c from './constants';
|
||||||
import {helper as h} from './helper';
|
import {helper as h} from './helper';
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@ -14,12 +15,14 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
const getFile = (pr: string, sha: string, file: string) =>
|
const getFile = (pr: string, sha: string, file: string) =>
|
||||||
h.runCmd(`curl -iL ${scheme}://pr${pr}-${h.getShordSha(sha)}.${host}/${file}`);
|
h.runCmd(`curl -iL ${scheme}://pr${pr}-${h.getShordSha(sha)}.${host}/${file}`);
|
||||||
const uploadBuild = (pr: string, sha: string, archive: string, authHeader = 'Token FOO') => {
|
const uploadBuild = (pr: string, sha: string, archive: string, authHeader = 'Token FOO') => {
|
||||||
// Using `FAKE_VERIFICATION_ERROR` or `FAKE_VERIFIED_NOT_TRUSTED` as `authHeader`,
|
|
||||||
// we can fake the response of the overwritten `BuildVerifier.verify()` method.
|
|
||||||
// (See 'lib/upload-server/index-test.ts'.)
|
|
||||||
const curlPost = `curl -iLX POST --header "Authorization: ${authHeader}"`;
|
const curlPost = `curl -iLX POST --header "Authorization: ${authHeader}"`;
|
||||||
return h.runCmd(`${curlPost} --data-binary "@${archive}" ${scheme}://${host}/create-build/${pr}/${sha}`);
|
return h.runCmd(`${curlPost} --data-binary "@${archive}" ${scheme}://${host}/create-build/${pr}/${sha}`);
|
||||||
};
|
};
|
||||||
|
const prUpdated = (pr: number, action?: string) => {
|
||||||
|
const url = `${scheme}://${host}/pr-updated`;
|
||||||
|
const payloadStr = JSON.stringify({number: pr, action});
|
||||||
|
return h.runCmd(`curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000);
|
beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000);
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -29,7 +32,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('for a new PR', () => {
|
describe('for a new/non-existing PR', () => {
|
||||||
|
|
||||||
it('should be able to upload and serve a public build', done => {
|
it('should be able to upload and serve a public build', done => {
|
||||||
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
|
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
|
||||||
@ -54,7 +57,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
|
|
||||||
h.createDummyArchive(pr9, sha9, archivePath);
|
h.createDummyArchive(pr9, sha9, archivePath);
|
||||||
|
|
||||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||||
then(() => Promise.all([
|
then(() => Promise.all([
|
||||||
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
|
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
|
||||||
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
|
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
|
||||||
@ -74,7 +77,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
|
|
||||||
h.createDummyArchive(pr9, sha9, archivePath);
|
h.createDummyArchive(pr9, sha9, archivePath);
|
||||||
|
|
||||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
|
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
|
||||||
then(h.verifyResponse(403, errorRegex9)).
|
then(h.verifyResponse(403, errorRegex9)).
|
||||||
then(() => {
|
then(() => {
|
||||||
expect(h.buildExists(pr9)).toBe(false);
|
expect(h.buildExists(pr9)).toBe(false);
|
||||||
@ -83,6 +86,18 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
then(done);
|
then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to notify that a PR has been updated (and do nothing)', done => {
|
||||||
|
prUpdated(+pr9).
|
||||||
|
then(h.verifyResponse(200)).
|
||||||
|
then(() => {
|
||||||
|
// The PR should still not exist.
|
||||||
|
expect(h.buildExists(pr9, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(pr9, '', true)).toBe(false);
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +138,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
h.createDummyBuild(pr9, sha0, false);
|
h.createDummyBuild(pr9, sha0, false);
|
||||||
h.createDummyArchive(pr9, sha9, archivePath);
|
h.createDummyArchive(pr9, sha9, archivePath);
|
||||||
|
|
||||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||||
then(() => Promise.all([
|
then(() => Promise.all([
|
||||||
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(404)),
|
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(404)),
|
||||||
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(404)),
|
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(404)),
|
||||||
@ -148,7 +163,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
h.createDummyBuild(pr9, sha0);
|
h.createDummyBuild(pr9, sha0);
|
||||||
h.createDummyArchive(pr9, sha9, archivePath);
|
h.createDummyArchive(pr9, sha9, archivePath);
|
||||||
|
|
||||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
|
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
|
||||||
then(h.verifyResponse(403, errorRegex9)).
|
then(h.verifyResponse(403, errorRegex9)).
|
||||||
then(() => {
|
then(() => {
|
||||||
expect(h.buildExists(pr9)).toBe(true);
|
expect(h.buildExists(pr9)).toBe(true);
|
||||||
@ -186,7 +201,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
h.createDummyBuild(pr9, sha9, false);
|
h.createDummyBuild(pr9, sha9, false);
|
||||||
h.createDummyArchive(pr9, sha9, archivePath);
|
h.createDummyArchive(pr9, sha9, archivePath);
|
||||||
|
|
||||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||||
then(h.verifyResponse(409)).
|
then(h.verifyResponse(409)).
|
||||||
then(() => {
|
then(() => {
|
||||||
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
|
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
|
||||||
@ -195,6 +210,110 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
|||||||
then(done);
|
then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to request re-checking visibility (if outdated)', done => {
|
||||||
|
const publicPr = pr9;
|
||||||
|
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
|
||||||
|
|
||||||
|
h.createDummyBuild(publicPr, sha9, false);
|
||||||
|
h.createDummyBuild(hiddenPr, sha9, true);
|
||||||
|
|
||||||
|
// PR visibilities are outdated (i.e. the opposte of what the should).
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(false);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
|
||||||
|
|
||||||
|
Promise.
|
||||||
|
all([
|
||||||
|
prUpdated(+publicPr).then(h.verifyResponse(200)),
|
||||||
|
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
|
||||||
|
]).
|
||||||
|
then(() => {
|
||||||
|
// PR visibilities should have been updated.
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
|
||||||
|
}).
|
||||||
|
then(() => {
|
||||||
|
h.deletePrDir(publicPr, true);
|
||||||
|
h.deletePrDir(hiddenPr, false);
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to request re-checking visibility (if up-to-date)', done => {
|
||||||
|
const publicPr = pr9;
|
||||||
|
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
|
||||||
|
|
||||||
|
h.createDummyBuild(publicPr, sha9, true);
|
||||||
|
h.createDummyBuild(hiddenPr, sha9, false);
|
||||||
|
|
||||||
|
// PR visibilities are already up-to-date.
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
|
||||||
|
|
||||||
|
Promise.
|
||||||
|
all([
|
||||||
|
prUpdated(+publicPr).then(h.verifyResponse(200)),
|
||||||
|
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
|
||||||
|
]).
|
||||||
|
then(() => {
|
||||||
|
// PR visibilities are still up-to-date.
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should reject a request if re-checking visibility fails', done => {
|
||||||
|
const errorPr = String(c.BV_getPrIsTrusted_error);
|
||||||
|
|
||||||
|
h.createDummyBuild(errorPr, sha9, true);
|
||||||
|
|
||||||
|
expect(h.buildExists(errorPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(errorPr, '', true)).toBe(true);
|
||||||
|
|
||||||
|
prUpdated(+errorPr).
|
||||||
|
then(h.verifyResponse(500, /Test/)).
|
||||||
|
then(() => {
|
||||||
|
// PR visibility should not have been updated.
|
||||||
|
expect(h.buildExists(errorPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(errorPr, '', true)).toBe(true);
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should reject a request if updating visibility fails', done => {
|
||||||
|
// One way to cause an error is to have both a public and a hidden directory for the same PR.
|
||||||
|
h.createDummyBuild(pr9, sha9, false);
|
||||||
|
h.createDummyBuild(pr9, sha9, true);
|
||||||
|
|
||||||
|
const hiddenPrDir = h.getPrDir(pr9, false);
|
||||||
|
const publicPrDir = h.getPrDir(pr9, true);
|
||||||
|
const bodyRegex = new RegExp(`Request to move '${hiddenPrDir}' to existing directory '${publicPrDir}'`);
|
||||||
|
|
||||||
|
expect(h.buildExists(pr9, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(pr9, '', true)).toBe(true);
|
||||||
|
|
||||||
|
prUpdated(+pr9).
|
||||||
|
then(h.verifyResponse(409, bodyRegex)).
|
||||||
|
then(() => {
|
||||||
|
// PR visibility should not have been updated.
|
||||||
|
expect(h.buildExists(pr9, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(pr9, '', true)).toBe(true);
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
@ -1,17 +1,31 @@
|
|||||||
// Imports
|
// Imports
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from './build-verifier';
|
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from '../upload-server/build-verifier';
|
||||||
import {UploadError} from './upload-error';
|
import {UploadError} from '../upload-server/upload-error';
|
||||||
|
import * as c from './constants';
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
// TODO(gkalpak): Add e2e tests to cover these interactions as well.
|
// TODO(gkalpak): Add e2e tests to cover these interactions as well.
|
||||||
GithubPullRequests.prototype.addComment = () => Promise.resolve();
|
GithubPullRequests.prototype.addComment = () => Promise.resolve();
|
||||||
|
BuildVerifier.prototype.getPrIsTrusted = (pr: number) => {
|
||||||
|
switch (pr) {
|
||||||
|
case c.BV_getPrIsTrusted_error:
|
||||||
|
// For e2e tests, fake an error.
|
||||||
|
return Promise.reject('Test');
|
||||||
|
case c.BV_getPrIsTrusted_notTrusted:
|
||||||
|
// For e2e tests, fake an untrusted PR (`false`).
|
||||||
|
return Promise.resolve(false);
|
||||||
|
default:
|
||||||
|
// For e2e tests, default to trusted PRs (`true`).
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
|
BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
|
||||||
switch (authHeader) {
|
switch (authHeader) {
|
||||||
case 'FAKE_VERIFICATION_ERROR':
|
case c.BV_verify_error:
|
||||||
// For e2e tests, fake a verification error.
|
// For e2e tests, fake a verification error.
|
||||||
return Promise.reject(new UploadError(403, `Error while verifying upload for PR ${expectedPr}: Test`));
|
return Promise.reject(new UploadError(403, `Error while verifying upload for PR ${expectedPr}: Test`));
|
||||||
case 'FAKE_VERIFIED_NOT_TRUSTED':
|
case c.BV_verify_verifiedNotTrusted:
|
||||||
// For e2e tests, fake a `verifiedNotTrusted` verification status.
|
// For e2e tests, fake a `verifiedNotTrusted` verification status.
|
||||||
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
|
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
|
||||||
default:
|
default:
|
||||||
@ -21,4 +35,4 @@ BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// tslint:disable-next-line: no-var-requires
|
// tslint:disable-next-line: no-var-requires
|
||||||
require('./index');
|
require('../upload-server/index');
|
@ -1,6 +1,7 @@
|
|||||||
// Imports
|
// Imports
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as c from './constants';
|
||||||
import {CmdResult, helper as h} from './helper';
|
import {CmdResult, helper as h} from './helper';
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@ -25,13 +26,13 @@ describe('upload-server (on HTTP)', () => {
|
|||||||
|
|
||||||
it('should disallow non-GET requests', done => {
|
it('should disallow non-GET requests', done => {
|
||||||
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
||||||
const bodyRegex = /^Unsupported method/;
|
const bodyRegex = /^Unknown resource/;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405, bodyRegex)),
|
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(405, bodyRegex)),
|
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405, bodyRegex)),
|
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405, bodyRegex)),
|
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
]).then(done);
|
]).then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ describe('upload-server (on HTTP)', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should reject requests for which the PR verification fails', done => {
|
it('should reject requests for which the PR verification fails', done => {
|
||||||
const headers = `--header "Authorization: FAKE_VERIFICATION_ERROR" ${xFileHeader}`;
|
const headers = `--header "Authorization: ${c.BV_verify_error}" ${xFileHeader}`;
|
||||||
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
||||||
const bodyRegex = new RegExp(`Error while verifying upload for PR ${pr}: Test`);
|
const bodyRegex = new RegExp(`Error while verifying upload for PR ${pr}: Test`);
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ describe('upload-server (on HTTP)', () => {
|
|||||||
|
|
||||||
[true, false].forEach(isPublic => describe(`(for ${isPublic ? 'public' : 'hidden'} builds)`, () => {
|
[true, false].forEach(isPublic => describe(`(for ${isPublic ? 'public' : 'hidden'} builds)`, () => {
|
||||||
const authorizationHeader2 = isPublic ?
|
const authorizationHeader2 = isPublic ?
|
||||||
authorizationHeader : '--header "Authorization: FAKE_VERIFIED_NOT_TRUSTED"';
|
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
|
||||||
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
|
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
|
||||||
|
|
||||||
|
|
||||||
@ -373,27 +374,194 @@ describe('upload-server (on HTTP)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe(`${host}/pr-updated`, () => {
|
||||||
|
const url = `http://${host}/pr-updated`;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const curl = (payload?: {number: number, action?: string}) => {
|
||||||
|
const payloadStr = payload && JSON.stringify(payload) || '';
|
||||||
|
return `curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
it('should disallow non-POST requests', done => {
|
||||||
|
const bodyRegex = /^Unknown resource in request/;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 400 for requests without a payload', done => {
|
||||||
|
const bodyRegex = /^Missing or empty 'number' field in request/;
|
||||||
|
|
||||||
|
h.runCmd(curl()).
|
||||||
|
then(h.verifyResponse(400, bodyRegex)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 400 for requests without a \'number\' field', done => {
|
||||||
|
const bodyRegex = /^Missing or empty 'number' field in request/;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(curl({} as any)).then(h.verifyResponse(400, bodyRegex)),
|
||||||
|
h.runCmd(curl({number: null} as any)).then(h.verifyResponse(400, bodyRegex)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should reject requests for which checking the PR visibility fails', done => {
|
||||||
|
h.runCmd(curl({number: c.BV_getPrIsTrusted_error})).
|
||||||
|
then(h.verifyResponse(500, /Test/)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for unknown paths', done => {
|
||||||
|
const mockPayload = JSON.stringify({number: +pr});
|
||||||
|
const cmdPrefix = `curl -iLX POST --data "${mockPayload}" http://${host}`;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should do nothing if PR\'s visibility is already up-to-date', done => {
|
||||||
|
const publicPr = pr;
|
||||||
|
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
|
||||||
|
const checkVisibilities = () => {
|
||||||
|
// Public build is already public.
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(true);
|
||||||
|
// Hidden build is already hidden.
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
h.createDummyBuild(publicPr, sha9, true);
|
||||||
|
h.createDummyBuild(hiddenPr, sha9, false);
|
||||||
|
checkVisibilities();
|
||||||
|
|
||||||
|
Promise.
|
||||||
|
all([
|
||||||
|
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
|
||||||
|
]).
|
||||||
|
// Visibilities should not have changed, because the specified action could not have triggered a change.
|
||||||
|
then(checkVisibilities).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should do nothing if \'action\' implies no visibility change', done => {
|
||||||
|
const publicPr = pr;
|
||||||
|
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
|
||||||
|
const checkVisibilities = () => {
|
||||||
|
// Public build is hidden atm.
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(false);
|
||||||
|
// Hidden build is public atm.
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
h.createDummyBuild(publicPr, sha9, false);
|
||||||
|
h.createDummyBuild(hiddenPr, sha9, true);
|
||||||
|
checkVisibilities();
|
||||||
|
|
||||||
|
Promise.
|
||||||
|
all([
|
||||||
|
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
|
||||||
|
]).
|
||||||
|
// Visibilities should not have changed, because the specified action could not have triggered a change.
|
||||||
|
then(checkVisibilities).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('when the visiblity has changed', () => {
|
||||||
|
const publicPr = pr;
|
||||||
|
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Create initial PR builds with opposite visibilities as the ones that will be reported:
|
||||||
|
// - The now public PR was previously hidden.
|
||||||
|
// - The now hidden PR was previously public.
|
||||||
|
h.createDummyBuild(publicPr, sha9, false);
|
||||||
|
h.createDummyBuild(hiddenPr, sha9, true);
|
||||||
|
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(false);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
// Expect PRs' visibility to have been updated:
|
||||||
|
// - The public PR should be actually public (previously it was hidden).
|
||||||
|
// - The hidden PR should be actually hidden (previously it was public).
|
||||||
|
expect(h.buildExists(publicPr, '', false)).toBe(false);
|
||||||
|
expect(h.buildExists(publicPr, '', true)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
|
||||||
|
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
|
||||||
|
|
||||||
|
h.deletePrDir(publicPr, true);
|
||||||
|
h.deletePrDir(hiddenPr, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should update the PR\'s visibility (action: undefined)', done => {
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(curl({number: +publicPr})).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(curl({number: +hiddenPr})).then(h.verifyResponse(200)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should update the PR\'s visibility (action: labeled)', done => {
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(curl({number: +publicPr, action: 'labeled'})).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(curl({number: +hiddenPr, action: 'labeled'})).then(h.verifyResponse(200)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should update the PR\'s visibility (action: unlabeled)', done => {
|
||||||
|
Promise.all([
|
||||||
|
h.runCmd(curl({number: +publicPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
|
||||||
|
h.runCmd(curl({number: +hiddenPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
|
||||||
|
]).then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/*`, () => {
|
describe(`${host}/*`, () => {
|
||||||
|
|
||||||
it('should respond with 404 for GET requests to unknown URLs', done => {
|
it('should respond with 404 for requests to unknown URLs', done => {
|
||||||
const bodyRegex = /^Unknown resource/;
|
const bodyRegex = /^Unknown resource/;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
h.runCmd(`curl -iL http://${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
|
h.runCmd(`curl -iL http://${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
h.runCmd(`curl -iL http://${host}/`).then(h.verifyResponse(404, bodyRegex)),
|
h.runCmd(`curl -iL http://${host}/`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
h.runCmd(`curl -iL http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
h.runCmd(`curl -iL http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
]).then(done);
|
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
});
|
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(404, bodyRegex)),
|
||||||
it('should respond with 405 for non-GET requests to any URL', done => {
|
|
||||||
const bodyRegex = /^Unsupported method/;
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(405, bodyRegex)),
|
|
||||||
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(405, bodyRegex)),
|
|
||||||
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(405, bodyRegex)),
|
|
||||||
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(405, bodyRegex)),
|
|
||||||
]).then(done);
|
]).then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,12 +20,14 @@
|
|||||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.17.2",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"jasmine": "^2.5.3",
|
"jasmine": "^2.5.3",
|
||||||
"jsonwebtoken": "^7.3.0",
|
"jsonwebtoken": "^7.3.0",
|
||||||
"shelljs": "^0.7.6"
|
"shelljs": "^0.7.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/body-parser": "^1.16.4",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
"@types/jasmine": "^2.5.43",
|
"@types/jasmine": "^2.5.43",
|
||||||
"@types/jsonwebtoken": "^7.2.0",
|
"@types/jsonwebtoken": "^7.2.0",
|
||||||
|
@ -43,178 +43,25 @@ describe('BuildCreator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('changePrVisibility()', () => {
|
|
||||||
let bcEmitSpy: jasmine.Spy;
|
|
||||||
let bcExistsSpy: jasmine.Spy;
|
|
||||||
let bcListShasByDate: jasmine.Spy;
|
|
||||||
let shellMvSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
bcEmitSpy = spyOn(bc, 'emit');
|
|
||||||
bcExistsSpy = spyOn(bc as any, 'exists');
|
|
||||||
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
|
|
||||||
shellMvSpy = spyOn(shell, 'mv');
|
|
||||||
|
|
||||||
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
|
||||||
bcListShasByDate.and.returnValue([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should return a promise', done => {
|
|
||||||
const promise = bc.changePrVisibility(pr, true);
|
|
||||||
promise.then(done); // Do not complete the test (and release the spies) synchronously
|
|
||||||
// to avoid running the actual `extractArchive()`.
|
|
||||||
|
|
||||||
expect(promise).toEqual(jasmine.any(Promise));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
[true, false].forEach(makePublic => {
|
|
||||||
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
|
|
||||||
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
|
|
||||||
|
|
||||||
|
|
||||||
it('should rename the directory', done => {
|
|
||||||
bc.changePrVisibility(pr, makePublic).
|
|
||||||
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
|
|
||||||
then(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should emit a ChangedPrVisibilityEvent on success', done => {
|
|
||||||
let emitted = false;
|
|
||||||
|
|
||||||
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
|
||||||
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
|
||||||
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
|
|
||||||
expect(evt.pr).toBe(+pr);
|
|
||||||
expect(evt.shas).toEqual(jasmine.any(Array));
|
|
||||||
expect(evt.isPublic).toBe(makePublic);
|
|
||||||
|
|
||||||
emitted = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
bc.changePrVisibility(pr, makePublic).
|
|
||||||
then(() => expect(emitted).toBe(true)).
|
|
||||||
then(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should include all shas in the emitted event', done => {
|
|
||||||
const shas = ['foo', 'bar', 'baz'];
|
|
||||||
let emitted = false;
|
|
||||||
|
|
||||||
bcListShasByDate.and.returnValue(Promise.resolve(shas));
|
|
||||||
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
|
||||||
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
|
|
||||||
|
|
||||||
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
|
||||||
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
|
|
||||||
expect(evt.pr).toBe(+pr);
|
|
||||||
expect(evt.shas).toBe(shas);
|
|
||||||
expect(evt.isPublic).toBe(makePublic);
|
|
||||||
|
|
||||||
emitted = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
bc.changePrVisibility(pr, makePublic).
|
|
||||||
then(() => expect(emitted).toBe(true)).
|
|
||||||
then(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('on error', () => {
|
|
||||||
|
|
||||||
it('should abort and skip further operations if the old directory does not exist', done => {
|
|
||||||
bcExistsSpy.and.callFake((dir: string) => dir !== oldPrDir);
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(err => {
|
|
||||||
expectToBeUploadError(err, 404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
|
|
||||||
expect(shellMvSpy).not.toHaveBeenCalled();
|
|
||||||
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
||||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should abort and skip further operations if the new directory does already exist', done => {
|
|
||||||
bcExistsSpy.and.returnValue(true);
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(err => {
|
|
||||||
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
|
|
||||||
expect(shellMvSpy).not.toHaveBeenCalled();
|
|
||||||
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
||||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should abort and skip further operations if it fails to rename the directory', done => {
|
|
||||||
shellMvSpy.and.throwError('');
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(() => {
|
|
||||||
expect(shellMvSpy).toHaveBeenCalled();
|
|
||||||
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
||||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should abort and skip further operations if it fails to list the SHAs', done => {
|
|
||||||
bcListShasByDate.and.throwError('');
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(() => {
|
|
||||||
expect(shellMvSpy).toHaveBeenCalled();
|
|
||||||
expect(bcListShasByDate).toHaveBeenCalled();
|
|
||||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should reject with an UploadError', done => {
|
|
||||||
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(err => {
|
|
||||||
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should pass UploadError instances unmodified', done => {
|
|
||||||
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
|
|
||||||
bc.changePrVisibility(pr, makePublic).catch(err => {
|
|
||||||
expectToBeUploadError(err, 543, 'Test');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('create()', () => {
|
describe('create()', () => {
|
||||||
let bcChangePrVisibilitySpy: jasmine.Spy;
|
|
||||||
let bcEmitSpy: jasmine.Spy;
|
let bcEmitSpy: jasmine.Spy;
|
||||||
let bcExistsSpy: jasmine.Spy;
|
let bcExistsSpy: jasmine.Spy;
|
||||||
let bcExtractArchiveSpy: jasmine.Spy;
|
let bcExtractArchiveSpy: jasmine.Spy;
|
||||||
|
let bcUpdatePrVisibilitySpy: jasmine.Spy;
|
||||||
let shellMkdirSpy: jasmine.Spy;
|
let shellMkdirSpy: jasmine.Spy;
|
||||||
let shellRmSpy: jasmine.Spy;
|
let shellRmSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
bcChangePrVisibilitySpy = spyOn(bc, 'changePrVisibility');
|
|
||||||
bcEmitSpy = spyOn(bc, 'emit');
|
bcEmitSpy = spyOn(bc, 'emit');
|
||||||
bcExistsSpy = spyOn(bc as any, 'exists');
|
bcExistsSpy = spyOn(bc as any, 'exists');
|
||||||
bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive');
|
bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive');
|
||||||
|
bcUpdatePrVisibilitySpy = spyOn(bc, 'updatePrVisibility');
|
||||||
shellMkdirSpy = spyOn(shell, 'mkdir');
|
shellMkdirSpy = spyOn(shell, 'mkdir');
|
||||||
shellRmSpy = spyOn(shell, 'rm');
|
shellRmSpy = spyOn(shell, 'rm');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
[true, false].forEach(isPublic => {
|
[true, false].forEach(isPublic => {
|
||||||
const otherVisPrDir = isPublic ? hiddenPrDir : publicPrDir;
|
|
||||||
const prDir = isPublic ? publicPrDir : hiddenPrDir;
|
const prDir = isPublic ? publicPrDir : hiddenPrDir;
|
||||||
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
|
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
|
||||||
|
|
||||||
@ -228,20 +75,12 @@ describe('BuildCreator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should not update the PR\'s visibility first if not necessary', done => {
|
|
||||||
bc.create(pr, sha, archive, isPublic).
|
|
||||||
then(() => expect(bcChangePrVisibilitySpy).not.toHaveBeenCalled()).
|
|
||||||
then(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should update the PR\'s visibility first if necessary', done => {
|
it('should update the PR\'s visibility first if necessary', done => {
|
||||||
bcChangePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
|
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
|
||||||
bcExistsSpy.and.callFake((dir: string) => dir === otherVisPrDir);
|
|
||||||
|
|
||||||
bc.create(pr, sha, archive, isPublic).
|
bc.create(pr, sha, archive, isPublic).
|
||||||
then(() => {
|
then(() => {
|
||||||
expect(bcChangePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
|
||||||
expect(shellMkdirSpy).toHaveBeenCalled();
|
expect(shellMkdirSpy).toHaveBeenCalled();
|
||||||
}).
|
}).
|
||||||
then(done);
|
then(done);
|
||||||
@ -286,7 +125,6 @@ describe('BuildCreator', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
existsValues = {
|
existsValues = {
|
||||||
[otherVisPrDir]: false,
|
|
||||||
[prDir]: false,
|
[prDir]: false,
|
||||||
[shaDir]: false,
|
[shaDir]: false,
|
||||||
};
|
};
|
||||||
@ -297,14 +135,12 @@ describe('BuildCreator', () => {
|
|||||||
|
|
||||||
it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
|
it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
|
||||||
const mockError = new UploadError(543, 'Test');
|
const mockError = new UploadError(543, 'Test');
|
||||||
|
bcUpdatePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
|
||||||
existsValues[otherVisPrDir] = true;
|
|
||||||
bcChangePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
|
|
||||||
|
|
||||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||||
expect(err).toBe(mockError);
|
expect(err).toBe(mockError);
|
||||||
|
|
||||||
expect(bcExistsSpy).toHaveBeenCalledTimes(1);
|
expect(bcExistsSpy).not.toHaveBeenCalled();
|
||||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
@ -327,8 +163,10 @@ describe('BuildCreator', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should detect existing build directory after visibility change', done => {
|
it('should detect existing build directory after visibility change', done => {
|
||||||
existsValues[otherVisPrDir] = true;
|
bcUpdatePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
|
||||||
bcChangePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
|
|
||||||
|
expect(bcExistsSpy(prDir)).toBe(false);
|
||||||
|
expect(bcExistsSpy(shaDir)).toBe(false);
|
||||||
|
|
||||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||||
@ -406,6 +244,190 @@ describe('BuildCreator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('updatePrVisibility()', () => {
|
||||||
|
let bcEmitSpy: jasmine.Spy;
|
||||||
|
let bcExistsSpy: jasmine.Spy;
|
||||||
|
let bcListShasByDate: jasmine.Spy;
|
||||||
|
let shellMvSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bcEmitSpy = spyOn(bc, 'emit');
|
||||||
|
bcExistsSpy = spyOn(bc as any, 'exists');
|
||||||
|
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
|
||||||
|
shellMvSpy = spyOn(shell, 'mv');
|
||||||
|
|
||||||
|
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
bcListShasByDate.and.returnValue([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should return a promise', done => {
|
||||||
|
const promise = bc.updatePrVisibility(pr, true);
|
||||||
|
promise.then(done); // Do not complete the test (and release the spies) synchronously
|
||||||
|
// to avoid running the actual `extractArchive()`.
|
||||||
|
|
||||||
|
expect(promise).toEqual(jasmine.any(Promise));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
[true, false].forEach(makePublic => {
|
||||||
|
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
|
||||||
|
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
|
||||||
|
|
||||||
|
|
||||||
|
it('should rename the directory', done => {
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('when the visibility is updated', () => {
|
||||||
|
|
||||||
|
it('should resolve to true', done => {
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(result => expect(result).toBe(true)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should rename the directory', done => {
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should emit a ChangedPrVisibilityEvent on success', done => {
|
||||||
|
let emitted = false;
|
||||||
|
|
||||||
|
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
||||||
|
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
||||||
|
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
|
||||||
|
expect(evt.pr).toBe(+pr);
|
||||||
|
expect(evt.shas).toEqual(jasmine.any(Array));
|
||||||
|
expect(evt.isPublic).toBe(makePublic);
|
||||||
|
|
||||||
|
emitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(() => expect(emitted).toBe(true)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should include all shas in the emitted event', done => {
|
||||||
|
const shas = ['foo', 'bar', 'baz'];
|
||||||
|
let emitted = false;
|
||||||
|
|
||||||
|
bcListShasByDate.and.returnValue(Promise.resolve(shas));
|
||||||
|
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
||||||
|
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
|
||||||
|
|
||||||
|
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
||||||
|
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
|
||||||
|
expect(evt.pr).toBe(+pr);
|
||||||
|
expect(evt.shas).toBe(shas);
|
||||||
|
expect(evt.isPublic).toBe(makePublic);
|
||||||
|
|
||||||
|
emitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(() => expect(emitted).toBe(true)).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should do nothing if the visibility is already up-to-date', done => {
|
||||||
|
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(result => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
||||||
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||||
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should do nothing if the PR directory does not exist', done => {
|
||||||
|
bcExistsSpy.and.returnValue(false);
|
||||||
|
bc.updatePrVisibility(pr, makePublic).
|
||||||
|
then(result => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
||||||
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||||
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
|
}).
|
||||||
|
then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('on error', () => {
|
||||||
|
|
||||||
|
it('should abort and skip further operations if both directories exist', done => {
|
||||||
|
bcExistsSpy.and.returnValue(true);
|
||||||
|
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||||
|
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
|
||||||
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
||||||
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||||
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should abort and skip further operations if it fails to rename the directory', done => {
|
||||||
|
shellMvSpy.and.throwError('');
|
||||||
|
bc.updatePrVisibility(pr, makePublic).catch(() => {
|
||||||
|
expect(shellMvSpy).toHaveBeenCalled();
|
||||||
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||||
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should abort and skip further operations if it fails to list the SHAs', done => {
|
||||||
|
bcListShasByDate.and.throwError('');
|
||||||
|
bc.updatePrVisibility(pr, makePublic).catch(() => {
|
||||||
|
expect(shellMvSpy).toHaveBeenCalled();
|
||||||
|
expect(bcListShasByDate).toHaveBeenCalled();
|
||||||
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should reject with an UploadError', done => {
|
||||||
|
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
||||||
|
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||||
|
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass UploadError instances unmodified', done => {
|
||||||
|
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
|
||||||
|
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||||
|
expectToBeUploadError(err, 543, 'Test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Protected methods
|
// Protected methods
|
||||||
|
|
||||||
describe('exists()', () => {
|
describe('exists()', () => {
|
||||||
|
@ -258,12 +258,12 @@ describe('uploadServerFactory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 405 for non-GET requests', done => {
|
it('should respond with 404 for non-GET requests', done => {
|
||||||
verifyRequests([
|
verifyRequests([
|
||||||
agent.put(`/create-build/${pr}/${sha}`).expect(405),
|
agent.put(`/create-build/${pr}/${sha}`).expect(404),
|
||||||
agent.post(`/create-build/${pr}/${sha}`).expect(405),
|
agent.post(`/create-build/${pr}/${sha}`).expect(404),
|
||||||
agent.patch(`/create-build/${pr}/${sha}`).expect(405),
|
agent.patch(`/create-build/${pr}/${sha}`).expect(404),
|
||||||
agent.delete(`/create-build/${pr}/${sha}`).expect(405),
|
agent.delete(`/create-build/${pr}/${sha}`).expect(404),
|
||||||
], done);
|
], done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -418,12 +418,12 @@ describe('uploadServerFactory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 405 for non-GET requests', done => {
|
it('should respond with 404 for non-GET requests', done => {
|
||||||
verifyRequests([
|
verifyRequests([
|
||||||
agent.put('/health-check').expect(405),
|
agent.put('/health-check').expect(404),
|
||||||
agent.post('/health-check').expect(405),
|
agent.post('/health-check').expect(404),
|
||||||
agent.patch('/health-check').expect(405),
|
agent.patch('/health-check').expect(404),
|
||||||
agent.delete('/health-check').expect(405),
|
agent.delete('/health-check').expect(404),
|
||||||
], done);
|
], done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -442,11 +442,141 @@ describe('uploadServerFactory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('GET *', () => {
|
describe('POST /pr-updated', () => {
|
||||||
|
const pr = '9';
|
||||||
|
const url = '/pr-updated';
|
||||||
|
let bvGetPrIsTrustedSpy: jasmine.Spy;
|
||||||
|
let bcUpdatePrVisibilitySpy: jasmine.Spy;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const createRequest = (num: number, action?: string) =>
|
||||||
|
agent.post(url).send({number: num, action});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted');
|
||||||
|
bcUpdatePrVisibilitySpy = spyOn(buildCreator, 'updatePrVisibility');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for non-POST requests', done => {
|
||||||
|
verifyRequests([
|
||||||
|
agent.get(url).expect(404),
|
||||||
|
agent.put(url).expect(404),
|
||||||
|
agent.patch(url).expect(404),
|
||||||
|
agent.delete(url).expect(404),
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 400 for requests without a payload', done => {
|
||||||
|
const responseBody = `Missing or empty 'number' field in request: POST ${url} {}`;
|
||||||
|
|
||||||
|
const request1 = agent.post(url);
|
||||||
|
const request2 = agent.post(url).send();
|
||||||
|
|
||||||
|
verifyRequests([
|
||||||
|
request1.expect(400, responseBody),
|
||||||
|
request2.expect(400, responseBody),
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 400 for requests without a \'number\' field', done => {
|
||||||
|
const responseBodyPrefix = `Missing or empty 'number' field in request: POST ${url}`;
|
||||||
|
|
||||||
|
const request1 = agent.post(url).send({});
|
||||||
|
const request2 = agent.post(url).send({number: null});
|
||||||
|
|
||||||
|
verifyRequests([
|
||||||
|
request1.expect(400, `${responseBodyPrefix} {}`),
|
||||||
|
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', done => {
|
||||||
|
const req = createRequest(+pr);
|
||||||
|
|
||||||
|
promisifyRequest(req).
|
||||||
|
then(() => expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9)).
|
||||||
|
then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should propagate errors from BuildVerifier', done => {
|
||||||
|
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
||||||
|
|
||||||
|
const req = createRequest(+pr).expect(500, 'Test');
|
||||||
|
|
||||||
|
promisifyRequest(req).
|
||||||
|
then(() => {
|
||||||
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||||
|
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
||||||
|
}).
|
||||||
|
then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', done => {
|
||||||
|
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
||||||
|
|
||||||
|
const req1 = createRequest(24);
|
||||||
|
const req2 = createRequest(42);
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
promisifyRequest(req1).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('24', false)),
|
||||||
|
promisifyRequest(req2).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('42', true)),
|
||||||
|
]).then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should propagate errors from BuildCreator', done => {
|
||||||
|
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
||||||
|
|
||||||
|
const req = createRequest(+pr).expect(500, 'Test');
|
||||||
|
verifyRequests([req], done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('on success', () => {
|
||||||
|
|
||||||
|
it('should respond with 200 (action: undefined)', done => {
|
||||||
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
|
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
||||||
|
verifyRequests(reqs, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (action: labeled)', done => {
|
||||||
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
|
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
||||||
|
verifyRequests(reqs, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (action: unlabeled)', done => {
|
||||||
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
|
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
||||||
|
verifyRequests(reqs, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', done => {
|
||||||
|
const promises = ['foo', 'notlabeled'].
|
||||||
|
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
|
||||||
|
map(promisifyRequest);
|
||||||
|
|
||||||
|
Promise.all(promises).
|
||||||
|
then(() => {
|
||||||
|
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
|
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
||||||
|
}).
|
||||||
|
then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
it('should respond with 404', done => {
|
|
||||||
const responseBody = 'Unknown resource in request: GET /some/url';
|
|
||||||
verifyRequests([agent.get('/some/url').expect(404, responseBody)], done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -454,14 +584,15 @@ describe('uploadServerFactory', () => {
|
|||||||
|
|
||||||
describe('ALL *', () => {
|
describe('ALL *', () => {
|
||||||
|
|
||||||
it('should respond with 405', done => {
|
it('should respond with 404', done => {
|
||||||
const responseFor = (method: string) => `Unsupported method in request: ${method.toUpperCase()} /some/url`;
|
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
||||||
|
|
||||||
verifyRequests([
|
verifyRequests([
|
||||||
agent.put('/some/url').expect(405, responseFor('put')),
|
agent.get('/some/url').expect(404, responseFor('get')),
|
||||||
agent.post('/some/url').expect(405, responseFor('post')),
|
agent.put('/some/url').expect(404, responseFor('put')),
|
||||||
agent.patch('/some/url').expect(405, responseFor('patch')),
|
agent.post('/some/url').expect(404, responseFor('post')),
|
||||||
agent.delete('/some/url').expect(405, responseFor('delete')),
|
agent.patch('/some/url').expect(404, responseFor('patch')),
|
||||||
|
agent.delete('/some/url').expect(404, responseFor('delete')),
|
||||||
], done);
|
], done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,13 +2,20 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/body-parser@^1.16.4":
|
||||||
|
version "1.16.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.4.tgz#96f3660e6f88a677fee7250f5a5e6d6bda3c76bb"
|
||||||
|
dependencies:
|
||||||
|
"@types/express" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/express-serve-static-core@*":
|
"@types/express-serve-static-core@*":
|
||||||
version "4.0.48"
|
version "4.0.48"
|
||||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz#b4fa06b0fce282e582b4535ff7fac85cc90173e9"
|
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz#b4fa06b0fce282e582b4535ff7fac85cc90173e9"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/express@^4.0.35":
|
"@types/express@*", "@types/express@^4.0.35":
|
||||||
version "4.0.36"
|
version "4.0.36"
|
||||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.36.tgz#14eb47de7ecb10319f0a2fb1cf971aa8680758c2"
|
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.36.tgz#14eb47de7ecb10319f0a2fb1cf971aa8680758c2"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -236,6 +243,21 @@ block-stream@*:
|
|||||||
dependencies:
|
dependencies:
|
||||||
inherits "~2.0.0"
|
inherits "~2.0.0"
|
||||||
|
|
||||||
|
body-parser@^1.17.2:
|
||||||
|
version "1.17.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee"
|
||||||
|
dependencies:
|
||||||
|
bytes "2.4.0"
|
||||||
|
content-type "~1.0.2"
|
||||||
|
debug "2.6.7"
|
||||||
|
depd "~1.1.0"
|
||||||
|
http-errors "~1.6.1"
|
||||||
|
iconv-lite "0.4.15"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.4.0"
|
||||||
|
raw-body "~2.2.0"
|
||||||
|
type-is "~1.6.15"
|
||||||
|
|
||||||
boom@2.x.x:
|
boom@2.x.x:
|
||||||
version "2.10.1"
|
version "2.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
|
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
|
||||||
@ -273,6 +295,10 @@ buffer-equal-constant-time@1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||||
|
|
||||||
|
bytes@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
|
||||||
|
|
||||||
caller-path@^0.1.0:
|
caller-path@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||||
@ -1158,6 +1184,10 @@ http-signature@~1.1.0:
|
|||||||
jsprim "^1.2.2"
|
jsprim "^1.2.2"
|
||||||
sshpk "^1.7.0"
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
|
iconv-lite@0.4.15:
|
||||||
|
version "0.4.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
|
||||||
|
|
||||||
ignore-by-default@^1.0.0:
|
ignore-by-default@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||||
@ -1958,6 +1988,14 @@ range-parser@~1.2.0:
|
|||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||||
|
|
||||||
|
raw-body@~2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
|
||||||
|
dependencies:
|
||||||
|
bytes "2.4.0"
|
||||||
|
iconv-lite "0.4.15"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
|
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
|
||||||
@ -2477,7 +2515,7 @@ unique-string@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
crypto-random-string "^1.0.0"
|
crypto-random-string "^1.0.0"
|
||||||
|
|
||||||
unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ appName=aio-upload-server-test
|
|||||||
if [[ "$1" == "stop" ]]; then
|
if [[ "$1" == "stop" ]]; then
|
||||||
pm2 delete $appName
|
pm2 delete $appName
|
||||||
else
|
else
|
||||||
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server/index-test.js \
|
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
|
||||||
--log /var/log/aio/upload-server-test.log \
|
--log /var/log/aio/upload-server-test.log \
|
||||||
--name $appName \
|
--name $appName \
|
||||||
--no-autorestart \
|
--no-autorestart \
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
|
|
||||||
TODO (gkalpak): Add docs. Mention:
|
TODO (gkalpak): Add docs. Mention:
|
||||||
- Travis' JWT addon (+ limitations).
|
- Travis' JWT addon (+ limitations).
|
||||||
Relevant files: `.travis.yml`
|
Relevant files: `.travis.yml`, `scripts/ci/env.sh`
|
||||||
- Testing on CI.
|
- Testing on CI.
|
||||||
Relevant files: `ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
||||||
- Preverifying on CI.
|
|
||||||
Relevant files: `ci/deploy.sh`, `aio/aio-builds-setup/scripts/travis-preverify-pr.sh`
|
|
||||||
- Deploying from CI.
|
- Deploying from CI.
|
||||||
Relevant files: `ci/deploy.sh`, `aio/scripts/deploy-preview.sh`
|
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-preview.sh`,
|
||||||
|
`aio/scripts/deploy-to-firebase.sh`
|
||||||
|
@ -80,13 +80,31 @@ More info on the possible HTTP status codes and their meaning can be found
|
|||||||
[here](overview--http-status-codes.md).
|
[here](overview--http-status-codes.md).
|
||||||
|
|
||||||
|
|
||||||
|
### Updating PR visibility
|
||||||
|
- nginx receives a natification that a PR has been updated and passes it through to the
|
||||||
|
upload-server. This could, for example, be sent by a GitHub webhook every time a PR's labels
|
||||||
|
change.
|
||||||
|
E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":<PR>,"action":"labeled"}`)
|
||||||
|
- The request contains the PR number (as `number`) and optionally the action that triggered the
|
||||||
|
request (as `action`) in the payload.
|
||||||
|
- The upload-server verifies the payload and determines whether the `action` (if specified) could
|
||||||
|
have led to PR visibility changes. Only requests that omit the `action` field altogether or
|
||||||
|
specify an action that can affect visibility are further processed.
|
||||||
|
(Currently, the only actions that are considered capable of affecting visibility are `labeled` and
|
||||||
|
`unlabeled`.)
|
||||||
|
- The upload-server re-checks and if necessary updates the PR's visibility.
|
||||||
|
|
||||||
|
More info on the possible HTTP status codes and their meaning can be found
|
||||||
|
[here](overview--http-status-codes.md).
|
||||||
|
|
||||||
|
|
||||||
### Serving build artifacts
|
### Serving build artifacts
|
||||||
- nginx receives a request for an uploaded resource on a subdomain corresponding to the PR and SHA.
|
- nginx receives a request for an uploaded resource on a subdomain corresponding to the PR and SHA.
|
||||||
E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource`
|
E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource`
|
||||||
- nginx maps the subdomain to the correct sub-directory and serves the resource.
|
- nginx maps the subdomain to the correct sub-directory and serves the resource.
|
||||||
E.g.: `/<PR>/<SHA>/path/to/resource`
|
E.g.: `/<PR>/<SHA>/path/to/resource`
|
||||||
|
|
||||||
Again, more info on the possible HTTP status codes and their meaning can be found
|
More info on the possible HTTP status codes and their meaning can be found
|
||||||
[here](overview--http-status-codes.md).
|
[here](overview--http-status-codes.md).
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,10 +42,6 @@ with a bried explanation of what they mean:
|
|||||||
- **403 (Forbidden)**:
|
- **403 (Forbidden)**:
|
||||||
Unable to verify build (e.g. invalid JWT token, or unable to talk to 3rd-party APIs, etc).
|
Unable to verify build (e.g. invalid JWT token, or unable to talk to 3rd-party APIs, etc).
|
||||||
|
|
||||||
- **404 (Not Found)**:
|
|
||||||
Tried to change PR visibility but the source directory did not exist.
|
|
||||||
(Currently, this can only happen as a rare race condition during build deployment.)
|
|
||||||
|
|
||||||
- **405 (Method Not Allowed)**:
|
- **405 (Method Not Allowed)**:
|
||||||
Request method other than POST.
|
Request method other than POST.
|
||||||
|
|
||||||
@ -57,6 +53,28 @@ with a bried explanation of what they mean:
|
|||||||
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
|
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
|
||||||
|
|
||||||
|
|
||||||
|
## `https://ngbuilds.io/health-check`
|
||||||
|
|
||||||
|
- **200 (OK)**:
|
||||||
|
The server is healthy (i.e. up and running and processing requests).
|
||||||
|
|
||||||
|
|
||||||
|
## `https://ngbuilds.io/pr-updated`
|
||||||
|
|
||||||
|
- **200 (OK)**:
|
||||||
|
Request processed successfully. Processing may or may not have resulted in further actions.
|
||||||
|
|
||||||
|
- **400 (Bad Request)**:
|
||||||
|
No payload or no `number` field in payload.
|
||||||
|
|
||||||
|
- **405 (Method Not Allowed)**:
|
||||||
|
Request method other than POST.
|
||||||
|
|
||||||
|
- **409 (Conflict)**:
|
||||||
|
Request to overwrite existing directory (i.e. directories for both visibilities exist).
|
||||||
|
(Normally, this should not happen.)
|
||||||
|
|
||||||
|
|
||||||
## `https://*.ngbuilds.io/*`
|
## `https://*.ngbuilds.io/*`
|
||||||
|
|
||||||
- **404 (Not Found)**:
|
- **404 (Not Found)**:
|
||||||
|
@ -16,13 +16,6 @@ available:
|
|||||||
Can be used for running the tests for `<aio-builds-setup-dir>/dockerbuild/scripts-js/`. This is
|
Can be used for running the tests for `<aio-builds-setup-dir>/dockerbuild/scripts-js/`. This is
|
||||||
useful for CI integration. See [here](misc--integrate-with-ci.md) for more info.
|
useful for CI integration. See [here](misc--integrate-with-ci.md) for more info.
|
||||||
|
|
||||||
- `travis-preverify-pr.sh`:
|
|
||||||
Can be used for "pre-verifying" a PR before uploading the artifacts to the server. It checks
|
|
||||||
whether the author of the PR is a member of one of the specified GitHub teams (therefore allowed
|
|
||||||
to upload build artifacts) or the PR has the specified "trusted PR" label (meaning it has been
|
|
||||||
manually verified by a trusted member). This is useful for CI integration.
|
|
||||||
See [here](misc--integrate-with-ci.md) for more info.
|
|
||||||
|
|
||||||
- `update-preview-server.sh`:
|
- `update-preview-server.sh`:
|
||||||
Can be used for updating the docker container (and image) based on the latest changes checked out
|
Can be used for updating the docker container (and image) based on the latest changes checked out
|
||||||
from a git repository. See [here](vm-setup--update-docker-container.md) for more info.
|
from a git repository. See [here](vm-setup--update-docker-container.md) for more info.
|
||||||
|
@ -96,7 +96,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
|
|
||||||
5. **Deploy the artifacts to the corresponding PR's directory.**
|
5. **Deploy the artifacts to the corresponding PR's directory.**
|
||||||
|
|
||||||
With the preceeding steps, we have verified that the uploaded artifacts have been uploaded by
|
With the preceding steps, we have verified that the uploaded artifacts have been uploaded by
|
||||||
Travis. Additionally, we have determined whether the PR can be trusted to have its previews
|
Travis. Additionally, we have determined whether the PR can be trusted to have its previews
|
||||||
publicly accessible or whether further verification is necessary. The artifacts will be stored to
|
publicly accessible or whether further verification is necessary. The artifacts will be stored to
|
||||||
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -eux -o pipefail
|
|
||||||
|
|
||||||
# Set up env
|
|
||||||
source "`dirname $0`/_env.sh"
|
|
||||||
|
|
||||||
# Build `scripts-js/`
|
|
||||||
(
|
|
||||||
cd "$SCRIPTS_JS_DIR"
|
|
||||||
yarn install
|
|
||||||
yarn build
|
|
||||||
)
|
|
||||||
|
|
||||||
# Preverify PR
|
|
||||||
AIO_GITHUB_ORGANIZATION="angular" \
|
|
||||||
AIO_GITHUB_TEAM_SLUGS="angular-core,aio-contributors" \
|
|
||||||
AIO_GITHUB_TOKEN=$(echo ${GITHUB_TEAM_MEMBERSHIP_CHECK_KEY} | rev) \
|
|
||||||
AIO_REPO_SLUG=$TRAVIS_REPO_SLUG \
|
|
||||||
AIO_TRUSTED_PR_LABEL="aio: preview" \
|
|
||||||
AIO_PREVERIFY_PR=$TRAVIS_PULL_REQUEST \
|
|
||||||
node "$SCRIPTS_JS_DIR/dist/lib/upload-server/index-preverify-pr"
|
|
||||||
|
|
||||||
# Exit codes:
|
|
||||||
# - 0: The PR can be automatically trusted (i.e. author belongs to trusted team or PR has the "trusted PR" label).
|
|
||||||
# - 1: An error occurred.
|
|
||||||
# - 2: The PR cannot be automatically trusted.
|
|
@ -14,13 +14,13 @@
|
|||||||
<h1>Example Snippets</h1>
|
<h1>Example Snippets</h1>
|
||||||
|
|
||||||
<!-- #docregion ngClass -->
|
<!-- #docregion ngClass -->
|
||||||
<div [ngClass]="{active: isActive}">
|
<div [ngClass]="{'active': isActive}">
|
||||||
<!-- #enddocregion ngClass -->
|
<!-- #enddocregion ngClass -->
|
||||||
[ngClass] active
|
[ngClass] active
|
||||||
</div>
|
</div>
|
||||||
<!-- #docregion ngClass -->
|
<!-- #docregion ngClass -->
|
||||||
<div [ngClass]="{active: isActive,
|
<div [ngClass]="{'active': isActive,
|
||||||
shazam: isImportant}">
|
'shazam': isImportant}">
|
||||||
<!-- #enddocregion ngClass -->
|
<!-- #enddocregion ngClass -->
|
||||||
[ngClass] active and boldly important
|
[ngClass] active and boldly important
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<p></p>
|
<p></p>
|
||||||
<!-- #docregion ngStyle -->
|
<!-- #docregion ngStyle -->
|
||||||
<div [ngStyle]="{color: colorPreference}">
|
<div [ngStyle]="{'color': colorPreference}">
|
||||||
<!-- #enddocregion ngStyle -->
|
<!-- #enddocregion ngStyle -->
|
||||||
color preference #1
|
color preference #1
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@ import { Hero } from './hero';
|
|||||||
const HEROES = [
|
const HEROES = [
|
||||||
new Hero('Windstorm', 'Weather mastery'),
|
new Hero('Windstorm', 'Weather mastery'),
|
||||||
new Hero('Mr. Nice', 'Killing them with kindness'),
|
new Hero('Mr. Nice', 'Killing them with kindness'),
|
||||||
new Hero('Magneta', 'Manipulates metalic objects')
|
new Hero('Magneta', 'Manipulates metallic objects')
|
||||||
];
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -9,6 +9,6 @@ describe('cli-quickstart App', () => {
|
|||||||
|
|
||||||
it('should display message saying app works', () => {
|
it('should display message saying app works', () => {
|
||||||
let pageTitle = element(by.css('app-root h1')).getText();
|
let pageTitle = element(by.css('app-root h1')).getText();
|
||||||
expect(pageTitle).toEqual('My First Angular App');
|
expect(pageTitle).toEqual('Welcome to My First Angular App!!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,6 @@ describe('my-app App', function() {
|
|||||||
|
|
||||||
it('should display message saying app works', () => {
|
it('should display message saying app works', () => {
|
||||||
page.navigateTo();
|
page.navigateTo();
|
||||||
expect(page.getParagraphText()).toEqual('app works!');
|
expect(page.getParagraphText()).toEqual('Welcome to app!!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true,
|
"outDir": "../out-tsc/e2e",
|
||||||
"declaration": false,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"lib": [
|
|
||||||
"es2016"
|
|
||||||
],
|
|
||||||
"outDir": "../dist/out-tsc-e2e",
|
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es5",
|
||||||
"types":[
|
"types": [
|
||||||
"jasmine",
|
"jasmine",
|
||||||
|
"jasminewd2",
|
||||||
"node"
|
"node"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
<h1>
|
<!--The content below is only a placeholder and can be replaced.-->
|
||||||
{{title}}
|
<div style="text-align:center">
|
||||||
</h1>
|
<h1>
|
||||||
|
Welcome to {{title}}!!
|
||||||
|
</h1>
|
||||||
|
<img width="300" alt="Angular logo" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo=">
|
||||||
|
</div>
|
||||||
|
<h2>Here are some links to help you start: </h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<h2><a target="_blank" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2><a target="_blank" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2><a target="_blank" href="http://angularjs.blogspot.ca/">Angular blog</a></h2>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
@ -17,16 +17,16 @@ describe('AppComponent', () => {
|
|||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it(`should have as title 'app works!'`, async(() => {
|
it(`should have as title 'app'`, async(() => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.debugElement.componentInstance;
|
const app = fixture.debugElement.componentInstance;
|
||||||
expect(app.title).toEqual('app works!');
|
expect(app.title).toEqual('app');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should render title in a h1 tag', async(() => {
|
it('should render title in a h1 tag', async(() => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
const compiled = fixture.debugElement.nativeElement;
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('app works!');
|
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!');
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { HttpModule } from '@angular/http';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
@ -10,9 +8,7 @@ import { AppComponent } from './app.component';
|
|||||||
AppComponent
|
AppComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule
|
||||||
FormsModule,
|
|
||||||
HttpModule
|
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>MyApp</title>
|
<title>MyApp</title>
|
||||||
@ -9,6 +9,6 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root>Loading...</app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -31,21 +31,21 @@
|
|||||||
// import 'core-js/es6/array';
|
// import 'core-js/es6/array';
|
||||||
// import 'core-js/es6/regexp';
|
// import 'core-js/es6/regexp';
|
||||||
// import 'core-js/es6/map';
|
// import 'core-js/es6/map';
|
||||||
|
// import 'core-js/es6/weak-map';
|
||||||
// import 'core-js/es6/set';
|
// import 'core-js/es6/set';
|
||||||
|
|
||||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||||
|
|
||||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
|
||||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
|
||||||
|
|
||||||
|
|
||||||
/** Evergreen browsers require these. **/
|
/** Evergreen browsers require these. **/
|
||||||
import 'core-js/es6/reflect';
|
import 'core-js/es6/reflect';
|
||||||
import 'core-js/es7/reflect';
|
import 'core-js/es7/reflect';
|
||||||
|
|
||||||
|
|
||||||
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
|
/**
|
||||||
|
* Required to support Web Animations `@angular/animation`.
|
||||||
|
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
|
||||||
|
**/
|
||||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||||
|
|
||||||
|
|
||||||
@ -66,3 +66,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||||
*/
|
*/
|
||||||
// import 'intl'; // Run `npm install --save intl`.
|
// import 'intl'; // Run `npm install --save intl`.
|
||||||
|
/**
|
||||||
|
* Need to import at least one locale-data with intl.
|
||||||
|
*/
|
||||||
|
// import 'intl/locale-data/jsonp/en';
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"lib": [
|
|
||||||
"es2016",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"outDir": "../out-tsc/app",
|
"outDir": "../out-tsc/app",
|
||||||
"target": "es5",
|
|
||||||
"module": "es2015",
|
"module": "es2015",
|
||||||
"baseUrl": "",
|
"baseUrl": "",
|
||||||
"types": []
|
"types": []
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"lib": [
|
|
||||||
"es2016"
|
|
||||||
],
|
|
||||||
"outDir": "../out-tsc/spec",
|
"outDir": "../out-tsc/spec",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es5",
|
||||||
"baseUrl": "",
|
"baseUrl": "",
|
||||||
"types": [
|
"types": [
|
||||||
"jasmine",
|
"jasmine",
|
||||||
@ -21,6 +14,7 @@
|
|||||||
"test.ts"
|
"test.ts"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.spec.ts"
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
5
aio/content/examples/cli-quickstart/src/typings.d.ts
vendored
Normal file
5
aio/content/examples/cli-quickstart/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* SystemJS module definition */
|
||||||
|
declare var module: NodeModule;
|
||||||
|
interface NodeModule {
|
||||||
|
id: string;
|
||||||
|
}
|
@ -2,13 +2,19 @@
|
|||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
|
"baseUrl": "src",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"target": "es5",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2016"
|
"es2016",
|
||||||
|
"dom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ if (!/e2e/.test(location.search)) {
|
|||||||
directives.push(CountdownLocalVarParentComponent);
|
directives.push(CountdownLocalVarParentComponent);
|
||||||
directives.push(CountdownViewChildParentComponent);
|
directives.push(CountdownViewChildParentComponent);
|
||||||
} else {
|
} else {
|
||||||
// In e2e test use CUSTOM_ELEMENTS_SCHEMA to supress unknown element errors
|
// In e2e test use CUSTOM_ELEMENTS_SCHEMA to suppress unknown element errors
|
||||||
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"description": "Hierachical Dependency Injection",
|
"description": "Hierarchical Dependency Injection",
|
||||||
"basePath": "src/",
|
"basePath": "src/",
|
||||||
"files":[
|
"files":[
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
|
@ -12,7 +12,7 @@ import { UserService } from './user.service';
|
|||||||
})
|
})
|
||||||
export class TitleComponent {
|
export class TitleComponent {
|
||||||
@Input() subtitle = '';
|
@Input() subtitle = '';
|
||||||
title = 'Angular Modules';
|
title = 'NgModules';
|
||||||
// #enddocregion v1
|
// #enddocregion v1
|
||||||
user = '';
|
user = '';
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ describe('Pipes', function () {
|
|||||||
return resetEle.click();
|
return resetEle.click();
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
expect(flyingHeroesEle.count()).toEqual(2, 'reset should restore orginal flying heroes');
|
expect(flyingHeroesEle.count()).toEqual(2, 'reset should restore original flying heroes');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
* Usage:
|
* Usage:
|
||||||
* value | exponentialStrength:exponent
|
* value | exponentialStrength:exponent
|
||||||
* Example:
|
* Example:
|
||||||
* {{ 2 | exponentialStrength:10}}
|
* {{ 2 | exponentialStrength:10 }}
|
||||||
* formats to: 1024
|
* formats to: 1024
|
||||||
*/
|
*/
|
||||||
@Pipe({name: 'exponentialStrength'})
|
@Pipe({name: 'exponentialStrength'})
|
||||||
|
@ -16,7 +16,7 @@ import { HeroService } from './hero.service'; // <-- #1 import service
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
ReactiveFormsModule // <-- #2 add to Angular module imports
|
ReactiveFormsModule // <-- #2 add to @NgModule imports
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -213,10 +213,10 @@
|
|||||||
<h2 id="myUnless">UnlessDirective</h2>
|
<h2 id="myUnless">UnlessDirective</h2>
|
||||||
<p>
|
<p>
|
||||||
The condition is currently
|
The condition is currently
|
||||||
<span [ngClass]="{ a: !condition, b: condition, unless: true }">{{condition}}</span>.
|
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>.
|
||||||
<button
|
<button
|
||||||
(click)="condition = !condition"
|
(click)="condition = !condition"
|
||||||
[ngClass] = "{ a: condition, b: !condition }" >
|
[ngClass] = "{ 'a': condition, 'b': !condition }" >
|
||||||
Toggle condition to {{condition ? 'false' : 'true'}}
|
Toggle condition to {{condition ? 'false' : 'true'}}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
@ -4,7 +4,7 @@ import { Directive } from '@angular/core';
|
|||||||
@Directive({
|
@Directive({
|
||||||
selector: '[tohValidator2]',
|
selector: '[tohValidator2]',
|
||||||
host: {
|
host: {
|
||||||
'attr.role': 'button',
|
'[attr.role]': 'role',
|
||||||
'(mouseenter)': 'onMouseEnter()'
|
'(mouseenter)': 'onMouseEnter()'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -161,7 +161,7 @@
|
|||||||
<!-- #docregion property-binding-syntax-1 -->
|
<!-- #docregion property-binding-syntax-1 -->
|
||||||
<img [src]="heroImageUrl">
|
<img [src]="heroImageUrl">
|
||||||
<hero-detail [hero]="currentHero"></hero-detail>
|
<hero-detail [hero]="currentHero"></hero-detail>
|
||||||
<div [ngClass]="{special: isSpecial}"></div>
|
<div [ngClass]="{'special': isSpecial}"></div>
|
||||||
<!-- #enddocregion property-binding-syntax-1 -->
|
<!-- #enddocregion property-binding-syntax-1 -->
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -496,7 +496,7 @@ bindon-ngModel
|
|||||||
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
|
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
|
||||||
|
|
||||||
<div class="bad curly special">Bad curly special</div>
|
<div class="bad curly special">Bad curly special</div>
|
||||||
<div [ngClass]="{bad:false, curly:true, special:true}">Curly special</div>
|
<div [ngClass]="{'bad':false, 'curly':true, 'special':true}">Curly special</div>
|
||||||
|
|
||||||
<a class="to-toc" href="#toc">top</a>
|
<a class="to-toc" href="#toc">top</a>
|
||||||
|
|
||||||
|
@ -140,9 +140,9 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
setCurrentClasses() {
|
setCurrentClasses() {
|
||||||
// CSS classes: added/removed per current state of component properties
|
// CSS classes: added/removed per current state of component properties
|
||||||
this.currentClasses = {
|
this.currentClasses = {
|
||||||
saveable: this.canSave,
|
'saveable': this.canSave,
|
||||||
modified: !this.isUnchanged,
|
'modified': !this.isUnchanged,
|
||||||
special: this.isSpecial
|
'special': this.isSpecial
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// #enddocregion setClasses
|
// #enddocregion setClasses
|
||||||
|
@ -273,7 +273,7 @@ export class InnerCompWithExternalTemplateComponent { }
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'bad-template-comp',
|
selector: 'bad-template-comp',
|
||||||
templateUrl: './non-existant.html'
|
templateUrl: './non-existent.html'
|
||||||
})
|
})
|
||||||
export class BadTemplateUrlComponent { }
|
export class BadTemplateUrlComponent { }
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ function heroModuleSetup() {
|
|||||||
// #enddocregion route-no-id
|
// #enddocregion route-no-id
|
||||||
|
|
||||||
// #docregion route-bad-id
|
// #docregion route-bad-id
|
||||||
describe('when navigate to non-existant hero id', () => {
|
describe('when navigate to non-existent hero id', () => {
|
||||||
beforeEach( async(() => {
|
beforeEach( async(() => {
|
||||||
activatedRoute.testParamMap = { id: 99999 };
|
activatedRoute.testParamMap = { id: 99999 };
|
||||||
createComponent();
|
createComponent();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="phone">
|
<div *ngIf="phone">
|
||||||
<div class="phone-images">
|
<div class="phone-images">
|
||||||
<img [src]="img" class="phone"
|
<img [src]="img" class="phone"
|
||||||
[ngClass]="{selected: img === mainImageUrl}"
|
[ngClass]="{'selected': img === mainImageUrl}"
|
||||||
*ngFor="let img of phone.images" />
|
*ngFor="let img of phone.images" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="phone">
|
<div *ngIf="phone">
|
||||||
<div class="phone-images">
|
<div class="phone-images">
|
||||||
<img [src]="img" class="phone"
|
<img [src]="img" class="phone"
|
||||||
[ngClass]="{selected: img === mainImageUrl}"
|
[ngClass]="{'selected': img === mainImageUrl}"
|
||||||
*ngFor="let img of phone.images" />
|
*ngFor="let img of phone.images" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1000,7 +1000,7 @@ For more information on pipes, see [Pipes](guide/pipes).
|
|||||||
|
|
||||||
|
|
||||||
## Modules/controllers/components
|
## Modules/controllers/components
|
||||||
In both AngularJS and Angular, Angular modules help you organize your application into cohesive blocks of functionality.
|
In both AngularJS and Angular, modules help you organize your application into cohesive blocks of functionality.
|
||||||
|
|
||||||
In AngularJS, you write the code that provides the model and the methods for the view in a **controller**.
|
In AngularJS, you write the code that provides the model and the methods for the view in a **controller**.
|
||||||
In Angular, you build a **component**.
|
In Angular, you build a **component**.
|
||||||
@ -1080,18 +1080,18 @@ The Angular code is shown using TypeScript.
|
|||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
|
||||||
### Angular modules
|
### NgModules
|
||||||
<code-example hideCopy path="ajs-quick-reference/src/app/app.module.1.ts" linenums="false">
|
<code-example hideCopy path="ajs-quick-reference/src/app/app.module.1.ts" linenums="false">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
Angular modules, defined with the `NgModule` decorator, serve the same purpose:
|
NgModules, defined with the `NgModule` decorator, serve the same purpose:
|
||||||
|
|
||||||
* `imports`: specifies the list of other modules that this module depends upon
|
* `imports`: specifies the list of other modules that this module depends upon
|
||||||
* `declaration`: keeps track of your components, pipes, and directives.
|
* `declaration`: keeps track of your components, pipes, and directives.
|
||||||
|
|
||||||
For more information on modules, see [Angular Modules (NgModule)](guide/ngmodule).
|
For more information on modules, see [NgModules](guide/ngmodule).
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -475,7 +475,7 @@ You'll need separate TypeScript configuration files such as these:
|
|||||||
<div class="callout is-helpful">
|
<div class="callout is-helpful">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
@Types and node modules
|
`@types` and node modules
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
In the file structure of _this particular sample project_,
|
In the file structure of _this particular sample project_,
|
||||||
@ -568,7 +568,7 @@ Run the following command to generate the map.
|
|||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
|
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
|
||||||
showing exactly which application and Angular modules and classes are included in the bundle.
|
showing exactly which application and NgModules and classes are included in the bundle.
|
||||||
|
|
||||||
Here's the map for _Tour of Heroes_.
|
Here's the map for _Tour of Heroes_.
|
||||||
|
|
||||||
|
@ -31,21 +31,21 @@ You'll learn the details in the pages that follow. For now, focus on the big pic
|
|||||||
<img src="generated/images/guide/architecture/module.png" alt="Component" class="left">
|
<img src="generated/images/guide/architecture/module.png" alt="Component" class="left">
|
||||||
|
|
||||||
|
|
||||||
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
|
Angular apps are modular and Angular has its own modularity system called _NgModules_.
|
||||||
|
|
||||||
_Angular modules_ are a big deal.
|
NgModules are a big deal.
|
||||||
This page introduces modules; the [Angular modules](guide/ngmodule) page covers them in depth.
|
This page introduces modules; the [NgModules](guide/ngmodule) page covers them in depth.
|
||||||
|
|
||||||
<br class="clear">
|
<br class="clear">
|
||||||
|
|
||||||
Every Angular app has at least one Angular module class, [the _root module_](guide/bootstrapping "AppModule: the root module"),
|
Every Angular app has at least one NgModule class, [the _root module_](guide/bootstrapping "Bootstrapping"),
|
||||||
conventionally named `AppModule`.
|
conventionally named `AppModule`.
|
||||||
|
|
||||||
While the _root module_ may be the only module in a small application, most apps have many more
|
While the _root module_ may be the only module in a small application, most apps have many more
|
||||||
_feature modules_, each a cohesive block of code dedicated to an application domain,
|
_feature modules_, each a cohesive block of code dedicated to an application domain,
|
||||||
a workflow, or a closely related set of capabilities.
|
a workflow, or a closely related set of capabilities.
|
||||||
|
|
||||||
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
|
An NgModule, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
@ -87,12 +87,12 @@ During development you're likely to bootstrap the `AppModule` in a `main.ts` fil
|
|||||||
|
|
||||||
<code-example path="architecture/src/main.ts" title="src/main.ts" linenums="false"></code-example>
|
<code-example path="architecture/src/main.ts" title="src/main.ts" linenums="false"></code-example>
|
||||||
|
|
||||||
### Angular modules vs. JavaScript modules
|
### NgModules vs. JavaScript modules
|
||||||
|
|
||||||
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
The NgModule — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
||||||
|
|
||||||
JavaScript also has its own module system for managing collections of JavaScript objects.
|
JavaScript also has its own module system for managing collections of JavaScript objects.
|
||||||
It's completely different and unrelated to the Angular module system.
|
It's completely different and unrelated to the NgModule system.
|
||||||
|
|
||||||
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
||||||
The module declares some objects to be public by marking them with the `export` key word.
|
The module declares some objects to be public by marking them with the `export` key word.
|
||||||
@ -124,7 +124,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
|||||||
|
|
||||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||||
|
|
||||||
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
You also import NgModules_ from Angular _libraries_ using JavaScript import statements:
|
||||||
|
|
||||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ Hang in there. The confusion yields to clarity with time and experience.
|
|||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
Learn more from the [Angular modules](guide/ngmodule) page.
|
Learn more from the [NgModules](guide/ngmodule) page.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Bootstrapping
|
# Bootstrapping
|
||||||
|
|
||||||
An Angular module class describes how the application parts fit together.
|
An NgModule class describes how the application parts fit together.
|
||||||
Every application has at least one Angular module, the _root_ module
|
Every application has at least one NgModule, the _root_ module
|
||||||
that you [bootstrap](guide/bootstrapping#main) to launch the application.
|
that you [bootstrap](#main) to launch the application.
|
||||||
You can call it anything you want. The conventional name is `AppModule`.
|
You can call it anything you want. The conventional name is `AppModule`.
|
||||||
|
|
||||||
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
||||||
@ -17,14 +17,14 @@ You'll evolve this module as your application grows.
|
|||||||
After the `import` statements, you come to a class adorned with the
|
After the `import` statements, you come to a class adorned with the
|
||||||
**`@NgModule`** [_decorator_](guide/glossary#decorator '"Decorator" explained').
|
**`@NgModule`** [_decorator_](guide/glossary#decorator '"Decorator" explained').
|
||||||
|
|
||||||
The `@NgModule` decorator identifies `AppModule` as an Angular module class (also called an `NgModule` class).
|
The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
|
||||||
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
|
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
|
||||||
|
|
||||||
* **_imports_** — the `BrowserModule` that this and every application needs to run in a browser.
|
* **_imports_** — the `BrowserModule` that this and every application needs to run in a browser.
|
||||||
* **_declarations_** — the application's lone component, which is also ...
|
* **_declarations_** — the application's lone component, which is also ...
|
||||||
* **_bootstrap_** — the _root_ component that Angular creates and inserts into the `index.html` host web page.
|
* **_bootstrap_** — the _root_ component that Angular creates and inserts into the `index.html` host web page.
|
||||||
|
|
||||||
The [Angular Modules (NgModule)](guide/ngmodule) guide dives deeply into the details of Angular modules.
|
The [NgModules](guide/ngmodule) guide dives deeply into the details of NgModules.
|
||||||
All you need to know at the moment is a few basics about these three properties.
|
All you need to know at the moment is a few basics about these three properties.
|
||||||
|
|
||||||
|
|
||||||
@ -33,8 +33,8 @@ All you need to know at the moment is a few basics about these three properties.
|
|||||||
|
|
||||||
### The _imports_ array
|
### The _imports_ array
|
||||||
|
|
||||||
Angular modules are a way to consolidate features that belong together into discrete units.
|
NgModules are a way to consolidate features that belong together into discrete units.
|
||||||
Many features of Angular itself are organized as Angular modules.
|
Many features of Angular itself are organized as NgModules.
|
||||||
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
|
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
|
||||||
Eventually you may create a feature module.
|
Eventually you may create a feature module.
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ Other guide and cookbook pages will tell you when you need to add additional mod
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
The `import` statements at the top of the file and the Angular module's `imports` array
|
The `import` statements at the top of the file and the NgModule's `imports` array
|
||||||
are unrelated and have completely different jobs.
|
are unrelated and have completely different jobs.
|
||||||
|
|
||||||
The _JavaScript_ `import` statements give you access to symbols _exported_ by other files
|
The _JavaScript_ `import` statements give you access to symbols _exported_ by other files
|
||||||
@ -70,8 +70,8 @@ You add `import` statements to almost every application file.
|
|||||||
They have nothing to do with Angular and Angular knows nothing about them.
|
They have nothing to do with Angular and Angular knows nothing about them.
|
||||||
|
|
||||||
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
|
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
|
||||||
It tells Angular about specific _other_ Angular modules — all of them classes decorated with `@NgModule` —
|
It tells Angular about specific _other_ NgModules—all of them classes decorated
|
||||||
that the application needs to function properly.
|
with `@NgModule`—that the application needs to function properly.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
|
|||||||
|
|
||||||
### The _bootstrap_ array
|
### The _bootstrap_ array
|
||||||
|
|
||||||
You launch the application by [_bootstrapping_](guide/bootstrapping#main) the root `AppModule`.
|
You launch the application by [_bootstrapping_](#main) the root `AppModule`.
|
||||||
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
||||||
and inserts each one into the browser DOM.
|
and inserts each one into the browser DOM.
|
||||||
|
|
||||||
@ -127,13 +127,6 @@ Which brings us to the _bootstrapping_ process itself.
|
|||||||
|
|
||||||
{@a main}
|
{@a main}
|
||||||
|
|
||||||
|
|
||||||
<l-main-section>
|
|
||||||
|
|
||||||
</l-main-section>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Bootstrap in _main.ts_
|
## Bootstrap in _main.ts_
|
||||||
|
|
||||||
There are many ways to bootstrap an application.
|
There are many ways to bootstrap an application.
|
||||||
@ -178,11 +171,11 @@ This file is very stable. Once you've set it up, you may never change it again.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## More about Angular Modules
|
## More about NgModules
|
||||||
|
|
||||||
Your initial app has only a single module, the _root_ module.
|
Your initial app has only a single module, the _root_ module.
|
||||||
As your app grows, you'll consider subdividing it into multiple "feature" modules,
|
As your app grows, you'll consider subdividing it into multiple "feature" modules,
|
||||||
some of which can be loaded later ("lazy loaded") if and when the user chooses
|
some of which can be loaded later ("lazy loaded") if and when the user chooses
|
||||||
to visit those features.
|
to visit those features.
|
||||||
|
|
||||||
When you're ready to explore these possibilities, visit the [Angular Modules (NgModule)](guide/ngmodule) guide.
|
When you're ready to explore these possibilities, visit the [NgModules](guide/ngmodule) guide.
|
||||||
|
@ -79,7 +79,7 @@ including sections named outlets, wildcard routes, and preload strategies.
|
|||||||
## HTTP: how to set default request headers (and other request options) (2016-12-14)
|
## HTTP: how to set default request headers (and other request options) (2016-12-14)
|
||||||
|
|
||||||
Added section on how to set default request headers (and other request options) to
|
Added section on how to set default request headers (and other request options) to
|
||||||
[HTTP](guide/http#override-default-request-options) guide.
|
HTTP guide.
|
||||||
|
|
||||||
## Testing: added component test plunkers (2016-12-02)
|
## Testing: added component test plunkers (2016-12-02)
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ The new "angular-in-memory-web-api" has new features.
|
|||||||
|
|
||||||
## "Style Guide" with _NgModules_ (2016-09-27)
|
## "Style Guide" with _NgModules_ (2016-09-27)
|
||||||
|
|
||||||
[StyleGuide](guide/styleguide) explains recommended conventions for Angular modules (NgModule).
|
[StyleGuide](guide/styleguide) explains recommended conventions for NgModules.
|
||||||
Barrels now are far less useful and have been removed from the style guide;
|
Barrels now are far less useful and have been removed from the style guide;
|
||||||
they remain valuable but are not a matter of Angular style.
|
they remain valuable but are not a matter of Angular style.
|
||||||
Also relaxed the rule that discouraged use of the `@Component.host` property.
|
Also relaxed the rule that discouraged use of the `@Component.host` property.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Cheat Sheet
|
<h1 class="no-toc">Cheat Sheet</h1>
|
||||||
|
|
||||||
<div id="cheatsheet">
|
<div id="cheatsheet">
|
||||||
<table class="is-full-width is-fixed-layout">
|
<table class="is-full-width is-fixed-layout">
|
||||||
@ -23,28 +23,28 @@
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||||
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
|
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||||
<td><p>List of components, directives, and pipes that belong to this module.</p>
|
<td><p>List of components, directives, and pipes that belong to this module.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||||
<td><p>List of modules to import into this module. Everything from the imported modules
|
<td><p>List of modules to import into this module. Everything from the imported modules
|
||||||
is available to <code>declarations</code> of this module.</p>
|
is available to <code>declarations</code> of this module.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||||
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
|
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||||
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
|
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||||
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
|
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -56,61 +56,61 @@ is available to <code>declarations</code> of this module.</p>
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||||
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
|
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||||
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
|
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||||
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
|
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||||
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
|
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||||
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
|
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||||
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
|
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
|
||||||
<code><div [title]="'Hello ' + ponyName"></code></p>
|
<code><div [title]="'Hello ' + ponyName"></code></p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||||
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
|
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||||
<td><p>Sets up two-way data binding. Equivalent to: <code><my-cmp [title]="name" (titleChange)="name=$event"></code></p>
|
<td><p>Sets up two-way data binding. Equivalent to: <code><my-cmp [title]="name" (titleChange)="name=$event"></code></p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||||
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
|
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||||
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
|
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
|
||||||
<code><ng-template [myUnless]="myExpression"><p>...</p></ng-template></code></p>
|
<code><ng-template [myUnless]="myExpression"><p>...</p></ng-template></code></p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||||
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
|
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||||
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
|
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||||
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
|
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||||
<td><p>An <code><svg></code> root element is detected as an SVG element automatically, without the prefix.</p>
|
<td><p>An <code><svg></code> root element is detected as an SVG element automatically, without the prefix.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -124,19 +124,19 @@ is available to <code>declarations</code> of this module.</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||||
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
|
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||||
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
|
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
||||||
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
|
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><div <b>[ngClass]</b>="{active: isActive, disabled: isDisabled}"></code></td>
|
<td><code><div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"></code></td>
|
||||||
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
|
<td><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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -150,7 +150,7 @@ is available to <code>declarations</code> of this module.</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||||
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
|
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -164,19 +164,19 @@ is available to <code>declarations</code> of this module.</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||||
<td><p>Declares that a class is a component and provides metadata about the component.</p>
|
<td><p>Declares that a class is a component and provides metadata about the component.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||||
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
|
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||||
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
|
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||||
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
@ -191,13 +191,13 @@ is available to <code>declarations</code> of this module.</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||||
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
|
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
|
||||||
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
|
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
|
||||||
<p>Does not support parent-child relationship selectors.</p>
|
<p>Does not support parent-child relationship selectors.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||||
<td><p>List of dependency injection providers for this directive and its children.</p>
|
<td><p>List of dependency injection providers for this directive and its children.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -212,19 +212,19 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><b>moduleId:</b> module.id</code></td>
|
<td><code><b>moduleId:</b> module.id</code></td>
|
||||||
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
|
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||||
<td><p>List of dependency injection providers scoped to this component's view.</p>
|
<td><p>List of dependency injection providers scoped to this component's view.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||||
<td><p>Inline template or external template URL of the component's view.</p>
|
<td><p>Inline template or external template URL of the component's view.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||||
<td><p>List of inline CSS styles or external stylesheet URLs for styling the component’s view.</p>
|
<td><p>List of inline CSS styles or external stylesheet URLs for styling the component’s view.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -238,36 +238,36 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||||
<td><p>Declares an input property that you can update via property binding (example:
|
<td><p>Declares an input property that you can update via property binding (example:
|
||||||
<code><my-cmp [myProperty]="someExpression"></code>).</p>
|
<code><my-cmp [myProperty]="someExpression"></code>).</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||||
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code><my-cmp (myEvent)="doSomething()"></code>).</p>
|
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code><my-cmp (myEvent)="doSomething()"></code>).</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||||
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
|
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||||
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
|
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||||
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
|
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||||
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
|
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||||
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
|
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||||
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
|
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -281,39 +281,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||||
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
|
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||||
<td><p>Called after every change to input properties and before processing content or child views.</p>
|
<td><p>Called after every change to input properties and before processing content or child views.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||||
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
|
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||||
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
|
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||||
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
|
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||||
<td><p>Called after every check of the component's or directive's content.</p>
|
<td><p>Called after every check of the component's or directive's content.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||||
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
|
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||||
<td><p>Called after every check of the component's view. Applies to components only.</p>
|
<td><p>Called after every check of the component's view. Applies to components only.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||||
<td><p>Called once, before the instance is destroyed.</p>
|
<td><p>Called once, before the instance is destroyed.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -325,15 +325,15 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
|
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
|
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||||
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
|
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -347,39 +347,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||||
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
|
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||||
<td><p>Marks the location to load the component of the active route.</p>
|
<td><p>Marks the location to load the component of the active route.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||||
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
|
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||||
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
|
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||||
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||||
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
|
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||||
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -119,12 +119,13 @@ if some ancestor element has the CSS class `theme-light`.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### /deep/
|
### (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/` selector to force a style down through the child component tree into all the child component views.
|
Use the `/deep/` shadow-piercing descendant combinator to force a style down through the child
|
||||||
The `/deep/` selector works to any depth of nested components, and it applies to both the view
|
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
|
The following example targets all `<h3>` elements, from the host element down
|
||||||
@ -134,17 +135,24 @@ through this component to all of its child elements in the DOM.
|
|||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
|
The `/deep/` combinator also has the aliases `>>>`, and `::ng-deep`.
|
||||||
|
|
||||||
|
|
||||||
<div class="alert is-important">
|
<div class="alert is-important">
|
||||||
|
|
||||||
Use the `/deep/` and `>>>` selectors only with *emulated* view encapsulation.
|
Use `/deep/`, `>>>` and `::ng-deep` only with *emulated* view encapsulation.
|
||||||
Emulated is the default and most commonly used view encapsulation. For more information, see the
|
Emulated is the default and most commonly used view encapsulation. For more information, see the
|
||||||
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
|
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert is-important">
|
||||||
|
|
||||||
|
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/features/6750456638341120) and tools.
|
||||||
|
As such we plan to drop support in Angular (for all 3 of `/deep/`, `>>>` and `::ng-deep`).
|
||||||
|
Until then `::ng-deep` should be preferred for a broader compatibility with the tools.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{@a loading-styles}
|
{@a loading-styles}
|
||||||
|
|
||||||
## Loading component styles
|
## Loading component styles
|
||||||
|
@ -244,7 +244,7 @@ You can limit the scope of an injected service to a *branch* of the application
|
|||||||
by providing that service *at the sub-root component for that branch*.
|
by providing that service *at the sub-root component for that branch*.
|
||||||
This example shows how similar providing a service to a sub-root component is
|
This example shows how similar providing a service to a sub-root component is
|
||||||
to providing a service in the root `AppComponent`. The syntax is the same.
|
to providing a service in the root `AppComponent`. The syntax is the same.
|
||||||
Here, the `HeroService` is availble to the `HeroesBaseComponent` because it is in the `providers` array:
|
Here, the `HeroService` is available to the `HeroesBaseComponent` because it is in the `providers` array:
|
||||||
|
|
||||||
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="injection" title="src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)">
|
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="injection" title="src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)">
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ Read more about `ngIf` and `*` in the [ngIf section](guide/template-syntax#ngIf)
|
|||||||
|
|
||||||
|
|
||||||
The template expression inside the double quotes,
|
The template expression inside the double quotes,
|
||||||
`*ngIf="heros.length > 3"`, looks and behaves much like TypeScript.
|
`*ngIf="heroes.length > 3"`, looks and behaves much like TypeScript.
|
||||||
When the component's list of heroes has more than three items, Angular adds the paragraph
|
When the component's list of heroes has more than three items, Angular adds the paragraph
|
||||||
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
to the DOM and the message appears. If there are three or fewer items, Angular omits the
|
||||||
paragraph, so no message appears. For more information,
|
paragraph, so no message appears. For more information,
|
||||||
|
@ -40,9 +40,9 @@ The flat folder approach allows us to shuffle the apparent navigation structure
|
|||||||
|
|
||||||
The doc generation process consumes the markdown files in the `content/guide` directory and produces JSON files in the `src/generated/docs/guide` directory, which is also flat. Those JSON files contain a combination of document metadata and HTML content.
|
The doc generation process consumes the markdown files in the `content/guide` directory and produces JSON files in the `src/generated/docs/guide` directory, which is also flat. Those JSON files contain a combination of document metadata and HTML content.
|
||||||
|
|
||||||
The reader request a page by its Page URL. The doc viewer fetches the corresponding JSON file, interprets it, and renders it as fully-formed HTML page.
|
The reader requests a page by its Page URL. The doc viewer fetches the corresponding JSON file, interprets it, and renders it as fully-formed HTML page.
|
||||||
|
|
||||||
Page URLs mirror the `content` file structure. A guide page URL is in the form `guide/{page-name}`. _This_ guide page is located at `context/guide/docs-style-guide.md` and its URL is `guide/docs-style-guide`.
|
Page URLs mirror the `content` file structure. The URL for the page of a guide is in the form `guide/{page-name}`. The page for _this_ "Authors Style Guide" is located at `content/guide/docs-style-guide.md` and its URL is `guide/docs-style-guide`.
|
||||||
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
@ -64,7 +64,7 @@ While documentation guide pages ultimately render as HTML, almost all of them ar
|
|||||||
|
|
||||||
Markdown is easier to read and to edit than HTML. Many editors (including Visual Studio Code) can render markdown as you type it.
|
Markdown is easier to read and to edit than HTML. Many editors (including Visual Studio Code) can render markdown as you type it.
|
||||||
|
|
||||||
From time to time you'll have to step away from markdown and write a portion of the document in HTML. markdown allows you to mix HTML and markdown in the same document.
|
From time to time you'll have to step away from markdown and write a portion of the document in HTML. Markdown allows you to mix HTML and markdown in the same document.
|
||||||
|
|
||||||
Standard markdown processors don't allow you to put markdown _within_ HTML tags. But the Angular documentation markdown processor **supports markdown within HTML**, as long as you follow one rule:
|
Standard markdown processors don't allow you to put markdown _within_ HTML tags. But the Angular documentation markdown processor **supports markdown within HTML**, as long as you follow one rule:
|
||||||
|
|
||||||
@ -77,14 +77,14 @@ Standard markdown processors don't allow you to put markdown _within_ HTML tags.
|
|||||||
```html
|
```html
|
||||||
<div class="alert is-critical">
|
<div class="alert is-critical">
|
||||||
|
|
||||||
**Always** follow every opening and closing opening HTML tag with _a blank line_.
|
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
It is customary but not required to precede the _closing HTML_ tag with a blank tag as well.
|
It is customary but not required to _precede_ the _closing HTML_ tag with a blank line as well.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ Begin the title with the markdown `#` character. Alternatively, you can write th
|
|||||||
|
|
||||||
**Only one title (`<h1>`) per document!**
|
**Only one title (`<h1>`) per document!**
|
||||||
|
|
||||||
Title text should be in "Title Case", which means that you use capital letters to start the first works and all _principal_. 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
|
```html
|
||||||
# The Meat of the Matter
|
# The Meat of the Matter
|
||||||
@ -113,7 +113,7 @@ Title text should be in "Title Case", which means that you use capital letters t
|
|||||||
|
|
||||||
A typical document is divided into sections.
|
A typical document is divided into sections.
|
||||||
|
|
||||||
All section heading text should be in "Sentence Case", which means the first word is capitalized and all other words are lower case.
|
All section heading text should be in "Sentence case", which means the first word is capitalized and all other words are lower case.
|
||||||
|
|
||||||
**Always follow the section heading with at least one blank line.**
|
**Always follow the section heading with at least one blank line.**
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ In all other cases, code snippets should be generated automatically from tested
|
|||||||
|
|
||||||
One of the documentation design goals is that guide page code snippets should be examples of real, working code.
|
One of the documentation design goals is that guide page code snippets should be examples of real, working code.
|
||||||
|
|
||||||
We meet this goal by displaying code snippets that are derived directly from standalone code samples, written specifically for these guide page.
|
We meet this goal by displaying code snippets that are derived directly from standalone code samples, written specifically for these guide pages.
|
||||||
|
|
||||||
The author of a guide page is responsible for the code sample that supports that page.
|
The author of a guide page is responsible for the code sample that supports that page.
|
||||||
The author must also write end-to-end tests for the sample.
|
The author must also write end-to-end tests for the sample.
|
||||||
@ -343,10 +343,11 @@ Here's the brief markup that produced that lengthy snippet:
|
|||||||
```html
|
```html
|
||||||
<code-example
|
<code-example
|
||||||
path="docs-style-guide/src/app/app.module.ts"
|
path="docs-style-guide/src/app/app.module.ts"
|
||||||
title="src/app/app.module.ts"></code-example>
|
title="src/app/app.module.ts">
|
||||||
|
</code-example>
|
||||||
```
|
```
|
||||||
|
|
||||||
You identified the snippet's source file by setting the `path` attribute to sample folder's location_within `content/examples`.
|
You identified the snippet's source file by setting the `path` attribute to sample folder's location _within_ `content/examples`.
|
||||||
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
||||||
|
|
||||||
You added a header to tell the reader where to find the file by setting the `title` attribute.
|
You added a header to tell the reader where to find the file by setting the `title` attribute.
|
||||||
@ -369,7 +370,7 @@ You control the _code-example_ output by setting one or more of its attributes:
|
|||||||
|
|
||||||
* `region`- displays the source file fragment with that region name; regions are identified by _docregion_ markup in the source file, as explained [below](#region "Displaying a code fragment").
|
* `region`- displays the source file fragment with that region name; regions are identified by _docregion_ markup in the source file, as explained [below](#region "Displaying a code fragment").
|
||||||
|
|
||||||
* `linenums`- value may be `true`, `false`, or a `number`. When not specified, line numbers are automatically displayed when there are 10 or more lines of code. The rarely used `number` option starts line numbering at the given value. `linenums=4` sets the starting line number to 4.
|
* `linenums`- value may be `true`, `false`, or a `number`. When not specified, line numbers are automatically displayed when there are greater than 10 lines of code. The rarely used `number` option starts line numbering at the given value. `linenums=4` sets the starting line number to 4.
|
||||||
|
|
||||||
* `class`- code snippets can be styled with the CSS classes `no-box`, `code-shell`, and `avoid`.
|
* `class`- code snippets can be styled with the CSS classes `no-box`, `code-shell`, and `avoid`.
|
||||||
|
|
||||||
@ -385,7 +386,8 @@ Often you want to focus on a fragment of code within a sample code file. In this
|
|||||||
|
|
||||||
<code-example
|
<code-example
|
||||||
path="docs-style-guide/src/app/app.module.ts"
|
path="docs-style-guide/src/app/app.module.ts"
|
||||||
region="class"></code-example>
|
region="class">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
First you surround that fragment in the source file with a named _docregion_ as described [below](#source-code-markup).
|
First you surround that fragment in the source file with a named _docregion_ as described [below](#source-code-markup).
|
||||||
Then you reference that _docregion_ in the `region` attribute of the `<code-example>` like this
|
Then you reference that _docregion_ in the `region` attribute of the `<code-example>` like this
|
||||||
@ -394,12 +396,13 @@ Then you reference that _docregion_ in the `region` attribute of the `<code-exam
|
|||||||
```html
|
```html
|
||||||
<code-example
|
<code-example
|
||||||
path="docs-style-guide/src/app/app.module.ts"
|
path="docs-style-guide/src/app/app.module.ts"
|
||||||
region="class"></code-example>
|
region="class">
|
||||||
|
</code-example>
|
||||||
```
|
```
|
||||||
|
|
||||||
A couple of observations:
|
A couple of observations:
|
||||||
|
|
||||||
1. The `region` value, `"class"`, is the name of the `#docregion` in the source file. Confirm that by looking at `content/examples/ocs-style-guide/src/app/app.module.ts`
|
1. The `region` value, `"class"`, is the name of the `#docregion` in the source file. Confirm that by looking at `content/examples/docs-style-guide/src/app/app.module.ts`
|
||||||
|
|
||||||
1. Omitting the `title` is fine when the source of the fragment is obvious. We just said that this is a fragment of the `app.module.ts` file which was displayed immediately above, in full, with a header.
|
1. Omitting the `title` is fine when the source of the fragment is obvious. We just said that this is a fragment of the `app.module.ts` file which was displayed immediately above, in full, with a header.
|
||||||
There's no need to repeat the header.
|
There's no need to repeat the header.
|
||||||
@ -431,21 +434,20 @@ Here's the markup for an "avoid" example in the
|
|||||||
{@a code-tabs}
|
{@a code-tabs}
|
||||||
### Code Tabs
|
### Code Tabs
|
||||||
|
|
||||||
Code tabs display code much like Code examples do. The added advantage is that they can display mutiple code samples within a tabbed interface. Each tab is displayed using Code-pane.
|
Code tabs display code much like _code examples_ do. The added advantage is that they can display mutiple code samples within a tabbed interface. Each tab is displayed using _code pane_.
|
||||||
|
|
||||||
#### Code-tabs attributes
|
#### Code-tabs attributes
|
||||||
|
|
||||||
* `linenums`: The value can be `true`, `false` or a number indicating the starting line number. If not specified, line numbers are are enabled only when code for a tab pane has 10 or more lines.
|
* `linenums`: The value can be `true`, `false` or a number indicating the starting line number. If not specified, line numbers are enabled only when code for a tab pane has greater than 10 lines of code.
|
||||||
|
|
||||||
#### Code-pane attributes
|
#### Code-pane attributes
|
||||||
|
|
||||||
* `path` - a file in the content/examples folder
|
* `path` - a file in the content/examples folder
|
||||||
* `title` - seen in the header of a tab
|
* `title` - seen in the header of a tab
|
||||||
* `linenums` - overrides the `linenums` property at the `code-tabs` level for this particular pane. The value can be `true`, `false` or a number indicating the starting line number. If not specified, line numbers are are enabled only when there are 10 or more lines.
|
* `linenums` - overrides the `linenums` property at the `code-tabs` level for this particular pane. The value can be `true`, `false` or a number indicating the starting line number. If not specified, line numbers are enabled only when the number of lines of code are greater than 10.
|
||||||
|
|
||||||
The next example displays multiple code tabs, each with its own title.
|
The next example displays multiple code tabs, each with its own title.
|
||||||
Line numbers are explicitly disabled for all panes at the `<code-tabs>` level.
|
It demonstrates control over display of line numbers at both the `<code-tabs>` and `<code-pane>` levels.
|
||||||
The second pane adds line numbers back for itself.
|
|
||||||
|
|
||||||
<code-tabs linenums="false">
|
<code-tabs linenums="false">
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -470,6 +472,9 @@ The second pane adds line numbers back for itself.
|
|||||||
|
|
||||||
Here's the markup for that example.
|
Here's the markup for that example.
|
||||||
|
|
||||||
|
Note how the `linenums` attribute in the `<code-tabs>` explicitly disables numbering for all panes.
|
||||||
|
The `linenums` attribute in the second pane restores line numbering for _itself only_.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<code-tabs linenums="false">
|
<code-tabs linenums="false">
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -763,7 +768,7 @@ To link to a plunker defined by a named `plnkr.json` file, set the `plnkr` attri
|
|||||||
|
|
||||||
<h3 class="no-toc">Live Example without download</h3>
|
<h3 class="no-toc">Live Example without download</h3>
|
||||||
|
|
||||||
To skip the download link, add the `noDownload` attribute
|
To skip the download link, add the `noDownload` attribute.
|
||||||
|
|
||||||
<live-example noDownload>Just the plunker</live-example>
|
<live-example noDownload>Just the plunker</live-example>
|
||||||
|
|
||||||
@ -781,7 +786,7 @@ To skip the live plunker link and only link to the download, add the `downloadOn
|
|||||||
<live-example downloadOnly>Download only</live-example>
|
<live-example downloadOnly>Download only</live-example>
|
||||||
```
|
```
|
||||||
|
|
||||||
<h3 class="no-toc"Embedded live example></h3>
|
<h3 class="no-toc">Embedded live example</h3>
|
||||||
|
|
||||||
By default, a live example link opens a plunker in a separate browser tab.
|
By default, a live example link opens a plunker in a separate browser tab.
|
||||||
You can embed the plunker within the guide page itself by adding the `embedded` attribute.
|
You can embed the plunker within the guide page itself by adding the `embedded` attribute.
|
||||||
|
@ -168,10 +168,8 @@ on custom validation directives.
|
|||||||
|
|
||||||
* Binding to the new `formErrors.name` property is sufficient to display all name validation error messages.
|
* Binding to the new `formErrors.name` property is sufficient to display all name validation error messages.
|
||||||
|
|
||||||
|
|
||||||
{@a component-class}
|
{@a component-class}
|
||||||
|
|
||||||
|
|
||||||
### Component class
|
### Component class
|
||||||
The original component code for Template 1 stayed the same; however,
|
The original component code for Template 1 stayed the same; however,
|
||||||
Template 2 requires some changes in the component. This section covers the code
|
Template 2 requires some changes in the component. This section covers the code
|
||||||
@ -323,7 +321,7 @@ This allows you to do the following:
|
|||||||
|
|
||||||
* Add, change, and remove validation functions on the fly.
|
* Add, change, and remove validation functions on the fly.
|
||||||
* Manipulate the control model dynamically from within the component.
|
* Manipulate the control model dynamically from within the component.
|
||||||
* [Test](guide/form-validation#testing) validation and control logic with isolated unit tests.
|
* [Test](guide/form-validation#testing-considerations) validation and control logic with isolated unit tests.
|
||||||
|
|
||||||
The following sample re-writes the hero form in Reactive Forms style.
|
The following sample re-writes the hero form in Reactive Forms style.
|
||||||
|
|
||||||
|
@ -214,10 +214,10 @@ There are three changes:
|
|||||||
|
|
||||||
1. You import `FormsModule` and the new `HeroFormComponent`.
|
1. You import `FormsModule` and the new `HeroFormComponent`.
|
||||||
|
|
||||||
1. You add the `FormsModule` to the list of `imports` defined in the `ngModule` decorator. This gives the application
|
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
|
||||||
access to all of the template-driven forms features, including `ngModel`.
|
access to all of the template-driven forms features, including `ngModel`.
|
||||||
|
|
||||||
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `ngModule` decorator. This makes
|
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `@NgModule` decorator. This makes
|
||||||
the `HeroFormComponent` component visible throughout this module.
|
the `HeroFormComponent` component visible throughout this module.
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,15 +25,8 @@ to a module factory, meaning you don't need to include the Angular compiler in y
|
|||||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||||
|
|
||||||
|
|
||||||
## Angular module
|
|
||||||
|
|
||||||
Helps you organize an application into cohesive blocks of functionality.
|
</div>
|
||||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
|
||||||
|
|
||||||
Every Angular application has an application root-module class. By convention, the class is
|
|
||||||
called `AppModule` and resides in a file named `app.module.ts`.
|
|
||||||
|
|
||||||
For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) page.
|
|
||||||
|
|
||||||
|
|
||||||
## Annotation
|
## Annotation
|
||||||
@ -115,7 +108,7 @@ The Angular [scoped packages](guide/glossary#scoped-package) each have a barrel
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
You can often achieve the same result using [Angular modules](guide/glossary#angular-module) instead.
|
You can often achieve the same result using [NgModules](guide/glossary#ngmodule) instead.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -132,7 +125,11 @@ between a "token"—also referred to as a "key"—and a dependency [prov
|
|||||||
|
|
||||||
## Bootstrap
|
## Bootstrap
|
||||||
|
|
||||||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
You launch an Angular application by "bootstrapping" it using the application root NgModule (`AppModule`).
|
||||||
|
|
||||||
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
||||||
which is the first component that is loaded for the application.
|
which is the first component that is loaded for the application.
|
||||||
For more information, see the [Setup](guide/setup) page.
|
For more information, see the [Setup](guide/setup) page.
|
||||||
@ -346,13 +343,13 @@ elements and their children.
|
|||||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||||
|
|
||||||
The latest approved version of JavaScript is
|
The latest approved version of JavaScript is
|
||||||
[ECMAScript 2016](http://www.ecma-international.org/ecma-262/7.0/)
|
[ECMAScript 2017](http://www.ecma-international.org/ecma-262/8.0/)
|
||||||
(also known as "ES2016" or "ES7"). Many Angular developers write their applications
|
(also known as "ES2017" or "ES8"). Many Angular developers write their applications
|
||||||
in ES7 or a dialect that strives to be
|
in ES8 or a dialect that strives to be
|
||||||
compatible with it, such as [TypeScript](guide/glossary#typescript).
|
compatible with it, such as [TypeScript](guide/glossary#typescript).
|
||||||
|
|
||||||
Most modern browsers only support the much older "ECMAScript 5" (also known as "ES5") standard.
|
Most modern browsers only support the much older "ECMAScript 5" (also known as "ES5") standard.
|
||||||
Applications written in ES2016, ES2015, or one of their dialects must be [transpiled](guide/glossary#transpile)
|
Applications written in ES2017, ES2016, ES2015, or one of their dialects must be [transpiled](guide/glossary#transpile)
|
||||||
to ES5 JavaScript.
|
to ES5 JavaScript.
|
||||||
|
|
||||||
Angular developers can write in ES5 directly.
|
Angular developers can write in ES5 directly.
|
||||||
@ -475,8 +472,8 @@ Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
|||||||
|
|
||||||
Angular has the following types of modules:
|
Angular has the following types of modules:
|
||||||
|
|
||||||
* [Angular modules](guide/glossary#angular-module).
|
* [NgModules](guide/glossary#ngmodule).
|
||||||
For details and examples, see the [Angular Modules](guide/ngmodule) page.
|
For details and examples, see the [NgModules](guide/ngmodule) page.
|
||||||
* ES2015 modules, as described in this section.
|
* ES2015 modules, as described in this section.
|
||||||
|
|
||||||
|
|
||||||
@ -493,7 +490,7 @@ In general, you assemble an application from many modules, both the ones you wri
|
|||||||
A module *exports* something of value in that code, typically one thing such as a class;
|
A module *exports* something of value in that code, typically one thing such as a class;
|
||||||
a module that needs that class *imports* it.
|
a module that needs that class *imports* it.
|
||||||
|
|
||||||
The structure of Angular modules and the import/export syntax
|
The structure of NgModules and the import/export syntax
|
||||||
is based on the [ES2015 module standard](http://www.2ality.com/2014/09/es6-modules-final.html).
|
is based on the [ES2015 module standard](http://www.2ality.com/2014/09/es6-modules-final.html).
|
||||||
|
|
||||||
An application that adheres to this standard requires a module loader to
|
An application that adheres to this standard requires a module loader to
|
||||||
@ -511,6 +508,24 @@ You rarely access Angular feature modules directly. You usually import them from
|
|||||||
|
|
||||||
{@a N}
|
{@a N}
|
||||||
|
|
||||||
|
|
||||||
|
## NgModule
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Helps you organize an application into cohesive blocks of functionality.
|
||||||
|
An NgModule identifies the components, directives, and pipes that the application uses along with the list of external NgModules that the application needs, such as `FormsModule`.
|
||||||
|
|
||||||
|
Every Angular application has an application root-module class. By convention, the class is
|
||||||
|
called `AppModule` and resides in a file named `app.module.ts`.
|
||||||
|
|
||||||
|
For details and examples, see [NgModules](guide/ngmodule).
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{@a O}
|
{@a O}
|
||||||
|
|
||||||
## Observable
|
## Observable
|
||||||
@ -614,7 +629,9 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
|||||||
|
|
||||||
## Router module
|
## Router module
|
||||||
|
|
||||||
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
A separate [NgModule](guide/glossary#ngmodule) that provides the necessary service providers and directives for navigating through application views.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](guide/router) page.
|
For more information, see the [Routing & Navigation](guide/router) page.
|
||||||
|
|
||||||
@ -633,7 +650,7 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
|||||||
A way to group related *npm* packages.
|
A way to group related *npm* packages.
|
||||||
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
||||||
|
|
||||||
Angular modules are delivered within *scoped packages* such as `@angular/core`,
|
NgModules are delivered within *scoped packages* such as `@angular/core`,
|
||||||
`@angular/common`, `@angular/platform-browser-dynamic`, `@angular/http`, and `@angular/router`.
|
`@angular/common`, `@angular/platform-browser-dynamic`, `@angular/http`, and `@angular/router`.
|
||||||
|
|
||||||
Import a scoped package the same way that you import a normal package.
|
Import a scoped package the same way that you import a normal package.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -397,8 +397,7 @@ created under test or before you decide to display it.
|
|||||||
Constructors should do no more than set the initial local variables to simple values.
|
Constructors should do no more than set the initial local variables to simple values.
|
||||||
|
|
||||||
An `ngOnInit()` is a good place for a component to fetch its initial data. The
|
An `ngOnInit()` is a good place for a component to fetch its initial data. The
|
||||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) and [HTTP Client](guide/http#oninit)
|
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) guide shows how.
|
||||||
guides show how.
|
|
||||||
|
|
||||||
|
|
||||||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||||
|
@ -1327,7 +1327,7 @@ Here's an _NgModule_ class with imports, exports, and declarations.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Of course you use _JavaScript_ modules to write _Angular_ modules as seen in the complete `contact.module.ts` file:
|
Of course you use _JavaScript_ modules to write NgModules as seen in the complete `contact.module.ts` file:
|
||||||
|
|
||||||
<code-example path="ngmodule/src/app/contact/contact.module.2.ts" title="src/app/contact/contact.module.ts" linenums="false">
|
<code-example path="ngmodule/src/app/contact/contact.module.2.ts" title="src/app/contact/contact.module.ts" linenums="false">
|
||||||
|
|
||||||
|
@ -163,7 +163,6 @@ without waiting for Angular updates.
|
|||||||
***angular-in-memory-web-api***: An Angular-supported library that simulates a remote server's web api
|
***angular-in-memory-web-api***: An Angular-supported library that simulates a remote server's web api
|
||||||
without requiring an actual server or real HTTP calls.
|
without requiring an actual server or real HTTP calls.
|
||||||
Good for demos, samples, and early stage development (before you even have a server).
|
Good for demos, samples, and early stage development (before you even have a server).
|
||||||
Read about it in the [HTTP Client](guide/http#in-mem-web-api) page.
|
|
||||||
|
|
||||||
***bootstrap***: [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
|
***bootstrap***: [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
|
||||||
Some of the samples improve their appearance with *bootstrap*.
|
Some of the samples improve their appearance with *bootstrap*.
|
||||||
|
@ -140,7 +140,7 @@ This is the _root component_ and it is named `app-root`.
|
|||||||
You can find it in `./src/app/app.component.ts`.
|
You can find it in `./src/app/app.component.ts`.
|
||||||
|
|
||||||
|
|
||||||
Open the component file and change the `title` property from _app works!_ to _My First Angular App_:
|
Open the component file and change the `title` property from _Welcome to app!!_ to _Welcome to My First Angular App!!_:
|
||||||
|
|
||||||
|
|
||||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false"></code-example>
|
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false"></code-example>
|
||||||
@ -331,7 +331,7 @@ Any files outside of this folder are meant to support building your app.
|
|||||||
The main HTML page that is served when someone visits your site.
|
The main HTML page that is served when someone visits your site.
|
||||||
Most of the time you'll never need to edit it.
|
Most of the time you'll never need to edit it.
|
||||||
The CLI automatically adds all `js` and `css` files when building your app so you
|
The CLI automatically adds all `js` and `css` files when building your app so you
|
||||||
never need to add any `<script>` or `<link>` tags here manually.
|
never need to add any `<script>` or `<link>` tags here manually.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -464,7 +464,7 @@ These files go in the root folder next to `src/`.
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
Inside `e2e/` live the End-to-End tests.
|
Inside `e2e/` live the end-to-end tests.
|
||||||
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
||||||
just so happens to test your main app.
|
just so happens to test your main app.
|
||||||
That's also why they have their own `tsconfig.e2e.json`.
|
That's also why they have their own `tsconfig.e2e.json`.
|
||||||
@ -493,7 +493,7 @@ These files go in the root folder next to `src/`.
|
|||||||
|
|
||||||
Configuration for Angular CLI.
|
Configuration for Angular CLI.
|
||||||
In this file you can set several defaults and also configure what files are included
|
In this file you can set several defaults and also configure what files are included
|
||||||
when your project is build.
|
when your project is built.
|
||||||
Check out the official documentation if you want to know more.
|
Check out the official documentation if you want to know more.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
@ -614,7 +614,7 @@ If you do not bind the value, the select shows the first option from the data mo
|
|||||||
The component _class_ defines control properties without regard for their representation in the template.
|
The component _class_ defines control properties without regard for their representation in the template.
|
||||||
You define the `state`, `power`, and `sidekick` controls the same way you defined the `name` control.
|
You define the `state`, `power`, and `sidekick` controls the same way you defined the `name` control.
|
||||||
You tie these controls to the template HTML elements in the same way,
|
You tie these controls to the template HTML elements in the same way,
|
||||||
specifiying the `FormControl` name with the `formControlName` directive.
|
specifying the `FormControl` name with the `formControlName` directive.
|
||||||
|
|
||||||
See the API reference for more information about
|
See the API reference for more information about
|
||||||
[radio buttons](api/forms/RadioControlValueAccessor "API: RadioControlValueAccessor"),
|
[radio buttons](api/forms/RadioControlValueAccessor "API: RadioControlValueAccessor"),
|
||||||
@ -1220,7 +1220,7 @@ Place a button on the form so the user can add a new _secret lair_ and wire it t
|
|||||||
|
|
||||||
Be sure to **add the `type="button"` attribute**.
|
Be sure to **add the `type="button"` attribute**.
|
||||||
In fact, you should always specify a button's `type`.
|
In fact, you should always specify a button's `type`.
|
||||||
Without an explict type, the button type defaults to "submit".
|
Without an explicit type, the button type defaults to "submit".
|
||||||
When you later add a _form submit_ action, every "submit" button triggers the submit action which
|
When you later add a _form submit_ action, every "submit" button triggers the submit action which
|
||||||
might do something like save the current changes.
|
might do something like save the current changes.
|
||||||
You do not want to save changes when the user clicks the _Add a Secret Lair_ button.
|
You do not want to save changes when the user clicks the _Add a Secret Lair_ button.
|
||||||
|
@ -255,11 +255,11 @@ During each navigation, the `Router` emits navigation events through the `Router
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code>RouteConfigLoadStart</code>
|
<code>RouteConfigLoadEnd</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
An [event](api/router/RouteConfigLoadStart) triggered after a route has been lazy loaded.
|
An [event](api/router/RouteConfigLoadEnd) triggered after a route has been lazy loaded.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -347,7 +347,7 @@ Here are the key `Router` terms and their meanings:
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
A separate Angular module that provides the necessary service providers
|
A separate NgModule that provides the necessary service providers
|
||||||
and directives for navigating through application views.
|
and directives for navigating through application views.
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@ -3794,7 +3794,7 @@ Take the final step and detach the admin feature set from the main application.
|
|||||||
The root `AppModule` must neither load nor reference the `AdminModule` or its files.
|
The root `AppModule` must neither load nor reference the `AdminModule` or its files.
|
||||||
|
|
||||||
In `app.module.ts`, remove the `AdminModule` import statement from the top of the file
|
In `app.module.ts`, remove the `AdminModule` import statement from the top of the file
|
||||||
and remove the `AdminModule` from the Angular module's `imports` array.
|
and remove the `AdminModule` from the NgModule's `imports` array.
|
||||||
|
|
||||||
|
|
||||||
{@a can-load-guard}
|
{@a can-load-guard}
|
||||||
|
@ -282,36 +282,7 @@ This technique is effective because all browsers implement the _same origin poli
|
|||||||
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
|
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
|
||||||
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
|
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
|
||||||
|
|
||||||
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
|
Angular's `HttpClient` has built-in support for the client-side half of this technique. Read about it more in the [HttpClient guide](/guide/http).
|
||||||
The default `CookieXSRFStrategy` is turned on automatically.
|
|
||||||
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
|
|
||||||
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
|
|
||||||
|
|
||||||
The server must do its part by setting the
|
|
||||||
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
|
|
||||||
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
|
|
||||||
|
|
||||||
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
|
|
||||||
cryptographically secure random number generator, and expire in a day or two.
|
|
||||||
|
|
||||||
Your server may use a different cookie or header name for this purpose.
|
|
||||||
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
|
|
||||||
|
|
||||||
<code-example language="typescript">
|
|
||||||
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Or you can implement and provide an entirely custom `XSRFStrategy`:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example language="typescript">
|
|
||||||
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
For information about CSRF at the Open Web Application Security Project (OWASP), see
|
||||||
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29">Cross-Site Request Forgery (CSRF)</a> and
|
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29">Cross-Site Request Forgery (CSRF)</a> and
|
||||||
@ -337,7 +308,7 @@ This attack is only successful if the returned JSON is executable as JavaScript.
|
|||||||
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
|
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
|
||||||
well-known string `")]}',\n"`.
|
well-known string `")]}',\n"`.
|
||||||
|
|
||||||
Angular's `Http` library recognizes this convention and automatically strips the string
|
Angular's `HttpClient` library recognizes this convention and automatically strips the string
|
||||||
`")]}',\n"` from all responses before further parsing.
|
`")]}',\n"` from all responses before further parsing.
|
||||||
|
|
||||||
For more information, see the XSSI section of this [Google web security blog
|
For more information, see the XSSI section of this [Google web security blog
|
||||||
|
@ -199,7 +199,7 @@ If you do, this page can help you understand their purpose.
|
|||||||
|
|
||||||
|
|
||||||
A list of files that you can delete if you want to purge your setup of the
|
A list of files that you can delete if you want to purge your setup of the
|
||||||
original QuickStart Seed testing and git maintainence artifacts.
|
original QuickStart Seed testing and git maintenance artifacts.
|
||||||
See instructions in the optional
|
See instructions in the optional
|
||||||
[_Deleting non-essential files_](guide/setup#non-essential "Setup: Deleting non-essential files") section.
|
[_Deleting non-essential files_](guide/setup#non-essential "Setup: Deleting non-essential files") section.
|
||||||
*Do this only in the beginning to avoid accidentally deleting your own tests and git setup!*
|
*Do this only in the beginning to avoid accidentally deleting your own tests and git setup!*
|
||||||
|
@ -2129,12 +2129,12 @@ discourage the `I` prefix.
|
|||||||
<a href="#toc">Back to top</a>
|
<a href="#toc">Back to top</a>
|
||||||
|
|
||||||
|
|
||||||
## Application structure and Angular modules
|
## Application structure and NgModules
|
||||||
|
|
||||||
Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the app is heading down the road.
|
Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the app is heading down the road.
|
||||||
|
|
||||||
All of the app's code goes in a folder named `src`.
|
All of the app's code goes in a folder named `src`.
|
||||||
All feature areas are in their own folder, with their own Angular module.
|
All feature areas are in their own folder, with their own NgModule.
|
||||||
|
|
||||||
All content is one asset per file. Each component, service, and pipe is in its own file.
|
All content is one asset per file. Each component, service, and pipe is in its own file.
|
||||||
All third party vendor scripts are stored in another folder and not in the `src` folder.
|
All third party vendor scripts are stored in another folder and not in the `src` folder.
|
||||||
@ -2779,7 +2779,7 @@ and more difficult in a flat structure.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Do** create an Angular module for each feature area.
|
**Do** create an NgModule for each feature area.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2790,7 +2790,7 @@ and more difficult in a flat structure.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** Angular modules make it easy to lazy load routable features.
|
**Why?** NgModules make it easy to lazy load routable features.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2801,7 +2801,7 @@ and more difficult in a flat structure.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** Angular modules make it easier to isolate, test, and re-use features.
|
**Why?** NgModules make it easier to isolate, test, and re-use features.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2827,7 +2827,7 @@ and more difficult in a flat structure.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Do** create an Angular module in the app's root folder,
|
**Do** create an NgModule in the app's root folder,
|
||||||
for example, in `/src/app`.
|
for example, in `/src/app`.
|
||||||
|
|
||||||
|
|
||||||
@ -2839,7 +2839,7 @@ for example, in `/src/app`.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** Every app requires at least one root Angular module.
|
**Why?** Every app requires at least one root NgModule.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2888,7 +2888,7 @@ for example, in `/src/app`.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Do** create an Angular module for all distinct features in an application;
|
**Do** create an NgModule for all distinct features in an application;
|
||||||
for example, a `Heroes` feature.
|
for example, a `Heroes` feature.
|
||||||
|
|
||||||
|
|
||||||
@ -3127,7 +3127,7 @@ that may need features from another common module; for example,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesireable results.
|
**Why?** A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesirable results.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -3377,7 +3377,7 @@ Yet they're too big and messy to leave loose in the root folder.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** A lazily loaded feature module that directly imports the `CoreModule` will make its own copy of services and likely have undesireable results.
|
**Why?** A lazily loaded feature module that directly imports the `CoreModule` will make its own copy of services and likely have undesirable results.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1155,7 +1155,7 @@ other HTML elements, attributes, properties, and components.
|
|||||||
They are usually applied to elements as if they were HTML attributes, hence the name.
|
They are usually applied to elements as if they were HTML attributes, hence the name.
|
||||||
|
|
||||||
Many details are covered in the [_Attribute Directives_](guide/attribute-directives) guide.
|
Many details are covered in the [_Attribute Directives_](guide/attribute-directives) guide.
|
||||||
Many Angular modules such as the [`RouterModule`](guide/router "Routing and Navigation")
|
Many NgMdules such as the [`RouterModule`](guide/router "Routing and Navigation")
|
||||||
and the [`FormsModule`](guide/forms "Forms") define their own attribute directives.
|
and the [`FormsModule`](guide/forms "Forms") define their own attribute directives.
|
||||||
This section is an introduction to the most commonly used attribute directives:
|
This section is an introduction to the most commonly used attribute directives:
|
||||||
|
|
||||||
@ -1260,7 +1260,7 @@ Two-way data binding with the `NgModel` directive makes that easy. Here's an exa
|
|||||||
#### _FormsModule_ is required to use _ngModel_
|
#### _FormsModule_ is required to use _ngModel_
|
||||||
|
|
||||||
Before using the `ngModel` directive in a two-way data binding,
|
Before using the `ngModel` directive in a two-way data binding,
|
||||||
you must import the `FormsModule` and add it to the Angular module's `imports` list.
|
you must import the `FormsModule` and add it to the NgModule's `imports` list.
|
||||||
Learn more about the `FormsModule` and `ngModel` in the
|
Learn more about the `FormsModule` and `ngModel` in the
|
||||||
[Forms](guide/forms#ngModel) guide.
|
[Forms](guide/forms#ngModel) guide.
|
||||||
|
|
||||||
@ -1371,7 +1371,7 @@ _This_ section is an introduction to the common structural directives:
|
|||||||
### NgIf
|
### NgIf
|
||||||
|
|
||||||
You can add or remove an element from the DOM by applying an `NgIf` directive to
|
You can add or remove an element from the DOM by applying an `NgIf` directive to
|
||||||
that element (called the _host elment_).
|
that element (called the _host element_).
|
||||||
Bind the directive to a condition expression like `isActive` in this example.
|
Bind the directive to a condition expression like `isActive` in this example.
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="NgIf-1" title="src/app/app.component.html" linenums="false">
|
<code-example path="template-syntax/src/app/app.component.html" region="NgIf-1" title="src/app/app.component.html" linenums="false">
|
||||||
@ -1950,7 +1950,7 @@ In this mode, typed variables disallow null and undefined by default. The type c
|
|||||||
The type checker also throws an error if it can't determine whether a variable will be null or undefined at runtime.
|
The type checker also throws an error if it can't determine whether a variable will be null or undefined at runtime.
|
||||||
You may know that can't happen but the type checker doesn't know.
|
You may know that can't happen but the type checker doesn't know.
|
||||||
You tell the type checker that it can't happen by applying the post-fix
|
You tell the type checker that it can't happen by applying the post-fix
|
||||||
[_non-null assertion operator (!)_]((http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
|
[_non-null assertion operator (!)_](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
|
||||||
|
|
||||||
The _Angular_ **non-null assertion operator (`!`)** serves the same purpose in an Angular template.
|
The _Angular_ **non-null assertion operator (`!`)** serves the same purpose in an Angular template.
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ For a discussion of the unit testing setup files, [see below](guide/testing#setu
|
|||||||
{@a isolated-v-testing-utilities}
|
{@a isolated-v-testing-utilities}
|
||||||
|
|
||||||
|
|
||||||
### Isolated unit tests vs. the Angular testing utilites
|
### Isolated unit tests vs. the Angular testing utilities
|
||||||
|
|
||||||
[Isolated unit tests](guide/testing#isolated-unit-tests "Unit testing without the Angular testing utilities")
|
[Isolated unit tests](guide/testing#isolated-unit-tests "Unit testing without the Angular testing utilities")
|
||||||
examine an instance of a class all by itself without any dependence on Angular or any injected values.
|
examine an instance of a class all by itself without any dependence on Angular or any injected values.
|
||||||
@ -427,7 +427,7 @@ and re-attach it to a dynamically-constructed Angular test module
|
|||||||
tailored specifically for this battery of tests.
|
tailored specifically for this battery of tests.
|
||||||
|
|
||||||
The `configureTestingModule` method takes an `@NgModule`-like metadata object.
|
The `configureTestingModule` method takes an `@NgModule`-like metadata object.
|
||||||
The metadata object can have most of the properties of a normal [Angular module](guide/ngmodule).
|
The metadata object can have most of the properties of a normal [NgModule](guide/ngmodule).
|
||||||
|
|
||||||
_This metadata object_ simply declares the component to test, `BannerComponent`.
|
_This metadata object_ simply declares the component to test, `BannerComponent`.
|
||||||
The metadata lack `imports` because (a) the default testing module configuration already has what `BannerComponent` needs
|
The metadata lack `imports` because (a) the default testing module configuration already has what `BannerComponent` needs
|
||||||
|
@ -247,12 +247,10 @@ next to the original _ES5_ version for comparison:
|
|||||||
</code-pane>
|
</code-pane>
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
|
{@a name-constructor}
|
||||||
|
|
||||||
<div class="callout is-helpful">
|
<div class="callout is-helpful">
|
||||||
|
|
||||||
{@a name-constructor}
|
|
||||||
|
|
||||||
### Name the constructor
|
|
||||||
|
|
||||||
A **named** constructor displays clearly in the console log
|
A **named** constructor displays clearly in the console log
|
||||||
if the component throws a runtime error.
|
if the component throws a runtime error.
|
||||||
An **unnamed** constructor displays as an anonymous function, for example, `class0`,
|
An **unnamed** constructor displays as an anonymous function, for example, `class0`,
|
||||||
@ -431,7 +429,7 @@ by calling the `@Inject()` decorator with the injection token.
|
|||||||
In the following example, the token is the string `'heroName'`.
|
In the following example, the token is the string `'heroName'`.
|
||||||
|
|
||||||
The other JavaScript dialects add a `parameters` array to the class constructor function.
|
The other JavaScript dialects add a `parameters` array to the class constructor function.
|
||||||
Each item constrains a new instance of `Inject`:
|
Each item contains a new instance of `Inject`:
|
||||||
|
|
||||||
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
||||||
* _ES5_—simply list the string tokens.
|
* _ES5_—simply list the string tokens.
|
||||||
|
@ -62,7 +62,7 @@ There are a few rules in particular that will make it much easier to do
|
|||||||
* The [Folders-by-Feature Structure](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure)
|
* The [Folders-by-Feature Structure](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure)
|
||||||
and [Modularity](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modularity)
|
and [Modularity](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modularity)
|
||||||
rules define similar principles on a higher level of abstraction: Different parts of the
|
rules define similar principles on a higher level of abstraction: Different parts of the
|
||||||
application should reside in different directories and Angular modules.
|
application should reside in different directories and NgModules.
|
||||||
|
|
||||||
When an application is laid out feature per feature in this way, it can also be
|
When an application is laid out feature per feature in this way, it can also be
|
||||||
migrated one feature at a time. For applications that don't already look like
|
migrated one feature at a time. For applications that don't already look like
|
||||||
@ -382,12 +382,12 @@ that describes Angular assets in metadata. The differences blossom from there.
|
|||||||
|
|
||||||
In a hybrid application you run both versions of Angular at the same time.
|
In a hybrid application you run both versions of Angular at the same time.
|
||||||
That means that you need at least one module each from both AngularJS and Angular.
|
That means that you need at least one module each from both AngularJS and Angular.
|
||||||
You will import `UpgradeModule` inside the Angular module, and then use it for
|
You will import `UpgradeModule` inside the NgModule, and then use it for
|
||||||
bootstrapping the AngularJS module.
|
bootstrapping the AngularJS module.
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
Learn more about Angular modules at the [NgModule guide](guide/ngmodule).
|
Read more about [NgModules](guide/ngmodule).
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -485,7 +485,7 @@ Because `HeroDetailComponent` is an Angular component, you must also add it to t
|
|||||||
|
|
||||||
And because this component is being used from the AngularJS module, and is an entry point into
|
And because this component is being used from the AngularJS module, and is an entry point into
|
||||||
the Angular application, you must add it to the `entryComponents` for the
|
the Angular application, you must add it to the `entryComponents` for the
|
||||||
Angular module.
|
NgModule.
|
||||||
|
|
||||||
<code-example path="upgrade-module/src/app/downgrade-static/app.module.ts" region="ngmodule" title="app.module.ts">
|
<code-example path="upgrade-module/src/app/downgrade-static/app.module.ts" region="ngmodule" title="app.module.ts">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
@ -243,7 +243,7 @@ Everything seems fine while you move about _within_ the app.
|
|||||||
But you'll see the problem right away if you refresh the browser
|
But you'll see the problem right away if you refresh the browser
|
||||||
or paste a link to an app page (called a "deep link") into the browser address bar.
|
or paste a link to an app page (called a "deep link") into the browser address bar.
|
||||||
|
|
||||||
You'll most likely get a *404 - Page Not Found* response from the serer
|
You'll most likely get a *404 - Page Not Found* response from the server
|
||||||
for any address other than `/` or `/index.html`.
|
for any address other than `/` or `/index.html`.
|
||||||
|
|
||||||
You have to configure the server to return `index.html` for requests to these "unknown" pages.
|
You have to configure the server to return `index.html` for requests to these "unknown" pages.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 6.9 KiB |
@ -2,7 +2,7 @@
|
|||||||
<h1 class="banner-headline no-toc no-anchor">Events</h1>
|
<h1 class="banner-headline no-toc no-anchor">Events</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="l-content ">
|
<article>
|
||||||
<p>Where we'll be presenting:</p>
|
<p>Where we'll be presenting:</p>
|
||||||
<table class="is-full-width">
|
<table class="is-full-width">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
<h1 class="banner-headline no-toc no-anchor">Features & Benefits</h1>
|
<h1 class="banner-headline no-toc no-anchor">Features & Benefits</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="l-content ">
|
<article>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<div>
|
<div>
|
||||||
<div class="feature-section">
|
<div class="feature-section">
|
||||||
<div class="feature-header">
|
<div class="feature-header">
|
||||||
<div class="text-headline">Cross Platform</div>
|
<div class="text-headline">Cross Platform</div>
|
||||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-row">
|
<div class="feature-row">
|
||||||
|
|
||||||
@ -28,12 +28,13 @@
|
|||||||
<p class="text-body">Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.</p>
|
<p class="text-body">Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-section">
|
<div class="feature-section">
|
||||||
<div class="feature-header">
|
<div class="feature-header">
|
||||||
<div class="text-headline">Speed and Performance</div>
|
<div class="text-headline">Speed and Performance</div>
|
||||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-row">
|
<div class="feature-row">
|
||||||
|
|
||||||
@ -52,12 +53,13 @@
|
|||||||
<p class="text-body">Angular apps load quickly with the new Component Router, which delivers automatic code-splitting so users only load code required to render the view they request.</p>
|
<p class="text-body">Angular apps load quickly with the new Component Router, which delivers automatic code-splitting so users only load code required to render the view they request.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-section">
|
<div class="feature-section">
|
||||||
<div class="feature-header">
|
<div class="feature-header">
|
||||||
<div class="text-headline">Productivity</div>
|
<div class="text-headline">Productivity</div>
|
||||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-row">
|
<div class="feature-row">
|
||||||
|
|
||||||
@ -76,12 +78,13 @@
|
|||||||
<p class="text-body">Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.</p>
|
<p class="text-body">Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-section">
|
<div class="feature-section">
|
||||||
<div class="feature-header">
|
<div class="feature-header">
|
||||||
<div class="text-headline">Full Development Story</div>
|
<div class="text-headline">Full Development Story</div>
|
||||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-row">
|
<div class="feature-row">
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--FULL HEADER BLOCK-->
|
<!--FULL HEADER BLOCK-->
|
||||||
<header class="l-relative">
|
<header>
|
||||||
|
|
||||||
<!--BACKGROUND IMAGE-->
|
<!--BACKGROUND IMAGE-->
|
||||||
<div class="background-sky hero"></div>
|
<div class="background-sky hero"></div>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="l-content">
|
<article>
|
||||||
<div class="home-rows">
|
<div class="home-rows">
|
||||||
|
|
||||||
<!--Announcement Bar-->
|
<!--Announcement Bar-->
|
||||||
@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<a href="guide/quickstart">
|
<a href="guide/quickstart">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="../assets/images/icons/code-icon.svg" height="70px">
|
<img src="assets/images/icons/code-icon.svg" height="70px">
|
||||||
<div class="card-text-container">
|
<div class="card-text-container">
|
||||||
<div class="text-headline">Get Started</div>
|
<div class="text-headline">Get Started</div>
|
||||||
<p>Start building your Angular application.</p>
|
<p>Start building your Angular application.</p>
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
<header class="hero background-sky">
|
|
||||||
<h1 class="hero-title no-toc">News</h1>
|
|
||||||
<div class="clear"></div>
|
|
||||||
</header>
|
|
||||||
<article class="l-content ">
|
|
||||||
<div class="grid-fluid l-space-bottom-2">
|
|
||||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Core Team</h3></div>
|
|
||||||
<div class="clear"></div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-fluid">
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Oct 12, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="http://angularjs.blogspot.com/2016/10/angular-210-now-available.html">Angular
|
|
||||||
2.1.0 Now Available</a></div>
|
|
||||||
<p>Angular version 2.1.0 - incremental-metamorphosis - is a minor release following our
|
|
||||||
announced adoption of Semantic Versioning...</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/stephenfluin.jpg">
|
|
||||||
<div class="posted">Posted by <b>Stephen Fluin</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Oct 7, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="http://angularjs.blogspot.com/2016/10/versioning-and-releasing-angular.html">Versioning
|
|
||||||
and Releasing Angular</a></div>
|
|
||||||
<p>In order for the ecosystem around Angular to thrive, developers need stability from the
|
|
||||||
Angular framework so that reusable components and libraries, tools and learned practices
|
|
||||||
don’t go obsolete unexpectedly...</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/igor-minar.jpg">
|
|
||||||
<div class="posted">Posted by <b>Igor Minar</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
|
||||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Developer Community</h3>
|
|
||||||
</div>
|
|
||||||
<div class="clear"></div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-fluid">
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Oct 30, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="https://www.thepolyglotdeveloper.com/2016/10/use-pre-populated-sqlite-database-nativescript-angular-2/">Use
|
|
||||||
A Pre-Populated SQLite Database With NativeScript And Angular 2</a></div>
|
|
||||||
<p>I figured it would be a good idea to demonstrate how to ship a NativeScript Angular 2
|
|
||||||
application with a pre-filled SQLite database rather than populating it on-the-fly....</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
|
||||||
<div class="posted">Posted by <b>Nic Raboy</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Oct 13, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="http://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html">Two-way
|
|
||||||
Data Binding in Angular 2</a></div>
|
|
||||||
<p>If there was one feature in Angular that made us go “Wow”, then it was probably its
|
|
||||||
two-way data binding system. Changes in the application state have been automagically
|
|
||||||
reflected into the view...</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/angular-gde-bio-placeholder.png">
|
|
||||||
<div class="posted">Posted by <b>Pascal Precht</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-fluid">
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Oct 10, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="http://www.creativebloq.com/how-to/build-a-material-design-app-with-angular-2">Build
|
|
||||||
a Material Design app with Angular 2</a></div>
|
|
||||||
<p>This walkthrough reveals how to create a DialogComponent and to-do app with Angular
|
|
||||||
Material and the Angular CLI...</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
|
||||||
<div class="posted">Posted by <b>Daniel Zen</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="date">Sept 30, 2016</div>
|
|
||||||
<div class="title"><a target="_blank"
|
|
||||||
href="http://www.simb.co/angular-cli-using-docker/?platform=hootsuite">Using
|
|
||||||
Angular CLI to create Angular 2 applications in Docker</a></div>
|
|
||||||
<p>Angular CLI is a great tool for developing Angular 2 applications. I thought it would be
|
|
||||||
fun to do a quick demo...</p>
|
|
||||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
|
||||||
<div class="posted">Posted by <b>Simeon Bateman</b></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
|
||||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase">Twitter</h3></div>
|
|
||||||
<div class="clear"></div>
|
|
||||||
<div class="grid-fluid">
|
|
||||||
<div class="c3"><p></p></div>
|
|
||||||
<div class="c6">
|
|
||||||
<div class="article-card">
|
|
||||||
<div class="title"><a href="http://twitter.com/angularjs" data-show-count="false"
|
|
||||||
class="twitter-follow-button">Follow @angularjs</a></div>
|
|
||||||
<p><a class="twitter-timeline" data-chrome="nofooter noborders noheader"
|
|
||||||
href="http://twitter.com/angularjs" data-widget-id="700150278465523713"></a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
13
aio/content/marketing/resources-contributing.md
Normal file
13
aio/content/marketing/resources-contributing.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Contributing to resources.json
|
||||||
|
|
||||||
|
## About this list
|
||||||
|
We maintain a small list of some of the top Angular resources from across the community, stored in `resources.json`. This list is not intended to be comprehensive, but to act as a starting point to connect Angular developers to the rest of the community.
|
||||||
|
|
||||||
|
## How do I get listed?
|
||||||
|
While we can't accept all contributions, qualifying contributions can be submitted via a PR adding yourself to the `resources.json` file. All contributions should be in the appropriate section and must meet the following criteria:
|
||||||
|
|
||||||
|
1. Your contribution must be valid, and contain a link to a page talking specifically about using Angular
|
||||||
|
1. Your contribution should have a clear and concise title and description
|
||||||
|
1. Your resource should follow our brand guidelines (see our [Presskit](https://angular.io/presskit))
|
||||||
|
1. Your resource should have significant benefit to Angular developers
|
||||||
|
1. Your resource should already have traction and praise from Angular developers
|
@ -227,6 +227,12 @@
|
|||||||
"UI Components": {
|
"UI Components": {
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"resources": {
|
"resources": {
|
||||||
|
"DevExtreme": {
|
||||||
|
"desc": "50+ UI components including data grid, pivot grid, scheduler, charts, editors, maps and other multi-purpose controls for creating highly responsive web applications for touch devices and traditional desktops.",
|
||||||
|
"rev": true,
|
||||||
|
"title": "DevExtreme",
|
||||||
|
"url": "https://js.devexpress.com/Overview/Angular/"
|
||||||
|
},
|
||||||
"234237": {
|
"234237": {
|
||||||
"desc": "UX guidelines, HTML/CSS framework, and Angular components working together to craft exceptional experiences",
|
"desc": "UX guidelines, HTML/CSS framework, and Angular components working together to craft exceptional experiences",
|
||||||
"rev": true,
|
"rev": true,
|
||||||
@ -310,7 +316,14 @@
|
|||||||
"desc": "Angular UI Components including Grids, Charts, Scheduling and more.",
|
"desc": "Angular UI Components including Grids, Charts, Scheduling and more.",
|
||||||
"rev": true,
|
"rev": true,
|
||||||
"title": "jQWidgets",
|
"title": "jQWidgets",
|
||||||
"url": "https://www.jqwidgets.com/angular/"
|
"url": "http://www.jqwidgets.com/angular/"
|
||||||
|
},
|
||||||
|
"amexio": {
|
||||||
|
"desc": "Amexio (Angular MetaMagic EXtensions for Inputs and Outputs) is a rich set of Angular components powered by Bootstrap for Responsive Design. UI Components include Standard Form Components, Data Grids, Tree Grids, Tabs etc. Open Source (Apache 2 License) & Free and backed by MetaMagic Global Inc",
|
||||||
|
"rev": true,
|
||||||
|
"title": "Amexio - Angular Extensions",
|
||||||
|
"url": "http://www.amexio.tech/",
|
||||||
|
"logo": "http://www.amexio.org/amexio-logo.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,12 +515,6 @@
|
|||||||
"title": "Learn Angular (francais)",
|
"title": "Learn Angular (francais)",
|
||||||
"url": "http://www.learn-angular.fr/"
|
"url": "http://www.learn-angular.fr/"
|
||||||
},
|
},
|
||||||
"sad200": {
|
|
||||||
"desc": "Free Angular training delivered by SFEIR in France",
|
|
||||||
"rev": true,
|
|
||||||
"title": "SFEIR School (French)",
|
|
||||||
"url": "https://school.sfeir.com/project/sad-200/"
|
|
||||||
},
|
|
||||||
"toddmotto-ultimateangular": {
|
"toddmotto-ultimateangular": {
|
||||||
"desc": "Online courses providing in-depth coverage of the Angular ecosystem, AngularJS, Angular and TypeScript, with functional code samples and a full-featured seed environment. Get a deep understanding of Angular and TypeScript from foundation to functional application, then move on to advanced topics with Todd Motto and collaborators.",
|
"desc": "Online courses providing in-depth coverage of the Angular ecosystem, AngularJS, Angular and TypeScript, with functional code samples and a full-featured seed environment. Get a deep understanding of Angular and TypeScript from foundation to functional application, then move on to advanced topics with Todd Motto and collaborators.",
|
||||||
"rev": true,
|
"rev": true,
|
||||||
@ -594,6 +601,12 @@
|
|||||||
"rev": true,
|
"rev": true,
|
||||||
"title": "Learn Javascript (Russian)",
|
"title": "Learn Javascript (Russian)",
|
||||||
"url": "https://learn.javascript.ru/courses/angular"
|
"url": "https://learn.javascript.ru/courses/angular"
|
||||||
|
},
|
||||||
|
"sa200": {
|
||||||
|
"desc": "Free Angular training delivered by SFEIR in France",
|
||||||
|
"rev": true,
|
||||||
|
"title": "SFEIR School (French)",
|
||||||
|
"url": "https://school.sfeir.com/project/sa200/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/http",
|
"url": "guide/http",
|
||||||
"title": "Server Communication",
|
"title": "HttpClient",
|
||||||
"tooltip": "Use HTTP to talk to a remote server."
|
"tooltip": "Use HTTP to talk to a remote server."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -470,6 +470,8 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"docVersions": [
|
"docVersions": [
|
||||||
{ "title": "v2", "url": "https://v2.angular.io" }
|
{ "title": "v2", "url": "https://v2.angular.io" },
|
||||||
|
{ "title": "AngularDart", "url": "https://webdev.dartlang.org/angular" }
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ Here's the complete `HeroDetailComponent`.
|
|||||||
|
|
||||||
|
|
||||||
## Declare _HeroDetailComponent_ in the _AppModule_
|
## Declare _HeroDetailComponent_ in the _AppModule_
|
||||||
Every component must be declared in one—and only one—Angular module.
|
Every component must be declared in one—and only one—NgModule.
|
||||||
|
|
||||||
Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it.
|
Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it.
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ This module declares only the two application components, `AppComponent` and `He
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modules") guide.
|
Read more about NgModules in the [NgModules](guide/ngmodule "NgModules") guide.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -449,8 +449,8 @@ Here's what you achieved in this page:
|
|||||||
|
|
||||||
* You created a reusable component.
|
* You created a reusable component.
|
||||||
* You learned how to make a component accept input.
|
* You learned how to make a component accept input.
|
||||||
* You learned to declare the required application directives in an Angular module. You
|
* You learned to declare the required application directives in an NgModule. You
|
||||||
listed the directives in the `NgModule` decorator's `declarations` array.
|
listed the directives in the `@NgModule` decorator's `declarations` array.
|
||||||
* You learned to bind a parent component to a child component.
|
* You learned to bind a parent component to a child component.
|
||||||
|
|
||||||
Your app should look like this <live-example></live-example>.
|
Your app should look like this <live-example></live-example>.
|
||||||
|
@ -30,7 +30,7 @@ You can keep building the Tour of Heroes without pausing to recompile or refresh
|
|||||||
|
|
||||||
## Providing HTTP Services
|
## Providing HTTP Services
|
||||||
|
|
||||||
The `HttpModule` is not a core Angular module.
|
The `HttpModule` is not a core NgModule.
|
||||||
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
|
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
|
||||||
and is shipped in a separate script file as part of the Angular npm package.
|
and is shipped in a separate script file as part of the Angular npm package.
|
||||||
|
|
||||||
@ -83,11 +83,7 @@ Added hero "Zero" to confirm that the data service can handle a hero with `id==0
|
|||||||
Don't worry about the details of this backend substitution; you can
|
Don't worry about the details of this backend substitution; you can
|
||||||
skip it when you have a real web API server.
|
skip it when you have a real web API server.
|
||||||
|
|
||||||
Read more about the in-memory web API in the
|
div>
|
||||||
[Appendix: Tour of Heroes in-memory web api](guide/http#in-mem-web-api)
|
|
||||||
section of the [HTTP Client](guide/http#in-mem-web-api) page.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Heroes and HTTP
|
## Heroes and HTTP
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"redirects": [
|
"redirects": [
|
||||||
// cli-quickstart.html glossary.html, quickstart.html, http.html, style-guide.html, styleguide
|
// cli-quickstart.html glossary.html, quickstart.html, http.html, style-guide.html, styleguide
|
||||||
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/cli-quickstart"},
|
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
|
||||||
|
{"type": 301, "source": "/guide/cli-quickstart", "destination": "/guide/quickstart"},
|
||||||
{"type": 301, "source": "/docs/ts/latest/glossary.html", "destination": "/guide/glossary"},
|
{"type": 301, "source": "/docs/ts/latest/glossary.html", "destination": "/guide/glossary"},
|
||||||
{"type": 301, "source": "/docs/ts/latest/quickstart.html", "destination": "/guide/quickstart"},
|
{"type": 301, "source": "/docs/ts/latest/quickstart.html", "destination": "/guide/quickstart"},
|
||||||
{"type": 301, "source": "/docs/ts/latest/guide/server-communication.html", "destination": "/guide/http"},
|
{"type": 301, "source": "/docs/ts/latest/guide/server-communication.html", "destination": "/guide/http"},
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"routing": {
|
"routing": {
|
||||||
"index": "/index.html",
|
"index": "/index.html",
|
||||||
"routes": {
|
"routes": {
|
||||||
"^(?!/docs/ts/latest|/styleguide).*/(?!e?plnkr)[^/.]*$": {
|
"^(?!/docs/ts/latest|/guide/cli-quickstart|/styleguide).*/(?!e?plnkr)[^/.]*$": {
|
||||||
"match": "regex"
|
"match": "regex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
||||||
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
||||||
"docs-test": "node tools/transforms/test.js",
|
"docs-test": "node tools/transforms/test.js",
|
||||||
|
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
||||||
"boilerplate:add": "node ./tools/examples/add-example-boilerplate add",
|
"boilerplate:add": "node ./tools/examples/add-example-boilerplate add",
|
||||||
"boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove",
|
"boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove",
|
||||||
@ -47,32 +48,31 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "4.2.1",
|
"@angular/animations": "^4.2.4",
|
||||||
"@angular/common": "4.2.1",
|
"@angular/common": "^4.2.4",
|
||||||
"@angular/compiler": "4.2.1",
|
"@angular/compiler": "^4.2.4",
|
||||||
"@angular/core": "4.2.1",
|
"@angular/core": "^4.2.4",
|
||||||
"@angular/forms": "4.2.1",
|
"@angular/forms": "^4.2.4",
|
||||||
"@angular/http": "4.2.1",
|
"@angular/http": "^4.2.4",
|
||||||
"@angular/material": "^2.0.0-beta.7",
|
"@angular/material": "^2.0.0-beta.7",
|
||||||
"@angular/platform-browser": "4.2.1",
|
"@angular/platform-browser": "^4.2.4",
|
||||||
"@angular/platform-browser-dynamic": "4.2.1",
|
"@angular/platform-browser-dynamic": "^4.2.4",
|
||||||
"@angular/platform-server": "4.2.1",
|
"@angular/platform-server": "^4.2.4",
|
||||||
"@angular/router": "4.2.1",
|
"@angular/router": "^4.2.4",
|
||||||
"@angular/service-worker": "^1.0.0-beta.16",
|
"@angular/service-worker": "^1.0.0-beta.16",
|
||||||
"classlist.js": "^1.1.20150312",
|
"classlist.js": "^1.1.20150312",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"jasmine": "^2.6.0",
|
"jasmine": "^2.6.0",
|
||||||
"ng-pwa-tools": "^0.0.10",
|
"ng-pwa-tools": "^0.0.10",
|
||||||
"ngo-loader": "alxhub/ngo",
|
"ngo": "angular/ngo",
|
||||||
"purify": "igorminar/purify",
|
|
||||||
"rxjs": "^5.2.0",
|
"rxjs": "^5.2.0",
|
||||||
"tslib": "^1.7.1",
|
"tslib": "^1.7.1",
|
||||||
"web-animations-js": "^2.2.5",
|
"web-animations-js": "^2.2.5",
|
||||||
"zone.js": "^0.8.4"
|
"zone.js": "^0.8.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "angular/cli-builds#webpack-next",
|
"@angular/cli": "angular/cli-builds#webpack-next",
|
||||||
"@angular/compiler-cli": "4.2.1",
|
"@angular/compiler-cli": "^4.2.4",
|
||||||
"@types/jasmine": "^2.5.52",
|
"@types/jasmine": "^2.5.52",
|
||||||
"@types/node": "~6.0.60",
|
"@types/node": "~6.0.60",
|
||||||
"archiver": "^1.3.0",
|
"archiver": "^1.3.0",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"concurrently": "^3.4.0",
|
"concurrently": "^3.4.0",
|
||||||
"cross-spawn": "^5.1.0",
|
"cross-spawn": "^5.1.0",
|
||||||
"dgeni": "^0.4.7",
|
"dgeni": "^0.4.7",
|
||||||
"dgeni-packages": "^0.19.1",
|
"dgeni-packages": "^0.20.0-rc.6",
|
||||||
"entities": "^1.1.1",
|
"entities": "^1.1.1",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-plugin-jasmine": "^2.2.0",
|
"eslint-plugin-jasmine": "^2.2.0",
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
set -u -e -o pipefail
|
set -u -e -o pipefail
|
||||||
|
|
||||||
declare -A limitUncompressed
|
declare -A limitUncompressed
|
||||||
limitUncompressed=(["inline"]=1600 ["main"]=600000 ["polyfills"]=40000)
|
limitUncompressed=(["inline"]=1600 ["main"]=600000 ["polyfills"]=35000)
|
||||||
declare -A limitGzip7
|
declare -A limitGzip7
|
||||||
limitGzip7=(["inline"]=1000 ["main"]=140000 ["polyfills"]=12000)
|
limitGzip7=(["inline"]=1000 ["main"]=140000 ["polyfills"]=12500)
|
||||||
declare -A limitGzip9
|
declare -A limitGzip9
|
||||||
limitGzip9=(["inline"]=1000 ["main"]=140000 ["polyfills"]=12000)
|
limitGzip9=(["inline"]=1000 ["main"]=140000 ["polyfills"]=12500)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user