Compare commits
776 Commits
Author | SHA1 | Date | |
---|---|---|---|
bf656d64b8 | |||
3b183ce9b5 | |||
f635c3ecec | |||
dd6e8424aa | |||
1035c6a3ee | |||
b49b274a16 | |||
a4ebf3fb6d | |||
9997ab5ef9 | |||
c109aada2c | |||
57ce13aa1c | |||
2fd127d000 | |||
2f28e6a62d | |||
bf1a13e5e1 | |||
ab9e114fee | |||
448ab9c465 | |||
d769b441c0 | |||
50ccbe744d | |||
b29e709208 | |||
017a087f9b | |||
4b522572e6 | |||
3808434eec | |||
34aff0b3a1 | |||
7cb4183f58 | |||
960d32dd4f | |||
be0382b50c | |||
7ac4b76336 | |||
00b5c7b49b | |||
96f38562bd | |||
94b98aa819 | |||
1c9b06504b | |||
dd8a85158e | |||
164f79a7b0 | |||
327c614799 | |||
1aa8cfbf74 | |||
95d0626a1e | |||
5183bbffbe | |||
e76a570908 | |||
9afc9a7464 | |||
931e603f80 | |||
45732e5b91 | |||
b2db32b715 | |||
9e32dc7c95 | |||
a19b690338 | |||
bab5b68910 | |||
3daeadd235 | |||
ff15043e48 | |||
ea20ae63d0 | |||
7777a99fe5 | |||
9ebb4c02a2 | |||
7fbeb04b7c | |||
42ee50fc22 | |||
163b7c94a9 | |||
071934e92a | |||
735dfd3b1a | |||
70cd112872 | |||
989555352d | |||
99736750fc | |||
0a3f8173f0 | |||
6a64ac4151 | |||
062fe5c2cf | |||
4b494f23f5 | |||
bd186c7ef9 | |||
1657c997cd | |||
1e69d601fb | |||
34b6d5fff9 | |||
632f66a461 | |||
f7b17a4784 | |||
9562324ea4 | |||
880c0add56 | |||
64c96186da | |||
26209fca49 | |||
7f03528dbc | |||
d17602f31d | |||
7d0e17530b | |||
053bf27fb3 | |||
39f42bad1c | |||
3f8ac238f1 | |||
e0e2038718 | |||
67608a907e | |||
7acdad6921 | |||
7466a99dda | |||
912f3d186f | |||
e26cb21e4e | |||
be4edf15ee | |||
d5e9405d4f | |||
d1b7bb52e7 | |||
1312693f88 | |||
72ff9c880c | |||
3060b3e29b | |||
ee28b64d74 | |||
1246ba53c7 | |||
c29ff722a0 | |||
67ad9468d3 | |||
f922808b8d | |||
67435d456c | |||
fbfce79b93 | |||
2a14dfa4ba | |||
8e71ad6027 | |||
25289664ea | |||
e5644204dc | |||
69b9758ab8 | |||
7ea5161d4d | |||
b0879046b7 | |||
456f23f76a | |||
9623e7c639 | |||
83302d193e | |||
807070fe83 | |||
3ac8a63499 | |||
6a24db2bc6 | |||
50d1cba174 | |||
44c05c05af | |||
7d08722e80 | |||
13cdd13511 | |||
9320ec0f43 | |||
5dd225cb43 | |||
1b1c8ee545 | |||
10e414f617 | |||
b46fa92ae5 | |||
0bdea1f69c | |||
5a31bde649 | |||
decc0b840d | |||
55d54c7e97 | |||
ccceff5ecc | |||
6c6bc95ac0 | |||
c9cfcfa728 | |||
f543d71cc3 | |||
3a5cb1cb11 | |||
ab6f055479 | |||
3683c6a188 | |||
4006c9b6e6 | |||
245b85f72a | |||
bfeed842ee | |||
77942cc690 | |||
7aed64d3a1 | |||
9953fe7c00 | |||
3cce4afa0d | |||
8f25321787 | |||
51dfdd5dd1 | |||
fdaf573073 | |||
35bf95281f | |||
7a78889994 | |||
19c4e705ff | |||
36d6e6076e | |||
868047e87f | |||
5f1273ba2e | |||
355a7cae3c | |||
4c615f7de7 | |||
79466baef8 | |||
b0070dfb9a | |||
9ed4e3df60 | |||
a2da485d90 | |||
9cb17ecc39 | |||
35936864bc | |||
532e53678d | |||
f859d83298 | |||
ac68c75e26 | |||
fe45b9cebd | |||
aaaa34021c | |||
730679964f | |||
cb59d87489 | |||
d216a46412 | |||
a2878b0b1d | |||
5977b72e9c | |||
7373da9b11 | |||
783a682a7d | |||
d22418d417 | |||
4b1fd98093 | |||
935ef13096 | |||
f4b60588fb | |||
15dadb92ef | |||
ce06a75ebf | |||
9889276b15 | |||
d0f7eadc09 | |||
4b132c9848 | |||
46729c76a0 | |||
f22deb2e2d | |||
57de9fc41a | |||
31034f5146 | |||
c5899f4cd4 | |||
ab379ab72a | |||
32e479ffec | |||
391c708d7e | |||
c51331689f | |||
68fadd9b97 | |||
2ad1bb4eb9 | |||
794c3595d4 | |||
b807106f54 | |||
86e6a2099a | |||
9993c72335 | |||
f455518d80 | |||
7cf5807100 | |||
9523991a9b | |||
9acd04c192 | |||
c091d40fb0 | |||
b7baf632c0 | |||
4c0d4fc649 | |||
5b3c08b237 | |||
68f2e0c391 | |||
9c1c945489 | |||
ef5338663d | |||
380b3d7653 | |||
4decc8521d | |||
4d544bcb46 | |||
4c819f79b2 | |||
ac3252a73b | |||
a08af77b70 | |||
aac08e0438 | |||
63b795ae4a | |||
5f6900ecc0 | |||
325e8010e9 | |||
632b19d5c2 | |||
add1198b88 | |||
0ed2df2a36 | |||
bc1f2d6411 | |||
d7326d81ba | |||
c683f74225 | |||
b286abeabe | |||
eeebe28c0f | |||
ffc6e199bf | |||
a01acec7fe | |||
021f4344b1 | |||
f113b49909 | |||
d8d276c245 | |||
e42bd012f9 | |||
6d6b0ff1ad | |||
f378454c92 | |||
c8c8436e58 | |||
b31c8b6063 | |||
897261efdc | |||
35d70ff265 | |||
fc0a7959a4 | |||
182c08bee1 | |||
e2bc0ad6c2 | |||
73333ee3e5 | |||
c819859598 | |||
79aefa7659 | |||
e1990a5a80 | |||
4cff5b2964 | |||
459758231b | |||
f29b218060 | |||
39a67548ac | |||
bc88f318f6 | |||
a5b7008c8e | |||
0aafbac99b | |||
ac5aa8f46d | |||
1fb3c4ffee | |||
3c8aa0b301 | |||
15a2b8f622 | |||
d19108531c | |||
354d1944bb | |||
eaccd03ed7 | |||
343df337f4 | |||
9b14483824 | |||
bd42caf1c7 | |||
7db8111973 | |||
74fef157e6 | |||
9661bed3ba | |||
95168e4de0 | |||
13c3e241c8 | |||
8d098d389a | |||
79a2567aa3 | |||
5649acd03f | |||
ebd01e8e79 | |||
e08955b557 | |||
04dfca41f4 | |||
129f69c3bc | |||
c7e2930f25 | |||
6a62ed2245 | |||
482e12c940 | |||
0c344715e5 | |||
cf095d982d | |||
23ec88ef23 | |||
2bd767c4a6 | |||
1e02cd9961 | |||
bc02e19831 | |||
47eb2122c0 | |||
b8422b41bb | |||
8ac4dd6447 | |||
206ae7a233 | |||
79b6256789 | |||
72dce34f42 | |||
7d39bc68fb | |||
32ad2438ca | |||
fc4b993d98 | |||
ff028f0b39 | |||
fef9cebed0 | |||
c08549ae38 | |||
3808416479 | |||
cf8ad24dcf | |||
cee7448efc | |||
7f1cace2a2 | |||
56c86c7e79 | |||
82a14dc107 | |||
a880686081 | |||
026b60cd70 | |||
cea2e0477c | |||
96f9f03d25 | |||
9931bd7576 | |||
48094835bf | |||
e42c1b0da8 | |||
e7ade38731 | |||
d5f47d6b71 | |||
64aa6701f6 | |||
12ccf57340 | |||
c634176035 | |||
4bb10d224c | |||
5d689469f6 | |||
855ad8804e | |||
29d3f3f6dd | |||
33101359c6 | |||
44eef5c343 | |||
68b7847b4c | |||
2b2e841e5b | |||
549de1e21a | |||
48e73c1558 | |||
41ac58ab7d | |||
f37cf52b4c | |||
927323f24e | |||
b94436d86c | |||
bc5cb8153e | |||
34b848ad51 | |||
d7e5bbf2d0 | |||
a9a81f91cf | |||
07c10e2844 | |||
df5999a739 | |||
3fb0da2de5 | |||
8f0fcc3f71 | |||
ca1e56dc8b | |||
d0e710d472 | |||
bc7f962039 | |||
78d42a9503 | |||
dd5e35ee67 | |||
f91b0455c0 | |||
e8bab1349f | |||
e952c65759 | |||
5241ea086d | |||
cbbad1b791 | |||
96ee898cee | |||
2c40a86b61 | |||
a53a559f5a | |||
6de393b2b8 | |||
56283ed594 | |||
ddd3bf83c7 | |||
9b1bb370a3 | |||
976389836e | |||
f76a9ad156 | |||
6f1100a7e9 | |||
b99d7ed5bf | |||
f47f2628e1 | |||
5653874683 | |||
21e566d9bc | |||
bdbb2f9bfa | |||
2e32d4ee17 | |||
8f81dba367 | |||
aedebaf025 | |||
47f4412650 | |||
a09c3923db | |||
10a656fc38 | |||
8dc2b119fb | |||
f2ba55f2fb | |||
00d3666d95 | |||
21009b06a1 | |||
379e8c5e19 | |||
f3b552f51f | |||
c5b594e351 | |||
86a3be8610 | |||
d5bd86ae5d | |||
a9099e8f70 | |||
96d6b79ada | |||
13ccdfd89d | |||
a0c4b2d8f0 | |||
7ba0cb7c93 | |||
d83f9d432a | |||
e3633888ed | |||
ed266daf2c | |||
89af5291de | |||
91d79939be | |||
96eb79b1c7 | |||
83a1334876 | |||
2a21ca09d2 | |||
ddc13352e9 | |||
62be8c2e2f | |||
d2dfd48be0 | |||
d6cd041cbd | |||
694b8ae779 | |||
152de20774 | |||
34ec9244a6 | |||
268e9772d5 | |||
e2c67ba155 | |||
dcad0544d7 | |||
1bc6f64eb5 | |||
397b047eff | |||
d96e962da3 | |||
b0cb134815 | |||
2a672a97ab | |||
71007ef9b2 | |||
51c26b8afb | |||
1e7a873cf4 | |||
13b8399d0c | |||
010e35d995 | |||
234661b3d6 | |||
4a04ab8823 | |||
a417b2b419 | |||
2c66523222 | |||
25c1f331d6 | |||
59aab14394 | |||
c1ae3c16e8 | |||
51c0d9cae9 | |||
08dfbc5475 | |||
2f1bc1aa1a | |||
cc29b9cf93 | |||
bd0eb0d1d4 | |||
e84da1981d | |||
abd29f5049 | |||
6def18a95e | |||
34be51898d | |||
1e3460be0b | |||
31349fde90 | |||
910381ddbd | |||
20b9c61d4c | |||
e964319fe9 | |||
26cd9f5433 | |||
e73e864f87 | |||
73047483a1 | |||
6f168b7a0f | |||
a469c2c412 | |||
38f624d7e3 | |||
b424b3187e | |||
00f13110be | |||
ccb4a396f0 | |||
d539122466 | |||
b89a7dd4a2 | |||
9533cc9809 | |||
06d04002fd | |||
6143da66b2 | |||
a080ffc743 | |||
a8210d010b | |||
c9844a2f01 | |||
4815b92495 | |||
d76a7d6f7c | |||
6e6489a408 | |||
1d8e821276 | |||
6e828bba88 | |||
1f59f2f04d | |||
371df35624 | |||
3809e0fcae | |||
b06f1c0087 | |||
2379ad1a4b | |||
dd2a650c34 | |||
72b3b27348 | |||
f394ba0e27 | |||
268cf7989c | |||
9ee29ecdd4 | |||
42072c4d0d | |||
29761ea5f8 | |||
a22fb91e1a | |||
0386c44acc | |||
0fe708ff82 | |||
b069514818 | |||
0024d68add | |||
317d40d879 | |||
ab32ac6bb7 | |||
5653fada32 | |||
c230173716 | |||
366195e182 | |||
b1902db0cb | |||
a081d207f8 | |||
6ed79934c4 | |||
6a0f78fabf | |||
3634575d89 | |||
f596930c8c | |||
b3c56f021f | |||
864c22fb79 | |||
4d83a0a789 | |||
2569fd4d75 | |||
c73ebe79b3 | |||
110ab2230b | |||
4a61cb3fab | |||
4dc5afb5c6 | |||
8c2c6b5d1a | |||
f71cce7f9b | |||
53c1efb50a | |||
b6ccd9f7bd | |||
8b614d4e1b | |||
7f905da335 | |||
be24f9f0cb | |||
66ffa360df | |||
9bcd8c2425 | |||
8fa099158e | |||
b00038c847 | |||
18f129f536 | |||
efb453cb73 | |||
658f49f650 | |||
a37bcc3bfe | |||
a59d4da304 | |||
22e7f7e99f | |||
3d41739021 | |||
668bfcec9d | |||
eb1fe19088 | |||
22d58fc89b | |||
27e2039630 | |||
5c95b4b3a3 | |||
61218f5f0b | |||
7500f0eafb | |||
68acc5b355 | |||
7d3b70c2af | |||
7ce291c72a | |||
6ae1e63c89 | |||
c8baace554 | |||
d33e0091df | |||
b97d770e60 | |||
a3158bff27 | |||
d6e91ba545 | |||
cdb0215d0b | |||
396766104b | |||
e77f0fd6e6 | |||
cdd4c9be63 | |||
29705dd8f2 | |||
ea68ba048a | |||
9081efa961 | |||
9e179cb311 | |||
3211432d2a | |||
a7134dbc37 | |||
a528636f56 | |||
f87b499dde | |||
a45f2bfb8f | |||
94332affd3 | |||
52605aa2d8 | |||
4e36f0cd68 | |||
d5b70e0c66 | |||
f33dbf42fd | |||
6176974832 | |||
69b57b2dca | |||
831e71ea3c | |||
fc89479044 | |||
f54f3856cb | |||
c8b70ae8e4 | |||
9ed3bd6b4f | |||
11e2d9da1a | |||
b05d4a5007 | |||
469d5e0448 | |||
21a14407f6 | |||
d2be3d5775 | |||
f2aa9c6a7f | |||
4708cb91ef | |||
21d22ce4ad | |||
e9026a5201 | |||
f053a3f274 | |||
31f0f5b3c3 | |||
07d8d3994c | |||
116946fb11 | |||
503905c807 | |||
cc55d609ce | |||
de03abbd34 | |||
6482f6f0fe | |||
abcc430310 | |||
9605456b66 | |||
9ee6702fa9 | |||
92c8752d0a | |||
68bfe686d8 | |||
d604ef7cf0 | |||
36c4c8daa9 | |||
cc6f36a9d7 | |||
643766637e | |||
a800a5118a | |||
3b8b7f4087 | |||
9b820555a3 | |||
364459c576 | |||
8347bb0d2d | |||
4ce70b9edf | |||
1f1103913a | |||
01ec5fd6b0 | |||
be2cf4dfd6 | |||
eeb81b9370 | |||
b5f354f2fb | |||
a0a29fdd27 | |||
26066f282e | |||
b40c437379 | |||
82e2725154 | |||
c13901f4c8 | |||
6a2130117f | |||
4e45f2c481 | |||
78f477652e | |||
98f336c0fb | |||
9117fa199c | |||
0c4209f4b9 | |||
14ac7ad6b4 | |||
85106375ac | |||
ecb5dc03f9 | |||
bbb3f8fa60 | |||
09711507f9 | |||
3ac7070009 | |||
c869b143c6 | |||
e0314b5d90 | |||
1bb30147d3 | |||
fb2c5241fc | |||
97d8b5ed88 | |||
4a4d6fb0e6 | |||
2016afdbff | |||
c8c1aa7fc0 | |||
409860a4da | |||
2b128a47b9 | |||
209cc7e1b0 | |||
2d759927d4 | |||
7058072ff6 | |||
33fd7e0784 | |||
2befc65777 | |||
6f085f8610 | |||
5be186035f | |||
fba276d3d1 | |||
9c92a6fc7a | |||
6c4da9dcd3 | |||
b64fed1ba3 | |||
8bbce3feff | |||
6c359afce6 | |||
a3f1e2cb42 | |||
090824526b | |||
bfdbdc2ee6 | |||
638ff760a5 | |||
74518c4b2e | |||
ebf508fcd0 | |||
1039bea53b | |||
a2593cbfb1 | |||
70a3deb8a5 | |||
843479449d | |||
732026c3f5 | |||
ec6d6175d2 | |||
af9ced9026 | |||
c6e5b971d6 | |||
dbdbbdbe86 | |||
fefc860f35 | |||
02e201ab1a | |||
7bf5a43385 | |||
2fe05abbc4 | |||
2505c077d7 | |||
066fc6a0ca | |||
07ab98bbb0 | |||
16c03c0f38 | |||
3355502f2f | |||
02c15a2448 | |||
6861bc5b06 | |||
b8887ddf16 | |||
4933e103d3 | |||
7d006c5005 | |||
67ad59c245 | |||
397530ab24 | |||
a9881bb18e | |||
c1587029db | |||
2b906f652f | |||
c67f1bb38e | |||
637ae135c5 | |||
4eb8ac6de9 | |||
ba1e25f53f | |||
97b5cb2e3b | |||
795e1e8a38 | |||
aea8832243 | |||
1e7ca22078 | |||
26a15cc534 | |||
b0d86c1c2f | |||
dc0715142f | |||
4e264781ee | |||
79a9c71422 | |||
15cc85c54a | |||
725bae1921 | |||
eb999300d9 | |||
afa6b9e794 | |||
0822dc70f2 | |||
728d98d3a9 | |||
2f4abbf5a1 | |||
1000fb8406 | |||
b38931b484 | |||
1fb7111da1 | |||
c2c12e52fe | |||
28c7a4efbc | |||
4f741e74e1 | |||
6bacd32fbd | |||
183757daa2 | |||
adf510f986 | |||
74bce18190 | |||
3ba5220839 | |||
5982425436 | |||
140248ade0 | |||
e60737f63c | |||
7b89711402 | |||
f1223628a6 | |||
1b4269ad85 | |||
a224df43af | |||
d46a961509 | |||
20b453008f | |||
06a1974a48 | |||
3d7f555044 | |||
06af7943a4 | |||
fa70a2a650 | |||
bde4402675 | |||
7d6b258778 | |||
5342aeaafd | |||
1dd2eaa7d2 | |||
af07ffc2ad | |||
2b6e1f0f4b | |||
7a4fb44f8d | |||
88da8f3d52 | |||
01e6dab544 | |||
a9ecf4b929 | |||
166ddaadca | |||
1e5327872d | |||
367841d237 | |||
d5b73832bf | |||
7075c418f9 | |||
466e026f6f | |||
4976a58780 | |||
bafe1a0d2a | |||
c8a4fb1faf | |||
64516da6b0 | |||
6e2a1877ab | |||
aafd502bcb | |||
4cb1074850 | |||
76d8eb021c | |||
3f6fc00d73 | |||
5254d3447d | |||
4ee9db959a | |||
3f20a2fb5a | |||
e3834b7001 | |||
36648293a8 | |||
cd89eb8404 | |||
e99d860393 | |||
24789e9ad9 | |||
f94f9640d0 | |||
4d5167ec83 | |||
efc6684cd3 | |||
2ef777b0b2 | |||
fe14f180a6 | |||
87419097da | |||
9a6d26e05b | |||
6a797d5401 | |||
89e8b6fc0e | |||
f82b6b2ed7 | |||
a87d44c187 | |||
43d0e3dd72 | |||
5b32aa4486 | |||
844d510d3f | |||
2f70e90493 | |||
45cf5b5dad | |||
4ad2f11919 | |||
d7aa20d912 | |||
a673494412 | |||
07e6de5788 | |||
6f1685ab98 | |||
67588ec606 | |||
ee2c050521 | |||
185b932138 | |||
5e98421d33 | |||
8e65891985 | |||
7f59170f77 | |||
9ea112473b | |||
16f0ac38b8 | |||
15df853622 | |||
d3c0915598 | |||
ce98634dfd | |||
342678486d | |||
e8d4211d5c | |||
6a4d66d432 | |||
a3cf61b7cf | |||
a1b185b723 | |||
601064e41d | |||
e265ccd82c | |||
dd44f63c73 | |||
1d051c5841 | |||
323faf954b | |||
3169edd77a | |||
8de304c15a | |||
0d1d5898e3 | |||
6fe865b080 | |||
e0c0c44d99 | |||
13a0d527f6 | |||
ed7aa1c3e5 | |||
f902b5ec59 |
@ -20,18 +20,6 @@ build --announce_rc
|
||||
# We use this when uploading artifacts after the build finishes
|
||||
build --symlink_prefix=dist/
|
||||
|
||||
# Enable experimental CircleCI bazel remote cache proxy
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
build --experimental_remote_spawn_cache --remote_rest_cache=http://localhost:7643
|
||||
|
||||
# Prevent unstable environment variables from tainting cache keys
|
||||
build --experimental_strict_action_env
|
||||
|
||||
# Save downloaded repositories such as the go toolchain
|
||||
# This directory can then be included in the CircleCI cache
|
||||
# It should save time running the first build
|
||||
build --experimental_repository_cache=/home/circleci/bazel_repository_cache
|
||||
|
||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
||||
@ -40,3 +28,6 @@ build --local_resources=14336,8.0,1.0
|
||||
|
||||
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
||||
test --flaky_test_attempts=2
|
||||
|
||||
# More details on failures
|
||||
build --verbose_failures=true
|
||||
|
@ -26,6 +26,11 @@ var_4: &setup-bazel-remote-cache
|
||||
command: ~/bazel-remote-proxy -backend circleci://
|
||||
background: true
|
||||
|
||||
var_5: &setup_bazel_remote_execution
|
||||
run:
|
||||
name: "Setup bazel RBE remote execution"
|
||||
command: openssl aes-256-cbc -d -in .circleci/gcp_token -k "${CIRCLE_PROJECT_REPONAME}" -out /home/circleci/.gcp_credentials && echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV && sudo bash -c "cat .circleci/rbe-bazel.rc >> /etc/bazel.bazelrc"
|
||||
|
||||
# Settings common to each job
|
||||
anchor_1: &job_defaults
|
||||
working_directory: ~/ng
|
||||
@ -42,19 +47,18 @@ version: 2
|
||||
jobs:
|
||||
lint:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
|
||||
# Check BUILD.bazel formatting before we have a node_modules directory
|
||||
# Then we don't need any exclude pattern to avoid checking those files
|
||||
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
|
||||
- run: 'yarn buildifier -mode=check ||
|
||||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
||||
# Run the skylark linter to check our Bazel rules
|
||||
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)
|
||||
# which has no replacement, see https://github.com/bazelbuild/bazel/issues/4858
|
||||
- run: 'find . -type f -name "*.bzl" |
|
||||
xargs java -jar /usr/local/bin/Skylint_deploy.jar --disable-checks=deprecated-api ||
|
||||
- run: 'yarn skylint ||
|
||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)'
|
||||
|
||||
- restore_cache:
|
||||
@ -70,22 +74,20 @@ jobs:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: ls /home/circleci/bazel_repository_cache || true
|
||||
- run: bazel info release
|
||||
- run: bazel run @nodejs//:yarn
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||
# Setup remote execution and run RBE-compatible tests.
|
||||
- *setup_bazel_remote_execution
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only,-local
|
||||
# Now run RBE incompatible tests locally.
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only,local --test_tag_filters=-manual,-ivy-only,local
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
@ -119,15 +121,10 @@ jobs:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- *setup_bazel_remote_execution
|
||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
|
||||
|
||||
test_ivy_aot:
|
||||
@ -137,15 +134,10 @@ jobs:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- *setup_bazel_remote_execution
|
||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
||||
|
||||
# This job should only be run on PR builds, where `CIRCLE_PR_NUMBER` is defined.
|
||||
@ -166,6 +158,21 @@ jobs:
|
||||
# `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile`
|
||||
destination: aio/dist/aio-snapshot.tgz
|
||||
|
||||
# This job should only be run on PR builds, where `CIRCLE_PR_NUMBER` is defined.
|
||||
test_aio_preview:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
- run: yarn install --cwd aio --frozen-lockfile --non-interactive
|
||||
- run:
|
||||
name: Wait for preview and run tests
|
||||
command: |
|
||||
source "./scripts/ci/env.sh" print
|
||||
xvfb-run --auto-servernum node aio/scripts/test-preview.js $CIRCLE_PR_NUMBER $CIRCLE_SHA1 $AIO_MIN_PWA_SCORE
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
# It duplicates some work with the job above: we build the bazel packages
|
||||
@ -180,12 +187,9 @@ jobs:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- *setup_bazel_remote_execution
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
@ -252,7 +256,11 @@ jobs:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
- run: xvfb-run --auto-servernum ./aio/scripts/test-production.sh
|
||||
- run:
|
||||
name: Run tests against the deployed apps
|
||||
command: |
|
||||
source "./scripts/ci/env.sh" print
|
||||
xvfb-run --auto-servernum ./aio/scripts/test-production.sh $AIO_MIN_PWA_SCORE
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@ -268,6 +276,9 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: /pull\/\d+/
|
||||
- test_aio_preview:
|
||||
requires:
|
||||
- aio_preview
|
||||
- integration_test:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
@ -299,4 +310,4 @@ workflows:
|
||||
|
||||
notify:
|
||||
webhooks:
|
||||
- url: https://ngbuilds.io/circle-build
|
||||
- url: https://ngbuilds.io/circle-build
|
||||
|
BIN
.circleci/gcp_token
Normal file
BIN
.circleci/gcp_token
Normal file
Binary file not shown.
77
.circleci/rbe-bazel.rc
Normal file
77
.circleci/rbe-bazel.rc
Normal file
@ -0,0 +1,77 @@
|
||||
# These options are enabled when running on CI with Remote Build Execution.
|
||||
|
||||
################################################################
|
||||
# Toolchain related flags for remote build execution. #
|
||||
################################################################
|
||||
# Remote Build Execution requires a strong hash function, such as SHA256.
|
||||
startup --host_jvm_args=-Dbazel.DigestFunction=SHA256
|
||||
|
||||
# Depending on how many machines are in the remote execution instance, setting
|
||||
# this higher can make builds faster by allowing more jobs to run in parallel.
|
||||
# Setting it too high can result in jobs that timeout, however, while waiting
|
||||
# for a remote machine to execute them.
|
||||
build --jobs=150
|
||||
|
||||
# Set several flags related to specifying the platform, toolchain and java
|
||||
# properties.
|
||||
# These flags are duplicated rather than imported from (for example)
|
||||
# %workspace%/configs/ubuntu16_04_clang/1.0/toolchain.bazelrc to make this
|
||||
# bazelrc a standalone file that can be copied more easily.
|
||||
# These flags should only be used as is for the rbe-ubuntu16-04 container
|
||||
# and need to be adapted to work with other toolchain containers.
|
||||
build --host_javabase=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:jdk8
|
||||
build --javabase=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:jdk8
|
||||
build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
|
||||
build --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
|
||||
build --crosstool_top=@bazel_toolchains//configs/ubuntu16_04_clang/1.0/bazel_0.15.0/default:toolchain
|
||||
build --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
|
||||
# Platform flags:
|
||||
# The toolchain container used for execution is defined in the target indicated
|
||||
# by "extra_execution_platforms", "host_platform" and "platforms".
|
||||
# If you are using your own toolchain container, you need to create a platform
|
||||
# target with "constraint_values" that allow for the toolchain specified with
|
||||
# "extra_toolchains" to be selected (given constraints defined in
|
||||
# "exec_compatible_with").
|
||||
# More about platforms: https://docs.bazel.build/versions/master/platforms.html
|
||||
build --extra_toolchains=@bazel_toolchains//configs/ubuntu16_04_clang/1.0/bazel_0.15.0/cpp:cc-toolchain-clang-x86_64-default
|
||||
build --extra_execution_platforms=//tools:rbe_ubuntu1604-angular
|
||||
build --host_platform=//tools:rbe_ubuntu1604-angular
|
||||
build --platforms=//tools:rbe_ubuntu1604-angular
|
||||
|
||||
# Set various strategies so that all actions execute remotely. Mixing remote
|
||||
# and local execution will lead to errors unless the toolchain and remote
|
||||
# machine exactly match the host machine.
|
||||
build --spawn_strategy=remote
|
||||
build --strategy=Javac=remote
|
||||
build --strategy=Closure=remote
|
||||
build --genrule_strategy=remote
|
||||
build --define=EXECUTOR=remote
|
||||
|
||||
# Enable the remote cache so action results can be shared across machines,
|
||||
# developers, and workspaces.
|
||||
build --remote_cache=remotebuildexecution.googleapis.com
|
||||
|
||||
# Enable remote execution so actions are performed on the remote systems.
|
||||
build --remote_executor=remotebuildexecution.googleapis.com
|
||||
|
||||
# Remote instance.
|
||||
build --remote_instance_name=projects/internal-200822/instances/default_instance
|
||||
|
||||
# Enable encryption.
|
||||
build --tls_enabled=true
|
||||
|
||||
# Enforce stricter environment rules, which eliminates some non-hermetic
|
||||
# behavior and therefore improves both the remote cache hit rate and the
|
||||
# correctness and repeatability of the build.
|
||||
build --experimental_strict_action_env=true
|
||||
|
||||
# Set a higher timeout value, just in case.
|
||||
build --remote_timeout=3600
|
||||
|
||||
# Enable authentication. This will pick up application default credentials by
|
||||
# default. You can use --auth_credentials=some_file.json to use a service
|
||||
# account credential instead.
|
||||
build --auth_enabled=true
|
||||
|
||||
# Do not accept remote cache.
|
||||
build --remote_accept_cached=false
|
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -10,17 +10,17 @@ Please check if your PR fulfills the following requirements:
|
||||
What kind of change does this PR introduce?
|
||||
|
||||
<!-- Please check the one that applies to this PR using "x". -->
|
||||
```
|
||||
[ ] Bugfix
|
||||
[ ] Feature
|
||||
[ ] Code style update (formatting, local variables)
|
||||
[ ] Refactoring (no functional changes, no api changes)
|
||||
[ ] Build related changes
|
||||
[ ] CI related changes
|
||||
[ ] Documentation content changes
|
||||
[ ] angular.io application / infrastructure changes
|
||||
[ ] Other... Please describe:
|
||||
```
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Feature
|
||||
- [ ] Code style update (formatting, local variables)
|
||||
- [ ] Refactoring (no functional changes, no api changes)
|
||||
- [ ] Build related changes
|
||||
- [ ] CI related changes
|
||||
- [ ] Documentation content changes
|
||||
- [ ] angular.io application / infrastructure changes
|
||||
- [ ] Other... Please describe:
|
||||
|
||||
|
||||
## What is the current behavior?
|
||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||
@ -32,10 +32,10 @@ Issue Number: N/A
|
||||
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
```
|
||||
[ ] Yes
|
||||
[ ] No
|
||||
```
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
|
@ -49,12 +49,14 @@ env:
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_local
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=aio_local"
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
|
||||
|
98
CHANGELOG.md
98
CHANGELOG.md
@ -1,31 +1,88 @@
|
||||
<a name="6.1.9"></a>
|
||||
## [6.1.9](https://github.com/angular/angular/compare/6.1.8...6.1.9) (2018-09-26)
|
||||
<a name="7.0.0"></a>
|
||||
# [7.0.0](https://github.com/angular/angular/compare/7.0.0-rc.1...7.0.0) (2018-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
### Release Highlights & Update instructions
|
||||
|
||||
* **service-worker:** do not blow up when caches are unwritable ([#26042](https://github.com/angular/angular/issues/26042)) ([a169743](https://github.com/angular/angular/commit/a169743))
|
||||
Angular v7 is .
|
||||
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v7 release announcement](https://blog.angular.io/TODO).
|
||||
|
||||
|
||||
### Dependency updates
|
||||
|
||||
<a name="6.1.8"></a>
|
||||
## [6.1.8](https://github.com/angular/angular/compare/6.1.7...6.1.8) (2018-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([2d0e642](https://github.com/angular/angular/commit/2d0e642))
|
||||
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([91dd160](https://github.com/angular/angular/commit/91dd160))
|
||||
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([48d7f4e](https://github.com/angular/angular/commit/48d7f4e))
|
||||
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([678b420](https://github.com/angular/angular/commit/678b420))
|
||||
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([a31cfc5](https://github.com/angular/angular/commit/a31cfc5))
|
||||
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([e117b1f](https://github.com/angular/angular/commit/e117b1f))
|
||||
* @angular/core now depends on
|
||||
* TypeScript 3.1
|
||||
* RxJS 6.3
|
||||
* @angular/platform-server now depends on Domino 2.1
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([97ae7ae](https://github.com/angular/angular/commit/97ae7ae))
|
||||
* **ivy:** enable .ngfactory.js generation in g3 only ([#25392](https://github.com/angular/angular/issues/25392)) ([1c44b71](https://github.com/angular/angular/commit/1c44b71))
|
||||
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
|
||||
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
|
||||
* **compiler-cli:** add support to extend `angularCompilerOptions` ([#22717](https://github.com/angular/angular/issues/22717)) ([d7e5bbf](https://github.com/angular/angular/commit/d7e5bbf)), closes [#22684](https://github.com/angular/angular/issues/22684)
|
||||
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([2a21ca0](https://github.com/angular/angular/commit/2a21ca0))
|
||||
* **elements:** enable Shadow DOM v1 and slots ([#24861](https://github.com/angular/angular/issues/24861)) ([c9844a2](https://github.com/angular/angular/commit/c9844a2))
|
||||
* **platform-server:** update domino to v2.1.0 ([#25564](https://github.com/angular/angular/issues/25564)) ([3fb0da2](https://github.com/angular/angular/commit/3fb0da2))
|
||||
* **router:** warn if navigation triggered outside Angular zone ([#24959](https://github.com/angular/angular/issues/24959)) ([010e35d](https://github.com/angular/angular/commit/010e35d)), closes [#15770](https://github.com/angular/angular/issues/15770) [#15946](https://github.com/angular/angular/issues/15946) [#24728](https://github.com/angular/angular/issues/24728)
|
||||
* **router:** add UrlSegment[] to CanLoad interface ([#13127](https://github.com/angular/angular/issues/13127)) ([07d8d39](https://github.com/angular/angular/commit/07d8d39)), closes [#12411](https://github.com/angular/angular/issues/12411)
|
||||
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
|
||||
* **bazel:** Cache fileNameToModuleName lookups ([#25731](https://github.com/angular/angular/issues/25731)) ([f394ba0](https://github.com/angular/angular/commit/f394ba0))
|
||||
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
|
||||
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
|
||||
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([6ed7993](https://github.com/angular/angular/commit/6ed7993))
|
||||
* **bazel:** only lookup amd module-name tags in .d.ts files ([#25710](https://github.com/angular/angular/issues/25710)) ([42072c4](https://github.com/angular/angular/commit/42072c4))
|
||||
* **bazel:** protractor rule should include *.e2e-spec.js ([#25701](https://github.com/angular/angular/issues/25701)) ([3809e0f](https://github.com/angular/angular/commit/3809e0f))
|
||||
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([ddc1335](https://github.com/angular/angular/commit/ddc1335))
|
||||
* **benchpress:** Use performance.mark() instead of console.time() ([#24114](https://github.com/angular/angular/issues/24114)) ([06d0400](https://github.com/angular/angular/commit/06d0400))
|
||||
* **common:** register locale data for all equivalent closure locales ([#25867](https://github.com/angular/angular/issues/25867)) ([d83f9d4](https://github.com/angular/angular/commit/d83f9d4))
|
||||
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
|
||||
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
|
||||
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([00d3666](https://github.com/angular/angular/commit/00d3666))
|
||||
* **compiler:** add hostVars and support pure functions in host bindings ([#25626](https://github.com/angular/angular/issues/25626)) ([b424b31](https://github.com/angular/angular/commit/b424b31))
|
||||
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
||||
* **compiler:** update compiler to generate new slot allocations ([#25607](https://github.com/angular/angular/issues/25607)) ([27e2039](https://github.com/angular/angular/commit/27e2039))
|
||||
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
||||
* **core:** add missing `peerDependency ` to `[@angular](https://github.com/angular)/compiler` ([#26033](https://github.com/angular/angular/issues/26033)) ([549de1e](https://github.com/angular/angular/commit/549de1e)), closes [/github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e#diff-58563046c4439699f2e6a89187099a54](https://github.com//github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e/issues/diff-58563046c4439699f2e6a89187099a54)
|
||||
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
||||
* **core:** do not clear element content when using shadow dom ([#24861](https://github.com/angular/angular/issues/24861)) ([6e828bb](https://github.com/angular/angular/commit/6e828bb))
|
||||
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([1f59f2f](https://github.com/angular/angular/commit/1f59f2f))
|
||||
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
|
||||
* **elements:** add compiler dependency ([#24861](https://github.com/angular/angular/issues/24861)) ([6143da6](https://github.com/angular/angular/commit/6143da6))
|
||||
* **elements:** add compiler to integration ([#24861](https://github.com/angular/angular/issues/24861)) ([a080ffc](https://github.com/angular/angular/commit/a080ffc))
|
||||
* **elements:** strict null checks ([#24861](https://github.com/angular/angular/issues/24861)) ([a8210d0](https://github.com/angular/angular/commit/a8210d0))
|
||||
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
||||
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([8dc2b11](https://github.com/angular/angular/commit/8dc2b11))
|
||||
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
||||
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([00b5c7b](https://github.com/angular/angular/commit/00b5c7b))
|
||||
* **service-worker:** do not blow up when caches are unwritable ([#26042](https://github.com/angular/angular/issues/26042)) ([2bd767c](https://github.com/angular/angular/commit/2bd767c))
|
||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||
* **upgrade:** trigger `$destroy` event on upgraded component element ([#25357](https://github.com/angular/angular/issues/25357)) ([2a672a9](https://github.com/angular/angular/commit/2a672a9)), closes [#25334](https://github.com/angular/angular/issues/25334)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="6.1.10"></a>
|
||||
## [6.1.10](https://github.com/angular/angular/compare/6.1.9...6.1.10) (2018-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **platform-browser:** fix [#22155](https://github.com/angular/angular/issues/22155), destroy hammer manager when `HammerInstance.off()` is run ([#22156](https://github.com/angular/angular/issues/22156)) ([3b4d9dc](https://github.com/angular/angular/commit/3b4d9dc))
|
||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([623adbb](https://github.com/angular/angular/commit/623adbb)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="6.1.9"></a>
|
||||
## [6.1.9](https://github.com/angular/angular/compare/6.1.8...6.1.9) (2018-09-26)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -55,6 +112,8 @@
|
||||
|
||||
Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6 instead. sorry! :-)
|
||||
|
||||
|
||||
|
||||
<a name="6.1.4"></a>
|
||||
## [6.1.4](https://github.com/angular/angular/compare/6.1.3...6.1.4) (2018-08-22)
|
||||
|
||||
@ -89,9 +148,6 @@ Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6
|
||||
<a name="6.1.1"></a>
|
||||
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** correct tsickle dependency version to fix typescript 2.9 compatibility ([fec29fa](https://github.com/angular/angular/commit/317c7087c56b72aa74cd6d6a8f719e6e7fec29fa))
|
||||
|
||||
|
||||
|
@ -71,6 +71,8 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
1. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add.
|
||||
Discussing the design up front helps to ensure that we're ready to accept your work.
|
||||
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
||||
1. Fork the angular/angular repo.
|
||||
|
62
WORKSPACE
62
WORKSPACE
@ -5,28 +5,30 @@ workspace(name = "angular")
|
||||
#
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.17.0.zip",
|
||||
strip_prefix = "rules_typescript-0.17.0",
|
||||
sha256 = "1626ee2cc9770af6950bfc77dffa027f9aedf330fe2ea2ee7e504428927bd95d",
|
||||
strip_prefix = "rules_typescript-0.17.0",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.17.0.zip",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
|
||||
|
||||
rules_typescript_dependencies()
|
||||
|
||||
http_archive(
|
||||
name = "bazel_toolchains",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||
],
|
||||
strip_prefix = "bazel-toolchains-5124557861ebf4c0b67f98180bff1f8551e0b421",
|
||||
sha256 = "c3b08805602cd1d2b67ebe96407c1e8c6ed3d4ce55236ae2efe2f1948f38168d",
|
||||
name = "bazel_toolchains",
|
||||
sha256 = "c3b08805602cd1d2b67ebe96407c1e8c6ed3d4ce55236ae2efe2f1948f38168d",
|
||||
strip_prefix = "bazel-toolchains-5124557861ebf4c0b67f98180bff1f8551e0b421",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
|
||||
strip_prefix = "rules_sass-1.11.0",
|
||||
sha256 = "dbe9fb97d5a7833b2a733eebc78c9c1e3880f676ac8af16e58ccf2139cbcad03",
|
||||
strip_prefix = "rules_sass-1.11.0",
|
||||
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
|
||||
)
|
||||
|
||||
# This commit matches the version of buildifier in angular/ngcontainer
|
||||
@ -36,25 +38,25 @@ BAZEL_BUILDTOOLS_VERSION = "49a6c199e3fbf5d94534b2771868677d3f9c6de9"
|
||||
|
||||
http_archive(
|
||||
name = "com_github_bazelbuild_buildtools",
|
||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||
sha256 = "edf39af5fc257521e4af4c40829fffe8fba6d0ebff9f4dd69a6f8f1223ae047b",
|
||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||
)
|
||||
|
||||
# Fetching the Bazel source code allows us to compile the Skylark linter
|
||||
http_archive(
|
||||
name = "io_bazel",
|
||||
url = "https://github.com/bazelbuild/bazel/archive/0.17.1.zip",
|
||||
strip_prefix = "bazel-0.17.1",
|
||||
sha256 = "ace8cced3b21e64a8fdad68508e9b0644201ec848ad583651719841d567fc66d",
|
||||
strip_prefix = "bazel-0.17.1",
|
||||
url = "https://github.com/bazelbuild/bazel/archive/0.17.1.zip",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_skydoc",
|
||||
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
|
||||
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
|
||||
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
|
||||
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
|
||||
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
|
||||
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
|
||||
)
|
||||
|
||||
# We have a source dependency on the Devkit repository, because it's built with
|
||||
@ -65,16 +67,16 @@ http_archive(
|
||||
# ts_library rules in the devkit repository.
|
||||
http_archive(
|
||||
name = "angular_cli",
|
||||
url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip",
|
||||
strip_prefix = "angular-cli-6.1.0-rc.0",
|
||||
sha256 = "8cf320ea58c321e103f39087376feea502f20eaf79c61a4fdb05c7286c8684fd",
|
||||
strip_prefix = "angular-cli-6.1.0-rc.0",
|
||||
url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/v1.0.5.zip",
|
||||
strip_prefix = "brotli-1.0.5",
|
||||
sha256 = "774b893a0700b0692a76e2e5b7e7610dbbe330ffbe3fe864b4b52ca718061d5a",
|
||||
strip_prefix = "brotli-1.0.5",
|
||||
url = "https://github.com/google/brotli/archive/v1.0.5.zip",
|
||||
)
|
||||
|
||||
#
|
||||
@ -104,21 +106,24 @@ If you are on a Mac and using Homebrew, there is a breaking change to the instal
|
||||
See https://blog.bazel.build/2018/08/22/bazel-homebrew.html
|
||||
|
||||
""")
|
||||
|
||||
node_repositories(
|
||||
node_version = "10.9.0",
|
||||
package_json = ["//:package.json"],
|
||||
preserve_symlinks = True,
|
||||
node_version = "10.9.0",
|
||||
yarn_version = "1.9.2",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
go_rules_dependencies()
|
||||
|
||||
go_register_toolchains()
|
||||
|
||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||
|
||||
web_test_repositories()
|
||||
|
||||
browser_repositories(
|
||||
chromium = True,
|
||||
firefox = True,
|
||||
@ -136,7 +141,20 @@ ng_setup_workspace()
|
||||
# Skylark documentation generation
|
||||
|
||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||
|
||||
sass_repositories()
|
||||
|
||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
||||
|
||||
skydoc_repositories()
|
||||
|
||||
##################################
|
||||
# Prevent Bazel from trying to build rxjs under angular devkit
|
||||
local_repository(
|
||||
name = "rxjs_ignore_nested_1",
|
||||
path = "node_modules/@angular-devkit/core/node_modules/rxjs/src",
|
||||
)
|
||||
local_repository(
|
||||
name = "rxjs_ignore_nested_2",
|
||||
path = "node_modules/@angular-devkit/schematics/node_modules/rxjs/src",
|
||||
)
|
||||
|
@ -22,8 +22,8 @@ Here are the most important tasks you might need to use:
|
||||
* `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 test` - run all the unit tests once.
|
||||
* `yarn test --watch` - 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 test --watch=false` - run all the unit tests once.
|
||||
* `yarn e2e` - run all the e2e tests for the doc-viewer.
|
||||
|
||||
* `yarn docs` - generate all the docs from the source files.
|
||||
@ -56,14 +56,9 @@ It's necessary to remove the temporary files, because otherwise they're displaye
|
||||
|
||||
## Using ServiceWorker locally
|
||||
|
||||
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
|
||||
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
|
||||
with webpack serving the files from memory).
|
||||
|
||||
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
|
||||
with `yarn http-server dist -p 4200`.
|
||||
|
||||
For more details see #16745.
|
||||
Running `yarn start` (even when explicitly targeting production mode) does not set up the
|
||||
ServiceWorker. If you want to test the ServiceWorker locally, you can use `yarn build` and then
|
||||
serve the files in `dist/` with `yarn http-server dist -p 4200`.
|
||||
|
||||
|
||||
## Guide to authoring
|
||||
|
@ -1,2 +1,2 @@
|
||||
# Periodically clean up builds that do not correspond to currently open PRs
|
||||
0 12 * * * root /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
||||
0 12 * * * /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
||||
|
@ -36,6 +36,11 @@ server {
|
||||
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
|
||||
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
|
||||
|
||||
error_page 404 /404.html;
|
||||
location "=/404.html" {
|
||||
internal;
|
||||
}
|
||||
|
||||
location "~/[^/]+\.[^/]+$" {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
@ -66,6 +71,21 @@ server {
|
||||
return 200 '';
|
||||
}
|
||||
|
||||
# Check PRs previewability
|
||||
location "~^/can-have-public-preview/\d+/?$" {
|
||||
if ($request_method != "GET") {
|
||||
add_header Allow "GET";
|
||||
return 405;
|
||||
}
|
||||
|
||||
proxy_pass_request_headers on;
|
||||
proxy_redirect off;
|
||||
proxy_method GET;
|
||||
proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri;
|
||||
|
||||
resolver 127.0.0.1;
|
||||
}
|
||||
|
||||
# Notify about CircleCI builds
|
||||
location "~^/circle-build/?$" {
|
||||
if ($request_method != "POST") {
|
||||
|
@ -5,12 +5,12 @@ import * as shell from 'shelljs';
|
||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||
import {GithubApi} from '../common/github-api';
|
||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||
import {assertNotMissingOrEmpty, createLogger, getPrInfoFromDownloadPath} from '../common/utils';
|
||||
import {assertNotMissingOrEmpty, getPrInfoFromDownloadPath, Logger} from '../common/utils';
|
||||
|
||||
// Classes
|
||||
export class BuildCleaner {
|
||||
|
||||
private logger = createLogger('BuildCleaner');
|
||||
private logger = new Logger('BuildCleaner');
|
||||
|
||||
// Constructor
|
||||
constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
|
||||
@ -122,6 +122,6 @@ export class BuildCleaner {
|
||||
this.logger.log(`Existing downloads: ${existingDownloads.length}`);
|
||||
this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
|
||||
|
||||
toRemove.forEach(filePath => shell.rm(filePath));
|
||||
toRemove.forEach(filePath => shell.rm(path.join(this.downloadsDir, filePath)));
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export class CircleCiApi {
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`);
|
||||
}
|
||||
return response.json<BuildInfo>();
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`CircleCI build info request failed (${error.message})`);
|
||||
}
|
||||
@ -77,7 +77,7 @@ export class CircleCiApi {
|
||||
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`);
|
||||
const artifacts = await response.json<ArtifactResponse>();
|
||||
const artifacts = await response.json() as ArtifactResponse;
|
||||
const artifact = artifacts.find(item => item.path === artifactPath);
|
||||
if (!artifact) {
|
||||
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
||||
|
@ -38,7 +38,8 @@ export class GithubApi {
|
||||
return this.request<T>('post', path, data);
|
||||
}
|
||||
|
||||
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 0): Promise<T[]> {
|
||||
// In GitHub API paginated requests, page numbering is 1-based. (https://developer.github.com/v3/#pagination)
|
||||
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 1): Promise<T[]> {
|
||||
const perPage = 100;
|
||||
const params = {
|
||||
...baseParams,
|
||||
|
@ -74,6 +74,6 @@ export class GithubPullRequests {
|
||||
*/
|
||||
public fetchFiles(pr: number): Promise<FileInfo[]> {
|
||||
assert(pr > 0, `Invalid PR number: ${pr}`);
|
||||
return this.api.get<FileInfo[]>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
||||
return this.api.getPaginated<FileInfo>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
export const runTests = (specFiles: string[], helpers?: string[]) => {
|
||||
// We can't use `import` here, because of the following mess:
|
||||
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
||||
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
||||
//
|
||||
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
||||
// `jasmine-core` module and the `jasmine` module).
|
||||
// tslint:disable-next-line: no-var-requires variable-name
|
||||
const Jasmine = require('jasmine');
|
||||
// We can't use `import...from` here, because of the following mess:
|
||||
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
||||
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
||||
//
|
||||
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
||||
// `jasmine-core` module and the `jasmine` module).
|
||||
import Jasmine = require('jasmine');
|
||||
import 'source-map-support/register';
|
||||
|
||||
export const runTests = (specFiles: string[]) => {
|
||||
const config = {
|
||||
helpers,
|
||||
random: true,
|
||||
spec_files: specFiles,
|
||||
stopSpecOnExpectationFailure: true,
|
||||
@ -16,7 +16,7 @@ export const runTests = (specFiles: string[], helpers?: string[]) => {
|
||||
|
||||
process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason));
|
||||
|
||||
const runner = new Jasmine();
|
||||
const runner = new Jasmine({});
|
||||
runner.loadConfig(config);
|
||||
runner.onComplete((passed: boolean) => process.exit(passed ? 0 : 1));
|
||||
runner.execute();
|
||||
|
@ -74,12 +74,25 @@ export const getEnvVar = (name: string, isOptional = false): string => {
|
||||
return value || '';
|
||||
};
|
||||
|
||||
export function createLogger(scope: string) {
|
||||
const padding = ' '.repeat(20 - scope.length);
|
||||
return {
|
||||
error: (...args: any[]) => console.error(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||
info: (...args: any[]) => console.info(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||
log: (...args: any[]) => console.log(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||
warn: (...args: any[]) => console.warn(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||
};
|
||||
/**
|
||||
* A basic logger implementation.
|
||||
* Delegates to `console`, but prepends each message with the current date and specified scope (i.e caller).
|
||||
*/
|
||||
export class Logger {
|
||||
private padding = ' '.repeat(20 - this.scope.length);
|
||||
|
||||
/**
|
||||
* Create a new `Logger` instance for the specified `scope`.
|
||||
* @param scope The logger's scope (added to all messages).
|
||||
*/
|
||||
constructor(private scope: string) {}
|
||||
|
||||
public error(...args: any[]) { this.callMethod('error', args); }
|
||||
public info(...args: any[]) { this.callMethod('info', args); }
|
||||
public log(...args: any[]) { this.callMethod('log', args); }
|
||||
public warn(...args: any[]) { this.callMethod('warn', args); }
|
||||
|
||||
private callMethod(method: 'error' | 'info' | 'log' | 'warn', args: any[]) {
|
||||
console[method](`[${new Date()}]`, `${this.scope}:${this.padding}`, ...args);
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as shell from 'shelljs';
|
||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||
import {assertNotMissingOrEmpty, computeShortSha, createLogger} from '../common/utils';
|
||||
import {assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils';
|
||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||
import {PreviewServerError} from './preview-error';
|
||||
|
||||
// Classes
|
||||
export class BuildCreator extends EventEmitter {
|
||||
|
||||
private logger = createLogger('BuildCreator');
|
||||
private logger = new Logger('BuildCreator');
|
||||
|
||||
// Constructor
|
||||
constructor(protected buildsDir: string) {
|
||||
|
@ -4,7 +4,7 @@ import {dirname} from 'path';
|
||||
import {mkdir} from 'shelljs';
|
||||
import {promisify} from 'util';
|
||||
import {CircleCiApi} from '../common/circle-ci-api';
|
||||
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, createLogger} from '../common/utils';
|
||||
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, Logger} from '../common/utils';
|
||||
import {PreviewServerError} from './preview-error';
|
||||
|
||||
export interface GithubInfo {
|
||||
@ -19,7 +19,7 @@ export interface GithubInfo {
|
||||
* A helper that can get information about builds and download build artifacts.
|
||||
*/
|
||||
export class BuildRetriever {
|
||||
private logger = createLogger('BuildRetriever');
|
||||
private logger = new Logger('BuildRetriever');
|
||||
constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
|
||||
assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
|
||||
assertNotMissingOrEmpty('downloadDir', downloadDir);
|
||||
@ -34,7 +34,7 @@ export class BuildRetriever {
|
||||
const buildInfo = await this.api.getBuildInfo(buildNum);
|
||||
const githubInfo: GithubInfo = {
|
||||
org: buildInfo.username,
|
||||
pr: getPrfromBranch(buildInfo.branch),
|
||||
pr: getPrFromBranch(buildInfo.branch),
|
||||
repo: buildInfo.reponame,
|
||||
sha: buildInfo.vcs_revision,
|
||||
success: !buildInfo.failed,
|
||||
@ -73,7 +73,7 @@ export class BuildRetriever {
|
||||
}
|
||||
}
|
||||
|
||||
function getPrfromBranch(branch: string): number {
|
||||
function getPrFromBranch(branch: string): number {
|
||||
// CircleCI only exposes PR numbers via the `branch` field :-(
|
||||
const match = /^pull\/(\d+)$/.exec(branch);
|
||||
if (!match) {
|
||||
|
@ -2,11 +2,12 @@
|
||||
import * as bodyParser from 'body-parser';
|
||||
import * as express from 'express';
|
||||
import * as http from 'http';
|
||||
import {AddressInfo} from 'net';
|
||||
import {CircleCiApi} from '../common/circle-ci-api';
|
||||
import {GithubApi} from '../common/github-api';
|
||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||
import {GithubTeams} from '../common/github-teams';
|
||||
import {assert, assertNotMissingOrEmpty, createLogger} from '../common/utils';
|
||||
import {assert, assertNotMissingOrEmpty, Logger} from '../common/utils';
|
||||
import {BuildCreator} from './build-creator';
|
||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||
import {BuildRetriever} from './build-retriever';
|
||||
@ -31,7 +32,7 @@ export interface PreviewServerConfig {
|
||||
trustedPrLabel: string;
|
||||
}
|
||||
|
||||
const logger = createLogger('PreviewServer');
|
||||
const logger = new Logger('PreviewServer');
|
||||
|
||||
// Classes
|
||||
export class PreviewServerFactory {
|
||||
@ -52,7 +53,7 @@ export class PreviewServerFactory {
|
||||
const httpServer = http.createServer(middleware as any);
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const info = httpServer.address();
|
||||
const info = httpServer.address() as AddressInfo;
|
||||
logger.info(`Up and running (and listening on ${info.address}:${info.port})...`);
|
||||
});
|
||||
|
||||
@ -63,10 +64,36 @@ export class PreviewServerFactory {
|
||||
buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
|
||||
const middleware = express();
|
||||
const jsonParser = bodyParser.json();
|
||||
const significantFilesRe = new RegExp(cfg.significantFilesPattern);
|
||||
|
||||
// RESPOND TO IS-ALIVE PING
|
||||
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
||||
|
||||
// RESPOND TO CAN-HAVE-PUBLIC-PREVIEW CHECK
|
||||
const canHavePublicPreviewRe = /^\/can-have-public-preview\/(\d+)\/?$/;
|
||||
middleware.get(canHavePublicPreviewRe, async (req, res) => {
|
||||
try {
|
||||
const pr = +canHavePublicPreviewRe.exec(req.url)![1];
|
||||
|
||||
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
||||
// Cannot have preview: PR did not touch relevant files: `aio/` or `packages/` (except for spec files).
|
||||
res.send({canHavePublicPreview: false, reason: 'No significant files touched.'});
|
||||
logger.log(`PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`);
|
||||
} else if (!await buildVerifier.getPrIsTrusted(pr)) {
|
||||
// Cannot have preview: PR not automatically verifiable as "trusted".
|
||||
res.send({canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'});
|
||||
logger.log(`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`);
|
||||
} else {
|
||||
// Can have preview.
|
||||
res.send({canHavePublicPreview: true, reason: null});
|
||||
logger.log(`PR:${pr} - Can have a public preview.`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Previewability check error', err);
|
||||
respondWithError(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
// CIRCLE_CI BUILD COMPLETE WEBHOOK
|
||||
middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => {
|
||||
try {
|
||||
@ -107,7 +134,7 @@ export class PreviewServerFactory {
|
||||
`Invalid webhook: expected "githubRepo" property to equal "${cfg.githubRepo}" but got "${repo}".`);
|
||||
|
||||
// Do not deploy unless this PR has touched relevant files: `aio/` or `packages/` (except for spec files)
|
||||
if (!await buildVerifier.getSignificantFilesChanged(pr, new RegExp(cfg.significantFilesPattern))) {
|
||||
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
||||
res.sendStatus(204);
|
||||
logger.log(`PR:${pr}, Build:${buildNum} - ` +
|
||||
`Skipping preview processing because this PR did not touch any significant files.`);
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
AIO_NGINX_PORT_HTTPS,
|
||||
AIO_WWW_USER,
|
||||
} from '../common/env-variables';
|
||||
import {computeShortSha, createLogger} from '../common/utils';
|
||||
import {computeShortSha, Logger} from '../common/utils';
|
||||
|
||||
// Interfaces - Types
|
||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||
@ -31,7 +31,7 @@ class Helper {
|
||||
https: AIO_NGINX_PORT_HTTPS,
|
||||
};
|
||||
|
||||
private logger = createLogger('TestHelper');
|
||||
private logger = new Logger('TestHelper');
|
||||
|
||||
// Constructor
|
||||
constructor() {
|
||||
@ -105,7 +105,7 @@ class Helper {
|
||||
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
|
||||
}
|
||||
|
||||
public verifyResponse(status: number | [number, string], regex = /^/): VerifyCmdResultFn {
|
||||
public verifyResponse(status: number | [number, string], regex: string | RegExp = /^/): VerifyCmdResultFn {
|
||||
let statusCode: number;
|
||||
let statusText: string;
|
||||
|
||||
@ -180,26 +180,42 @@ class Helper {
|
||||
}
|
||||
}
|
||||
|
||||
interface DefaultCurlOptions {
|
||||
defaultMethod?: CurlOptions['method'];
|
||||
defaultOptions?: CurlOptions['options'];
|
||||
defaultHeaders?: CurlOptions['headers'];
|
||||
defaultData?: CurlOptions['data'];
|
||||
defaultExtraPath?: CurlOptions['extraPath'];
|
||||
}
|
||||
|
||||
interface CurlOptions {
|
||||
method?: string;
|
||||
options?: string;
|
||||
headers?: string[];
|
||||
data?: any;
|
||||
url?: string;
|
||||
extraPath?: string;
|
||||
}
|
||||
|
||||
export function makeCurl(baseUrl: string) {
|
||||
export function makeCurl(baseUrl: string, {
|
||||
defaultMethod = 'POST',
|
||||
defaultOptions = '',
|
||||
defaultHeaders = ['Content-Type: application/json'],
|
||||
defaultData = {},
|
||||
defaultExtraPath = '',
|
||||
}: DefaultCurlOptions = {}) {
|
||||
return function curl({
|
||||
method = 'POST',
|
||||
options = '',
|
||||
data = {},
|
||||
method = defaultMethod,
|
||||
options = defaultOptions,
|
||||
headers = defaultHeaders,
|
||||
data = defaultData,
|
||||
url = baseUrl,
|
||||
extraPath = '',
|
||||
extraPath = defaultExtraPath,
|
||||
}: CurlOptions) {
|
||||
const dataString = data ? JSON.stringify(data) : '';
|
||||
const cmd = `curl -iLX ${method} ` +
|
||||
`${options} ` +
|
||||
`--header "Content-Type: application/json" ` +
|
||||
headers.map(header => `--header "${header}" `).join('') +
|
||||
`--data '${dataString}' ` +
|
||||
`${url}${extraPath}`;
|
||||
return helper.runCmd(cmd);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import * as nock from 'nock';
|
||||
import * as tar from 'tar-stream';
|
||||
import {gzipSync} from 'zlib';
|
||||
import {createLogger, getEnvVar} from '../common/utils';
|
||||
import {getEnvVar, Logger} from '../common/utils';
|
||||
import {BuildNums, PrNums, SHA} from './constants';
|
||||
|
||||
// We are using the `nock` library to fake responses from REST requests, when testing.
|
||||
@ -14,7 +14,7 @@ import {BuildNums, PrNums, SHA} from './constants';
|
||||
// below and return a suitable response. This is quite complicated to setup since the
|
||||
// response from, say, CircleCI will affect what request is made to, say, Github.
|
||||
|
||||
const logger = createLogger('NOCK');
|
||||
const logger = new Logger('mock-external-apis');
|
||||
|
||||
const log = (...args: any[]) => {
|
||||
// Filter out non-matching URL checks
|
||||
@ -76,7 +76,7 @@ const GITHUB_PULLS_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/p
|
||||
const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`;
|
||||
|
||||
const getIssueUrl = (prNum: number) => `${GITHUB_ISSUES_URL}/${prNum}`;
|
||||
const getFilesUrl = (prNum: number) => `${GITHUB_PULLS_URL}/${prNum}/files`;
|
||||
const getFilesUrl = (prNum: number, pageNum = 1) => `${GITHUB_PULLS_URL}/${prNum}/files?page=${pageNum}&per_page=100`;
|
||||
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
||||
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
||||
|
||||
@ -97,7 +97,7 @@ const githubApi = nock(GITHUB_API_HOST).log(log).persist().matchHeader('Authoriz
|
||||
//////////////////////////////
|
||||
|
||||
// GENERAL responses
|
||||
githubApi.get(GITHUB_TEAMS_URL + '?page=0&per_page=100').reply(200, TEST_TEAM_INFO);
|
||||
githubApi.get(GITHUB_TEAMS_URL + '?page=1&per_page=100').reply(200, TEST_TEAM_INFO);
|
||||
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
||||
|
||||
// BUILD_INFO errors
|
||||
|
@ -3,6 +3,7 @@ import * as path from 'path';
|
||||
import {rm} from 'shelljs';
|
||||
import {AIO_BUILDS_DIR, AIO_NGINX_HOSTNAME, AIO_NGINX_PORT_HTTP, AIO_NGINX_PORT_HTTPS} from '../common/env-variables';
|
||||
import {computeShortSha} from '../common/utils';
|
||||
import {PrNums} from './constants';
|
||||
import {helper as h} from './helper';
|
||||
import {customMatchers} from './jasmine-custom-matchers';
|
||||
|
||||
@ -252,6 +253,42 @@ describe(`nginx`, () => {
|
||||
});
|
||||
|
||||
|
||||
describe(`${host}/can-have-public-preview`, () => {
|
||||
const baseUrl = `${scheme}://${host}/can-have-public-preview`;
|
||||
|
||||
|
||||
it('should disallow non-GET requests', async () => {
|
||||
await Promise.all([
|
||||
h.runCmd(`curl -iLX POST ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||
h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||
h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||
h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should pass requests through to the preview server', async () => {
|
||||
await h.runCmd(`curl -iLX GET ${baseUrl}/${PrNums.CHANGED_FILES_ERROR}`).
|
||||
then(h.verifyResponse(500, /CHANGED_FILES_ERROR/));
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 for unknown paths', async () => {
|
||||
const cmdPrefix = `curl -iLX GET ${baseUrl}`;
|
||||
|
||||
await Promise.all([
|
||||
h.runCmd(`${cmdPrefix}/foo/42`).then(h.verifyResponse(404)),
|
||||
h.runCmd(`${cmdPrefix}-foo/42`).then(h.verifyResponse(404)),
|
||||
h.runCmd(`${cmdPrefix}nfoo/42`).then(h.verifyResponse(404)),
|
||||
h.runCmd(`${cmdPrefix}/42/foo`).then(h.verifyResponse(404)),
|
||||
h.runCmd(`${cmdPrefix}/f00`).then(h.verifyResponse(404)),
|
||||
h.runCmd(`${cmdPrefix}/`).then(h.verifyResponse(404)),
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe(`${host}/circle-build`, () => {
|
||||
|
||||
it('should disallow non-POST requests', done => {
|
||||
@ -287,6 +324,7 @@ describe(`nginx`, () => {
|
||||
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
|
||||
]).then(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -18,6 +18,92 @@ describe('preview-server', () => {
|
||||
afterEach(() => h.cleanUp());
|
||||
|
||||
|
||||
describe(`${host}/can-have-public-preview`, () => {
|
||||
const curl = makeCurl(`${host}/can-have-public-preview`, {
|
||||
defaultData: null,
|
||||
defaultExtraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`,
|
||||
defaultHeaders: [],
|
||||
defaultMethod: 'GET',
|
||||
});
|
||||
|
||||
|
||||
it('should disallow non-GET requests', async () => {
|
||||
const bodyRegex = /^Unknown resource in request/;
|
||||
|
||||
await Promise.all([
|
||||
curl({method: 'POST'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({method: 'PUT'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({method: 'PATCH'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({method: 'DELETE'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 for unknown paths', async () => {
|
||||
const bodyRegex = /^Unknown resource in request/;
|
||||
|
||||
await Promise.all([
|
||||
curl({extraPath: `/foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({extraPath: `-foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({extraPath: `nfoo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}/foo`}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({extraPath: '/f00'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
curl({extraPath: '/'}).then(h.verifyResponse(404, bodyRegex)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 500 if checking for significant file changes fails', async () => {
|
||||
await Promise.all([
|
||||
curl({extraPath: `/${PrNums.CHANGED_FILES_404}`}).then(h.verifyResponse(500, /CHANGED_FILES_404/)),
|
||||
curl({extraPath: `/${PrNums.CHANGED_FILES_ERROR}`}).then(h.verifyResponse(500, /CHANGED_FILES_ERROR/)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 200 (false) if no significant files were touched', async () => {
|
||||
const expectedResponse = JSON.stringify({
|
||||
canHavePublicPreview: false,
|
||||
reason: 'No significant files touched.',
|
||||
});
|
||||
|
||||
await curl({extraPath: `/${PrNums.CHANGED_FILES_NONE}`}).then(h.verifyResponse(200, expectedResponse));
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 500 if checking "trusted" status fails', async () => {
|
||||
await curl({extraPath: `/${PrNums.TRUST_CHECK_ERROR}`}).then(h.verifyResponse(500, 'TRUST_CHECK_ERROR'));
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 200 (false) if the PR is not automatically verifiable as "trusted"', async () => {
|
||||
const expectedResponse = JSON.stringify({
|
||||
canHavePublicPreview: false,
|
||||
reason: 'Not automatically verifiable as \\"trusted\\".',
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
curl({extraPath: `/${PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||
curl({extraPath: `/${PrNums.TRUST_CHECK_UNTRUSTED}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 200 (true) if the PR can have a public preview', async () => {
|
||||
const expectedResponse = JSON.stringify({
|
||||
canHavePublicPreview: true,
|
||||
reason: null,
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||
curl({extraPath: `/${PrNums.TRUST_CHECK_TRUSTED_LABEL}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe(`${host}/circle-build`, () => {
|
||||
|
||||
const curl = makeCurl(`${host}/circle-build`);
|
||||
|
@ -7,43 +7,49 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn build --watch",
|
||||
"build": "yarn ~~build",
|
||||
"prebuild-watch": "yarn prebuild",
|
||||
"build-watch": "yarn ~~build-watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"predev": "yarn build || true",
|
||||
"dev": "run-p ~~build-watch ~~test-watch",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"pre~~test-only": "yarn lint",
|
||||
"~~test-only": "node dist/test",
|
||||
"pretest": "yarn build",
|
||||
"test": "yarn ~~test-only",
|
||||
"pretest-watch": "yarn build",
|
||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||
"pretest-watch": "yarn pretest",
|
||||
"test-watch": "yarn ~~test-watch",
|
||||
"~~build": "tsc",
|
||||
"~~build-watch": "yarn ~~build --watch",
|
||||
"pre~~test-only": "yarn lint",
|
||||
"~~test-only": "node dist/test",
|
||||
"~~test-watch": "nodemon --delay 1 --exec \"yarn ~~test-only\" --watch dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.2",
|
||||
"body-parser": "^1.18.3",
|
||||
"delete-empty": "^2.0.0",
|
||||
"express": "^4.15.4",
|
||||
"jasmine": "^2.8.0",
|
||||
"nock": "^9.2.5",
|
||||
"node-fetch": "^2.1.2",
|
||||
"shelljs": "^0.8.1",
|
||||
"tar-stream": "^1.6.0",
|
||||
"tslib": "^1.7.1"
|
||||
"express": "^4.16.3",
|
||||
"jasmine": "^3.2.0",
|
||||
"nock": "^9.6.1",
|
||||
"node-fetch": "^2.2.0",
|
||||
"shelljs": "^0.8.2",
|
||||
"source-map-support": "^0.5.9",
|
||||
"tar-stream": "^1.6.1",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.16.5",
|
||||
"@types/express": "^4.0.37",
|
||||
"@types/jasmine": "^2.6.0",
|
||||
"@types/nock": "^9.1.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/node-fetch": "^1.6.8",
|
||||
"@types/body-parser": "^1.17.0",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/jasmine": "^2.8.8",
|
||||
"@types/nock": "^9.3.0",
|
||||
"@types/node": "^10.9.2",
|
||||
"@types/node-fetch": "^2.1.2",
|
||||
"@types/shelljs": "^0.8.0",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
"supertest": "^3.0.0",
|
||||
"tslint": "^5.7.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||
"typescript": "^2.5.2"
|
||||
"@types/supertest": "^2.0.5",
|
||||
"nodemon": "^1.18.3",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"supertest": "^3.1.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||
"typescript": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,28 @@ import * as shell from 'shelljs';
|
||||
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
|
||||
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
|
||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||
import {Logger} from '../../lib/common/utils';
|
||||
|
||||
const EXISTING_BUILDS = [10, 20, 30, 40];
|
||||
const EXISTING_DOWNLOADS = [
|
||||
'downloads/10-ABCDEF0-build.zip',
|
||||
'downloads/10-1234567-build.zip',
|
||||
'downloads/20-ABCDEF0-build.zip',
|
||||
'downloads/20-1234567-build.zip',
|
||||
'10-ABCDEF0-build.zip',
|
||||
'10-1234567-build.zip',
|
||||
'20-ABCDEF0-build.zip',
|
||||
'20-1234567-build.zip',
|
||||
];
|
||||
const OPEN_PRS = [10, 40];
|
||||
const ANY_DATE = jasmine.any(String);
|
||||
|
||||
// Tests
|
||||
describe('BuildCleaner', () => {
|
||||
let loggerErrorSpy: jasmine.Spy;
|
||||
let loggerLogSpy: jasmine.Spy;
|
||||
let cleaner: BuildCleaner;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'error');
|
||||
spyOn(console, 'log');
|
||||
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', 'build.zip');
|
||||
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
||||
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
||||
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '/downloads', 'build.zip');
|
||||
});
|
||||
|
||||
describe('constructor()', () => {
|
||||
@ -51,11 +54,13 @@ describe('BuildCleaner', () => {
|
||||
toThrowError('Missing or empty required parameter \'githubToken\'!');
|
||||
});
|
||||
|
||||
|
||||
it('should throw if \'downloadsDir\' is empty', () => {
|
||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')).
|
||||
toThrowError('Missing or empty required parameter \'downloadsDir\'!');
|
||||
});
|
||||
|
||||
|
||||
it('should throw if \'artifactPath\' is empty', () => {
|
||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')).
|
||||
toThrowError('Missing or empty required parameter \'artifactPath\'!');
|
||||
@ -85,9 +90,12 @@ describe('BuildCleaner', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should return a promise', () => {
|
||||
it('should return a promise', async () => {
|
||||
const promise = cleaner.cleanUp();
|
||||
expect(promise).toEqual(jasmine.any(Promise));
|
||||
|
||||
// Do not complete the test and release the spies synchronously, to avoid running the actual implementations.
|
||||
await promise;
|
||||
});
|
||||
|
||||
|
||||
@ -160,6 +168,7 @@ describe('BuildCleaner', () => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
|
||||
try {
|
||||
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
|
||||
@ -168,6 +177,7 @@ describe('BuildCleaner', () => {
|
||||
expect(err).toBe('Test');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -277,11 +287,14 @@ describe('BuildCleaner', () => {
|
||||
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
|
||||
});
|
||||
|
||||
|
||||
it('should log the number of open PRs', () => {
|
||||
promise.then(prNumbers => {
|
||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||
ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -301,9 +314,9 @@ describe('BuildCleaner', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should get the contents of the builds directory', () => {
|
||||
it('should get the contents of the downloads directory', () => {
|
||||
expect(fsReaddirSpy).toHaveBeenCalled();
|
||||
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('downloads');
|
||||
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/downloads');
|
||||
});
|
||||
|
||||
|
||||
@ -317,7 +330,7 @@ describe('BuildCleaner', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should resolve with the returned files (as numbers)', done => {
|
||||
it('should resolve with the returned file names', done => {
|
||||
promise.then(result => {
|
||||
expect(result).toEqual(EXISTING_DOWNLOADS);
|
||||
done();
|
||||
@ -383,8 +396,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
cleaner.removeDir('/foo/bar');
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
jasmine.any(String), 'BuildCleaner: ', 'ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
||||
});
|
||||
|
||||
});
|
||||
@ -401,8 +413,8 @@ describe('BuildCleaner', () => {
|
||||
it('should log the number of existing builds and builds to be removed', () => {
|
||||
cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]);
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing builds: 3');
|
||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Removing 2 build(s): 1, 2');
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith('Existing builds: 3');
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 build(s): 1, 2');
|
||||
});
|
||||
|
||||
|
||||
@ -454,25 +466,36 @@ describe('BuildCleaner', () => {
|
||||
|
||||
|
||||
describe('removeUnnecessaryDownloads()', () => {
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(shell, 'rm');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
});
|
||||
|
||||
|
||||
it('should log the number of existing downloads and downloads to be removed', () => {
|
||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith('Existing downloads: 4');
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 download(s): 20-ABCDEF0-build.zip, 20-1234567-build.zip');
|
||||
});
|
||||
|
||||
|
||||
it('should construct full paths to directories (by prepending \'downloadsDir\')', () => {
|
||||
cleaner.removeUnnecessaryDownloads(['dl-1', 'dl-2', 'dl-3'], []);
|
||||
|
||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-1'));
|
||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-2'));
|
||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-3'));
|
||||
});
|
||||
|
||||
|
||||
it('should remove the downloads that do not correspond to open PRs', () => {
|
||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||
expect(shell.rm).toHaveBeenCalledTimes(2);
|
||||
expect(shell.rm).toHaveBeenCalledWith('downloads/20-ABCDEF0-build.zip');
|
||||
expect(shell.rm).toHaveBeenCalledWith('downloads/20-1234567-build.zip');
|
||||
expect(shellRmSpy).toHaveBeenCalledTimes(2);
|
||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-ABCDEF0-build.zip'));
|
||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-1234567-build.zip'));
|
||||
});
|
||||
|
||||
|
||||
it('should log the number of existing builds and builds to be removed', () => {
|
||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing downloads: 4');
|
||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ',
|
||||
'Removing 2 download(s): downloads/20-ABCDEF0-build.zip, downloads/20-1234567-build.zip');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -126,8 +126,8 @@ describe('GithubApi', () => {
|
||||
(api as any).getPaginated('/foo/bar');
|
||||
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 0, per_page: 100});
|
||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 0, per_page: 100});
|
||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 1, per_page: 100});
|
||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 1, per_page: 100});
|
||||
});
|
||||
|
||||
|
||||
@ -162,9 +162,9 @@ describe('GithubApi', () => {
|
||||
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
||||
|
||||
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
||||
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(0)]);
|
||||
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(1)]);
|
||||
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(2)]);
|
||||
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
|
||||
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
|
||||
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
|
||||
|
||||
expect(data).toEqual(allItems);
|
||||
|
||||
|
@ -4,13 +4,13 @@ import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||
|
||||
// Tests
|
||||
describe('GithubPullRequests', () => {
|
||||
|
||||
let githubApi: jasmine.SpyObj<GithubApi>;
|
||||
|
||||
beforeEach(() => {
|
||||
githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']);
|
||||
});
|
||||
|
||||
|
||||
describe('constructor()', () => {
|
||||
|
||||
it('should throw if \'githubOrg\' is missing or empty', () => {
|
||||
@ -95,16 +95,14 @@ describe('GithubPullRequests', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('fetchAll()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
|
||||
beforeEach(() => {
|
||||
prs = new GithubPullRequests(githubApi, 'foo', 'bar');
|
||||
spyOn(console, 'log');
|
||||
});
|
||||
beforeEach(() => prs = new GithubPullRequests(githubApi, 'foo', 'bar'));
|
||||
|
||||
|
||||
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
||||
@ -131,8 +129,10 @@ describe('GithubPullRequests', () => {
|
||||
githubApi.getPaginated.and.returnValue('Test');
|
||||
expect(prs.fetchAll() as any).toBe('Test');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('fetchFiles()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
|
||||
@ -141,21 +141,21 @@ describe('GithubPullRequests', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should make a GET request to GitHub with the correct pathname', () => {
|
||||
it('should make a paginated GET request to GitHub with the correct pathname', () => {
|
||||
prs.fetchFiles(42);
|
||||
expect(githubApi.get).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
||||
expect(githubApi.getPaginated).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
||||
});
|
||||
|
||||
|
||||
it('should resolve with the data returned from GitHub', done => {
|
||||
const expected: any = [{ sha: 'ABCDE', filename: 'a/b/c'}, { sha: '12345', filename: 'x/y/z' }];
|
||||
githubApi.get.and.callFake(() => Promise.resolve(expected));
|
||||
prs.fetch(42).then(data => {
|
||||
const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}];
|
||||
githubApi.getPaginated.and.callFake(() => Promise.resolve(expected));
|
||||
prs.fetchFiles(42).then(data => {
|
||||
expect(data).toEqual(expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Imports
|
||||
import {resolve as resolvePath} from 'path';
|
||||
import {
|
||||
assert,
|
||||
assertNotMissingOrEmpty,
|
||||
@ -6,6 +7,7 @@ import {
|
||||
computeShortSha,
|
||||
getEnvVar,
|
||||
getPrInfoFromDownloadPath,
|
||||
Logger,
|
||||
} from '../../lib/common/utils';
|
||||
|
||||
// Tests
|
||||
@ -19,6 +21,7 @@ describe('utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assert', () => {
|
||||
it('should throw if passed a false value', () => {
|
||||
expect(() => assert(false, 'error message')).toThrowError('error message');
|
||||
@ -29,6 +32,7 @@ describe('utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('computeArtifactDownloadPath', () => {
|
||||
it('should compute an absolute path based on the artifact info provided', () => {
|
||||
const downloadDir = '/a/b/c';
|
||||
@ -36,10 +40,11 @@ describe('utils', () => {
|
||||
const sha = 'ABCDEF1234567';
|
||||
const artifactPath = 'a/path/to/file.zip';
|
||||
const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath);
|
||||
expect(path).toEqual('/a/b/c/123-ABCDEF1-file.zip');
|
||||
expect(path).toBe(resolvePath('/a/b/c/123-ABCDEF1-file.zip'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getPrInfoFromDownloadPath', () => {
|
||||
it('should extract the PR and SHA from the file path', () => {
|
||||
const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip');
|
||||
@ -48,6 +53,7 @@ describe('utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assertNotMissingOrEmpty()', () => {
|
||||
|
||||
it('should throw if passed an empty value', () => {
|
||||
@ -122,4 +128,79 @@ describe('utils', () => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('Logger', () => {
|
||||
let consoleErrorSpy: jasmine.Spy;
|
||||
let consoleInfoSpy: jasmine.Spy;
|
||||
let consoleLogSpy: jasmine.Spy;
|
||||
let consoleWarnSpy: jasmine.Spy;
|
||||
let logger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleErrorSpy = spyOn(console, 'error');
|
||||
consoleInfoSpy = spyOn(console, 'info');
|
||||
consoleLogSpy = spyOn(console, 'log');
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
|
||||
logger = new Logger('TestScope');
|
||||
});
|
||||
|
||||
|
||||
it('should delegate to `console`', () => {
|
||||
logger.error('foo');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(consoleErrorSpy.calls.argsFor(0)).toContain('foo');
|
||||
|
||||
logger.info('bar');
|
||||
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
||||
expect(consoleInfoSpy.calls.argsFor(0)).toContain('bar');
|
||||
|
||||
logger.log('baz');
|
||||
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
||||
expect(consoleLogSpy.calls.argsFor(0)).toContain('baz');
|
||||
|
||||
logger.warn('qux');
|
||||
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(consoleWarnSpy.calls.argsFor(0)).toContain('qux');
|
||||
});
|
||||
|
||||
|
||||
it('should prepend messages with the current date and logger\'s scope', () => {
|
||||
const mockDate = new Date(1337);
|
||||
const expectedDateStr = `[${mockDate}]`;
|
||||
const expectedScopeStr = 'TestScope: ';
|
||||
|
||||
jasmine.clock().mockDate(mockDate);
|
||||
jasmine.clock().withMock(() => {
|
||||
logger.error();
|
||||
logger.info();
|
||||
logger.log();
|
||||
logger.warn();
|
||||
});
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||
expect(consoleInfoSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||
});
|
||||
|
||||
|
||||
it('should pass all arguments to `console`', () => {
|
||||
const someString = jasmine.any(String);
|
||||
|
||||
logger.error('foo1', 'foo2');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(someString, someString, 'foo1', 'foo2');
|
||||
|
||||
logger.info('bar1', 'bar2');
|
||||
expect(consoleInfoSpy).toHaveBeenCalledWith(someString, someString, 'bar1', 'bar2');
|
||||
|
||||
logger.log('baz1', 'baz2');
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(someString, someString, 'baz1', 'baz2');
|
||||
|
||||
logger.warn('qux1', 'qux2');
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(someString, someString, 'qux1', 'qux2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,6 +0,0 @@
|
||||
declare namespace jasmine {
|
||||
export interface DoneFn extends Function {
|
||||
(): void;
|
||||
fail: (message: Error | string) => void;
|
||||
}
|
||||
}
|
@ -3,5 +3,4 @@ import {runTests} from '../lib/common/run-tests';
|
||||
|
||||
// Run
|
||||
const specFiles = [`${__dirname}/**/*.spec.js`];
|
||||
const helpers = [`${__dirname}/helpers.js`];
|
||||
runTests(specFiles, helpers);
|
||||
runTests(specFiles);
|
||||
|
@ -5,6 +5,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as shell from 'shelljs';
|
||||
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
||||
import {Logger} from '../../lib/common/utils';
|
||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
||||
@ -491,7 +492,7 @@ describe('BuildCreator', () => {
|
||||
beforeEach(() => {
|
||||
cpExecCbs = [];
|
||||
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
consoleWarnSpy = spyOn(Logger.prototype, 'warn');
|
||||
shellChmodSpy = spyOn(shell, 'chmod');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||
@ -513,8 +514,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
it('should log (as a warning) any stderr output if extracting succeeded', done => {
|
||||
(bc as any).extractArchive('foo', 'bar').
|
||||
then(() => expect(consoleWarnSpy)
|
||||
.toHaveBeenCalledWith(jasmine.any(String), 'BuildCreator: ', 'This is stderr')).
|
||||
then(() => expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr')).
|
||||
then(done);
|
||||
|
||||
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
||||
|
@ -1,11 +1,13 @@
|
||||
import * as fs from 'fs';
|
||||
import * as nock from 'nock';
|
||||
import {resolve as resolvePath} from 'path';
|
||||
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||
import {Logger} from '../../lib/common/utils';
|
||||
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
||||
|
||||
describe('BuildRetriever', () => {
|
||||
const MAX_DOWNLOAD_SIZE = 10000;
|
||||
const DOWNLOAD_DIR = '/DOWNLOAD/DIR';
|
||||
const DOWNLOAD_DIR = resolvePath('/DOWNLOAD/DIR');
|
||||
const BASE_URL = 'http://test.com';
|
||||
const ARTIFACT_PATH = '/some/path/build.zip';
|
||||
|
||||
@ -29,10 +31,6 @@ describe('BuildRetriever', () => {
|
||||
vcs_revision: 'COMMIT',
|
||||
};
|
||||
|
||||
spyOn(console, 'log');
|
||||
spyOn(console, 'warn');
|
||||
spyOn(console, 'error');
|
||||
|
||||
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
||||
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
|
||||
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
|
||||
@ -91,6 +89,7 @@ describe('BuildRetriever', () => {
|
||||
let retriever: BuildRetriever;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(Logger.prototype, 'warn');
|
||||
retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
||||
});
|
||||
|
||||
@ -133,11 +132,14 @@ describe('BuildRetriever', () => {
|
||||
|
||||
it('should write the artifact file to disk', async () => {
|
||||
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
|
||||
const downloadPath = resolvePath(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`);
|
||||
|
||||
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
|
||||
expect(writeFileSpy)
|
||||
.toHaveBeenCalledWith(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`, jasmine.any(Buffer), jasmine.any(Function));
|
||||
expect(writeFileSpy).toHaveBeenCalledWith(downloadPath, jasmine.any(Buffer), jasmine.any(Function));
|
||||
|
||||
const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1];
|
||||
expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS);
|
||||
|
||||
artifactRequest.done();
|
||||
});
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
import * as express from 'express';
|
||||
import * as http from 'http';
|
||||
import * as supertest from 'supertest';
|
||||
import {promisify} from 'util';
|
||||
import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||
import {GithubApi} from '../../lib/common/github-api';
|
||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||
import {GithubTeams} from '../../lib/common/github-teams';
|
||||
import {Logger} from '../../lib/common/utils';
|
||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||
import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
|
||||
@ -38,15 +38,18 @@ describe('PreviewServerFactory', () => {
|
||||
significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
|
||||
trustedPrLabel: 'trusted: pr-label',
|
||||
};
|
||||
let loggerErrorSpy: jasmine.Spy;
|
||||
let loggerInfoSpy: jasmine.Spy;
|
||||
let loggerLogSpy: jasmine.Spy;
|
||||
|
||||
// Helpers
|
||||
const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
|
||||
PreviewServerFactory.create({...defaultConfig, ...partialConfig});
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'error');
|
||||
spyOn(console, 'info');
|
||||
spyOn(console, 'log');
|
||||
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
||||
loggerInfoSpy = spyOn(Logger.prototype, 'info');
|
||||
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
||||
});
|
||||
|
||||
describe('create()', () => {
|
||||
@ -140,11 +143,10 @@ describe('PreviewServerFactory', () => {
|
||||
const server = createPreviewServer();
|
||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||
|
||||
expect(console.info).not.toHaveBeenCalled();
|
||||
expect(loggerInfoSpy).not.toHaveBeenCalled();
|
||||
|
||||
server.emit('listening');
|
||||
expect(console.info).toHaveBeenCalledWith(
|
||||
jasmine.any(String), 'PreviewServer: ', 'Up and running (and listening on foo:1337)...');
|
||||
expect(loggerInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...');
|
||||
});
|
||||
|
||||
});
|
||||
@ -241,10 +243,6 @@ describe('PreviewServerFactory', () => {
|
||||
let buildCreator: BuildCreator;
|
||||
let agent: supertest.SuperTest<supertest.Test>;
|
||||
|
||||
// Helpers
|
||||
const promisifyRequest = async (req: supertest.Request) => await promisify(req.end.bind(req))();
|
||||
const verifyRequests = async (reqs: supertest.Request[]) => await Promise.all(reqs.map(promisifyRequest));
|
||||
|
||||
beforeEach(() => {
|
||||
const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
|
||||
defaultConfig.circleCiToken);
|
||||
@ -257,14 +255,15 @@ describe('PreviewServerFactory', () => {
|
||||
buildCreator = new BuildCreator(defaultConfig.buildsDir);
|
||||
|
||||
const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator,
|
||||
defaultConfig);
|
||||
defaultConfig);
|
||||
agent = supertest.agent(middleware);
|
||||
});
|
||||
|
||||
|
||||
describe('GET /health-check', () => {
|
||||
|
||||
it('should respond with 200', async () => {
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
agent.get('/health-check').expect(200),
|
||||
agent.get('/health-check/').expect(200),
|
||||
]);
|
||||
@ -272,7 +271,7 @@ describe('PreviewServerFactory', () => {
|
||||
|
||||
|
||||
it('should respond with 404 for non-GET requests', async () => {
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
agent.put('/health-check').expect(404),
|
||||
agent.post('/health-check').expect(404),
|
||||
agent.patch('/health-check').expect(404),
|
||||
@ -282,7 +281,7 @@ describe('PreviewServerFactory', () => {
|
||||
|
||||
|
||||
it('should respond with 404 if the path does not match exactly', async () => {
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
agent.get('/health-check/foo').expect(404),
|
||||
agent.get('/health-check-foo').expect(404),
|
||||
agent.get('/health-checknfoo').expect(404),
|
||||
@ -294,7 +293,104 @@ describe('PreviewServerFactory', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('/circle-build', () => {
|
||||
|
||||
describe('GET /can-have-public-preview/<pr>', () => {
|
||||
const baseUrl = '/can-have-public-preview';
|
||||
const pr = 777;
|
||||
const url = `${baseUrl}/${pr}`;
|
||||
let bvGetPrIsTrustedSpy: jasmine.Spy;
|
||||
let bvGetSignificantFilesChangedSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
|
||||
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').
|
||||
and.returnValue(Promise.resolve(true));
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 for non-GET requests', async () => {
|
||||
await Promise.all([
|
||||
agent.put(url).expect(404),
|
||||
agent.post(url).expect(404),
|
||||
agent.patch(url).expect(404),
|
||||
agent.delete(url).expect(404),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 if the path does not match exactly', async () => {
|
||||
await Promise.all([
|
||||
agent.get('/can-have-public-preview/42/foo').expect(404),
|
||||
agent.get('/can-have-public-preview-foo/42').expect(404),
|
||||
agent.get('/can-have-public-previewnfoo/42').expect(404),
|
||||
agent.get('/foo/can-have-public-preview/42').expect(404),
|
||||
agent.get('/foo-can-have-public-preview/42').expect(404),
|
||||
agent.get('/fooncan-have-public-preview/42').expect(404),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should respond appropriately if the PR did not touch any significant files', async () => {
|
||||
bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false));
|
||||
|
||||
const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'};
|
||||
const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`;
|
||||
|
||||
await agent.get(url).expect(200, expectedResponse);
|
||||
|
||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||
});
|
||||
|
||||
|
||||
it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
|
||||
bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false));
|
||||
|
||||
const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
|
||||
const expectedLog =
|
||||
`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`;
|
||||
|
||||
await agent.get(url).expect(200, expectedResponse);
|
||||
|
||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||
});
|
||||
|
||||
|
||||
it('should respond appropriately if the PR can have a preview', async () => {
|
||||
const expectedResponse = {canHavePublicPreview: true, reason: null};
|
||||
const expectedLog = `PR:${pr} - Can have a public preview.`;
|
||||
|
||||
await agent.get(url).expect(200, expectedResponse);
|
||||
|
||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
|
||||
bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error'));
|
||||
|
||||
await agent.get(url).expect(500, 'getSignificantFilesChanged error');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
|
||||
});
|
||||
|
||||
|
||||
it('should respond with error if `getPrIsTrusted()` fails', async () => {
|
||||
const error = new Error('getPrIsTrusted error');
|
||||
bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
|
||||
|
||||
await agent.get(url).expect(500, 'getPrIsTrusted error');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('POST /circle-build', () => {
|
||||
let getGithubInfoSpy: jasmine.Spy;
|
||||
let getSignificantFilesChangedSpy: jasmine.Spy;
|
||||
let downloadBuildArtifactSpy: jasmine.Spy;
|
||||
@ -359,8 +455,8 @@ describe('PreviewServerFactory', () => {
|
||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||
expect(getGithubInfoSpy).not.toHaveBeenCalled();
|
||||
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
|
||||
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
||||
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||
expect(createBuildSpy).not.toHaveBeenCalled();
|
||||
@ -371,7 +467,7 @@ describe('PreviewServerFactory', () => {
|
||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
||||
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
|
||||
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
|
||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||
@ -467,7 +563,7 @@ describe('PreviewServerFactory', () => {
|
||||
|
||||
|
||||
it('should respond with 404 for non-POST requests', async () => {
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
agent.get(url).expect(404),
|
||||
agent.put(url).expect(404),
|
||||
agent.patch(url).expect(404),
|
||||
@ -482,7 +578,7 @@ describe('PreviewServerFactory', () => {
|
||||
const request1 = agent.post(url);
|
||||
const request2 = agent.post(url).send();
|
||||
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
request1.expect(400, responseBody),
|
||||
request2.expect(400, responseBody),
|
||||
]);
|
||||
@ -495,7 +591,7 @@ describe('PreviewServerFactory', () => {
|
||||
const request1 = agent.post(url).send({});
|
||||
const request2 = agent.post(url).send({number: null});
|
||||
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
request1.expect(400, `${responseBodyPrefix} {}`),
|
||||
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
||||
]);
|
||||
@ -503,7 +599,7 @@ describe('PreviewServerFactory', () => {
|
||||
|
||||
|
||||
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
|
||||
await promisifyRequest(createRequest(+pr));
|
||||
await createRequest(+pr);
|
||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||
});
|
||||
|
||||
@ -511,9 +607,8 @@ describe('PreviewServerFactory', () => {
|
||||
it('should propagate errors from BuildVerifier', async () => {
|
||||
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
||||
|
||||
const req = createRequest(+pr).expect(500, 'Test');
|
||||
await createRequest(+pr).expect(500, 'Test');
|
||||
|
||||
await promisifyRequest(req);
|
||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -522,19 +617,17 @@ describe('PreviewServerFactory', () => {
|
||||
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
|
||||
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
||||
|
||||
await promisifyRequest(createRequest(24));
|
||||
await createRequest(24);
|
||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
|
||||
|
||||
await promisifyRequest(createRequest(42));
|
||||
await createRequest(42);
|
||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
|
||||
});
|
||||
|
||||
|
||||
it('should propagate errors from BuildCreator', async () => {
|
||||
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
||||
|
||||
const req = createRequest(+pr).expect(500, 'Test');
|
||||
await verifyRequests([req]);
|
||||
await createRequest(+pr).expect(500, 'Test');
|
||||
});
|
||||
|
||||
|
||||
@ -544,7 +637,7 @@ describe('PreviewServerFactory', () => {
|
||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||
|
||||
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
||||
await verifyRequests(reqs);
|
||||
await Promise.all(reqs);
|
||||
});
|
||||
|
||||
|
||||
@ -552,7 +645,7 @@ describe('PreviewServerFactory', () => {
|
||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||
|
||||
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
||||
await verifyRequests(reqs);
|
||||
await Promise.all(reqs);
|
||||
});
|
||||
|
||||
|
||||
@ -560,14 +653,13 @@ describe('PreviewServerFactory', () => {
|
||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||
|
||||
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
||||
await verifyRequests(reqs);
|
||||
await Promise.all(reqs);
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
|
||||
const promises = ['foo', 'notlabeled'].
|
||||
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
|
||||
map(promisifyRequest);
|
||||
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200]));
|
||||
|
||||
await Promise.all(promises);
|
||||
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||
@ -584,7 +676,7 @@ describe('PreviewServerFactory', () => {
|
||||
it('should respond with 404', async () => {
|
||||
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
||||
|
||||
await verifyRequests([
|
||||
await Promise.all([
|
||||
agent.get('/some/url').expect(404, responseFor('get')),
|
||||
agent.put('/some/url').expect(404, responseFor('put')),
|
||||
agent.post('/some/url').expect(404, responseFor('post')),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
set -eu -o pipefail
|
||||
|
||||
# Set up env variables
|
||||
export AIO_CIRCLE_CI_TOKEN=UNUSED_CIRCLE_CI_TOKEN
|
||||
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
|
||||
|
||||
# Run the clean-up
|
||||
|
@ -5,4 +5,5 @@ TODO (gkalpak): Add docs. Mention:
|
||||
- Testing on CI.
|
||||
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
||||
- Deploying from CI.
|
||||
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-to-firebase.sh`
|
||||
Relevant files: `.circleci/config.yml`, `scripts/ci/deploy.sh`, `aio/scripts/build-artifacts.sh`,
|
||||
`aio/scripts/deploy-to-firebase.sh`
|
||||
|
@ -34,34 +34,31 @@ container:
|
||||
|
||||
|
||||
### On CI (CircleCI)
|
||||
- Build job completes successfully.
|
||||
- The CI script checks whether the build job was initiated by a PR against the angular/angular
|
||||
master branch.
|
||||
- The CI script checks whether the PR has touched any files that might affect the angular.io app
|
||||
(currently the `aio/` or `packages/` directories, ignoring spec files).
|
||||
- The CI script builds the angular.io project.
|
||||
- The CI script gzips and stores the build artifacts in the CI infrastructure.
|
||||
- When the build completes CircleCI triggers a webhook on the preview-server.
|
||||
- When the build completes, CircleCI triggers a webhook on the preview-server.
|
||||
|
||||
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
|
||||
|
||||
|
||||
### Hosting build artifacts
|
||||
|
||||
- nginx receives the webhook trigger and passes it through to the preview server.
|
||||
- The preview-server runs several preliminary checks to determine whether the request is valid and
|
||||
whether the corresponding PR can have a (public or non-public) preview (more details can be found
|
||||
[here](overview--security-model.md)).
|
||||
- The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
|
||||
- The preview-server makes a request to this URL to receive the artifact - failing if the size
|
||||
exceeds the specified max file size - and stores it in a temporary location.
|
||||
- The preview-server runs several checks to determine whether the request should be accepted and
|
||||
whether it should be publicly accessible or stored for later verification (more details can be
|
||||
found [here](overview--security-model.md)).
|
||||
- The preview-server runs more checks to determine whether the preview should be publicly accessible
|
||||
or stored for later verification (more details can be found [here](overview--security-model.md)).
|
||||
- The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
|
||||
builds for the same PR had been previously deployed as non-public and the current build has been
|
||||
automatically verified, all previous builds are made public as well.
|
||||
If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
|
||||
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
|
||||
- The preview-server verifies that it is not trying to overwrite an existing build.
|
||||
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the first
|
||||
few characters of the SHA: `<PR>/<SHA>/`
|
||||
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the
|
||||
first few characters of the SHA: `<PR>/<SHA>/`
|
||||
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
|
||||
number and SHA.)
|
||||
- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
|
||||
@ -101,8 +98,8 @@ More info on the possible HTTP status codes and their meaning can be found
|
||||
|
||||
### Removing obsolete artifacts
|
||||
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a
|
||||
clean-up tasks once a day. The task retrieves all open PRs from GitHub and removes all directories
|
||||
that do not correspond with an open PR.
|
||||
clean-up task once a day. The task retrieves all open PRs from GitHub and removes all directories
|
||||
that do not correspond to an open PR.
|
||||
|
||||
|
||||
### Health-check
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Overview - HTTP Status Codes
|
||||
|
||||
|
||||
This is a list of all the possible HTTP status codes returned by the nginx and preview servers, along
|
||||
with a brief explanation of what they mean:
|
||||
This is a list of all the possible HTTP status codes returned by the nginx and preview servers,
|
||||
along with a brief explanation of what they mean:
|
||||
|
||||
|
||||
## `http://*.ngbuilds.io/*`
|
||||
@ -25,6 +25,23 @@ with a brief explanation of what they mean:
|
||||
File not found.
|
||||
|
||||
|
||||
## `https://ngbuilds.io/can-have-public-preview/<pr>`
|
||||
|
||||
- **200 (OK)**:
|
||||
Whether the PR can have a public preview (based on its author, label, changed files).
|
||||
_Response type:_ JSON
|
||||
_Response format:_
|
||||
```ts
|
||||
{
|
||||
canHavePublicPreview: boolean,
|
||||
reason: string | null,
|
||||
}
|
||||
```
|
||||
|
||||
- **405 (Method Not Allowed)**:
|
||||
Request method other than GET.
|
||||
|
||||
|
||||
## `https://ngbuilds.io/circle-build`
|
||||
|
||||
- **201 (Created)**:
|
||||
|
@ -11,8 +11,8 @@ part of the CI process and serving them publicly.
|
||||
|
||||
## Security objectives
|
||||
|
||||
- **Prevent hosting arbitrary content to on servers.**
|
||||
Since there is no restriction on who can submit a PR, we cannot allow arbitrary untrusted PRs'
|
||||
- **Prevent hosting arbitrary content on our servers.**
|
||||
Since there is no restriction on who can submit a PR, we cannot allow arbitrary, untrusted PRs'
|
||||
build artifacts to be hosted.
|
||||
|
||||
- **Prevent overwriting other people's hosted build artifacts.**
|
||||
@ -40,40 +40,49 @@ part of the CI process and serving them publicly.
|
||||
### In a nutshell
|
||||
The implemented approach can be broken up to the following sub-tasks:
|
||||
|
||||
0. Receive notification from CircleCI of a completed build.
|
||||
1. Verify that the build is valid and download the artifact.
|
||||
2. Fetch the PR's metadata, including author and labels.
|
||||
3. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
||||
4. If necessary, update the corresponding PR's verification status.
|
||||
5. Deploy the artifacts to the corresponding PR's directory.
|
||||
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
||||
1. Receive notification from CircleCI of a completed build.
|
||||
2. Verify that the build is valid and can have a preview.
|
||||
3. Download the build artifact.
|
||||
4. Fetch the PR's metadata, including author and labels.
|
||||
5. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
||||
6. If necessary, update the corresponding PR's verification status.
|
||||
7. Deploy the artifacts to the corresponding PR's directory.
|
||||
8. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
||||
during deployment will remain valid until the artifacts are removed).
|
||||
7. Prevent hosted preview files from accessing anything outside their directory.
|
||||
9. Prevent hosted preview files from accessing anything outside their directory.
|
||||
|
||||
|
||||
### Implementation details
|
||||
This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
|
||||
0. **Receive notification from CircleCI of a completed build**
|
||||
1. **Receive notification from CircleCI of a completed build**
|
||||
|
||||
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
||||
The payload contains the number of the build that completed.
|
||||
|
||||
1. **Verify that the build is valid and download the artifact.**
|
||||
2. **Verify that the build is valid and can have a preview.**
|
||||
|
||||
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
||||
number and then run a direct query against the CircleCI API to get hold of the real data for
|
||||
the given build number.
|
||||
|
||||
If the build was not successful then we ignore this trigger. Otherwise we check that the
|
||||
associated github organisation and repository are what we expect (e.g. angular/angular).
|
||||
We perform a number of preliminary checks:
|
||||
- Was the webhook triggered by the designated CircleCI job (currently `aio_preview`)?
|
||||
- Was the build successful?
|
||||
- Are the associated GitHub organisation and repository what we expect (e.g. `angular/angular`)?
|
||||
- Has the PR touched any files that might affect the angular.io app (currently the `aio/` or
|
||||
`packages/` directories, ignoring spec files)?
|
||||
|
||||
Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that
|
||||
If any of the preliminary checks fails, the process is aborted and not preview is generated.
|
||||
|
||||
3. **Download the build artifact.**
|
||||
|
||||
Next we make another call to the CircleCI API to get a list of the URLs for artifacts of that
|
||||
build. If there is one that matches the configured artifact path, we download the contents of the
|
||||
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
||||
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
||||
|
||||
2. **Fetch the PR's metadata, including author and labels**.
|
||||
4. **Fetch the PR's metadata, including author and labels**.
|
||||
|
||||
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
||||
including the author's username and the labels - using the
|
||||
@ -81,7 +90,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
||||
[@mary-poppins](https://github.com/mary-poppins)).
|
||||
|
||||
3. **Check whether the PR can be automatically verified as "trusted"**.
|
||||
5. **Check whether the PR can be automatically verified as "trusted"**.
|
||||
|
||||
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
||||
and publicly accessible on the preview server. There are two ways to check that:
|
||||
@ -93,31 +102,32 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
||||
Here too, we use the token by @mary-poppins.
|
||||
|
||||
4. **If necessary update the corresponding PR's verification status**.
|
||||
6. **If necessary update the corresponding PR's verification status**.
|
||||
|
||||
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
||||
whether it is publicly accessible or not), based on the new verification status. For example, if
|
||||
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
||||
otherwise, the PR (and all the previously hosted previews) are made public. It works the same
|
||||
otherwise, the PR (and all the previously downloaded previews) are made public. It works the same
|
||||
way if a PR has gone from "trusted" to "not trusted".
|
||||
|
||||
5. **Deploy the artifacts to the corresponding PR's directory.**
|
||||
7. **Deploy the artifacts to the corresponding PR's directory.**
|
||||
|
||||
With the preceding steps, we have verified that the build artifacts are valid.
|
||||
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
|
||||
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
||||
Essentially, as long as sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to
|
||||
"project" the trust we have in a team's members through the PR to the build artifacts.
|
||||
With the preceding steps, we have verified that the build artifacts are valid. Additionally, we
|
||||
have determined whether the PR can be trusted to have its previews publicly accessible or whether
|
||||
further verification is necessary.
|
||||
|
||||
6. **Prevent overwriting previously deployed artifacts**.
|
||||
The artifacts will be stored to the PR's directory, but will not be publicly accessible unless
|
||||
the PR has been verified. Essentially, as long as sub-tasks 2, 3, 4 and 5 can be securely
|
||||
accomplished, it is possible to "project" the trust we have in a team's members through the PR to
|
||||
the build artifacts.
|
||||
|
||||
8. **Prevent overwriting previously deployed artifacts**.
|
||||
|
||||
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
||||
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js
|
||||
Express server) rejects builds that have already been handled.
|
||||
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js Express server) rejects builds that have already been handled.
|
||||
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
||||
|
||||
7. **Prevent hosted preview files from accessing anything outside their directory.**
|
||||
9. **Prevent hosted preview files from accessing anything outside their directory.**
|
||||
|
||||
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
||||
outside of the directory where the preview files are stored.
|
||||
@ -130,10 +140,10 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
This means that any secret access keys need only be stored on the preview-server and not on any of
|
||||
the CI build infrastructure (e.g. CircleCI).
|
||||
|
||||
- Each trusted PR author has full control over the content that is hosted as a preview for their PRs.
|
||||
Part of the security model relies on the trustworthiness of these authors.
|
||||
- Each trusted PR author has full control over the content that is hosted as a preview for their
|
||||
PRs. Part of the security model relies on the trustworthiness of these authors.
|
||||
|
||||
- Adding the specified label on a PR to mark it as trusted, gives the author full control over
|
||||
the content that is hosted for the specific PR preview (e.g. by pushing more commits to it).
|
||||
The user adding the label is responsible for ensuring that this control is not abused and that
|
||||
the PR is either closed (one way of another) or the access is revoked.
|
||||
- Adding the specified label on a PR to mark it as trusted, gives the author full control over the
|
||||
content that is hosted for the specific PR preview (e.g. by pushing more commits to it). The user
|
||||
adding the label is responsible for ensuring that this control is not abused and that the PR is
|
||||
either closed (one way of another) or the access is revoked.
|
||||
|
@ -8,7 +8,7 @@ Necessary secrets:
|
||||
1. `GITHUB_TOKEN`
|
||||
- Used for:
|
||||
- Retrieving open PRs without rate-limiting.
|
||||
- Retrieving PR author.
|
||||
- Retrieving PR info, such as author, labels, changed files.
|
||||
- Retrieving members of the trusted GitHub teams.
|
||||
- Posting comments with preview links on PRs.
|
||||
|
||||
@ -25,8 +25,9 @@ Necessary secrets:
|
||||
- Generate new token with the `public_repo` scope.
|
||||
|
||||
2. `CIRCLE_CI_TOKEN`
|
||||
- Visit https://circleci.com/gh/angular/angular/edit#api
|
||||
- Create an API token with `Build Artifacts` scope
|
||||
- Visit https://circleci.com/gh/angular/angular/edit#api.
|
||||
- Create an API token with `Build Artifacts` scope.
|
||||
|
||||
|
||||
## Save secrets on the VM
|
||||
|
||||
|
@ -33,7 +33,6 @@
|
||||
"src/assets",
|
||||
"src/generated",
|
||||
"src/app/search/search-worker.js",
|
||||
"src/favicon.ico",
|
||||
"src/pwa-manifest.json",
|
||||
"src/google385281288605d160.html",
|
||||
{
|
||||
@ -62,7 +61,8 @@
|
||||
"src": "src/environments/environment.ts",
|
||||
"replaceWith": "src/environments/environment.next.ts"
|
||||
}
|
||||
]
|
||||
],
|
||||
"serviceWorker": true
|
||||
},
|
||||
"stable": {
|
||||
"fileReplacements": [
|
||||
@ -70,7 +70,8 @@
|
||||
"src": "src/environments/environment.ts",
|
||||
"replaceWith": "src/environments/environment.stable.ts"
|
||||
}
|
||||
]
|
||||
],
|
||||
"serviceWorker": true
|
||||
},
|
||||
"archive": {
|
||||
"fileReplacements": [
|
||||
@ -78,7 +79,8 @@
|
||||
"src": "src/environments/environment.ts",
|
||||
"replaceWith": "src/environments/environment.archive.ts"
|
||||
}
|
||||
]
|
||||
],
|
||||
"serviceWorker": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -123,7 +125,6 @@
|
||||
"src/assets",
|
||||
"src/generated",
|
||||
"src/app/search/search-worker.js",
|
||||
"src/favicon.ico",
|
||||
"src/pwa-manifest.json",
|
||||
"src/google385281288605d160.html",
|
||||
{
|
||||
|
3
aio/content/cli-src/.gitignore
vendored
Normal file
3
aio/content/cli-src/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
package.json
|
||||
yarn.lock
|
102
aio/content/cli/index.md
Normal file
102
aio/content/cli/index.md
Normal file
@ -0,0 +1,102 @@
|
||||
<h1 class="no-toc">CLI Command Reference</h1>
|
||||
|
||||
The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications. You can use the tool directly in a command shell, or indirectly through an interactive UI such as [Angular Console](https://angularconsole.com).
|
||||
|
||||
## Installing Angular CLI
|
||||
|
||||
Major versions of Angular CLI follow the supported major version of Angular, but minor versions can be released separately.
|
||||
|
||||
Install the CLI using the `npm` package manager:
|
||||
<code-example format="." language="bash">
|
||||
npm install -g @angular/cli
|
||||
</code-example>
|
||||
|
||||
For details about changes between versions, and information about updating from previous releases,
|
||||
see the Releases tab on GitHub: https://github.com/angular/angular-cli/releases
|
||||
|
||||
## Basic workflow
|
||||
|
||||
Invoke the tool on the command line through the `ng` executable.
|
||||
Online help is available on the command line.
|
||||
Enter the following to list commands or options for a given command (such as [generate](cli/generate)) with a short description.
|
||||
|
||||
<code-example format="." language="bash">
|
||||
ng help
|
||||
ng generate --help
|
||||
</code-example>
|
||||
|
||||
To create, build, and serve a new, basic Angular project on a development server, go to the parent directory of your new workspace use the following commands:
|
||||
|
||||
<code-example format="." language="bash">
|
||||
ng new my-first-project
|
||||
cd my-first-project
|
||||
ng serve
|
||||
</code-example>
|
||||
|
||||
In your browser, open http://localhost:4200/ to see the new app run.
|
||||
|
||||
## Workspaces and project files
|
||||
|
||||
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
|
||||
A workspace can contain multiple apps and libraries.
|
||||
The initial app created by the [ng new](cli/new) command is at the top level of the workspace.
|
||||
When you generate an additional app or library in a workspace, it goes into a `projects/` subfolder.
|
||||
|
||||
A newly generated app contains the source files for a root module, with a root component and template.
|
||||
Each app has a `src` folder that contains the logic, data, and assets.
|
||||
|
||||
You can edit the generated files directly, or add to and modify them using CLI commands.
|
||||
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
|
||||
Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on apps and libraries, must be executed from within a workspace or project folder.
|
||||
|
||||
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
|
||||
|
||||
* See more about the [Workspace file structure](guide/file-structure).
|
||||
|
||||
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
|
||||
|
||||
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
|
||||
This is where you can set workspace-wide defaults, and specify configurations to use when the CLI builds a project for different targets.
|
||||
|
||||
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
|
||||
|
||||
* See the [complete schema](https://github.com/angular/angular-cli/wiki/angular-workspace) for `angular.json`.
|
||||
<!-- * Learn more about *configuration options for Angular(links to new guide or topics TBD)*. -->
|
||||
|
||||
|
||||
## CLI command-language syntax
|
||||
|
||||
Command syntax is shown as follows:
|
||||
|
||||
`ng` *commandNameOrAlias* *requiredArg* [*optionalArg*] `[options]`
|
||||
|
||||
* Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command.
|
||||
|
||||
* Option names are prefixed with a double dash (--).
|
||||
Option aliases are prefixed with a single dash (-).
|
||||
Arguments are not prefixed.
|
||||
For example: `ng build my-app -c production`
|
||||
|
||||
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
|
||||
|
||||
* Argument and option names can be given in either
|
||||
[camelCase or dash-case](guide/glossary#case-types).
|
||||
`--myOptionName` is equivalent to `--my-option-name`.
|
||||
|
||||
### Boolean and enumerated options
|
||||
|
||||
Boolean options have two forms: `--thisOption` sets the flag, `--noThisOption` clears it.
|
||||
If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.
|
||||
|
||||
Allowed values are given with each enumerated option description, with the default value in **bold**.
|
||||
|
||||
### Relative paths
|
||||
|
||||
Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root.
|
||||
|
||||
### Schematics
|
||||
|
||||
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
|
||||
In addition to any general options, each artifact or library defines its own options in a *schematic*.
|
||||
Schematic options are supplied to the command in the same format as immediate command options.
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
// #enddocregion import
|
||||
|
||||
// #docregion metadata
|
||||
// #docregion metadata, component
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@ -13,4 +13,4 @@ import { Component } from '@angular/core';
|
||||
export class AppComponent {
|
||||
title = 'My First Angular App!';
|
||||
}
|
||||
// #enddocregion title, class
|
||||
// #enddocregion title, class, component
|
||||
|
@ -8,11 +8,11 @@ import { Routes, RouterModule } from '@angular/router';
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'customers',
|
||||
loadChildren: 'app/customers/customers.module#CustomersModule'
|
||||
loadChildren: './customers/customers.module#CustomersModule'
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
loadChildren: 'app/orders/orders.module#OrdersModule'
|
||||
loadChildren: './orders/orders.module#OrdersModule'
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
|
@ -5,8 +5,8 @@ import { ContactModule } from './contact/contact.module.3';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' }
|
||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: './hero/hero.module.3#HeroModule' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -8,8 +8,8 @@ import { ContactModule } from './contact/contact.module';
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||
// #docregion lazy-routes
|
||||
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
|
||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: './hero/hero.module#HeroModule' }
|
||||
// #enddocregion lazy-routes
|
||||
];
|
||||
// #enddocregion routes
|
||||
|
@ -3,8 +3,8 @@ import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||
{ path: 'items', loadChildren: 'app/items/items.module#ItemsModule' },
|
||||
{ path: 'customers', loadChildren: 'app/customers/customers.module#CustomersModule' }
|
||||
{ path: 'items', loadChildren: './items/items.module#ItemsModule' },
|
||||
{ path: 'customers', loadChildren: './customers/customers.module#CustomersModule' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -4,7 +4,7 @@ import { Observable, of } from 'rxjs';
|
||||
// #docregion observer
|
||||
|
||||
// Create simple observable that emits three values
|
||||
const myObservable = Observable.of(1, 2, 3);
|
||||
const myObservable = of(1, 2, 3);
|
||||
|
||||
// Create observer object
|
||||
const myObserver = {
|
||||
|
@ -35,14 +35,14 @@ describe('Reactive forms', function () {
|
||||
|
||||
it('should update the name control when the Update Name button is clicked', async () => {
|
||||
await nameInput.sendKeys(nameText);
|
||||
const value = await nameInput.getAttribute('value');
|
||||
const value1 = await nameInput.getAttribute('value');
|
||||
|
||||
expect(value).toBe(nameText);
|
||||
expect(value1).toBe(nameText);
|
||||
await updateButton.click();
|
||||
|
||||
const value = await nameInput.getAttribute('value');
|
||||
const value2 = await nameInput.getAttribute('value');
|
||||
|
||||
expect(value).toBe('Nancy');
|
||||
expect(value2).toBe('Nancy');
|
||||
});
|
||||
|
||||
it('should update the displayed control value when the name control updated', async () => {
|
||||
|
@ -8,7 +8,7 @@ import { AboutComponent } from './about/about.component';
|
||||
RouterModule.forRoot([
|
||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full'},
|
||||
{ path: 'about', component: AboutComponent },
|
||||
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule'}
|
||||
{ path: 'heroes', loadChildren: './hero/hero.module#HeroModule'}
|
||||
])
|
||||
],
|
||||
exports: [ RouterModule ] // re-export the module declarations
|
||||
|
@ -1,8 +1,7 @@
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
import { async, fakeAsync, tick } from '@angular/core/testing';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { interval, of } from 'rxjs';
|
||||
import { delay, take } from 'rxjs/operators';
|
||||
|
||||
describe('Angular async helper', () => {
|
||||
let actuallyDone = false;
|
||||
@ -21,49 +20,120 @@ describe('Angular async helper', () => {
|
||||
});
|
||||
|
||||
it('should run async test with task',
|
||||
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
|
||||
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
|
||||
|
||||
it('should run async test with task', async(() => {
|
||||
const id = setInterval(() => {
|
||||
actuallyDone = true;
|
||||
clearInterval(id);
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
it('should run async test with successful promise', async(() => {
|
||||
const p = new Promise(resolve => { setTimeout(resolve, 10); });
|
||||
p.then(() => { actuallyDone = true; });
|
||||
}));
|
||||
const p = new Promise(resolve => { setTimeout(resolve, 10); });
|
||||
p.then(() => { actuallyDone = true; });
|
||||
}));
|
||||
|
||||
it('should run async test with failed promise', async(() => {
|
||||
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
|
||||
p.catch(() => { actuallyDone = true; });
|
||||
}));
|
||||
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
|
||||
p.catch(() => { actuallyDone = true; });
|
||||
}));
|
||||
|
||||
// Use done. Cannot use setInterval with async or fakeAsync
|
||||
// See https://github.com/angular/angular/issues/10127
|
||||
// Use done. Can also use async or fakeAsync.
|
||||
it('should run async test with successful delayed Observable', (done: DoneFn) => {
|
||||
const source = of(true).pipe(delay(10));
|
||||
source.subscribe(
|
||||
val => actuallyDone = true,
|
||||
err => fail(err),
|
||||
done
|
||||
);
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err), done);
|
||||
});
|
||||
|
||||
// Cannot use setInterval from within an async zone test
|
||||
// See https://github.com/angular/angular/issues/10127
|
||||
// xit('should run async test with successful delayed Observable', async(() => {
|
||||
// const source = of(true).pipe(delay(10));
|
||||
// source.subscribe(
|
||||
// val => actuallyDone = true,
|
||||
// err => fail(err)
|
||||
// );
|
||||
// }));
|
||||
// #docregion fake-async-test-tick
|
||||
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
tick(100);
|
||||
expect(called).toBe(true);
|
||||
}));
|
||||
// #enddocregion fake-async-test-tick
|
||||
|
||||
// // Fail message: Error: 1 periodic timer(s) still in the queue
|
||||
// // See https://github.com/angular/angular/issues/10127
|
||||
// xit('should run async test with successful delayed Observable', fakeAsync(() => {
|
||||
// const source = of(true).pipe(delay(10));
|
||||
// source.subscribe(
|
||||
// val => actuallyDone = true,
|
||||
// err => fail(err)
|
||||
// );
|
||||
// #docregion fake-async-test-date
|
||||
it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
|
||||
const start = Date.now();
|
||||
tick(100);
|
||||
const end = Date.now();
|
||||
expect(end - start).toBe(100);
|
||||
}));
|
||||
// #enddocregion fake-async-test-date
|
||||
|
||||
// tick();
|
||||
// }));
|
||||
// #docregion fake-async-test-rxjs
|
||||
it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => {
|
||||
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
|
||||
// to patch rxjs scheduler
|
||||
let result = null;
|
||||
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
|
||||
expect(result).toBeNull();
|
||||
tick(1000);
|
||||
expect(result).toBe('hello');
|
||||
|
||||
const start = new Date().getTime();
|
||||
let dateDiff = 0;
|
||||
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
|
||||
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(1000);
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(2000);
|
||||
}));
|
||||
// #enddocregion fake-async-test-rxjs
|
||||
|
||||
// #docregion fake-async-test-clock
|
||||
describe('use jasmine.clock()', () => {
|
||||
// need to config __zone_symbol__fakeAsyncPatchLock flag
|
||||
// before loading zone.js/dist/zone-testing
|
||||
beforeEach(() => { jasmine.clock().install(); });
|
||||
afterEach(() => { jasmine.clock().uninstall(); });
|
||||
it('should auto enter fakeAsync', () => {
|
||||
// is in fakeAsync now, don't need to call fakeAsync(testFn)
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
jasmine.clock().tick(100);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
// #enddocregion fake-async-test-clock
|
||||
|
||||
// #docregion async-test-promise-then
|
||||
describe('test jsonp', () => {
|
||||
function jsonp(url: string, callback: Function) {
|
||||
// do a jsonp call which is not zone aware
|
||||
}
|
||||
// need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag
|
||||
// before loading zone.js/dist/zone-testing
|
||||
it('should wait until promise.then is called', async(() => {
|
||||
let finished = false;
|
||||
new Promise((res, rej) => {
|
||||
jsonp('localhost:8080/jsonp', () => {
|
||||
// success callback and resolve the promise
|
||||
finished = true;
|
||||
res();
|
||||
});
|
||||
}).then(() => {
|
||||
// async will wait until promise.then is called
|
||||
// if __zone_symbol__supportWaitUnResolvedChainedPromise is set
|
||||
expect(finished).toBe(true);
|
||||
});
|
||||
}));
|
||||
});
|
||||
// #enddocregion async-test-promise-then
|
||||
|
||||
it('should run async test with successful delayed Observable', async(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
}));
|
||||
|
||||
it('should run async test with successful delayed Observable', fakeAsync(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
|
||||
tick(10);
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -202,10 +202,10 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
|
||||
### Bootstrapping
|
||||
|
||||
<code-example hideCopy path="ajs-quick-reference/src/main.ts" title="main.ts" linenums="false"></code-example>
|
||||
<code-example hideCopy path="ajs-quick-reference/src/main.ts" header="main.ts" linenums="false"></code-example>
|
||||
<br>
|
||||
|
||||
<code-example hideCopy path="ajs-quick-reference/src/app/app.module.1.ts" title="app.module.ts" linenums="false"></code-example>
|
||||
<code-example hideCopy path="ajs-quick-reference/src/app/app.module.1.ts" header="app.module.ts" linenums="false"></code-example>
|
||||
|
||||
|
||||
Angular doesn't have a bootstrap directive.
|
||||
|
@ -37,7 +37,7 @@ To get started with adding Angular animations to your project, import the animat
|
||||
|
||||
Import `BrowserAnimationsModule`, which introduces the animation capabilities into your Angular root application module.
|
||||
|
||||
<code-example path="animations/src/app/app.module.1.ts" title="src/app/app.module.ts" language="typescript" linenums="false">
|
||||
<code-example path="animations/src/app/app.module.1.ts" header="src/app/app.module.ts" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -49,7 +49,7 @@ Import `BrowserAnimationsModule`, which introduces the animation capabilities in
|
||||
|
||||
If you plan to use specific animation functions in component files, import those functions from `@angular/animations`.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="imports" language="typescript">
|
||||
<code-example path="animations/src/app/app.component.ts" header="src/app/app.component.ts" region="imports" language="typescript">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -61,7 +61,7 @@ If you plan to use specific animation functions in component files, import those
|
||||
|
||||
In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="decorator" language="typescript">
|
||||
<code-example path="animations/src/app/app.component.ts" header="src/app/app.component.ts" region="decorator" language="typescript">
|
||||
</code-example>
|
||||
|
||||
## Animating a simple transition
|
||||
@ -82,12 +82,12 @@ Use the `style()` function to define a set of styles to associate with a given s
|
||||
|
||||
Let's see how Angular's `state()` function works with the `style()` function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a background color of yellow.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state1" language="typescript">
|
||||
<code-example path="animations/src/app/open-close.component.ts" header="src/app/open-close.component.ts" region="state1" language="typescript">
|
||||
</code-example>
|
||||
|
||||
In the `closed` state, shown below, the button has a height of 100 pixels, an opacity of 0.5, and a background color of green.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state2" language="typescript">
|
||||
<code-example path="animations/src/app/open-close.component.ts" header="src/app/open-close.component.ts" region="state2" language="typescript">
|
||||
</code-example>
|
||||
|
||||
### Transitions and timing
|
||||
@ -134,7 +134,7 @@ The third argument, `easing`, controls how the animation [accelerates and decele
|
||||
|
||||
This example provides a state transition from `open` to `closed` with a one second transition between states.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
<code-example path="animations/src/app/open-close.component.ts" header="src/app/open-close.component.ts" language="typescript"
|
||||
region="transition1">
|
||||
</code-example>
|
||||
|
||||
@ -142,7 +142,7 @@ In the code snippet above, the `=>` operator indicates unidirectional transition
|
||||
|
||||
This example adds a state transition from the `closed` state to the `open` state with a 0.5 second transition animation arc.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
<code-example path="animations/src/app/open-close.component.ts" header="src/app/open-close.component.ts" language="typescript"
|
||||
region="transition2">
|
||||
</code-example>
|
||||
|
||||
@ -180,7 +180,7 @@ In this example, we'll name the trigger `openClose`, and attach it to the `butto
|
||||
|
||||
Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
<code-example path="animations/src/app/open-close.component.ts" header="src/app/open-close.component.ts" language="typescript"
|
||||
region="component" linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -194,7 +194,7 @@ The animation is executed or triggered when the expression value changes to a ne
|
||||
|
||||
The following code snippet binds the trigger to the value of the `isOpen` property.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.1.html" title="src/app/open-close.component.html"
|
||||
<code-example path="animations/src/app/open-close.component.1.html" header="src/app/open-close.component.html"
|
||||
region="compare">
|
||||
</code-example>
|
||||
|
||||
@ -216,15 +216,15 @@ Here are the code files discussed in the transition example.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" language="typescript"
|
||||
<code-pane header="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" language="typescript"
|
||||
region="component">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/open-close.component.html" path="animations/src/app/open-close.component.1.html"
|
||||
<code-pane header="src/app/open-close.component.html" path="animations/src/app/open-close.component.1.html"
|
||||
region="trigger">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/open-close.component.css" path="animations/src/app/open-close.component.css">
|
||||
<code-pane header="src/app/open-close.component.css" path="animations/src/app/open-close.component.css">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
@ -8,7 +8,7 @@ This guide explains how to specify metadata and apply available compiler options
|
||||
|
||||
<div class="alert is-helpful"
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
|
||||
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo">Watch compiler author Tobias Bosch explain the Angular compiler</a> at AngularConnect 2016.
|
||||
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@ Angular offers two ways to compile your application:
|
||||
1. **_Just-in-Time_ (JIT)**, which compiles your app in the browser at runtime.
|
||||
1. **_Ahead-of-Time_ (AOT)**, which compiles your app at build time.
|
||||
|
||||
JIT compilation is the default when you run the _build-only_ or the _build-and-serve-locally_ CLI commands:
|
||||
JIT compilation is the default when you run the [`ng build`](cli/build) (build only) or [`ng serve`](cli/serve) (build and serve locally) CLI commands:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng build
|
||||
@ -30,7 +30,7 @@ JIT compilation is the default when you run the _build-only_ or the _build-and-s
|
||||
|
||||
{@a compile}
|
||||
|
||||
For AOT compilation, append the `--aot` flags to the _build-only_ or the _build-and-serve-locally_ CLI commands:
|
||||
For AOT compilation, include the `--aot` option with the `ng build` or `ng serve` command:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng build --aot
|
||||
@ -41,7 +41,7 @@ For AOT compilation, append the `--aot` flags to the _build-only_ or the _build-
|
||||
|
||||
The `ng build` command with the `--prod` meta-flag (`ng build --prod`) compiles with AOT by default.
|
||||
|
||||
See the [CLI documentation](https://github.com/angular/angular-cli/wiki) for details, especially the [`build` topic](https://github.com/angular/angular-cli/wiki/build).
|
||||
See the [CLI command reference](cli) and [Building and serving Angular apps](guide/build) for more information.
|
||||
|
||||
</div>
|
||||
|
||||
@ -1241,7 +1241,7 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
|
||||
### Non-null type assertion operator
|
||||
|
||||
Use the [non-null type assertion operator](guide/template-syntax#non-null-assertion-operator)
|
||||
to suppress the `Object is possibly 'undefined'` error when it is incovienent to use
|
||||
to suppress the `Object is possibly 'undefined'` error when it is inconvenient to use
|
||||
`*ngIf` or when some constraint in the component ensures that the expression is always
|
||||
non-null when the binding expression is interpolated.
|
||||
|
||||
@ -1308,6 +1308,28 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
|
||||
}
|
||||
```
|
||||
|
||||
{@a tsconfig-extends}
|
||||
## Configuration inheritance with extends
|
||||
Similar to TypeScript Compiler, Angular Compiler also supports `extends` in the `tsconfig.json` on `angularCompilerOptions`. A tsconfig file can inherit configurations from another file using the `extends` property.
|
||||
The `extends` is a top level property parallel to `compilerOptions` and `angularCompilerOptions`.
|
||||
The configuration from the base file are loaded first, then overridden by those in the inheriting config file.
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
...
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
||||
|
||||
{@a compiler-options}
|
||||
## Angular template compiler options
|
||||
|
||||
@ -1344,7 +1366,7 @@ for example, the content of annotations (such as a component's template), which
|
||||
emits to the `.js` file but not to the `.d.ts` file.
|
||||
|
||||
This option should be set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
|
||||
are not valid for this style of TypeScript output. It is not recommeded to use `--outFile` with
|
||||
are not valid for this style of TypeScript output. It is not recommended to use `--outFile` with
|
||||
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
|
||||
|
||||
This option can also be set to `true` when using factory summaries because the factory summaries
|
||||
@ -1439,7 +1461,7 @@ JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
|
||||
### *annotationsAs*
|
||||
|
||||
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
|
||||
annotations and decorators are unnaffected. Default is `static fields`.
|
||||
annotations and decorators are unaffected. Default is `static fields`.
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
|
@ -15,7 +15,7 @@ Its `selectHero()` method sets a `selectedHero` property when the user clicks to
|
||||
The component acquires the heroes from a service, which is a TypeScript [parameter property](http://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) on the constructor.
|
||||
The service is provided to the component through the dependency injection system.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" header="src/app/hero-list.component.ts (class)" region="class"></code-example>
|
||||
|
||||
Angular creates, updates, and destroys components as the user moves through the application. Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()`.
|
||||
|
||||
@ -31,7 +31,7 @@ In addition to containing or pointing to the template, the `@Component` metadata
|
||||
|
||||
Here's an example of basic metadata for `HeroListComponent`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (metadata)" region="metadata"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" header="src/app/hero-list.component.ts (metadata)" region="metadata"></code-example>
|
||||
|
||||
This example shows some of the most useful `@Component` configuration options:
|
||||
|
||||
@ -63,7 +63,7 @@ A template looks like regular HTML, except that it also contains Angular [templa
|
||||
|
||||
For example, here is a template for the Tutorial's `HeroListComponent`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.html" title="src/app/hero-list.component.html"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.html" header="src/app/hero-list.component.html"></code-example>
|
||||
|
||||
This template uses typical HTML elements like `<h2>` and `<p>`, and also includes Angular template-syntax elements, `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<app-hero-detail>`. The template-syntax elements tell Angular how to render the HTML to the screen, using program logic and data.
|
||||
|
||||
@ -87,7 +87,7 @@ The following diagram shows the four forms of data binding markup. Each form has
|
||||
|
||||
This example from the `HeroListComponent` template uses three of these forms.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (binding)" region="binding"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" header="src/app/hero-list.component.html (binding)" region="binding"></code-example>
|
||||
|
||||
* The `{{hero.name}}` [*interpolation*](guide/displaying-data#interpolation)
|
||||
displays the component's `hero.name` property value within the `<li>` element.
|
||||
@ -101,7 +101,7 @@ Two-way data binding (used mainly in [template-driven forms](guide/forms))
|
||||
combines property and event binding in a single notation.
|
||||
Here's an example from the `HeroDetailComponent` template that uses two-way data binding with the `ngModel` directive.
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" header="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
|
||||
In two-way binding, a data property value flows to the input box from the component as with property binding.
|
||||
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||
@ -164,7 +164,7 @@ Just as for components, the metadata for a directive associates the decorated cl
|
||||
*Structural directives* alter layout by adding, removing, and replacing elements in the DOM.
|
||||
The example template uses two built-in structural directives to add application logic to how the view is rendered.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (structural)" region="structural"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" header="src/app/hero-list.component.html (structural)" region="structural"></code-example>
|
||||
|
||||
* [`*ngFor`](guide/displaying-data#ngFor) is an iterative; it tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data#ngIf) is a conditional; it includes the `HeroDetail` component only if a selected hero exists.
|
||||
@ -176,7 +176,7 @@ In templates they look like regular HTML attributes, hence the name.
|
||||
|
||||
The `ngModel` directive, which implements two-way data binding, is an example of an attribute directive. `ngModel` modifies the behavior of an existing element (typically `<input>`) by setting its display value property and responding to change events.
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" header="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
|
||||
Angular has more pre-defined directives that either alter the layout structure
|
||||
(for example, [ngSwitch](guide/template-syntax#ngSwitch))
|
||||
|
@ -23,7 +23,7 @@ An NgModule is defined by a class decorated with `@NgModule()`. The `@NgModule()
|
||||
|
||||
Here's a simple root NgModule definition.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" title="src/app/app.module.ts" linenums="false"></code-example>
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" header="src/app/app.module.ts" linenums="false"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
@ -4,20 +4,20 @@ After you understand the basic Angular building blocks, you can begin to learn m
|
||||
about the features and tools that are available to help you develop and deliver Angular applications.
|
||||
Here are some key features.
|
||||
|
||||
## Responsive programming tools
|
||||
## Responsive programming
|
||||
|
||||
* [Lifecycle hooks](guide/lifecycle-hooks): Tap into key moments in the lifetime of a component, from its creation to its destruction, by implementing the lifecycle hook interfaces.
|
||||
|
||||
* [Observables and event processing](guide/observables): How to use observables with components and services to publish and subscribe to messages of any type, such as user-interaction events and asynchronous operation results.
|
||||
|
||||
## Client-server interaction tools
|
||||
## Client-server interaction
|
||||
|
||||
* [HTTP](guide/http): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client.
|
||||
|
||||
* [Server-side Rendering](guide/universal): Angular Universal generates static application pages on the server through server-side rendering (SSR). This allows you to run your Angular app on the server in order to improve performance and show the first page quickly on mobile and low-powered devices, and also facilitate web crawlers.
|
||||
|
||||
* [Service Workers](guide/service-worker-intro): Use a service worker to reduce dependency on the network
|
||||
significantly improving the use experience.
|
||||
significantly improving the user experience.
|
||||
|
||||
## Domain-specific libraries
|
||||
|
||||
@ -28,23 +28,28 @@ without deep knowledge of animation techniques or CSS.
|
||||
|
||||
## Support for the development cycle
|
||||
|
||||
* [Compilation](guide/aot-compiler): Angular provides just-in-time (JIT) compilation for the development environment, and ahead-of-time (AOT) compilation for the production environment.
|
||||
|
||||
* [Testing platform](guide/testing): Run unit tests on your application parts as they interact with the Angular framework.
|
||||
|
||||
* [Internationalization](guide/i18n): Make your app available in multiple languages with Angular's internationalization (i18n) tools.
|
||||
|
||||
* [Compilation](guide/aot-compiler): Angular provides just-in-time (JIT) compilation for the development environment, and ahead-of-time (AOT) compilation for the production environment.
|
||||
|
||||
* [Security guidelines](guide/security): Learn about Angular's built-in protections against common web-app vulnerabilities and attacks such as cross-site scripting attacks.
|
||||
|
||||
## Setup and deployment tools
|
||||
## Setup, build, and deployment configuration
|
||||
|
||||
* [Setup for local development](guide/setup): Set up a new project for development with QuickStart.
|
||||
* [CLI Command Reference](cli): The Angular CLI is a command-line tool that you use to create projects, generate application and library code, and perform a variety of ongoing development tasks such as testing, bundling, and deployment.
|
||||
|
||||
* [Installation](guide/npm-packages): The [Angular CLI](https://cli.angular.io/), Angular applications, and Angular itself depend on features and functionality provided by libraries that are available as [npm](https://docs.npmjs.com/) packages.
|
||||
* [Workspace and File Structure](guide/file-structure): Understand the structure of Angular workspace and project folders.
|
||||
|
||||
* [npm Packages](guide/npm-packages): The Angular Framework, Angular CLI, and components used by Angular applications are packaged as [npm](https://docs.npmjs.com/) packages and distributed via the npm registry. The Angular CLI creates a default `package.json` file, which specifies a starter set of packages that work well together and jointly support many common application scenarios.
|
||||
|
||||
* [TypeScript configuration](guide/typescript-configuration): TypeScript is the primary language for Angular application development.
|
||||
|
||||
* [Browser support](guide/browser-support): Make your apps compatible across a wide range of browsers.
|
||||
|
||||
* [Building and Serving](guide/build): Learn to define different build and proxy server configurations for your project, such as development, staging, and production.
|
||||
|
||||
* [Deployment](guide/deployment): Learn techniques for deploying your Angular application to a remote server.
|
||||
|
||||
|
||||
|
@ -28,11 +28,11 @@ available to components through *dependency injection*.
|
||||
|
||||
Here's an example of a service class that logs to the browser console.
|
||||
|
||||
<code-example path="architecture/src/app/logger.service.ts" linenums="false" title="src/app/logger.service.ts (class)" region="class"></code-example>
|
||||
<code-example path="architecture/src/app/logger.service.ts" linenums="false" header="src/app/logger.service.ts (class)" region="class"></code-example>
|
||||
|
||||
Services can depend on other services. For example, here's a `HeroService` that depends on the `Logger` service, and also uses `BackendService` to get heroes. That service in turn might depend on the `HttpClient` service to fetch heroes asynchronously from a server.
|
||||
|
||||
<code-example path="architecture/src/app/hero.service.ts" linenums="false" title="src/app/hero.service.ts (class)" region="class"></code-example>
|
||||
<code-example path="architecture/src/app/hero.service.ts" linenums="false" header="src/app/hero.service.ts (class)" region="class"></code-example>
|
||||
|
||||
## Dependency injection (DI)
|
||||
|
||||
@ -62,7 +62,7 @@ A dependency doesn't have to be a service—it could be a function, for exam
|
||||
|
||||
When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types. For example, the constructor of `HeroListComponent` needs `HeroService`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (constructor)" region="ctor"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" header="src/app/hero-list.component.ts (constructor)" region="ctor"></code-example>
|
||||
|
||||
When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn't yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.
|
||||
|
||||
@ -82,7 +82,7 @@ or you can register providers with specific modules or components.
|
||||
You register providers in the metadata of the service (in the `@Injectable()` decorator),
|
||||
or in the `@NgModule()` or `@Component()` metadata
|
||||
|
||||
* By default, the Angular CLI command `ng generate service` registers a provider with the root injector for your service by including provider metadata in the `@Injectable()` decorator. The tutorial uses this method to register the provider of HeroService class definition.
|
||||
* By default, the Angular CLI command [`ng generate service`](cli/generate) registers a provider with the root injector for your service by including provider metadata in the `@Injectable()` decorator. The tutorial uses this method to register the provider of HeroService class definition.
|
||||
|
||||
```
|
||||
@Injectable({
|
||||
@ -111,6 +111,6 @@ or in the `@NgModule()` or `@Component()` metadata
|
||||
service with each new instance of that component.
|
||||
At the component level, register a service provider in the `providers` property of the `@Component()` metadata.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (component providers)" region="providers"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" header="src/app/hero-list.component.ts (component providers)" region="providers"></code-example>
|
||||
|
||||
For more detailed information, see the [Dependency Injection](guide/dependency-injection) section.
|
||||
|
@ -15,7 +15,7 @@ There are three kinds of directives in Angular:
|
||||
1. Attribute directives—change the appearance or behavior of an element, component, or another directive.
|
||||
|
||||
*Components* are the most common of the three directives.
|
||||
You saw a component for the first time in the [QuickStart](guide/quickstart) guide.
|
||||
You saw a component for the first time in the [Getting Started](guide/quickstart).
|
||||
|
||||
*Structural Directives* change the structure of the view.
|
||||
Two examples are [NgFor](guide/template-syntax#ngFor) and [NgIf](guide/template-syntax#ngIf).
|
||||
@ -37,13 +37,13 @@ This page demonstrates building a simple _appHighlight_ attribute
|
||||
directive to set an element's background color
|
||||
when the user hovers over that element. You can apply it like this:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (applied)" region="applied"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" header="src/app/app.component.html (applied)" region="applied"></code-example>
|
||||
|
||||
{@a write-directive}
|
||||
|
||||
### Write the directive code
|
||||
|
||||
Create the directive class file in a terminal window with this CLI command.
|
||||
Create the directive class file in a terminal window with the CLI command [`ng generate directive`](cli/generate).
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate directive highlight
|
||||
@ -59,9 +59,9 @@ _Directives_ must be declared in [Angular Modules](guide/ngmodules) in the same
|
||||
|
||||
The generated `src/app/highlight.directive.ts` is as follows:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.0.ts" title="src/app/highlight.directive.ts"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.0.ts" header="src/app/highlight.directive.ts"></code-example>
|
||||
|
||||
The imported `Directive` symbol provides the Angular the `@Directive` decorator.
|
||||
The imported `Directive` symbol provides Angular the `@Directive` decorator.
|
||||
|
||||
The `@Directive` decorator's lone configuration property specifies the directive's
|
||||
[CSS attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors), `[appHighlight]`.
|
||||
@ -92,7 +92,7 @@ Exporting `HighlightDirective` makes the directive accessible.
|
||||
|
||||
Now edit the generated `src/app/highlight.directive.ts` to look as follows:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" header="src/app/highlight.directive.ts"></code-example>
|
||||
|
||||
The `import` statement specifies an additional `ElementRef` symbol from the Angular `core` library:
|
||||
|
||||
@ -111,7 +111,7 @@ This first implementation sets the background color of the host element to yello
|
||||
|
||||
To use the new `HighlightDirective`, add a paragraph (`<p>`) element to the template of the root `AppComponent` and apply the directive as an attribute.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html" region="applied"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" header="src/app/app.component.html" region="applied"></code-example>
|
||||
|
||||
Now run the application to see the `HighlightDirective` in action.
|
||||
|
||||
@ -136,12 +136,12 @@ and respond by setting or clearing the highlight color.
|
||||
|
||||
Begin by adding `HostListener` to the list of imported symbols.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` decorator.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-methods)" region="mouse-methods"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (mouse-methods)" region="mouse-methods"></code-example>
|
||||
|
||||
The `@HostListener` decorator lets you subscribe to events of the DOM
|
||||
element that hosts an attribute directive, the `<p>` in this case.
|
||||
@ -162,11 +162,11 @@ The handlers delegate to a helper method that sets the color on the host DOM ele
|
||||
The helper method, `highlight`, was extracted from the constructor.
|
||||
The revised constructor simply declares the injected `el: ElementRef`.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (constructor)" region="ctor"></code-example>
|
||||
|
||||
Here's the updated directive in full:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" title="src/app/highlight.directive.ts"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" header="src/app/highlight.directive.ts"></code-example>
|
||||
|
||||
Run the app and confirm that the background color appears when
|
||||
the mouse hovers over the `p` and disappears as it moves out.
|
||||
@ -183,11 +183,11 @@ Currently the highlight color is hard-coded _within_ the directive. That's infle
|
||||
In this section, you give the developer the power to set the highlight color while applying the directive.
|
||||
|
||||
Begin by adding `Input` to the list of symbols imported from `@angular/core`.
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" header="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
|
||||
Add a `highlightColor` property to the directive class like this:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (highlightColor)" region="color"></code-example>
|
||||
|
||||
{@a input}
|
||||
|
||||
@ -200,19 +200,19 @@ Without that input metadata, Angular rejects the binding; see [below](guide/attr
|
||||
|
||||
Try it by adding the following directive binding variations to the `AppComponent` template:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-1"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" header="src/app/app.component.html (excerpt)" region="color-1"></code-example>
|
||||
|
||||
Add a `color` property to the `AppComponent`.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (class)" region="class"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.1.ts" linenums="false" header="src/app/app.component.ts (class)" region="class"></code-example>
|
||||
|
||||
Let it control the highlight color with a property binding.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-2"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" header="src/app/app.component.html (excerpt)" region="color-2"></code-example>
|
||||
|
||||
That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (color)" region="color"></code-example>
|
||||
|
||||
The `[appHighlight]` attribute binding both applies the highlighting directive to the `<p>` element
|
||||
and sets the directive's highlight color with a property binding.
|
||||
@ -221,7 +221,7 @@ That's a crisp, compact syntax.
|
||||
|
||||
You'll have to rename the directive's `highlightColor` property to `appHighlight` because that's now the color property binding name.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2"></code-example>
|
||||
|
||||
This is disagreeable. The word, `appHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||
|
||||
@ -233,23 +233,23 @@ Fortunately you can name the directive property whatever you want _and_ **_alias
|
||||
|
||||
Restore the original property name and specify the selector as the alias in the argument to `@Input`.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" header="src/app/highlight.directive.ts (color property with alias)" region="color"></code-example>
|
||||
|
||||
_Inside_ the directive the property is known as `highlightColor`.
|
||||
_Outside_ the directive, where you bind to it, it's known as `appHighlight`.
|
||||
|
||||
You get the best of both worlds: the property name you want and the binding syntax you want:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (color)" region="color"></code-example>
|
||||
|
||||
Now that you're binding via the alias to the `highlightColor`, modify the `onMouseEnter()` method to use that property.
|
||||
If someone neglects to bind to `appHighlightColor`, highlight the host element in red:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" header="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter"></code-example>
|
||||
|
||||
Here's the latest version of the directive class.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (excerpt)"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" header="src/app/highlight.directive.ts (excerpt)"></code-example>
|
||||
|
||||
## Write a harness to try it
|
||||
|
||||
@ -259,11 +259,11 @@ lets you pick the highlight color with a radio button and bind your color choice
|
||||
|
||||
Update <code>app.component.html</code> as follows:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (v2)" region="v2"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (v2)" region="v2"></code-example>
|
||||
|
||||
Revise the `AppComponent.color` so that it has no initial value.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (class)" region="class"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.ts" linenums="false" header="src/app/app.component.ts (class)" region="class"></code-example>
|
||||
|
||||
Here are the harness and directive in action.
|
||||
|
||||
@ -283,12 +283,12 @@ Let the template developer set the default color.
|
||||
|
||||
Add a second **input** property to `HighlightDirective` called `defaultColor`:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (defaultColor)" region="defaultColor"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" header="src/app/highlight.directive.ts (defaultColor)" region="defaultColor"></code-example>
|
||||
|
||||
Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`,
|
||||
then with the `defaultColor`, and falls back to "red" if both properties are undefined.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" header="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter"></code-example>
|
||||
|
||||
How do you bind to a second property when you're already binding to the `appHighlight` attribute name?
|
||||
|
||||
@ -296,7 +296,7 @@ As with components, you can add as many directive property bindings as you need
|
||||
The developer should be able to write the following template HTML to both bind to the `AppComponent.color`
|
||||
and fall back to "violet" as the default color.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (defaultColor)" region="defaultColor"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (defaultColor)" region="defaultColor"></code-example>
|
||||
|
||||
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||
because you made it _public_ with the `@Input` decorator.
|
||||
@ -319,12 +319,12 @@ This page covered how to:
|
||||
The final source code follows:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="app/app.component.ts" path="attribute-directives/src/app/app.component.ts"></code-pane>
|
||||
<code-pane title="app/app.component.html" path="attribute-directives/src/app/app.component.html"></code-pane>
|
||||
<code-pane title="app/highlight.directive.ts" path="attribute-directives/src/app/highlight.directive.ts"></code-pane>
|
||||
<code-pane title="app/app.module.ts" path="attribute-directives/src/app/app.module.ts"></code-pane>
|
||||
<code-pane title="main.ts" path="attribute-directives/src/main.ts"></code-pane>
|
||||
<code-pane title="index.html" path="attribute-directives/src/index.html"></code-pane>
|
||||
<code-pane header="app/app.component.ts" path="attribute-directives/src/app/app.component.ts"></code-pane>
|
||||
<code-pane header="app/app.component.html" path="attribute-directives/src/app/app.component.html"></code-pane>
|
||||
<code-pane header="app/highlight.directive.ts" path="attribute-directives/src/app/highlight.directive.ts"></code-pane>
|
||||
<code-pane header="app/app.module.ts" path="attribute-directives/src/app/app.module.ts"></code-pane>
|
||||
<code-pane header="main.ts" path="attribute-directives/src/main.ts"></code-pane>
|
||||
<code-pane header="index.html" path="attribute-directives/src/index.html"></code-pane>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
@ -338,11 +338,11 @@ You can also experience and download the <live-example title="Attribute Directiv
|
||||
In this demo, the `highlightColor` property is an ***input*** property of
|
||||
the `HighlightDirective`. You've seen it applied without an alias:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" header="src/app/highlight.directive.ts (color)" region="color"></code-example>
|
||||
|
||||
You've seen it with an alias:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" header="src/app/highlight.directive.ts (color)" region="color"></code-example>
|
||||
|
||||
Either way, the `@Input` decorator tells Angular that this property is
|
||||
_public_ and available for binding by a parent component.
|
||||
@ -374,7 +374,7 @@ You can tell if `@Input` is needed by the position of the property name in a bin
|
||||
|
||||
Now apply that reasoning to the following example:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (color)" region="color"></code-example>
|
||||
|
||||
* The `color` property in the expression on the right belongs to the template's component.
|
||||
The template and its component trust each other.
|
||||
|
@ -12,7 +12,7 @@ Every application has at least one Angular module, the _root_ module
|
||||
that you bootstrap to launch the application.
|
||||
By convention, it is usually called `AppModule`.
|
||||
|
||||
If you use the CLI to generate an app, the default `AppModule` is as follows:
|
||||
If you use the [Angular CLI](cli) to generate an app, the default `AppModule` is as follows:
|
||||
|
||||
```typescript
|
||||
/* JavaScript imports */
|
||||
@ -52,7 +52,7 @@ The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
|
||||
* **_bootstrap_**—the _root_ component that Angular creates and inserts
|
||||
into the `index.html` host web page.
|
||||
|
||||
The default CLI application only has one component, `AppComponent`, so it
|
||||
The default application created by the Angular CLI only has one component, `AppComponent`, so it
|
||||
is in both the `declarations` and the `bootstrap` arrays.
|
||||
|
||||
{@a declarations}
|
||||
@ -106,18 +106,18 @@ To use a directive, component, or pipe in a module, you must do a few things:
|
||||
Those three steps look like the following. In the file where you create your directive, export it.
|
||||
The following example, named `ItemDirective` is the default directive structure that the CLI generates in its own file, `item.directive.ts`:
|
||||
|
||||
<code-example path="bootstrapping/src/app/item.directive.ts" region="directive" title="src/app/item.directive.ts" linenums="false">
|
||||
<code-example path="bootstrapping/src/app/item.directive.ts" region="directive" header="src/app/item.directive.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The key point here is that you have to export it so you can import it elsewhere. Next, import it
|
||||
into the NgModule, in this example `app.module.ts`, with a JavaScript import statement:
|
||||
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="directive-import" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="directive-import" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
And in the same file, add it to the `@NgModule` `declarations` array:
|
||||
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="declarations" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="declarations" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
|
@ -122,7 +122,7 @@ Note that polyfills cannot magically transform an old, slow browser into a moder
|
||||
|
||||
## Enabling polyfills
|
||||
|
||||
[Angular CLI](https://github.com/angular/angular-cli/wiki) users enable polyfills through the `src/polyfills.ts` file that
|
||||
[Angular CLI](cli) users enable polyfills through the `src/polyfills.ts` file that
|
||||
the CLI created with your project.
|
||||
|
||||
This file incorporates the mandatory and many of the optional polyfills as JavaScript `import` statements.
|
||||
@ -140,7 +140,7 @@ For example, [if you need the web animations polyfill](http://caniuse.com/#feat=
|
||||
|
||||
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement as in the following example:
|
||||
|
||||
<code-example title="src/polyfills.ts">
|
||||
<code-example header="src/polyfills.ts">
|
||||
/**
|
||||
* Required to support Web Animations `@angular/platform-browser/animations`.
|
||||
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
|
||||
@ -553,7 +553,7 @@ computed with the <a href="http://closure-compiler.appspot.com/home">closure com
|
||||
|
||||
If you are not using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
|
||||
|
||||
<code-example title="src/index.html">
|
||||
<code-example header="src/index.html">
|
||||
<!-- pre-zone polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/web-animations-js/web-animations.min.js"></script>
|
||||
|
492
aio/content/guide/build.md
Normal file
492
aio/content/guide/build.md
Normal file
@ -0,0 +1,492 @@
|
||||
# Building and serving Angular apps
|
||||
|
||||
This page discusses build-specific configuration options for Angular projects.
|
||||
|
||||
{@a app-environments}
|
||||
|
||||
## Configuring application environments
|
||||
|
||||
You can define different named build configurations for your project, such as *stage* and *production*, with different defaults.
|
||||
|
||||
Each named build configuration can have defaults for any of the options that apply to the various build targets, such as `build`, `serve`, and `test`. The [Angular CLI](cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment.
|
||||
|
||||
The following figure shows how a project has multiple build targets, which can be executed using the named configurations that you define.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/build/build-config-targets.gif" alt="build configurations and targets">
|
||||
</figure>
|
||||
|
||||
### Configure environment-specific defaults
|
||||
|
||||
A project's `src/environments/` folder contains the base configuration file, `environment.ts`, which provides a default environment.
|
||||
You can add override defaults for additional environments, such as production and staging, in target-specific configuration files.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
└──myProject/src/environments/
|
||||
└──environment.ts
|
||||
└──environment.prod.ts
|
||||
└──environment.stage.ts
|
||||
```
|
||||
|
||||
The base file `environment.ts`, contains the default environment settings. For example:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
</code-example>
|
||||
|
||||
The `build` command uses this as the build target when no environment is specified.
|
||||
You can add further variables, either as additional properties on the environment object, or as separate objects.
|
||||
For example, the following adds a default for a variable to the default environment:
|
||||
|
||||
```
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: 'http://my-api-url'
|
||||
};
|
||||
```
|
||||
|
||||
You can add target-specific configuration files, such as `environment.prod.ts`.
|
||||
The following sets content sets default values for the production build target:
|
||||
|
||||
```
|
||||
export const environment = {
|
||||
production: true
|
||||
apiUrl: 'http://my-prod-url'
|
||||
};
|
||||
```
|
||||
|
||||
### Using environment-specific variables in your app
|
||||
|
||||
The following application structure configures build targets for production and staging environments:
|
||||
|
||||
```
|
||||
└── src
|
||||
└── app
|
||||
├── app.component.html
|
||||
└── app.component.ts
|
||||
└── environments
|
||||
├── environment.prod.ts
|
||||
├── environment.staging.ts
|
||||
└── environment.ts
|
||||
```
|
||||
|
||||
To use the environment configurations you have defined, your components must import the original environments file:
|
||||
|
||||
```
|
||||
import { environment } from './../environments/environment';
|
||||
```
|
||||
|
||||
This ensures that the build and serve commands can find the configurations for specific build targets.
|
||||
|
||||
The following code in the component file (`app.component.ts`) uses an environment variable defined in the configuration files.
|
||||
|
||||
```
|
||||
import { Component } from '@angular/core';
|
||||
import { environment } from './../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor() {
|
||||
console.log(environment.production); // Logs false for default environment
|
||||
}
|
||||
title = 'app works!';
|
||||
}
|
||||
```
|
||||
{@a file-replacement}
|
||||
|
||||
## Configure target-specific file replacements
|
||||
|
||||
The main CLI configuration file, `angular.json`, contains a `fileReplacements` section in the configuration for each build target, which allows you to replace any file with a target-specific version of that file.
|
||||
This is useful for including target-specific code or variables in a build that targets a specific environment, such as production or staging.
|
||||
|
||||
By default no files are replaced.
|
||||
You can add file replacements for specific build targets.
|
||||
For example:
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
...
|
||||
```
|
||||
|
||||
This means that when you build your production configuration (using `ng build --prod` or `ng build --configuration=production`), the `src/environments/environment.ts` file is replaced with the target-specific version of the file, `src/environments/environment.prod.ts`.
|
||||
|
||||
You can add additional configurations as required. To add a staging environment, create a copy of `src/environments/environment.ts` called `src/environments/environment.staging.ts`, then add a `staging` configuration to `angular.json`:
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
"production": { ... },
|
||||
"staging": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.staging.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can add more configuration options to this target environment as well.
|
||||
Any option that your build supports can be overridden in a build target configuration.
|
||||
|
||||
To build using the staging configuration, run `ng build --configuration=staging`.
|
||||
|
||||
You can also configure the `serve` command to use the targeted build configuration if you add it to the "serve:configurations" section of `angular.json`:
|
||||
|
||||
```
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "your-project-name:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "your-project-name:build:production"
|
||||
},
|
||||
"staging": {
|
||||
"browserTarget": "your-project-name:build:staging"
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
{@a size-budgets}
|
||||
|
||||
## Configure size budgets
|
||||
|
||||
As applications grow in functionality, they also grow in size.
|
||||
The CLI allows you to set size thresholds in your configuration to ensure that parts of your application stay within size boundaries that you define.
|
||||
|
||||
Define your size boundaries in the CLI configuration file, `angular.json`, in a `budgets` section for each [configured environment](#app-environments).
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"configurations": {
|
||||
"production": {
|
||||
...
|
||||
budgets: []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can specify size budgets for the entire app, and for particular parts.
|
||||
Each budget entry configures a budget of a given type.
|
||||
Specify size values in the following formats:
|
||||
|
||||
* 123 or 123b: Size in bytes
|
||||
|
||||
* 123kb: Size in kilobytes
|
||||
|
||||
* 123mb: Size in megabytes
|
||||
|
||||
* 12%: Percentage of size relative to baseline. (Not valid for baseline values.)
|
||||
|
||||
When you configure a budget, the build system warns or reports and error when a given part of the app reaches or exceeds a boundary size that you set.
|
||||
|
||||
Each budget entry is a JSON object with the following properties:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>type</td>
|
||||
<td>The type of budget. One of:
|
||||
|
||||
* bundle - The size of a specific bundle.
|
||||
* initial - The initial size of the app.
|
||||
* allScript - The size of all scripts.
|
||||
* all - The size of the entire app.
|
||||
* anyScript - The size of any one script.
|
||||
* any - The size of any file.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>
|
||||
|
||||
The name of the bundle (for `type=bundle`).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>baseline</td>
|
||||
<td>An absolute baseline size for percentage values. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maximumWarning</td>
|
||||
<td>Warns when a size exceeds this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maximumError</td>
|
||||
<td>Reports an error when the size exceeds this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>minimumWarning</td>
|
||||
<td>Warns when the size reaches this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>minimumError</td>
|
||||
<td>Reports an error when the size reaches this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>warning</td>
|
||||
<td>Warns when the size ??reaches or exceeds?? this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>error</td>
|
||||
<td>Reports an error when the size ??reaches or exceeds?? this threshold percentage of the baseline.</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
{@a browser-compat}
|
||||
|
||||
## Configuring browser compatibility
|
||||
|
||||
The CLI uses [Autoprefixer](https://github.com/postcss/autoprefixer) to ensure compatibility with different browser and browser versions.
|
||||
You may find it necessary to target specific browsers or exclude certain browser versions from your build.
|
||||
|
||||
Internally, Autoprefixer relies on a library called [Browserslist(https://github.com/ai/browserslist)] to figure out which browsers to support with prefixing.
|
||||
Browserlist looks for configuration options in a `browserlist` property of the package configuration file, or in a configuration file named `.browserslistrc`.
|
||||
Autoprefixer looks for the Browserlist configuration when it prefixes your CSS.
|
||||
|
||||
* You can tell Autoprefixer what browsers to target by adding a browserslist property to the package configuration file, `package.json`:
|
||||
```
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
```
|
||||
|
||||
* Alternatively, you can add a new file, `.browserslistrc`, to the project directory, that specifies browsers you want to support:
|
||||
```
|
||||
### Supported Browsers
|
||||
> 1%
|
||||
last 2 versions
|
||||
```
|
||||
|
||||
See the [browserslist repo](https://github.com/ai/browserslist) for more examples of how to target specific browsers and versions.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Backward compatibility
|
||||
|
||||
If you want to produce a progressive web app and are using [Lighthouse](https://developers.google.com/web/tools/lighthouse/) to grade the project, add the following browserslist entry to your `package.json` file, in order to eliminate the [old flexbox](https://developers.google.com/web/tools/lighthouse/audits/old-flexbox) prefixes:
|
||||
|
||||
```
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"not ie <= 10",
|
||||
"not ie_mob <= 10"
|
||||
]
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
{@a proxy}
|
||||
|
||||
## Proxying to a backend server
|
||||
|
||||
You can use the [proxying support](https://webpack.js.org/configuration/dev-server/#devserver-proxy) in the `webpack` dev server to divert certain URLs to a backend server, by passing a file to the `--proxy-config` build option.
|
||||
For example, to divert all calls for `http://localhost:4200/api` to a server running on `http://localhost:3000/api`, take the following steps.
|
||||
|
||||
1. Create a file `proxy.conf.json` in the projects `src/` folder, next to `package.json`.
|
||||
|
||||
1. Add the following content to the new proxy file:
|
||||
```
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. In the CLI configuration file, `angular.json`, add the `proxyConfig` option to the `serve` target:
|
||||
```
|
||||
...
|
||||
"architect": {
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "your-application-name:build",
|
||||
"proxyConfig": "src/proxy.conf.json"
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
1. To run the dev server with this proxy configuration, call `ng serve`.
|
||||
|
||||
You can edit the proxy configuration file to add configuration options; some examples are given below.
|
||||
For a description of all options, see [webpack DevServer documentation](https://webpack.js.org/configuration/dev-server/#devserver-proxy).
|
||||
|
||||
Note that if you edit the proxy configuration file, you must relaunch the `ng serve` process to make your changes effective.
|
||||
|
||||
### Rewrite the URL path
|
||||
|
||||
The `pathRewrite` proxy configuration option lets you rewrite the URL path at run time.
|
||||
For example, you can specify the following `pathRewrite` value to the proxy configuration to remove "api" from the end of a path.
|
||||
|
||||
```
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you need to access a backend that is not on `localhost`, set the `changeOrigin` option as well. For example:
|
||||
|
||||
```
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://npmjs.org",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
},
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To help determine whether your proxy is working as intended, set the `logLevel` option. For example:
|
||||
|
||||
```
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
},
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Proxy log levels are `info` (the default), `debug`, `warn`, `error`, and `silent`.
|
||||
|
||||
### Proxy multiple entries
|
||||
|
||||
You can proxy multiple entries to the same target by defining the configuration in JavaScript.
|
||||
|
||||
Set the proxy configuration file to `proxy.conf.js` (instead of `proxy.conf.json`), and specify configuration files as in the following example.
|
||||
|
||||
```
|
||||
const PROXY_CONFIG = [
|
||||
{
|
||||
context: [
|
||||
"/my",
|
||||
"/many",
|
||||
"/endpoints",
|
||||
"/i",
|
||||
"/need",
|
||||
"/to",
|
||||
"/proxy"
|
||||
],
|
||||
target: "http://localhost:3000",
|
||||
secure: false
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
||||
```
|
||||
|
||||
In the CLI configuration file, `angular.json`, point to the JavaScript proxy configuration file:
|
||||
|
||||
```
|
||||
...
|
||||
"architect": {
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "your-application-name:build",
|
||||
"proxyConfig": "src/proxy.conf.js"
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### Bypass the proxy
|
||||
|
||||
If you need to optionally bypass the proxy, or dynamically change the request before it's sent, add the bypass option, as shown in this JavaScript example.
|
||||
|
||||
```
|
||||
const PROXY_CONFIG = {
|
||||
"/api/proxy": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false,
|
||||
"bypass": function (req, res, proxyOptions) {
|
||||
if (req.headers.accept.indexOf("html") !== -1) {
|
||||
console.log("Skipping proxy for browser request.");
|
||||
return "/index.html";
|
||||
}
|
||||
req.headers["X-Custom-Header"] = "yes";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
||||
```
|
||||
|
||||
### Using corporate proxy
|
||||
|
||||
If you work behind a corporate proxy, the cannot directly proxy calls to any URL outside your local network.
|
||||
In this case, you can configure the backend proxy to redirect calls through your corporate proxy using an agent:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
npm install --save-dev https-proxy-agent
|
||||
</code-example>
|
||||
|
||||
When you define an environment variable `http_proxy` or `HTTP_PROXY`, an agent is automatically added to pass calls through your corporate proxy when running `npm start`.
|
||||
|
||||
Use the following content in the JavaScript configuration file.
|
||||
|
||||
```
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
var proxyConfig = [{
|
||||
context: '/api',
|
||||
target: 'http://your-remote-server.com:3000',
|
||||
secure: false
|
||||
}];
|
||||
|
||||
function setupForCorporateProxy(proxyConfig) {
|
||||
var proxyServer = process.env.http_proxy || process.env.HTTP_PROXY;
|
||||
if (proxyServer) {
|
||||
var agent = new HttpsProxyAgent(proxyServer);
|
||||
console.log('Using corporate proxy server: ' + proxyServer);
|
||||
proxyConfig.forEach(function(entry) {
|
||||
entry.agent = agent;
|
||||
});
|
||||
}
|
||||
return proxyConfig;
|
||||
}
|
||||
|
||||
module.exports = setupForCorporateProxy(proxyConfig);
|
||||
```
|
||||
|
@ -19,7 +19,7 @@ Observables are often compared to promises. Here are some key differences:
|
||||
|
||||
### Creation and subscription
|
||||
|
||||
* Observables are not executed until a consumer subcribes. The `subscribe()` executes the defined behavior once, and it can be called again. Each subscription has its own computation. Resubscription causes recomputation of values.
|
||||
* Observables are not executed until a consumer subscribes. The `subscribe()` executes the defined behavior once, and it can be called again. Each subscription has its own computation. Resubscription causes recomputation of values.
|
||||
|
||||
<code-example hideCopy>
|
||||
// declare a publishing operation
|
||||
|
@ -38,7 +38,7 @@ The following example demonstrates how to use `query()` and `stagger()` function
|
||||
|
||||
* Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="page-animations" language="typescript" linenums="false"></code-example>
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" header="src/app/hero-list-page.component.ts" region="page-animations" language="typescript" linenums="false"></code-example>
|
||||
|
||||
## Parallel animation using group() function
|
||||
|
||||
@ -51,7 +51,7 @@ You've seen how to add a delay between each successive animation. But you may al
|
||||
|
||||
In the following example, using groups on both `:enter` and `:leave` allow for two different timing configurations. They're applied to the same element in parallel, but run independently.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="src/app/hero-list-groups.component.ts (excerpt)" language="typescript" linenums="false"></code-example>
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" header="src/app/hero-list-groups.component.ts (excerpt)" language="typescript" linenums="false"></code-example>
|
||||
|
||||
## Sequential vs. parallel animations
|
||||
|
||||
@ -70,11 +70,11 @@ The filter works in real time as you type. Elements leave the page as you type e
|
||||
|
||||
The HTML template contains a trigger called `filterAnimation`.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.html" title="src/app/hero-list-page.component.html" region="filter-animations"></code-example>
|
||||
<code-example path="animations/src/app/hero-list-page.component.html" header="src/app/hero-list-page.component.html" region="filter-animations"></code-example>
|
||||
|
||||
The component file contains three transitions.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="filter-animations" language="typescript" linenums="false"></code-example>
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" header="src/app/hero-list-page.component.ts" region="filter-animations" language="typescript" linenums="false"></code-example>
|
||||
|
||||
The animation does the following:
|
||||
|
||||
|
@ -28,7 +28,7 @@ in which two or more components share information.
|
||||
typically adorned with [@Input decorations](guide/template-syntax#inputs-outputs).
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/hero-child.component.ts" title="component-interaction/src/app/hero-child.component.ts">
|
||||
<code-example path="component-interaction/src/app/hero-child.component.ts" header="component-interaction/src/app/hero-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -41,7 +41,7 @@ binding its `master` string property to the child's `master` alias,
|
||||
and each iteration's `hero` instance to the child's `hero` property.
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/hero-parent.component.ts" title="component-interaction/src/app/hero-parent.component.ts">
|
||||
<code-example path="component-interaction/src/app/hero-parent.component.ts" header="component-interaction/src/app/hero-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -61,7 +61,7 @@ The running application displays three heroes:
|
||||
E2E test that all children were instantiated and displayed as expected:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -79,7 +79,7 @@ The setter of the `name` input property in the child `NameChildComponent`
|
||||
trims the whitespace from a name and replaces an empty value with default text.
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/name-child.component.ts" title="component-interaction/src/app/name-child.component.ts">
|
||||
<code-example path="component-interaction/src/app/name-child.component.ts" header="component-interaction/src/app/name-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -88,7 +88,7 @@ trims the whitespace from a name and replaces an empty value with default text.
|
||||
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/name-parent.component.ts" title="component-interaction/src/app/name-parent.component.ts">
|
||||
<code-example path="component-interaction/src/app/name-parent.component.ts" header="component-interaction/src/app/name-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -105,7 +105,7 @@ Here's the `NameParentComponent` demonstrating name variations including a name
|
||||
E2E tests of input property setter with empty and non-empty names:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child-setter" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child-setter" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -134,7 +134,7 @@ Learn about `ngOnChanges()` in the [LifeCycle Hooks](guide/lifecycle-hooks) chap
|
||||
This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/version-child.component.ts" title="component-interaction/src/app/version-child.component.ts">
|
||||
<code-example path="component-interaction/src/app/version-child.component.ts" header="component-interaction/src/app/version-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -143,7 +143,7 @@ This `VersionChildComponent` detects changes to the `major` and `minor` input pr
|
||||
The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them.
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/version-parent.component.ts" title="component-interaction/src/app/version-parent.component.ts">
|
||||
<code-example path="component-interaction/src/app/version-parent.component.ts" header="component-interaction/src/app/version-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -164,7 +164,7 @@ Test that ***both*** input properties are set initially and that button clicks t
|
||||
the expected `ngOnChanges` calls and values:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child-onchanges" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="parent-to-child-onchanges" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -184,7 +184,7 @@ The child's `EventEmitter` property is an ***output property***,
|
||||
as seen in this `VoterComponent`:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/voter.component.ts" title="component-interaction/src/app/voter.component.ts">
|
||||
<code-example path="component-interaction/src/app/voter.component.ts" header="component-interaction/src/app/voter.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -196,7 +196,7 @@ The parent `VoteTakerComponent` binds an event handler called `onVoted()` that r
|
||||
payload `$event` and updates a counter.
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/votetaker.component.ts" title="component-interaction/src/app/votetaker.component.ts">
|
||||
<code-example path="component-interaction/src/app/votetaker.component.ts" header="component-interaction/src/app/votetaker.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -217,7 +217,7 @@ and the method processes it:
|
||||
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="child-to-parent" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="child-to-parent" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -240,7 +240,7 @@ The following is a child `CountdownTimerComponent` that repeatedly counts down t
|
||||
It has `start` and `stop` methods that control the clock and it displays a
|
||||
countdown status message in its own template.
|
||||
|
||||
<code-example path="component-interaction/src/app/countdown-timer.component.ts" title="component-interaction/src/app/countdown-timer.component.ts">
|
||||
<code-example path="component-interaction/src/app/countdown-timer.component.ts" header="component-interaction/src/app/countdown-timer.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -249,7 +249,7 @@ countdown status message in its own template.
|
||||
The `CountdownLocalVarParentComponent` that hosts the timer component is as follows:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/countdown-parent.component.ts" region="lv" title="component-interaction/src/app/countdown-parent.component.ts">
|
||||
<code-example path="component-interaction/src/app/countdown-parent.component.ts" region="lv" header="component-interaction/src/app/countdown-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -284,7 +284,7 @@ match the seconds displayed in the child's status message.
|
||||
Test also that clicking the *Stop* button pauses the countdown timer:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="countdown-timer-tests" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="countdown-timer-tests" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -324,7 +324,7 @@ is solely for the purpose of demonstration.
|
||||
|
||||
Here is the parent, `CountdownViewChildParentComponent`:
|
||||
|
||||
<code-example path="component-interaction/src/app/countdown-parent.component.ts" region="vc" title="component-interaction/src/app/countdown-parent.component.ts">
|
||||
<code-example path="component-interaction/src/app/countdown-parent.component.ts" region="vc" header="component-interaction/src/app/countdown-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -374,7 +374,7 @@ Components outside this component subtree have no access to the service or their
|
||||
This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children.
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/mission.service.ts" title="component-interaction/src/app/mission.service.ts">
|
||||
<code-example path="component-interaction/src/app/mission.service.ts" header="component-interaction/src/app/mission.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -384,7 +384,7 @@ The `MissionControlComponent` both provides the instance of the service that it
|
||||
(through the `providers` metadata array) and injects that instance into itself through its constructor:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/missioncontrol.component.ts" title="component-interaction/src/app/missioncontrol.component.ts">
|
||||
<code-example path="component-interaction/src/app/missioncontrol.component.ts" header="component-interaction/src/app/missioncontrol.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -394,7 +394,7 @@ The `AstronautComponent` also injects the service in its constructor.
|
||||
Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance:
|
||||
|
||||
|
||||
<code-example path="component-interaction/src/app/astronaut.component.ts" title="component-interaction/src/app/astronaut.component.ts">
|
||||
<code-example path="component-interaction/src/app/astronaut.component.ts" header="component-interaction/src/app/astronaut.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -433,7 +433,7 @@ Tests click buttons of both the parent `MissionControlComponent` and the `Astron
|
||||
and verify that the history meets expectations:
|
||||
|
||||
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="bidirectional-service" title="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
<code-example path="component-interaction/e2e/src/app.e2e-spec.ts" region="bidirectional-service" header="component-interaction/e2e/src/app.e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -21,7 +21,7 @@ One way to do this is to set the `styles` property in the component metadata.
|
||||
The `styles` property takes an array of strings that contain CSS code.
|
||||
Usually you give it one string, as in the following example:
|
||||
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" header="src/app/hero-app.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Style scope
|
||||
@ -71,7 +71,7 @@ Use the `:host` pseudo-class selector to target styles in the element that *host
|
||||
targeting elements *inside* the component's template).
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="host" title="src/app/hero-details.component.css" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="host" header="src/app/hero-details.component.css" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `:host` selector is the only way to target the host element. You can't reach
|
||||
@ -83,7 +83,7 @@ including another selector inside parentheses after `:host`.
|
||||
|
||||
The next example targets the host element again, but only when it also has the `active` CSS class.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" title="src/app/hero-details.component.css" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" header="src/app/hero-details.component.css" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### :host-context
|
||||
@ -99,7 +99,7 @@ up to the document root. The `:host-context()` selector is useful when combined
|
||||
The following example applies a `background-color` style to all `<h2>` elements *inside* the component, only
|
||||
if some ancestor element has the CSS class `theme-light`.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" title="src/app/hero-details.component.css" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" header="src/app/hero-details.component.css" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### (deprecated) `/deep/`, `>>>`, and `::ng-deep`
|
||||
@ -114,7 +114,7 @@ children and content children of the component.
|
||||
The following example targets all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" title="src/app/hero-details.component.css" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" header="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -154,7 +154,7 @@ You can add a `styles` array property to the `@Component` decorator.
|
||||
|
||||
Each string in the array defines some CSS for this component.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts (CSS inline)">
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" header="src/app/hero-app.component.ts (CSS inline)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-critical">
|
||||
@ -164,7 +164,7 @@ They are _not inherited_ by any components nested within the template nor by any
|
||||
|
||||
</div>
|
||||
|
||||
The CLI defines an empty `styles` array when you create the component with the `--inline-style` flag.
|
||||
The Angular CLI command [`ng generate component`](cli/generate) defines an empty `styles` array when you create the component with the `--inline-style` flag.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate component hero-app --inline-style
|
||||
@ -176,8 +176,8 @@ You can load styles from external CSS files by adding a `styleUrls` property
|
||||
to a component's `@Component` decorator:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="src/app/hero-app.component.ts (CSS in file)" path="component-styles/src/app/hero-app.component.1.ts"></code-pane>
|
||||
<code-pane title="src/app/hero-app.component.css" path="component-styles/src/app/hero-app.component.css"></code-pane>
|
||||
<code-pane header="src/app/hero-app.component.ts (CSS in file)" path="component-styles/src/app/hero-app.component.1.ts"></code-pane>
|
||||
<code-pane header="src/app/hero-app.component.css" path="component-styles/src/app/hero-app.component.css"></code-pane>
|
||||
</code-tabs>
|
||||
|
||||
<div class="alert is-critical">
|
||||
@ -193,7 +193,7 @@ They are _not inherited_ by any components nested within the template nor by any
|
||||
|
||||
</div>
|
||||
|
||||
The CLI creates an empty styles file for you by default and references that file in the component's generated `styleUrls`.
|
||||
When you use the Angular CLI command [`ng generate component`](cli/generate) without the `--inline-style` flag, it creates an empty styles file for you and references that file in the component's generated `styleUrls`.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate component hero-app
|
||||
@ -204,19 +204,20 @@ ng generate component hero-app
|
||||
You can embed CSS styles directly into the HTML template by putting them
|
||||
inside `<style>` tags.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles" title="src/app/hero-controls.component.ts">
|
||||
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles" header="src/app/hero-controls.component.ts">
|
||||
</code-example>
|
||||
|
||||
### Template link tags
|
||||
|
||||
You can also write `<link>` tags into the component's HTML template.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink" title="src/app/hero-team.component.ts">
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink" header="src/app/hero-team.component.ts">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
When building with the CLI, be sure to include the linked style file among the assets to be copied to the server as described in the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-asset-configuration).
|
||||
When building with the CLI, be sure to include the linked style file among the assets to be copied to the server as described in the [CLI wiki](https://github.com/angular/angular-cli/wiki/stories-asset-configuration).
|
||||
<!-- 2018-10-16: The link above is still the best source for this information. -->
|
||||
|
||||
Once included, the CLI will include the stylesheet, whether the link tag's href URL is relative to the application root or the component file.
|
||||
|
||||
@ -230,7 +231,7 @@ on the [MDN](https://developer.mozilla.org) site.
|
||||
|
||||
In this case, the URL is relative to the CSS file into which you're importing.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="import" title="src/app/hero-details.component.css (excerpt)">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="import" header="src/app/hero-details.component.css (excerpt)">
|
||||
</code-example>
|
||||
|
||||
### External and global style files
|
||||
@ -239,7 +240,9 @@ When building with the CLI, you must configure the `angular.json` to include _al
|
||||
|
||||
Register **global** style files in the `styles` section which, by default, is pre-configured with the global `styles.css` file.
|
||||
|
||||
See the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-global-styles) to learn more.
|
||||
See the [CLI wiki](https://github.com/angular/angular-cli/wiki/stories-global-styles) to learn more.
|
||||
<!-- 2018-10-16: The link above is still the best source for this information. -->
|
||||
|
||||
|
||||
### Non-CSS style files
|
||||
|
||||
@ -259,8 +262,10 @@ The CLI build process runs the pertinent CSS preprocessor.
|
||||
|
||||
When generating a component file with `ng generate component`, the CLI emits an empty CSS styles file (`.css`) by default.
|
||||
You can configure the CLI to default to your preferred CSS preprocessor
|
||||
as explained in the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-css-preprocessors
|
||||
as explained in the [CLI wiki](https://github.com/angular/angular-cli/wiki/stories-css-preprocessors
|
||||
"CSS Preprocessor integration").
|
||||
<!-- 2018-10-16: The link above is still the best source for this information. -->
|
||||
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
@ -298,7 +303,7 @@ Choose from the following modes:
|
||||
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" header="src/app/quest-summary.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
`ShadowDom` view encapsulation only works on browsers that have native support
|
||||
|
@ -21,7 +21,7 @@ constructor, and lets the framework provide them.
|
||||
|
||||
The following example shows that `AppComponent` declares its dependence on `LoggerService` and `UserContext`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/app.component.ts" region="ctor" title="src/app/app.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/app.component.ts" region="ctor" header="src/app/app.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -30,7 +30,7 @@ The following example shows that `AppComponent` declares its dependence on `Logg
|
||||
`UserService`, another service that gathers information about a particular user.
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/user-context.service.ts" region="injectables" title="user-context.service.ts (injection)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/user-context.service.ts" region="injectables" header="user-context.service.ts (injection)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -68,7 +68,7 @@ by providing that service *at the sub-root component for that branch*.
|
||||
This example shows how to make a different instance of `HeroService` available to `HeroesBaseComponent`
|
||||
by adding it to the `providers` array of the `@Component()` decorator of the sub-component.
|
||||
|
||||
<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" header="src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -103,7 +103,7 @@ This is called *sandboxing* because each service and component instance has its
|
||||
|
||||
In this example, `HeroBiosComponent` presents three instances of `HeroBioComponent`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="simple" title="ap/hero-bios.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="simple" header="ap/hero-bios.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -111,7 +111,7 @@ In this example, `HeroBiosComponent` presents three instances of `HeroBioCompone
|
||||
Each `HeroBioComponent` can edit a single hero's biography.
|
||||
`HeroBioComponent` relies on `HeroCacheService` to fetch, cache, and perform other persistence operations on that hero.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-cache.service.ts" region="service" title="src/app/hero-cache.service.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-cache.service.ts" region="service" header="src/app/hero-cache.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -122,7 +122,7 @@ as they'd be competing with each other to determine which hero to cache.
|
||||
Instead, each `HeroBioComponent` gets its *own* `HeroCacheService` instance
|
||||
by listing `HeroCacheService` in its metadata `providers` array.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bio.component.ts" region="component" title="src/app/hero-bio.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bio.component.ts" region="component" header="src/app/hero-bio.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -149,7 +149,7 @@ By default, the DI framework searches for a provider in the injector hierarchy,
|
||||
starting at the component's local injector of the component, and if necessary bubbling up
|
||||
through the injector tree until it reaches the root injector.
|
||||
|
||||
* The first injector configured with a provider supplies the the dependency (a service instance or value) to the constructor.
|
||||
* The first injector configured with a provider supplies the dependency (a service instance or value) to the constructor.
|
||||
|
||||
* If no provider is found in the root injector, the DI framework returns null to the constructor.
|
||||
|
||||
@ -179,13 +179,13 @@ that parent component becomes the host. The following example covers this second
|
||||
These decorators can be used individually or together, as shown in the example.
|
||||
This `HeroBiosAndContactsComponent` is a revision of `HeroBiosComponent` which you looked at [above](guide/dependency-injection-in-action#hero-bios-component).
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="hero-bios-and-contacts" title="src/app/hero-bios.component.ts (HeroBiosAndContactsComponent)">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="hero-bios-and-contacts" header="src/app/hero-bios.component.ts (HeroBiosAndContactsComponent)">
|
||||
|
||||
</code-example>
|
||||
|
||||
Focus on the template:
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="template" title="dependency-injection-in-action/src/app/hero-bios.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="template" header="dependency-injection-in-action/src/app/hero-bios.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -193,7 +193,7 @@ Now there's a new `<hero-contact>` element between the `<hero-bio>` tags.
|
||||
Angular *projects*, or *transcludes*, the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
|
||||
placing it in the `<ng-content>` slot of the `HeroBioComponent` template.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bio.component.ts" region="template" title="src/app/hero-bio.component.ts (template)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bio.component.ts" region="template" header="src/app/hero-bio.component.ts (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -206,13 +206,13 @@ The result is shown below, with the hero's telephone number from `HeroContactCom
|
||||
|
||||
Here's `HeroContactComponent`, which demonstrates the qualifying decorators.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-contact.component.ts" region="component" title="src/app/hero-contact.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-contact.component.ts" region="component" header="src/app/hero-contact.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Focus on the constructor parameters.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-contact.component.ts" region="ctor-params" title="src/app/hero-contact.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-contact.component.ts" region="ctor-params" header="src/app/hero-contact.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -256,17 +256,17 @@ the app throws an exception when it cannot find the required logger at the host
|
||||
|
||||
Using a custom provider allows you to provide a concrete implementation for implicit dependencies, such as built-in browser APIs. The following example uses an `InjectionToken` to provide the [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) browser API as a dependency in the `BrowserStorageService`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/storage.service.ts" title="src/app/storage.service.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/storage.service.ts" header="src/app/storage.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
The `factory` function returns the `localStorage` property that is attached to the browser window object. The `Inject` decorator is a constructor parameter used to specify a custom provider of a dependency. This custom provider can now be overriden during testing with a mock API of `localStorage` instead of interactive with real browser APIs.
|
||||
The `factory` function returns the `localStorage` property that is attached to the browser window object. The `Inject` decorator is a constructor parameter used to specify a custom provider of a dependency. This custom provider can now be overridden during testing with a mock API of `localStorage` instead of interactive with real browser APIs.
|
||||
|
||||
### Modify the provider search with `@Self` and `@SkipSelf`
|
||||
|
||||
Providers can also be scoped by injector through constructor parameter decorators. The following example overrides the `BROWSER_STORAGE` token in the `Component` class `providers` with the `sessionStorage` browser API. The same `BrowserStorageService` is injected twice in the constructor, decorated with `@Self` and `@SkipSelf` to define which injector handles the provider dependency.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/storage.component.ts" title="src/app/storage.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/storage.component.ts" header="src/app/storage.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -283,7 +283,7 @@ As a result, you might need to access a component's DOM element.
|
||||
To illustrate, here's a simplified version of `HighlightDirective` from
|
||||
the [Attribute Directives](guide/attribute-directives) page.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/highlight.directive.ts" title="src/app/highlight.directive.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/highlight.directive.ts" header="src/app/highlight.directive.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -297,7 +297,7 @@ whose `nativeElement` property exposes the DOM element for the directive to mani
|
||||
The sample code applies the directive's `myHighlight` attribute to two `<div>` tags,
|
||||
first without a value (yielding the default color) and then with an assigned color value.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/app.component.html" region="highlight" title="src/app/app.component.html (highlight)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/app.component.html" region="highlight" header="src/app/app.component.html (highlight)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -323,7 +323,7 @@ Angular passes this token to the injector and assigns the result to the paramete
|
||||
The following is a typical example.
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="ctor" title="src/app/hero-bios.component.ts (component constructor injection)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-bios.component.ts" region="ctor" header="src/app/hero-bios.component.ts (component constructor injection)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -366,7 +366,7 @@ It's visually simple: a few properties and the logs produced by a logger.
|
||||
The code behind it customizes how and where the DI framework provides dependencies.
|
||||
The use cases illustrate different ways to use the [*provide* object literal](guide/dependency-injection-providers#provide) to associate a definition object with a DI token.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="hero-of-the-month" title="hero-of-the-month.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="hero-of-the-month" header="hero-of-the-month.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -384,7 +384,7 @@ You can also use a value provider in a unit test to provide mock data in place o
|
||||
|
||||
The `HeroOfTheMonthComponent` example has two value providers.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-value" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-value" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -405,7 +405,7 @@ The title string literal is immediately available.
|
||||
The `someHero` variable in this example was set earlier in the file as shown below.
|
||||
You can't use a variable whose value will be defined later.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="some-hero" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="some-hero" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -425,7 +425,7 @@ extend the default class, or emulate the behavior of the real class in a test ca
|
||||
|
||||
The following code shows two examples in `HeroOfTheMonthComponent`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-class" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-class" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -446,7 +446,7 @@ Components outside the tree continue to receive the original `LoggerService` ins
|
||||
|
||||
`DateLoggerService` inherits from `LoggerService`; it appends the current date/time to each message:
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/date-logger.service.ts" region="date-logger-service" title="src/app/date-logger.service.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/date-logger.service.ts" region="date-logger-service" header="src/app/date-logger.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -458,7 +458,7 @@ The `useExisting` provider key lets you map one token to another.
|
||||
In effect, the first token is an *alias* for the service associated with the second token,
|
||||
creating two ways to access the same service object.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-existing" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-existing" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -470,13 +470,13 @@ You might want to shrink that API surface to just the members you actually need.
|
||||
In this example, the `MinimalLogger` [class-interface](#class-interface) reduces the API to two members:
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" title="src/app/minimal-logger.service.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" header="src/app/minimal-logger.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The following example puts `MinimalLogger` to use in a simplified version of `HeroOfTheMonthComponent`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts" title="src/app/hero-of-the-month.component.ts (minimal version)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts" header="src/app/hero-of-the-month.component.ts (minimal version)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -507,7 +507,7 @@ This is illustrated in the following image, which displays the logging date.
|
||||
The `useFactory` provider key lets you create a dependency object by calling a factory function,
|
||||
as in the following example.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-factory" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-factory" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -530,7 +530,7 @@ The `runnersUpFactory()` returns the *provider factory function*, which can use
|
||||
the passed-in state value and the injected services `Hero` and `HeroService`.
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/runners-up.ts" region="factory-synopsis" title="runners-up.ts (excerpt)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/runners-up.ts" region="factory-synopsis" header="runners-up.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -570,13 +570,13 @@ That's the subject of the next section.
|
||||
The previous *Hero of the Month* example used the `MinimalLogger` class
|
||||
as the token for a provider of `LoggerService`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-existing" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="use-existing" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
`MinimalLogger` is an abstract class.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" title="dependency-injection-in-action/src/app/minimal-logger.service.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" header="dependency-injection-in-action/src/app/minimal-logger.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -604,7 +604,7 @@ Using a class as an interface gives you the characteristics of an interface in a
|
||||
To minimize memory cost, however, the class should have *no implementation*.
|
||||
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript for a constructor function.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" region="minimal-logger-transpiled" title="dependency-injection-in-action/src/app/minimal-logger.service.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/minimal-logger.service.ts" region="minimal-logger-transpiled" header="dependency-injection-in-action/src/app/minimal-logger.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -633,13 +633,13 @@ another token that happens to have the same name.
|
||||
You encountered them twice in the *Hero of the Month* example,
|
||||
in the *title* value provider and in the *runnersUp* factory provider.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="provide-injection-token" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="provide-injection-token" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You created the `TITLE` token like this:
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="injection-token" title="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" region="injection-token" header="dependency-injection-in-action/src/app/hero-of-the-month.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -667,7 +667,7 @@ The `HeroesBaseComponent` can stand on its own.
|
||||
It demands its own instance of `HeroService` to get heroes
|
||||
and displays them in the order they arrive from the database.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="heroes-base" title="src/app/sorted-heroes.component.ts (HeroesBaseComponent)">
|
||||
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="heroes-base" header="src/app/sorted-heroes.component.ts (HeroesBaseComponent)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -693,7 +693,7 @@ You must provide the `HeroService` again for *this* component,
|
||||
then pass it down to the base class inside the constructor.
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="sorted-heroes" title="src/app/sorted-heroes.component.ts (SortedHeroesComponent)">
|
||||
<code-example path="dependency-injection-in-action/src/app/sorted-heroes.component.ts" region="sorted-heroes" header="src/app/sorted-heroes.component.ts (SortedHeroesComponent)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -731,7 +731,7 @@ appear *above* the class definition.
|
||||
|
||||
Break the circularity with `forwardRef`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" title="parent-finder.component.ts (AlexComponent providers)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" header="parent-finder.component.ts (AlexComponent providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -40,7 +40,7 @@ In the following example, the parent `AlexComponent` has several children includ
|
||||
{@a alex}
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-1" title="parent-finder.component.ts (AlexComponent v.1)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-1" header="parent-finder.component.ts (AlexComponent v.1)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -49,7 +49,7 @@ In the following example, the parent `AlexComponent` has several children includ
|
||||
*Cathy* reports whether or not she has access to *Alex*
|
||||
after injecting an `AlexComponent` into her constructor:
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="cathy" title="parent-finder.component.ts (CathyComponent)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="cathy" header="parent-finder.component.ts (CathyComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -98,7 +98,7 @@ inject its parent via the parent's base class*.
|
||||
The sample's `CraigComponent` explores this question. [Looking back](#alex),
|
||||
you see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" title="parent-finder.component.ts (Alex class signature)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" header="parent-finder.component.ts (Alex class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -106,13 +106,13 @@ you see that the `Alex` component *extends* (*inherits*) from a class named `Bas
|
||||
|
||||
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="craig" title="parent-finder.component.ts (CraigComponent)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="craig" header="parent-finder.component.ts (CraigComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Unfortunately, this does'nt work.
|
||||
Unfortunately, this doesn't work.
|
||||
The <live-example name="dependency-injection-in-action"></live-example>
|
||||
confirms that the `alex` parameter is null.
|
||||
*You cannot inject a parent by its base class.*
|
||||
@ -138,7 +138,7 @@ and add that provider to the `providers` array of the `@Component()` metadata fo
|
||||
{@a alex-providers}
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" title="parent-finder.component.ts (AlexComponent providers)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" header="parent-finder.component.ts (AlexComponent providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -149,7 +149,7 @@ The [*forwardRef*](guide/dependency-injection-in-action#forwardref) breaks the c
|
||||
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter,
|
||||
the same way you've done it before.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-class" title="parent-finder.component.ts (CarolComponent class)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-class" header="parent-finder.component.ts (CarolComponent class)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -177,7 +177,7 @@ That means he must both *inject* the `Parent` class interface to get *Alice* and
|
||||
|
||||
Here's *Barry*.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry" title="parent-finder.component.ts (BarryComponent)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry" header="parent-finder.component.ts (BarryComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -190,11 +190,11 @@ For now, focus on *Barry*'s constructor.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="Barry's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry-ctor">
|
||||
<code-pane header="Barry's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="Carol's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-ctor">
|
||||
<code-pane header="Carol's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -229,7 +229,7 @@ You [learned earlier](guide/dependency-injection-in-action#class-interface) that
|
||||
|
||||
The example defines a `Parent` class interface.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="parent" title="parent-finder.component.ts (Parent class-interface)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="parent" header="parent-finder.component.ts (Parent class-interface)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -241,7 +241,7 @@ Such a narrow interface helps decouple the child component class from its parent
|
||||
|
||||
A component that could serve as a parent *should* implement the class interface as the `AliceComponent` does.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-class-signature" title="parent-finder.component.ts (AliceComponent class signature)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-class-signature" header="parent-finder.component.ts (AliceComponent class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -251,7 +251,7 @@ Doing so adds clarity to the code. But it's not technically necessary.
|
||||
Although `AlexComponent` has a `name` property, as required by its `Base` class,
|
||||
its class signature doesn't mention `Parent`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" title="parent-finder.component.ts (AlexComponent class signature)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" header="parent-finder.component.ts (AlexComponent class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -277,19 +277,19 @@ It doesn't in this example *only* to demonstrate that the code will compile and
|
||||
Writing variations of the same parent *alias provider* gets old quickly,
|
||||
especially this awful mouthful with a [*forwardRef*](guide/dependency-injection-in-action#forwardref).
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can extract that logic into a helper function like the following.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-the-parent" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-the-parent" header="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now you can add a simpler, more meaningful parent provider to your components.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -299,14 +299,14 @@ The application might have a variety of parent types, each with its own class in
|
||||
|
||||
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent class interface.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-parent" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-parent" header="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
And here's how you could use it with a different parent type.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="beth-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="beth-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -130,7 +130,7 @@ like the title of the application or the address of a web API endpoint.
|
||||
These configuration objects aren't always instances of a class.
|
||||
They can be object literals, as shown in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" title="src/app/app.config.ts (excerpt)" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" header="src/app/app.config.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a interface-not-valid-token}
|
||||
@ -147,7 +147,7 @@ In TypeScript, an interface is a design-time artifact, and doesn't have a runtim
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This might seem strange if you're used to dependency injection in strongly typed languages where an interface is the preferred dependency lookup key.
|
||||
However, JavaScript, doesn't have interfaces, so when TypeScript is transpiled to JavaScript, the interface disappears.
|
||||
@ -157,13 +157,13 @@ There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
One alternative is to provide and inject the configuration object in an NgModule like `AppModule`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.module.ts" region="providers" title="src/app/app.module.ts (providers)"></code-example>
|
||||
<code-example path="dependency-injection/src/app/app.module.ts" region="providers" header="src/app/app.module.ts (providers)"></code-example>
|
||||
|
||||
Another solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an `InjectionToken` object.
|
||||
The following example shows how to define such a token.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="token" title="src/app/app.config.ts" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="token" header="src/app/app.config.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The type parameter, while optional, conveys the dependency's type to developers and tooling.
|
||||
@ -177,10 +177,10 @@ Register the dependency provider using the `InjectionToken` object:
|
||||
Now you can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject()` parameter decorator.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" title="src/app/app.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" header="src/app/app.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Although the `AppConfig` interface plays no role in dependency injection,
|
||||
it supports typing of the configuration object within the class.
|
||||
@ -215,21 +215,21 @@ who is authorized and who isn't.
|
||||
|
||||
To resolve this, we give the `HeroService` constructor a boolean flag to control display of secret heroes.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" title="src/app/heroes/hero.service.ts (excerpt)" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" header="src/app/heroes/hero.service.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You can inject `Logger`, but you can't inject the `isAuthorized` flag. Instead, you can use a factory provider to create a new logger instance for `HeroService`.
|
||||
|
||||
A factory provider needs a factory function.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" header="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Although `HeroService` has no access to `UserService`, the factory function does.
|
||||
You inject both `Logger` and `UserService` into the factory provider
|
||||
and let the injector pass them along to the factory function.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" header="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
* The `useFactory` field tells Angular that the provider is a factory function whose implementation is `heroServiceFactory`.
|
||||
@ -248,10 +248,10 @@ The following shows the new and the old implementations side-by-side.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v3)" path="dependency-injection/src/app/heroes/heroes.component.ts">
|
||||
<code-pane header="src/app/heroes/heroes.component (v3)" path="dependency-injection/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v2)" path="dependency-injection/src/app/heroes/heroes.component.1.ts">
|
||||
<code-pane header="src/app/heroes/heroes.component (v2)" path="dependency-injection/src/app/heroes/heroes.component.1.ts">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
@ -293,7 +293,7 @@ When you provide multiple sets of routes using [RouterModule.forRoot](api/router
|
||||
and [RouterModule.forChild](api/router/RouterModule#forchild) in a single module,
|
||||
the [ROUTES](api/router/ROUTES) token combines all the different provided sets of routes into a single value.
|
||||
|
||||
<div class="alert-is-helpful>
|
||||
<div class="alert is-helpful>
|
||||
|
||||
Search for [Constants in API documentation](api?type=const) to find more built-in tokens.
|
||||
|
||||
@ -309,7 +309,7 @@ When providers are tree-shakable, the Angular compiler removes the associated
|
||||
services from the final output when it determines that they are not used in your application.
|
||||
This significantly reduces the size of your bundles.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Ideally, if an application isn't injecting a service, it shouldn't be included in the final output.
|
||||
However, Angular has to be able to identify at build time whether the service will be required or not.
|
||||
@ -322,13 +322,13 @@ Thus, services provided at the NgModule or component level are not tree-shakable
|
||||
|
||||
The following example of non-tree-shakable providers in Angular configures a service provider for the injector of an NgModule.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" title="src/app/tree-shaking/service-and-modules.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" header="src/app/tree-shaking/service-and-modules.ts" linenums="false"> </code-example>
|
||||
|
||||
This module can then be imported into your application module
|
||||
to make the service available for injection in your app,
|
||||
as shown in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" title="src/app/tree-shaking/app.modules.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" header="src/app/tree-shaking/app.modules.ts" linenums="false"> </code-example>
|
||||
|
||||
When `ngc` runs, it compiles `AppModule` into a module factory, which contains definitions for all the providers declared in all the modules it includes. At runtime, this factory becomes an injector that instantiates these services.
|
||||
|
||||
@ -340,13 +340,13 @@ You can make a provider tree-shakable by specifying it in the `@Injectable()` de
|
||||
|
||||
The following example shows the tree-shakable equivalent to the `ServiceModule` example above.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" title="src/app/tree-shaking/service.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" header="src/app/tree-shaking/service.ts" linenums="false"> </code-example>
|
||||
|
||||
The service can be instantiated by configuring a factory function, as in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" title="src/app/tree-shaking/service.0.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" header="src/app/tree-shaking/service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
To override a tree-shakable provider, configure the injector of a specific NgModule or component with another provider, using the `providers: []` array syntax of the `@NgModule()` or `@Component()` decorator.
|
||||
|
||||
|
@ -9,7 +9,7 @@ DI is a coding pattern in which a class asks for dependencies from external sour
|
||||
|
||||
In Angular, the DI framework provides declared dependencies to a class when that class is instantiated. This guide explains how DI works in Angular, and how you use it to make your apps flexible, efficient, and robust, as well as testable and maintainable.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You can run the <live-example></live-example> of the sample app that accompanies this guide.
|
||||
|
||||
@ -19,16 +19,16 @@ Start by reviewing this simplified version of the _heroes_ feature
|
||||
from the [The Tour of Heroes](tutorial/). This simple version doesn't use DI; we'll walk through converting it to do so.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="src/app/heroes/heroes.component.ts" path="dependency-injection/src/app/heroes/heroes.component.1.ts" region="v1">
|
||||
<code-pane header="src/app/heroes/heroes.component.ts" path="dependency-injection/src/app/heroes/heroes.component.1.ts" region="v1">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/hero-list.component.ts" path="dependency-injection/src/app/heroes/hero-list.component.1.ts">
|
||||
<code-pane header="src/app/heroes/hero-list.component.ts" path="dependency-injection/src/app/heroes/hero-list.component.1.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/hero.ts" path="dependency-injection/src/app/heroes/hero.ts">
|
||||
<code-pane header="src/app/heroes/hero.ts" path="dependency-injection/src/app/heroes/hero.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/mock-heroes.ts" path="dependency-injection/src/app/heroes/mock-heroes.ts">
|
||||
<code-pane header="src/app/heroes/mock-heroes.ts" path="dependency-injection/src/app/heroes/mock-heroes.ts">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
@ -39,7 +39,7 @@ Its only purpose is to display `HeroListComponent`, which displays a list of her
|
||||
This version of the `HeroListComponent` gets heroes from the `HEROES` array, an in-memory collection
|
||||
defined in a separate `mock-heroes` file.
|
||||
|
||||
<code-example title="src/app/heroes/hero-list.component.ts (class)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts" region="class">
|
||||
<code-example header="src/app/heroes/hero-list.component.ts (class)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts" region="class">
|
||||
</code-example>
|
||||
|
||||
This approach works for prototyping, but is not robust or maintainable.
|
||||
@ -52,7 +52,7 @@ replace every use of the `HEROES` mock data.
|
||||
|
||||
The DI framework lets you supply data to a component from an injectable _service_ class, defined in its own file. To demonstrate, we'll create an injectable service class that provides a list of heroes, and register that class as a provider of that service.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Having multiple classes in the same file can be confusing. We generally recommend that you define components and services in separate files.
|
||||
|
||||
@ -68,7 +68,7 @@ See an example in the [DI Cookbook](guide/dependency-injection-in-action#forward
|
||||
|
||||
### Create an injectable service class
|
||||
|
||||
The [**Angular CLI**](https://cli.angular.io/) can generate a new `HeroService` class in the `src/app/heroes` folder with this command.
|
||||
The [Angular CLI](cli) can generate a new `HeroService` class in the `src/app/heroes` folder with this command.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate service heroes/hero
|
||||
@ -76,12 +76,12 @@ ng generate service heroes/hero
|
||||
|
||||
The command creates the following `HeroService` skeleton.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" title="src/app/heroes/hero.service.ts (CLI-generated)">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" header="src/app/heroes/hero.service.ts (CLI-generated)">
|
||||
</code-example>
|
||||
|
||||
The `@Injectable()` is an essential ingredient in every Angular service definition. The rest of the class has been written to expose a `getHeroes` method that returns the same mock data as before. (A real app would probably get its data asynchronously from a remote server, but we'll ignore that to focus on the mechanics of injecting the service.)
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.3.ts" title="src/app/heroes/hero.service.ts">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.3.ts" header="src/app/heroes/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -147,7 +147,7 @@ In order for `HeroListComponent` to get heroes from `HeroService`, it needs to a
|
||||
|
||||
You can tell Angular to inject a dependency in a component's constructor by specifying a **constructor parameter with the dependency type**. Here's the `HeroListComponent` constructor, asking for the `HeroService` to be injected.
|
||||
|
||||
<code-example title="src/app/heroes/hero-list.component (constructor signature)" path="dependency-injection/src/app/heroes/hero-list.component.ts"
|
||||
<code-example header="src/app/heroes/hero-list.component (constructor signature)" path="dependency-injection/src/app/heroes/hero-list.component.ts"
|
||||
region="ctor-signature">
|
||||
</code-example>
|
||||
|
||||
@ -155,10 +155,10 @@ Of course, `HeroListComponent` should do something with the injected `HeroServic
|
||||
Here's the revised component, making use of the injected service, side-by-side with the previous version for comparison.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="hero-list.component (with DI)" path="dependency-injection/src/app/heroes/hero-list.component.2.ts">
|
||||
<code-pane header="hero-list.component (with DI)" path="dependency-injection/src/app/heroes/hero-list.component.2.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="hero-list.component (without DI)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts">
|
||||
<code-pane header="hero-list.component (without DI)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
@ -197,7 +197,7 @@ Listing dependencies as constructor parameters may be all you need to test appli
|
||||
For example, you can create a new `HeroListComponent` with a mock service that you can manipulate
|
||||
under test.
|
||||
|
||||
<code-example path="dependency-injection/src/app/test.component.ts" region="spec" title="src/app/test.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/test.component.ts" region="spec" header="src/app/test.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -217,13 +217,13 @@ Here is the revised `HeroService` that injects `Logger`, side by side with the p
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/heroes/hero.service (v2)" path="dependency-injection/src/app/heroes/hero.service.2.ts">
|
||||
<code-pane header="src/app/heroes/hero.service (v2)" path="dependency-injection/src/app/heroes/hero.service.2.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/hero.service (v1)" path="dependency-injection/src/app/heroes/hero.service.1.ts">
|
||||
<code-pane header="src/app/heroes/hero.service (v1)" path="dependency-injection/src/app/heroes/hero.service.1.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/logger.service"
|
||||
<code-pane header="src/app/logger.service"
|
||||
path="dependency-injection/src/app/logger.service.ts">
|
||||
</code-pane>
|
||||
|
||||
@ -238,9 +238,9 @@ If Angular can't find that parameter information, it throws an error.
|
||||
Angular can only find the parameter information _if the class has a decorator of some kind_.
|
||||
The `@Injectable()` decorator is the standard decorator for service classes.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles]((guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`.
|
||||
The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles](guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`.
|
||||
|
||||
This means you're responsible for putting `@Injectable()` on your service classes.
|
||||
|
||||
@ -260,14 +260,14 @@ In simple examples, the dependency value is an *instance*, and
|
||||
the class *type* serves as its own lookup key.
|
||||
Here you get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="get-hero-service" title="src/app/injector.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="get-hero-service" header="src/app/injector.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The behavior is similar when you write a constructor that requires an injected class-based dependency.
|
||||
When you define a constructor parameter with the `HeroService` class type,
|
||||
Angular knows to inject the service associated with that `HeroService` class token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.ts" region="ctor-signature" title="src/app/heroes/hero-list.component.ts">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.ts" region="ctor-signature" header="src/app/heroes/hero-list.component.ts">
|
||||
</code-example>
|
||||
|
||||
Many dependency values are provided by classes, but not all. The expanded *provide* object lets you associate different kinds of providers with a DI token.
|
||||
@ -295,7 +295,7 @@ When using `@Optional()`, your code must be prepared for a null value. If you
|
||||
don't register a logger provider anywhere, the injector sets the
|
||||
value of `logger` to null.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
|
||||
`@Inject()` and `@Optional()` are _parameter decorators_. They alter the way the DI framework provides a dependency, by annotating the dependency parameter on the constructor of the class that requires the dependency.
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Deployment
|
||||
|
||||
This page describes techniques for deploying your Angular application to a remote server.
|
||||
When you are ready to deploy your Angular application to a remote server, you have various options for
|
||||
deployment.
|
||||
|
||||
{@a dev-deploy}
|
||||
{@a copy-files}
|
||||
@ -9,7 +10,7 @@ This page describes techniques for deploying your Angular application to a remot
|
||||
|
||||
For the simplest deployment, build for development and copy the output directory to a web server.
|
||||
|
||||
1. Start with the development build
|
||||
1. Start with the development build:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build
|
||||
@ -18,27 +19,214 @@ For the simplest deployment, build for development and copy the output directory
|
||||
|
||||
2. Copy _everything_ within the output folder (`dist/` by default) to a folder on the server.
|
||||
|
||||
3. Configure the server to redirect requests for missing files to `index.html`.
|
||||
Learn more about server-side redirects [below](#fallback).
|
||||
|
||||
3. If you copy the files into a server _sub-folder_, append the build flag, `--base-href` and set the `<base href>` appropriately.<br><br>
|
||||
This is _not_ a production deployment. It's not optimized, and it won't be fast for users.
|
||||
It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders. For the next steps in deployment, see [Optimize for production](#optimize).
|
||||
|
||||
For example, if the `index.html` is on the server at `/my/app/index.html`, set the _base href_ to
|
||||
`<base href="/my/app/">` like this.
|
||||
{@a deploy-to-github}
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build --base-href=/my/app/
|
||||
## Deploy to GitHub pages
|
||||
|
||||
Another simple way to deploy your Angular app is to use [GitHub Pages](https://help.github.com/articles/what-is-github-pages/).
|
||||
|
||||
1. You need to [create a GitHub account](https://github.com/join) if you don't have one, and then [create a repository](https://help.github.com/articles/create-a-repo/) for your project.
|
||||
Make a note of the user name and project name in GitHub.
|
||||
|
||||
1. Build your project using Github project name, with the Angular CLI command [`ng build`](cli/build) and the options shown here:
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build --prod --output-path docs --base-href <project_name>
|
||||
</code-example>
|
||||
|
||||
1. When the build is complete, make a copy of `docs/index.html` and name it `docs/404.html`.
|
||||
|
||||
1. Commit your changes and push.
|
||||
|
||||
1. On the GitHub project page, configure it to [publish from the docs folder](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch).
|
||||
|
||||
You can see your deployed page at `https://<user_name>.github.io/<project_name>/`.
|
||||
|
||||
<div class="alert is-helpful>
|
||||
|
||||
Check out [angular-cli-ghpages](https://github.com/angular-buch/angular-cli-ghpages), a full featured package that does all this for you and has extra functionality.
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
{@a server-configuration}
|
||||
|
||||
## Server configuration
|
||||
|
||||
This section covers changes you may have make to the server or to files deployed to the server.
|
||||
|
||||
{@a fallback}
|
||||
|
||||
### Routed apps must fallback to `index.html`
|
||||
|
||||
Angular apps are perfect candidates for serving with a simple static HTML server.
|
||||
You don't need a server-side engine to dynamically compose application pages because
|
||||
Angular does that on the client-side.
|
||||
|
||||
If the app uses the Angular router, you must configure the server
|
||||
to return the application's host page (`index.html`) when asked for a file that it does not have.
|
||||
|
||||
{@a deep-link}
|
||||
|
||||
A routed application should support "deep links".
|
||||
A _deep link_ is a URL that specifies a path to a component inside the app.
|
||||
For example, `http://www.mysite.com/heroes/42` is a _deep link_ to the hero detail page
|
||||
that displays the hero with `id: 42`.
|
||||
|
||||
There is no issue when the user navigates to that URL from within a running client.
|
||||
The Angular router interprets the URL and routes to that page and hero.
|
||||
|
||||
But clicking a link in an email, entering it in the browser address bar,
|
||||
or merely refreshing the browser while on the hero detail page —
|
||||
all of these actions are handled by the browser itself, _outside_ the running application.
|
||||
The browser makes a direct request to the server for that URL, bypassing the router.
|
||||
|
||||
A static server routinely returns `index.html` when it receives a request for `http://www.mysite.com/`.
|
||||
But it rejects `http://www.mysite.com/heroes/42` and returns a `404 - Not Found` error *unless* it is
|
||||
configured to return `index.html` instead.
|
||||
|
||||
#### Fallback configuration examples
|
||||
|
||||
There is no single configuration that works for every server.
|
||||
The following sections describe configurations for some of the most popular servers.
|
||||
The list is by no means exhaustive, but should provide you with a good starting point.
|
||||
|
||||
#### Development servers
|
||||
|
||||
During development, the [`ng serve`](cli/serve) CLI command lets you run your app in a local browser.
|
||||
The CLI recompiles the application each time you save a file,
|
||||
and reloads the browser with the newly compiled application.
|
||||
|
||||
The app is hosted in local memory and served on `http://localhost:4200/`, using [webpack-dev-server](https://webpack.js.org/guides/development/#webpack-dev-server).
|
||||
|
||||
{@a serve-from-disk}
|
||||
|
||||
Later in development, you might want a closer approximation of how your app will behave when deployed.
|
||||
You can output your distribution folder (`dist`) to disk, but you need to install a different web server.
|
||||
Try installing [lite-server](https://github.com/johnpapa/lite-server); like `webpack-dev-server`, it can automatically reload your browser when you write new files.
|
||||
|
||||
To get the live-reload experience, you will need to run two terminals.
|
||||
The first runs the build in a watch mode and compiles the application to the `dist` folder.
|
||||
The second runs the web server against the `dist` folder.
|
||||
The combination of these two processes provides the same behavior as `ng serve`.
|
||||
|
||||
1. Start the build in terminal A:
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build --watch
|
||||
</code-example>
|
||||
|
||||
1. Start the web server in terminal B:
|
||||
<code-example language="none" class="code-shell">
|
||||
lite-server --baseDir="dist"
|
||||
</code-example>
|
||||
The default browser opens to the appropriate URL.
|
||||
|
||||
* [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
|
||||
[Quickstart repo](https://github.com/angular/quickstart) is pre-configured to fallback to `index.html`.
|
||||
|
||||
* [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
|
||||
`historyApiFallback` entry in the dev server options as follows:
|
||||
|
||||
<code-example>
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||
}
|
||||
</code-example>
|
||||
|
||||
You'll see that the `<base href>` is set properly in the generated `dist/index.html`.<br><br>
|
||||
If you copy to the server's root directory, omit this step and leave the `<base href>` alone.<br><br>
|
||||
Learn more about the role of `<base href>` [below](guide/deployment#base-tag).
|
||||
#### Production servers
|
||||
|
||||
* [Apache](https://httpd.apache.org/): add a
|
||||
[rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html) to the `.htaccess` file as shown
|
||||
(https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
|
||||
|
||||
<code-example format=".">
|
||||
RewriteEngine On
|
||||
# If an existing asset or directory is requested go to it as it is
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# If the requested resource doesn't exist, use index.html
|
||||
RewriteRule ^ /index.html
|
||||
</code-example>
|
||||
|
||||
|
||||
4. Configure the server to redirect requests for missing files to `index.html`.
|
||||
Learn more about server-side redirects [below](guide/deployment#fallback).
|
||||
* [Nginx](http://nginx.org/): use `try_files`, as described in
|
||||
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
|
||||
modified to serve `index.html`:
|
||||
|
||||
<code-example format=".">
|
||||
try_files $uri $uri/ /index.html;
|
||||
</code-example>
|
||||
|
||||
|
||||
This is _not_ a production deployment. It's not optimized and it won't be fast for users.
|
||||
It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders.
|
||||
* [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||
[here](http://stackoverflow.com/a/26152011/2116927):
|
||||
|
||||
<code-example format='.'>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Angular Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="/index.html" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
* [GitHub Pages](https://pages.github.com/): you can't
|
||||
[directly configure](https://github.com/isaacs/github/issues/408)
|
||||
the GitHub Pages server, but you can add a 404 page.
|
||||
Copy `index.html` into `404.html`.
|
||||
It will still be served as the 404 response, but the browser will process that page and load the app properly.
|
||||
It's also a good idea to
|
||||
[serve from `docs/` on master](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch)
|
||||
and to
|
||||
[create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)
|
||||
|
||||
|
||||
* [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
|
||||
|
||||
<code-example format=".">
|
||||
"rewrites": [ {
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
} ]
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a cors}
|
||||
|
||||
### Requesting services from a different server (CORS)
|
||||
|
||||
Angular developers may encounter a
|
||||
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" title="Cross-origin resource sharing">
|
||||
<i>cross-origin resource sharing</i></a> error when making a service request (typically a data service request)
|
||||
to a server other than the application's own host server.
|
||||
Browsers forbid such requests unless the server permits them explicitly.
|
||||
|
||||
There isn't anything the client application can do about these errors.
|
||||
The server must be configured to accept the application's requests.
|
||||
Read about how to enable CORS for specific servers at
|
||||
<a href="http://enable-cors.org/server.html" title="Enabling CORS server">enable-cors.org</a>.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a optimize}
|
||||
|
||||
@ -65,14 +253,7 @@ The `--prod` _meta-flag_ engages the following optimization features.
|
||||
|
||||
The remaining [copy deployment steps](#copy-files) are the same as before.
|
||||
|
||||
You may further reduce bundle sizes by adding the `build-optimizer` flag.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build --prod --build-optimizer
|
||||
</code-example>
|
||||
|
||||
See the [CLI Documentation](https://github.com/angular/angular-cli/wiki/build)
|
||||
for details about available build options and what they do.
|
||||
See [`ng build`](cli/build) for more about CLI build options and what they do.
|
||||
|
||||
{@a enable-prod-mode}
|
||||
|
||||
@ -102,22 +283,24 @@ Configure the Angular Router to defer loading of all other modules (and their as
|
||||
or by [_lazy loading_](guide/router#asynchronous-routing "Lazy loading")
|
||||
them on demand.
|
||||
|
||||
#### Don't eagerly import something from a lazy loaded module
|
||||
<div class="alert is-helpful>
|
||||
|
||||
It's a common mistake.
|
||||
You've arranged to lazy load a module.
|
||||
But you unintentionally import it, with a JavaScript `import` statement,
|
||||
in a file that's eagerly loaded when the app starts, a file such as the root `AppModule`.
|
||||
#### Don't eagerly import something from a lazy-loaded module
|
||||
|
||||
If you mean to lazy-load a module, be careful not import it
|
||||
in a file that's eagerly loaded when the app starts (such as the root `AppModule`).
|
||||
If you do that, the module will be loaded immediately.
|
||||
|
||||
The bundling configuration must take lazy loading into consideration.
|
||||
Because lazy loaded modules aren't imported in JavaScript (as just noted), bundlers exclude them by default.
|
||||
Bundlers don't know about the router configuration and won't create separate bundles for lazy loaded modules.
|
||||
You have to create these bundles manually.
|
||||
Because lazy-loaded modules aren't imported in JavaScript, bundlers exclude them by default.
|
||||
Bundlers don't know about the router configuration and can't create separate bundles for lazy-loaded modules.
|
||||
You would have to create these bundles manually.
|
||||
|
||||
The CLI runs the
|
||||
[Angular Ahead-of-Time Webpack Plugin](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack)
|
||||
which automatically recognizes lazy loaded `NgModules` and creates separate bundles for them.
|
||||
which automatically recognizes lazy-loaded `NgModules` and creates separate bundles for them.
|
||||
|
||||
</div>
|
||||
|
||||
{@a measure}
|
||||
|
||||
@ -203,17 +386,12 @@ the subfolder is `my/app/` and you should add `<base href="/my/app/">` to the se
|
||||
When the `base` tag is mis-configured, the app fails to load and the browser console displays `404 - Not Found` errors
|
||||
for the missing files. Look at where it _tried_ to find those files and adjust the base tag appropriately.
|
||||
|
||||
## _build_ vs. _serve_
|
||||
## Building and serving for deployment
|
||||
|
||||
You'll probably prefer `ng build` for deployments.
|
||||
When you are designing and developing applications, you typically use `ng serve` to build your app for fast, local, iterative development.
|
||||
When you are ready to deploy, however, you must use the `ng build` command to build the app and deploy the build artifacts elsewhere.
|
||||
|
||||
The **ng build** command is intended for building the app and deploying the build artifacts elsewhere.
|
||||
The **ng serve** command is intended for fast, local, iterative development.
|
||||
|
||||
Both `ng build` and `ng serve` **clear the output folder** before they build the project.
|
||||
The `ng build` command writes generated build artifacts to the output folder.
|
||||
The `ng serve` command does not.
|
||||
It serves build artifacts from memory instead for a faster development experience.
|
||||
Both `ng build` and `ng serve` clear the output folder before they build the project, but only the `ng build` command writes the generated build artifacts to the output folder.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -222,160 +400,13 @@ To output to a different folder, change the `outputPath` in `angular.json`.
|
||||
|
||||
</div>
|
||||
|
||||
The `ng serve` command builds, watches, and serves the application from a local CLI development server.
|
||||
The `ng serve` command builds, watches, and serves the application from local memory, using a local development server.
|
||||
When you have deployed your app to another server, however, you might still want to serve the app so that you can continue to see changes that you make in it.
|
||||
You can do this by adding the `--watch` option to the `ng build` command.
|
||||
|
||||
The `ng build` command generates output files just once and does not serve them.
|
||||
The `ng build --watch` command will regenerate output files when source files change.
|
||||
This `--watch` flag is useful if you're building during development and
|
||||
are automatically re-deploying changes to another server.
|
||||
```
|
||||
ng build --watch
|
||||
```
|
||||
Like the `ng serve` command, this regenerates output files when source files change.
|
||||
|
||||
|
||||
See the [CLI `build` topic](https://github.com/angular/angular-cli/wiki/build) for more details and options.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a server-configuration}
|
||||
|
||||
## Server configuration
|
||||
|
||||
This section covers changes you may have make to the server or to files deployed to the server.
|
||||
|
||||
{@a fallback}
|
||||
|
||||
### Routed apps must fallback to `index.html`
|
||||
|
||||
Angular apps are perfect candidates for serving with a simple static HTML server.
|
||||
You don't need a server-side engine to dynamically compose application pages because
|
||||
Angular does that on the client-side.
|
||||
|
||||
If the app uses the Angular router, you must configure the server
|
||||
to return the application's host page (`index.html`) when asked for a file that it does not have.
|
||||
|
||||
{@a deep-link}
|
||||
|
||||
|
||||
A routed application should support "deep links".
|
||||
A _deep link_ is a URL that specifies a path to a component inside the app.
|
||||
For example, `http://www.mysite.com/heroes/42` is a _deep link_ to the hero detail page
|
||||
that displays the hero with `id: 42`.
|
||||
|
||||
There is no issue when the user navigates to that URL from within a running client.
|
||||
The Angular router interprets the URL and routes to that page and hero.
|
||||
|
||||
But clicking a link in an email, entering it in the browser address bar,
|
||||
or merely refreshing the browser while on the hero detail page —
|
||||
all of these actions are handled by the browser itself, _outside_ the running application.
|
||||
The browser makes a direct request to the server for that URL, bypassing the router.
|
||||
|
||||
A static server routinely returns `index.html` when it receives a request for `http://www.mysite.com/`.
|
||||
But it rejects `http://www.mysite.com/heroes/42` and returns a `404 - Not Found` error *unless* it is
|
||||
configured to return `index.html` instead.
|
||||
|
||||
#### Fallback configuration examples
|
||||
|
||||
There is no single configuration that works for every server.
|
||||
The following sections describe configurations for some of the most popular servers.
|
||||
The list is by no means exhaustive, but should provide you with a good starting point.
|
||||
|
||||
#### Development servers
|
||||
|
||||
* [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
|
||||
[Quickstart repo](https://github.com/angular/quickstart) is pre-configured to fallback to `index.html`.
|
||||
|
||||
|
||||
* [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
|
||||
`historyApiFallback` entry in the dev server options as follows:
|
||||
|
||||
<code-example>
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||
}
|
||||
</code-example>
|
||||
|
||||
|
||||
#### Production servers
|
||||
|
||||
* [Apache](https://httpd.apache.org/): add a
|
||||
[rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html) to the `.htaccess` file as shown
|
||||
(https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
|
||||
|
||||
<code-example format=".">
|
||||
RewriteEngine On
|
||||
# If an existing asset or directory is requested go to it as it is
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# If the requested resource doesn't exist, use index.html
|
||||
RewriteRule ^ /index.html
|
||||
</code-example>
|
||||
|
||||
|
||||
* [NGinx](http://nginx.org/): use `try_files`, as described in
|
||||
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
|
||||
modified to serve `index.html`:
|
||||
|
||||
<code-example format=".">
|
||||
try_files $uri $uri/ /index.html;
|
||||
</code-example>
|
||||
|
||||
|
||||
* [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||
[here](http://stackoverflow.com/a/26152011/2116927):
|
||||
|
||||
<code-example format='.'>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Angular Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="/index.html" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
* [GitHub Pages](https://pages.github.com/): you can't
|
||||
[directly configure](https://github.com/isaacs/github/issues/408)
|
||||
the GitHub Pages server, but you can add a 404 page.
|
||||
Copy `index.html` into `404.html`.
|
||||
It will still be served as the 404 response, but the browser will process that page and load the app properly.
|
||||
It's also a good idea to
|
||||
[serve from `docs/` on master](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch)
|
||||
and to
|
||||
[create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)
|
||||
|
||||
|
||||
* [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
|
||||
|
||||
<code-example format=".">
|
||||
"rewrites": [ {
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
} ]
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a cors}
|
||||
|
||||
### Requesting services from a different server (CORS)
|
||||
|
||||
Angular developers may encounter a
|
||||
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" title="Cross-origin resource sharing">
|
||||
<i>cross-origin resource sharing</i></a> error when making a service request (typically a data service request)
|
||||
to a server other than the application's own host server.
|
||||
Browsers forbid such requests unless the server permits them explicitly.
|
||||
|
||||
There isn't anything the client application can do about these errors.
|
||||
The server must be configured to accept the application's requests.
|
||||
Read about how to enable CORS for specific servers at
|
||||
<a href="http://enable-cors.org/server.html" title="Enabling CORS server">enable-cors.org</a>.
|
||||
For complete details of the CLI commands, see the [CLI command reference](cli).
|
||||
|
@ -31,7 +31,7 @@ The easiest way to display a component property
|
||||
is to bind the property name through interpolation.
|
||||
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||
|
||||
Follow the [quickstart](guide/quickstart) instructions for creating a new project
|
||||
Follow the [Getting Started](guide/quickstart) instructions for creating a new project
|
||||
named <code>displaying-data</code>.
|
||||
|
||||
Delete the <code>app.component.html</code> file. It is not needed for this example.
|
||||
@ -42,7 +42,7 @@ changing the template and the body of the component.
|
||||
When you're done, it should look like this:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" title="src/app/app.component.ts">
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" header="src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -54,7 +54,7 @@ The template displays the two component properties using double curly brace
|
||||
interpolation:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" linenums="false" header="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -98,7 +98,7 @@ The CSS `selector` in the `@Component` decorator specifies an element named `<ap
|
||||
That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/index.html" linenums="false" title="src/index.html (body)" region="body">
|
||||
<code-example path="displaying-data/src/index.html" linenums="false" header="src/index.html (body)" region="body">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -135,7 +135,7 @@ In either style, the template data bindings have the same access to the componen
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
By default, the Angular CLI generates components with a template file. You can override that with:
|
||||
By default, the Angular CLI command [`ng generate component`](cli/generate) generates components with a template file. You can override that with:
|
||||
|
||||
<code-example hideCopy language="sh" class="code-shell">
|
||||
ng generate component hero -it
|
||||
@ -164,7 +164,7 @@ This app uses more terse "variable assignment" style simply for brevity.
|
||||
To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" header="src/app/app.component.ts (class)" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -174,7 +174,7 @@ Now use the Angular `ngFor` directive in the template to display
|
||||
each item in the `heroes` list.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" header="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -185,7 +185,7 @@ in the `<li>` element is the Angular "repeater" directive.
|
||||
It marks that `<li>` element (and its children) as the "repeater template":
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (li)" region="li">
|
||||
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" header="src/app/app.component.ts (li)" region="li">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -252,7 +252,7 @@ of hero names into an array of `Hero` objects. For that you'll need a `Hero` cla
|
||||
With the following code:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts">
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" header="src/app/hero.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -266,7 +266,7 @@ The declaration of the constructor parameters takes advantage of a TypeScript sh
|
||||
Consider the first parameter:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (id)" region="id">
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" header="src/app/hero.ts (id)" region="id">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -286,7 +286,7 @@ After importing the `Hero` class, the `AppComponent.heroes` property can return
|
||||
of `Hero` objects:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (heroes)" region="heroes">
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" header="src/app/app.component.ts (heroes)" region="heroes">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -297,7 +297,7 @@ At the moment it displays the hero's `id` and `name`.
|
||||
Fix that to display only the hero's `name` property.
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
||||
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" header="src/app/app.component.ts (template)" region="template">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -317,7 +317,7 @@ The Angular `ngIf` directive inserts or removes an element based on a _truthy/fa
|
||||
To see it in action, add the following paragraph at the bottom of the template:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (message)" region="message">
|
||||
<code-example path="displaying-data/src/app/app.component.ts" linenums="false" header="src/app/app.component.ts (message)" region="message">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -375,19 +375,19 @@ Here's the final code:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="displaying-data/src/app/app.component.ts" region="final">
|
||||
<code-pane header="src/app/app.component.ts" path="displaying-data/src/app/app.component.ts" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/hero.ts" path="displaying-data/src/app/hero.ts">
|
||||
<code-pane header="src/app/hero.ts" path="displaying-data/src/app/hero.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/app.module.ts" path="displaying-data/src/app/app.module.ts">
|
||||
<code-pane header="src/app/app.module.ts" path="displaying-data/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="main.ts" path="displaying-data/src/main.ts">
|
||||
<code-pane header="main.ts" path="displaying-data/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
@ -310,22 +310,22 @@ _This_ "Authors Doc Style Guide" has its own sample application, located in the
|
||||
|
||||
The following _code-example_ displays the sample's `app.module.ts`.
|
||||
|
||||
<code-example path="docs-style-guide/src/app/app.module.ts" title="src/app/app.module.ts"></code-example>
|
||||
<code-example path="docs-style-guide/src/app/app.module.ts" header="src/app/app.module.ts"></code-example>
|
||||
|
||||
Here's the brief markup that produced that lengthy snippet:
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
title="src/app/app.module.ts">
|
||||
header="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`.
|
||||
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.
|
||||
Following convention, you set the `title` attribute to the file's location within the sample's root folder.
|
||||
You added a header to tell the reader where to find the file by setting the `header` attribute.
|
||||
Following convention, you set the `header` attribute to the file's location within the sample's root folder.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -343,7 +343,7 @@ If you want to include an ignored code file in your project and display it in a
|
||||
|
||||
The preferred way to un-ignore a file is to update the `content/examples/.gitignore` like this:
|
||||
|
||||
<code-example title="content/examples/.gitignore">
|
||||
<code-example header="content/examples/.gitignore">
|
||||
# my-guide
|
||||
!my-guide/src/something.js
|
||||
!my-guide/more-javascript*.js
|
||||
@ -357,7 +357,7 @@ You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
||||
* `path`- the path to the file in the `content/examples` folder.
|
||||
|
||||
* `title`- the header of the code listing.
|
||||
* `header`- the header of the code listing.
|
||||
|
||||
* `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").
|
||||
|
||||
@ -395,7 +395,7 @@ 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/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 `header` 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.
|
||||
|
||||
1. The line numbers disappeared. By default, the doc viewer omits line numbers when there are fewer than 10 lines of code; it adds line numbers after that. You can turn line numbers on or off explicitly by setting the `linenums` attribute.
|
||||
@ -415,11 +415,11 @@ Here's the markup for an "avoid" example in the
|
||||
<code-example
|
||||
path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts"
|
||||
region="example"
|
||||
title="app/heroes/hero-button/hero-button.component.ts">
|
||||
header="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
```
|
||||
|
||||
<code-example path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/hero-button/hero-button.component.ts">
|
||||
<code-example path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" header="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
|
||||
{@a code-tabs}
|
||||
@ -434,29 +434,29 @@ Code tabs display code much like _code examples_ do. The added advantage is tha
|
||||
#### Code-pane attributes
|
||||
|
||||
* `path` - a file in the content/examples folder
|
||||
* `title` - seen in the header of a tab
|
||||
* `header` - 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 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 header.
|
||||
It demonstrates control over display of line numbers at both the `<code-tabs>` and `<code-pane>` levels.
|
||||
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
header="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
header="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
header="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
header="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -469,21 +469,21 @@ The `linenums` attribute in the second pane restores line numbering for _itself
|
||||
```html
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
header="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
header="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
header="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
header="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -548,7 +548,7 @@ The `src/main.ts` is a simple example of a file with a single _#docregion_ at th
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/src/main.ts"
|
||||
title="src/main.ts"></code-example>
|
||||
header="src/main.ts"></code-example>
|
||||
|
||||
</div>
|
||||
|
||||
@ -622,12 +622,12 @@ Here's are the two corresponding code snippets displayed side-by-side.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
title="app.component.ts (class)"
|
||||
header="app.component.ts (class)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts (class-skeleton)"
|
||||
header="app.component.ts (class-skeleton)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class-skeleton">
|
||||
</code-pane>
|
||||
@ -660,12 +660,12 @@ Here's an example that excerpts certain scripts from `package.json` into a parti
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
header="package.json (selected scripts)"></code-example>
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
header="package.json (selected scripts)"></code-example>
|
||||
```
|
||||
|
||||
#### Partial file naming
|
||||
@ -689,7 +689,7 @@ Remember to exclude these files from stackblitz by listing them in the `stackbli
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/stackblitz.json"
|
||||
title="stackblitz.json"></code-example>
|
||||
header="stackblitz.json"></code-example>
|
||||
|
||||
{@a live-examples}
|
||||
## Live examples
|
||||
|
@ -35,7 +35,7 @@ The ad banner uses a helper directive called `AdDirective` to
|
||||
mark valid insertion points in the template.
|
||||
|
||||
|
||||
<code-example path="dynamic-component-loader/src/app/ad.directive.ts" title="src/app/ad.directive.ts" linenums="false">
|
||||
<code-example path="dynamic-component-loader/src/app/ad.directive.ts" header="src/app/ad.directive.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -62,7 +62,7 @@ To apply the `AdDirective`, recall the selector from `ad.directive.ts`,
|
||||
where to dynamically load components.
|
||||
|
||||
|
||||
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" title="src/app/ad-banner.component.ts (template)" linenums="false">
|
||||
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" header="src/app/ad-banner.component.ts (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -91,7 +91,7 @@ With its `getAds()` method, `AdBannerComponent` cycles through the array of `AdI
|
||||
and loads a new component every 3 seconds by calling `loadComponent()`.
|
||||
|
||||
|
||||
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="class" title="src/app/ad-banner.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="class" header="src/app/ad-banner.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -150,7 +150,7 @@ dynamically loaded components since they load at runtime.
|
||||
To ensure that the compiler still generates a factory,
|
||||
add dynamically loaded components to the `NgModule`'s `entryComponents` array:
|
||||
|
||||
<code-example path="dynamic-component-loader/src/app/app.module.ts" region="entry-components" title="src/app/app.module.ts (entry components)" linenums="false">
|
||||
<code-example path="dynamic-component-loader/src/app/app.module.ts" region="entry-components" header="src/app/app.module.ts (entry components)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -169,15 +169,15 @@ Here are two sample components and the `AdComponent` interface for reference:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-job-ad.component.ts" path="dynamic-component-loader/src/app/hero-job-ad.component.ts">
|
||||
<code-pane header="hero-job-ad.component.ts" path="dynamic-component-loader/src/app/hero-job-ad.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="hero-profile.component.ts" path="dynamic-component-loader/src/app/hero-profile.component.ts">
|
||||
<code-pane header="hero-profile.component.ts" path="dynamic-component-loader/src/app/hero-profile.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="ad.component.ts" path="dynamic-component-loader/src/app/ad.component.ts">
|
||||
<code-pane header="ad.component.ts" path="dynamic-component-loader/src/app/ad.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
@ -40,11 +40,11 @@ Bootstrap the `AppModule` in `main.ts`.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app.module.ts" path="dynamic-form/src/app/app.module.ts">
|
||||
<code-pane header="app.module.ts" path="dynamic-form/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="main.ts" path="dynamic-form/src/main.ts">
|
||||
<code-pane header="main.ts" path="dynamic-form/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -62,7 +62,7 @@ The _question_ is the most fundamental object in the model.
|
||||
The following `QuestionBase` is a fundamental question class.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/question-base.ts" title="src/app/question-base.ts">
|
||||
<code-example path="dynamic-form/src/app/question-base.ts" header="src/app/question-base.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -77,7 +77,7 @@ appropriate controls dynamically.
|
||||
via the `type` property.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/question-textbox.ts" title="src/app/question-textbox.ts" linenums="false">
|
||||
<code-example path="dynamic-form/src/app/question-textbox.ts" header="src/app/question-textbox.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -86,7 +86,7 @@ via the `type` property.
|
||||
`DropdownQuestion` presents a list of choices in a select box.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/question-dropdown.ts" title="src/app/question-dropdown.ts" linenums="false">
|
||||
<code-example path="dynamic-form/src/app/question-dropdown.ts" header="src/app/question-dropdown.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -97,7 +97,7 @@ In a nutshell, the form group consumes the metadata from the question model and
|
||||
allows you to specify default values and validation rules.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/question-control.service.ts" title="src/app/question-control.service.ts" linenums="false">
|
||||
<code-example path="dynamic-form/src/app/question-control.service.ts" header="src/app/question-control.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -112,11 +112,11 @@ to create components to represent the dynamic form.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="dynamic-form.component.html" path="dynamic-form/src/app/dynamic-form.component.html">
|
||||
<code-pane header="dynamic-form.component.html" path="dynamic-form/src/app/dynamic-form.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="dynamic-form.component.ts" path="dynamic-form/src/app/dynamic-form.component.ts">
|
||||
<code-pane header="dynamic-form.component.ts" path="dynamic-form/src/app/dynamic-form.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -132,11 +132,11 @@ question based on values in the data-bound question object.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="dynamic-form-question.component.html" path="dynamic-form/src/app/dynamic-form-question.component.html">
|
||||
<code-pane header="dynamic-form-question.component.html" path="dynamic-form/src/app/dynamic-form-question.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="dynamic-form-question.component.ts" path="dynamic-form/src/app/dynamic-form-question.component.ts">
|
||||
<code-pane header="dynamic-form-question.component.ts" path="dynamic-form/src/app/dynamic-form-question.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -169,7 +169,7 @@ directly since you imported `ReactiveFormsModule` from `AppModule`.
|
||||
and removing objects from the `questions` array.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/question.service.ts" title="src/app/question.service.ts">
|
||||
<code-example path="dynamic-form/src/app/question.service.ts" header="src/app/question.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -178,7 +178,7 @@ directly since you imported `ReactiveFormsModule` from `AppModule`.
|
||||
Finally, display an instance of the form in the `AppComponent` shell.
|
||||
|
||||
|
||||
<code-example path="dynamic-form/src/app/app.component.ts" title="app.component.ts">
|
||||
<code-example path="dynamic-form/src/app/app.component.ts" header="app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -107,7 +107,7 @@ The recently-developed [custom elements](https://developer.mozilla.org/en-US/doc
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Firefox</td>
|
||||
<td> Set the <code>dom.webcomponents.enabled</code> and <code>dom.webcomponents.customelements.enabled</code> preferences to true. Planned to be enabled by default in version 60/61.</td>
|
||||
<td> Set the <code>dom.webcomponents.enabled</code> and <code>dom.webcomponents.customelements.enabled</code> preferences to true. Planned to be enabled by default in version 63.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Edge</td>
|
||||
@ -119,7 +119,7 @@ The recently-developed [custom elements](https://developer.mozilla.org/en-US/doc
|
||||
|
||||
In browsers that support Custom Elements natively, the specification requires developers use ES2015 classes to define Custom Elements - developers can opt-in to this by setting the `target: "es2015"` property in their project's `tsconfig.json`. As Custom Element and ES2015 support may not be available in all browsers, developers can instead choose to use a polyfill to support older browsers and ES5 code.
|
||||
|
||||
Use the [Angular CLI](https://cli.angular.io/) to automatically set up your project with the correct polyfill: `ng add @angular/elements --name=*your_project_name*`.
|
||||
Use the [Angular CLI](cli) to automatically set up your project with the correct polyfill: `ng add @angular/elements --name=*your_project_name*`.
|
||||
- For more information about polyfills, see [polyfill documentation](https://www.webcomponents.org/polyfills).
|
||||
|
||||
- For more information about Angular browser support, see [Browser Support](guide/browser-support).
|
||||
@ -142,19 +142,19 @@ For comparison, the demo shows both methods. One button adds the popup using the
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="popup.component.ts" path="elements/src/app/popup.component.ts">
|
||||
<code-pane header="popup.component.ts" path="elements/src/app/popup.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="popup.service.ts" path="elements/src/app/popup.service.ts">
|
||||
<code-pane header="popup.service.ts" path="elements/src/app/popup.service.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.module.ts" path="elements/src/app/app.module.ts">
|
||||
<code-pane header="app.module.ts" path="elements/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component.ts" path="elements/src/app/app.component.ts">
|
||||
<code-pane header="app.component.ts" path="elements/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
@ -33,7 +33,7 @@ pipes that it shares.
|
||||
|
||||
## How to make a feature module
|
||||
|
||||
Assuming you already have a CLI generated app, create a feature
|
||||
Assuming you already have an app that you created with the [Angular CLI](cli), create a feature
|
||||
module using the CLI by entering the following command in the
|
||||
root project directory. Replace `CustomerDashboard` with the
|
||||
name of your module. You can omit the "Module" suffix from the name because the CLI appends it:
|
||||
@ -72,7 +72,7 @@ ng generate component customer-dashboard/CustomerDashboard
|
||||
This generates a folder for the new component within the customer-dashboard folder and updates the feature module with the `CustomerDashboardComponent` info:
|
||||
|
||||
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="customer-dashboard-component" title="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="customer-dashboard-component" header="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ The `CustomerDashboardComponent` is now in the JavaScript import list at the top
|
||||
|
||||
To incorporate the feature module into your app, you have to let the root module, `app.module.ts`, know about it. Notice the `CustomerDashboardModule` export at the bottom of `customer-dashboard.module.ts`. This exposes it so that other modules can get to it. To import it into the `AppModule`, add it to the imports in `app.module.ts` and to the `imports` array:
|
||||
|
||||
<code-example path="feature-modules/src/app/app.module.ts" region="app-module" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="feature-modules/src/app/app.module.ts" region="app-module" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -94,20 +94,20 @@ Now the `AppModule` knows about the feature module. If you were to add any servi
|
||||
|
||||
When the CLI generated the `CustomerDashboardComponent` for the feature module, it included a template, `customer-dashboard.component.html`, with the following markup:
|
||||
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard/customer-dashboard.component.html" region="feature-template" title="src/app/customer-dashboard/customer-dashboard/customer-dashboard.component.html" linenums="false">
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard/customer-dashboard.component.html" region="feature-template" header="src/app/customer-dashboard/customer-dashboard/customer-dashboard.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
To see this HTML in the `AppComponent`, you first have to export the `CustomerDashboardComponent` in the `CustomerDashboardModule`. In `customer-dashboard.module.ts`, just beneath the `declarations` array, add an `exports` array containing `CustomerDashboardModule`:
|
||||
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="component-exports" title="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
|
||||
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="component-exports" header="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, in the `AppComponent`, `app.component.html`, add the tag `<app-customer-dashboard>`:
|
||||
|
||||
<code-example path="feature-modules/src/app/app.component.html" region="app-component-template" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="feature-modules/src/app/app.component.html" region="app-component-template" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
|
126
aio/content/guide/file-structure.md
Normal file
126
aio/content/guide/file-structure.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Workspace and project file structure
|
||||
|
||||
You develop apps in the context of an Angular [workspace](guide/glossary#workspace). A workspace contains the files for one or more [projects](guide/glossary#project). A project is the set of files that comprise a standalone app, a library, or a set of end-to-end (e2e) tests.
|
||||
|
||||
The Angular CLI command `ng new <project_name>` gets you started.
|
||||
When you run this command, the CLI installs the necessary Angular npm packages and other dependencies in a new workspace, with a root folder named *project_name*.
|
||||
It also creates the following workspace and starter project files:
|
||||
|
||||
* An initial skeleton app project, also called *project_name* (in the `src/` subfolder).
|
||||
* An end-to-end test project (in the `e2e/` subfolder).
|
||||
* Related configuration files.
|
||||
|
||||
The initial app project contains a simple Welcome app, ready to run.
|
||||
|
||||
## Workspace files
|
||||
|
||||
The top level of the workspace contains a number of workspace-wide configuration files.
|
||||
|
||||
| WORKSPACE CONFIG FILES | PURPOSE |
|
||||
| :--------------------- | :------------------------------------------|
|
||||
| `.editorconfig` | Configuration for code editors. See [EditorConfig](https://editorconfig.org/). |
|
||||
| `.gitignore` | Specifies intentionally untracked files that [Git](https://git-scm.com/) should ignore. |
|
||||
| `angular.json` | CLI configuration for all projects in the workspace, including configuration options for build, serve, and test tools that the CLI uses, such as [Karma](https://karma-runner.github.io/) and [Protractor](http://www.protractortest.org/). |
|
||||
| `node_modules` | Provides [npm packages](guide/npm-packages) to the entire workspace. |
|
||||
| `package.json` | Lists package dependencies. See [npm documentation](https://docs.npmjs.com/files/package.json) for the specific format and contents of this file.|
|
||||
| `tsconfig.app.json` | Default [TypeScript](https://www.typescriptlang.org/) configuration for apps in the workspace. |
|
||||
| `tsconfig.spec.json` | Default TypeScript configuration for e2e test apps in the workspace. |
|
||||
| `tslint.json` | Default [TSLint](https://palantir.github.io/tslint/) configuration for apps in the workspace. |
|
||||
| `README.md` | Introductory documentation. |
|
||||
| `package-lock.json` | Provides version information for all packages installed into `node_modules` by the npm client. See [npm documentation](https://docs.npmjs.com/files/package-lock.json) for details. If you use the yarn client, this file will be [yarn.lock](https://yarnpkg.com/lang/en/docs/yarn-lock/) instead. |
|
||||
|
||||
All projects within a workspace share this configuration context.
|
||||
Project-specific [TypeScript](https://www.typescriptlang.org/) configuration files inherit from the workspace-wide `tsconfig.*.json`, and app-specific [TSLint](https://palantir.github.io/tslint/) configuration files inherit from the workspace-wide `tslint.json`.
|
||||
|
||||
### Default app project files
|
||||
|
||||
The CLI command `ng new my-app` creates a workspace folder named "my-app" and generates a new app skeleton.
|
||||
This initial app is the *default app* for CLI commands (unless you change the default after creating additional apps).
|
||||
|
||||
A newly generated app contains the source files for a root module, with a root component and template.
|
||||
When the workspace file structure is in place, you can use the `ng generate` command on the command line to add functionality and data to the initial app.
|
||||
|
||||
<div class="alert is-helpful>
|
||||
|
||||
Besides using the CLI on the command line, You can also use an interactive development environment like [Angular Console](https://angular.console.com), or manipulate files directly in the app's source folder and configuration files.
|
||||
|
||||
</div>
|
||||
|
||||
The `src/` subfolder contains the source files (app logic, data, and assets), along with configuration files for the initial app.
|
||||
Workspace-wide `node_modules` dependencies are visible to this project.
|
||||
|
||||
| APP SOURCE & CONFIG FILES | PURPOSE |
|
||||
| :--------------------- | :------------------------------------------|
|
||||
| `app/` | Contains the component files in which your app logic and data are defined. See details in [App source folder](#app-src) below. |
|
||||
| `assets/` | Contains image files and other asset files to be copied as-is when you build your application. |
|
||||
| `environments/` | Contains build configuration options for particular target environments. By default there is an unnamed standard development environment and a production ("prod") environment. You can define additional target environment configurations. |
|
||||
| `browserlist` | Configures sharing of target browsers and Node.js versions among various front-end tools. See [Browserlist on GitHub](https://github.com/browserslist/browserslist) for more information. |
|
||||
| `favicon.ico` | An icon to use for this app in the bookmark bar. |
|
||||
| `index.html` | The main HTML page that is served when someone visits your site. The CLI automatically adds all JavaScript and CSS files when building your app, so you typically don't need to add any `<script>` or` <link>` tags here manually. |
|
||||
| `main.ts` | The main entry point for your app. Compiles the application with the [JIT compiler](https://angular.io/guide/glossary#jit) and bootstraps the application's root module (AppModule) to run in the browser. You can also use the [AOT compiler](https://angular.io/guide/aot-compiler) without changing any code by appending the `--aot` flag to the CLI `build` and `serve` commands. |
|
||||
| `polyfills.ts` | Provides polyfill scripts for browser support. |
|
||||
| `styles.sass` | Lists CSS files that supply styles for a project. The extension reflects the style preprocessor you have configured for the project. |
|
||||
| `test.ts` | The main entry point for your unit tests, with some Angular-specific configuration. You don't typically need to edit this file. |
|
||||
| `tsconfig.app.json` | Inherits from the workspace-wide `tsconfig.json` file. |
|
||||
| `tsconfig.spec.json` | Inherits from the workspace-wide `tsconfig.json` file. |
|
||||
| `tslint.json` | Inherits from the workspace-wide `tslint.json` file. |
|
||||
|
||||
### Default app project e2e files
|
||||
|
||||
An `e2e/` subfolder contains configuration and source files for a set of end-to-end tests that correspond to the initial app.
|
||||
Workspace-wide `node_modules` dependencies are visible to this project.
|
||||
|
||||
<code-example language="none" linenums="false">
|
||||
my-app/
|
||||
e2e/ (end-to-end test app for my-app)
|
||||
src/ (app source files)
|
||||
protractor.conf.js (test-tool config)
|
||||
tsconfig.e2e.json (TypeScript config inherits from workspace tsconfig.json)
|
||||
</code-example>
|
||||
|
||||
### Project folders for additional apps and libraries
|
||||
|
||||
When you generate new projects in a workspace,
|
||||
the CLI creates a new *workspace*`/projects` folder, and adds the generated files there.
|
||||
|
||||
When you generate an app (`ng generate application my-other-app`), the CLI adds folders under `projects/` for both the app and its corresponding end-to-end tests. Newly generated libraries are also added under `projects/`.
|
||||
|
||||
<code-example language="none" linenums="false">
|
||||
my-app/
|
||||
...
|
||||
projects/ (additional apps and libs)
|
||||
my-other-app/ (a second app)
|
||||
src/
|
||||
(config files)
|
||||
my-other-app-e2e/ (corresponding test app)
|
||||
src/
|
||||
(config files)
|
||||
my-lib/ (a generated library)
|
||||
(config files)
|
||||
</code-example>
|
||||
|
||||
{@a app-src}
|
||||
## App source folder
|
||||
|
||||
Inside the `src/` folder, the `app/` folder contains your app's logic and data. Angular components, templates, and styles go here. An `assets/` subfolder contains images and anything else your app needs. Files at the top level of `src/` support testing and running your app.
|
||||
|
||||
<code-example language="none" linenums="false">
|
||||
src/
|
||||
app/
|
||||
app.component.css
|
||||
app.component.html
|
||||
app.component.spec.ts
|
||||
app.component.ts
|
||||
app.module.ts
|
||||
assets/...
|
||||
...
|
||||
</code-example>
|
||||
|
||||
| APP SOURCE FILES | PURPOSE |
|
||||
| :-------------------------- | :------------------------------------------|
|
||||
| `app/app.component.ts` | Defines the logic for the app's root component, named `AppComponent`. The view associated with this root component becomes the root of the [view hierarchy](guide/glossary#view-hierarchy) as you add components and services to your app. |
|
||||
| `app/app.component.html` | Defines the HTML template associated with the root `AppComponent`. |
|
||||
| `app/app.component.css` | Defines the base CSS stylesheet for the root `AppComponent`. |
|
||||
| `app/app.component.spec.ts` | Defines a unit test for the root `AppComponent`. |
|
||||
| `app/app.module.ts` | Defines the root module, named `AppModule`, that tells Angular how to assemble the application. Initially declares only the `AppComponent`. As you add more components to the app, they must be declared here. |
|
||||
| `assets/*` | Contains image files and other asset files to be copied as-is when you build your application. |
|
@ -29,7 +29,7 @@ either a list of validation errors, which results in an INVALID status, or null,
|
||||
You can then inspect the control's state by exporting `ngModel` to a local template variable.
|
||||
The following example exports `NgModel` into a variable called `name`:
|
||||
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg" title="template/hero-form-template.component.html (name)" linenums="false">
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg" header="template/hero-form-template.component.html (name)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -92,7 +92,7 @@ built-in validators—this time, in function form. See below:
|
||||
|
||||
{@a reactive-component-class}
|
||||
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="form-group" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="form-group" header="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Note that:
|
||||
@ -106,7 +106,7 @@ for the template.
|
||||
|
||||
If you look at the template for the name input again, it is fairly similar to the template-driven example.
|
||||
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg" title="reactive/hero-form-reactive.component.html (name with error msg)" linenums="false">
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg" header="reactive/hero-form-reactive.component.html (name with error msg)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Key takeaways:
|
||||
@ -125,7 +125,7 @@ Consider the `forbiddenNameValidator` function from previous
|
||||
[examples](guide/form-validation#reactive-component-class) in
|
||||
this guide. Here's what the definition of that function looks like:
|
||||
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" header="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name and returns a validator function.
|
||||
@ -148,7 +148,7 @@ at which point the form uses the last value emitted for validation.
|
||||
In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly
|
||||
to the `FormControl`.
|
||||
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="custom-validator" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="custom-validator" header="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Adding to template-driven forms
|
||||
@ -161,19 +161,19 @@ The corresponding `ForbiddenValidatorDirective` serves as a wrapper around the `
|
||||
Angular recognizes the directive's role in the validation process because the directive registers itself
|
||||
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validators.
|
||||
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" title="shared/forbidden-name.directive.ts (providers)" linenums="false">
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" header="shared/forbidden-name.directive.ts (providers)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The directive class then implements the `Validator` interface, so that it can easily integrate
|
||||
with Angular forms. Here is the rest of the directive to help you get an idea of how it all
|
||||
comes together:
|
||||
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
|
||||
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" header="shared/forbidden-name.directive.ts (directive)">
|
||||
</code-example>
|
||||
|
||||
Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector, `appForbiddenName`, to any input element to activate it. For example:
|
||||
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" title="template/hero-form-template.component.html (forbidden-name-input)" linenums="false">
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" header="template/hero-form-template.component.html (forbidden-name-input)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -204,7 +204,7 @@ Like in AngularJS, Angular automatically mirrors many control properties onto th
|
||||
The hero form uses the `.ng-valid` and `.ng-invalid` classes to
|
||||
set the color of each form control's border.
|
||||
|
||||
<code-example path="form-validation/src/assets/forms.css" title="forms.css (status classes)">
|
||||
<code-example path="form-validation/src/assets/forms.css" header="forms.css (status classes)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -245,7 +245,7 @@ const heroForm = new FormGroup({
|
||||
|
||||
The validator code is as follows:
|
||||
|
||||
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-validator" title="shared/identity-revealed.directive.ts" linenums="false">
|
||||
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-validator" header="shared/identity-revealed.directive.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The identity validator implements the `ValidatorFn` interface. It takes an Angular control object as an argument and returns either null if the form is valid, or `ValidationErrors` otherwise.
|
||||
@ -255,7 +255,7 @@ First we retrieve the child controls by calling the `FormGroup`'s [get](api/form
|
||||
If the values do not match, the hero's identity remains secret, and we can safely return null. Otherwise, the hero's identity is revealed and we must mark the form as invalid by returning an error object.
|
||||
|
||||
Next, to provide better user experience, we show an appropriate error message when the form is invalid.
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="cross-validation-error-message" title="reactive/hero-form-template.component.html" linenums="false">
|
||||
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="cross-validation-error-message" header="reactive/hero-form-template.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Note that we check if:
|
||||
@ -265,15 +265,15 @@ Note that we check if:
|
||||
### Adding to template driven forms
|
||||
First we must create a directive that will wrap the validator function. We provide it as the validator using the `NG_VALIDATORS` token. If you are not sure why, or you do not fully understand the syntax, revisit the previous [section](guide/form-validation#adding-to-template-driven-forms).
|
||||
|
||||
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-directive" title="shared/identity-revealed.directive.ts" linenums="false">
|
||||
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-directive" header="shared/identity-revealed.directive.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Next, we have to add the directive to the html template. Since the validator must be registered at the highest level in the form, we put the directive on the `form` tag.
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-register-validator" title="template/hero-form-template.component.html" linenums="false">
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-register-validator" header="template/hero-form-template.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
To provide better user experience, we show an appropriate error message when the form is invalid.
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-error-message" title="template/hero-form-template.component.html" linenums="false">
|
||||
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-error-message" header="template/hero-form-template.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Note that we check if:
|
||||
|
@ -84,7 +84,7 @@ The abstraction of the form model promotes simplicity over structure. The templa
|
||||
|
||||
## Data flow in forms
|
||||
|
||||
When building forms in Angular, it's important to understand how the the framework handles data flowing from the user or from programmatic changes. Reactive and template-driven forms follow two different strategies when handling form input. The data flow examples below begin with the favorite color input field example from above, and they show how changes to favorite color are handled in reactive forms compared to template-driven forms.
|
||||
When building forms in Angular, it's important to understand how the framework handles data flowing from the user or from programmatic changes. Reactive and template-driven forms follow two different strategies when handling form input. The data flow examples below begin with the favorite color input field example from above, and they show how changes to favorite color are handled in reactive forms compared to template-driven forms.
|
||||
|
||||
### Data flow in reactive forms
|
||||
|
||||
@ -169,7 +169,7 @@ The following tests use the favorite color components mentioned earlier to verif
|
||||
|
||||
The following test verifies the view to model data flow:
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="view-to-model" title="Favorite color test - view to model">
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="view-to-model" header="Favorite color test - view to model">
|
||||
</code-example>
|
||||
|
||||
The steps performed in the view to model test.
|
||||
@ -180,7 +180,7 @@ The steps performed in the view to model test.
|
||||
|
||||
The following test verifies the model to view data flow:
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="model-to-view" title="Favorite color test - model to view">
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="model-to-view" header="Favorite color test - model to view">
|
||||
</code-example>
|
||||
|
||||
The steps performed in the model to view test.
|
||||
@ -197,7 +197,7 @@ The following tests use the favorite color components mentioned earlier to verif
|
||||
|
||||
The following test verifies the view to model data flow:
|
||||
|
||||
<code-example path="forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts" region="view-to-model" title="Favorite color test - view to model">
|
||||
<code-example path="forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts" region="view-to-model" header="Favorite color test - view to model">
|
||||
</code-example>
|
||||
|
||||
The steps performed in the view to model test.
|
||||
@ -209,7 +209,7 @@ The steps performed in the view to model test.
|
||||
|
||||
The following test verifies the model to view data flow:
|
||||
|
||||
<code-example path="forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts" region="model-to-view" title="Favorite color test - model to view">
|
||||
<code-example path="forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts" region="model-to-view" header="Favorite color test - model to view">
|
||||
</code-example>
|
||||
|
||||
The steps performed in the model to view test.
|
||||
@ -253,7 +253,7 @@ To learn more about reactive forms, see the following guides:
|
||||
* [Form Validation](guide/form-validation#reactive-form-validation)
|
||||
* [Dynamic forms](guide/dynamic-form)
|
||||
|
||||
To learn more about tempate-driven forms, see the following guides:
|
||||
To learn more about template-driven forms, see the following guides:
|
||||
|
||||
* [Template-driven Forms](guide/forms)
|
||||
* [Form Validation](guide/form-validation#template-driven-validation)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Forms
|
||||
# Template-driven forms
|
||||
|
||||
Forms are the mainstay of business applications.
|
||||
You use forms to log in, submit a help request, place an order, book a flight,
|
||||
@ -7,6 +7,8 @@ schedule a meeting, and perform countless other data-entry tasks.
|
||||
In developing a form, it's important to create a data-entry experience that guides the
|
||||
user efficiently and effectively through the workflow.
|
||||
|
||||
## Introduction to Template-driven forms
|
||||
|
||||
Developing forms requires design skills (which are out of scope for this page), as well as framework support for
|
||||
*two-way data binding, change tracking, validation, and error handling*,
|
||||
which you'll learn about on this page.
|
||||
@ -24,8 +26,6 @@ You can run the <live-example></live-example> in Stackblitz and download the cod
|
||||
|
||||
{@a template-driven}
|
||||
|
||||
## Template-driven forms
|
||||
|
||||
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
|
||||
the form-specific directives and techniques described in this page.
|
||||
|
||||
@ -99,7 +99,7 @@ A model can be as simple as a "property bag" that holds facts about a thing of a
|
||||
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||
and one optional field (`alterEgo`).
|
||||
|
||||
Using the Angular CLI, generate a new class named `Hero`:
|
||||
Using the Angular CLI command [`ng generate class`](cli/generate), generate a new class named `Hero`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
@ -109,7 +109,7 @@ Using the Angular CLI, generate a new class named `Hero`:
|
||||
|
||||
With this content:
|
||||
|
||||
<code-example path="forms/src/app/hero.ts" title="src/app/hero.ts">
|
||||
<code-example path="forms/src/app/hero.ts" header="src/app/hero.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -132,7 +132,7 @@ An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||
to handle data and user interactions programmatically.
|
||||
Begin with the class because it states, in brief, what the hero editor can do.
|
||||
|
||||
Using the Angular CLI, generate a new component named `HeroForm`:
|
||||
Using the Angular CLI command [`ng generate component`](cli/generate), generate a new component named `HeroForm`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
@ -142,7 +142,7 @@ Using the Angular CLI, generate a new component named `HeroForm`:
|
||||
|
||||
With this content:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (v1)" region="v1">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" header="src/app/hero-form/hero-form.component.ts (v1)" region="v1">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -176,7 +176,7 @@ Because template-driven forms are in their own module, you need to add the `Form
|
||||
|
||||
Update it with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.module.ts" title="src/app/app.module.ts">
|
||||
<code-example path="forms/src/app/app.module.ts" header="src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -204,7 +204,7 @@ Update it with the following:
|
||||
|
||||
Replace the contents of its template with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.component.html" title="src/app/app.component.html">
|
||||
<code-example path="forms/src/app/app.component.html" header="src/app/app.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -221,7 +221,7 @@ Replace the contents of its template with the following:
|
||||
|
||||
Update the template file with the following contents:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="start" title="src/app/hero-form/hero-form.component.html">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="start" header="src/app/hero-form/hero-form.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -259,7 +259,7 @@ Bootstrap gives the form a little style.
|
||||
|
||||
To add the stylesheet, open `styles.css` and add the following import line at the top:
|
||||
|
||||
<code-example path="forms/src/styles.1.css" linenums="false" title="src/styles.css">
|
||||
<code-example path="forms/src/styles.1.css" linenums="false" header="src/styles.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -274,7 +274,7 @@ a technique seen previously in the [Displaying Data](guide/displaying-data) page
|
||||
|
||||
Add the following HTML *immediately below* the *Alter Ego* group:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (powers)" region="powers">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (powers)" region="powers">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -307,7 +307,7 @@ makes binding the form to the model easy.
|
||||
|
||||
Find the `<input>` tag for *Name* and update it like this:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-1">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -325,7 +325,7 @@ You need one more addition to display the data. Declare
|
||||
a template variable for the form. Update the `<form>` tag with
|
||||
`#heroForm="ngForm"` as follows:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -391,7 +391,7 @@ Then you can confirm that two-way data binding works *for the entire hero model*
|
||||
|
||||
After revision, the core of the form should look like this:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModel-2">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModel-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -493,7 +493,7 @@ You can leverage those class names to change the appearance of the control.
|
||||
Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy`
|
||||
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-2">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -535,13 +535,13 @@ on the left of the input box:
|
||||
You achieve this effect by adding these class definitions to a new `forms.css` file
|
||||
that you add to the project as a sibling to `index.html`:
|
||||
|
||||
<code-example path="forms/src/assets/forms.css" title="src/assets/forms.css">
|
||||
<code-example path="forms/src/assets/forms.css" header="src/assets/forms.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
Update the `<head>` of `index.html` to include this style sheet:
|
||||
|
||||
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (styles)" region="styles">
|
||||
<code-example path="forms/src/index.html" linenums="false" header="src/index.html (styles)" region="styles">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -564,7 +564,7 @@ To achieve this effect, extend the `<input>` tag with the following:
|
||||
|
||||
Here's an example of an error message added to the _name_ input box:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -583,7 +583,7 @@ Here you created a variable called `name` and gave it the value "ngModel".
|
||||
You control visibility of the name error message by binding properties of the `name`
|
||||
control to the message `<div>` element's `hidden` property.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -609,11 +609,11 @@ power to valid values.
|
||||
Now you'll add a new hero in this form.
|
||||
Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" title="src/app/hero-form/hero-form.component.html (New Hero button)">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" header="src/app/hero-form/hero-form.component.html (New Hero button)">
|
||||
|
||||
</code-example>
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" title="src/app/hero-form/hero-form.component.ts (New Hero method)" linenums="false">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" header="src/app/hero-form/hero-form.component.ts (New Hero method)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -634,7 +634,7 @@ Replacing the hero object *did not restore the pristine state* of the form contr
|
||||
You have to clear all of the flags imperatively, which you can do
|
||||
by calling the form's `reset()` method after calling the `newHero()` method.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" title="src/app/hero-form/hero-form.component.html (Reset the form)">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" header="src/app/hero-form/hero-form.component.html (Reset the form)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -651,7 +651,7 @@ A "form submit" is useless at the moment.
|
||||
To make it useful, bind the form's `ngSubmit` event property
|
||||
to the hero form component's `onSubmit()` method:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -664,7 +664,7 @@ You'll bind the form's overall validity via
|
||||
the `heroForm` variable to the button's `disabled` property
|
||||
using an event binding. Here's the code:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -703,7 +703,7 @@ hide the data entry area and display something else.
|
||||
Wrap the form in a `<div>` and bind
|
||||
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -711,7 +711,7 @@ The main form is visible from the start because the
|
||||
`submitted` property is false until you submit the form,
|
||||
as this fragment from the `HeroFormComponent` shows:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" header="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -721,7 +721,7 @@ as planned.
|
||||
Now the app needs to show something else while the form is in the submitted state.
|
||||
Add the following HTML below the `<div>` wrapper you just wrote:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" header="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -752,35 +752,35 @@ Here’s the code for the final version of the application:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-form/hero-form.component.ts" path="forms/src/app/hero-form/hero-form.component.ts" region="final">
|
||||
<code-pane header="hero-form/hero-form.component.ts" path="forms/src/app/hero-form/hero-form.component.ts" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="hero-form/hero-form.component.html" path="forms/src/app/hero-form/hero-form.component.html" region="final">
|
||||
<code-pane header="hero-form/hero-form.component.html" path="forms/src/app/hero-form/hero-form.component.html" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="hero.ts" path="forms/src/app/hero.ts">
|
||||
<code-pane header="hero.ts" path="forms/src/app/hero.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.module.ts" path="forms/src/app/app.module.ts">
|
||||
<code-pane header="app.module.ts" path="forms/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component.html" path="forms/src/app/app.component.html">
|
||||
<code-pane header="app.component.html" path="forms/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component.ts" path="forms/src/app/app.component.ts">
|
||||
<code-pane header="app.component.ts" path="forms/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="main.ts" path="forms/src/main.ts">
|
||||
<code-pane header="main.ts" path="forms/src/main.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="forms.css" path="forms/src/assets/forms.css">
|
||||
<code-pane header="forms.css" path="forms/src/assets/forms.css">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
@ -71,7 +71,7 @@ of some of the things they contain:
|
||||
|
||||
When you use these Angular modules, import them in `AppModule`,
|
||||
or your feature module as appropriate, and list them in the `@NgModule`
|
||||
`imports` array. For example, in the basic app generated by the CLI,
|
||||
`imports` array. For example, in the basic app generated by the [Angular CLI](cli),
|
||||
`BrowserModule` is the first import at the top of the `AppModule`,
|
||||
`app.module.ts`.
|
||||
|
||||
|
@ -77,7 +77,7 @@ A way to initialize and launch an app or system.
|
||||
|
||||
In Angular, an app's root NgModule (`AppModule`) has a `bootstrap` property that identifies the app's top-level [components](guide/glossary#component).
|
||||
During the bootstrap process, Angular creates and inserts these components into the `index.html` host web page.
|
||||
You can bootstrap multiple apps in the same `index.html`. Each app ccontains its own components.
|
||||
You can bootstrap multiple apps in the same `index.html`. Each app contains its own components.
|
||||
|
||||
Learn more in [Bootstrapping](guide/bootstrapping).
|
||||
|
||||
@ -130,10 +130,10 @@ A [decorator](guide/glossary#decorator) statement immediately before a field in
|
||||
|
||||
## command-line interface (CLI)
|
||||
|
||||
The [Angular CLI](https://cli.angular.io/) is a command-line tool for managing the Angular development cycle. Use it to create the initial filesystem scaffolding for a [workspace](guide/glossary#workspace) or [project](guide/glossary#project), and to run [schematics](guide/glossary#schematic) that add and modify code for initial generic versions of various elements. The CLI supports all stages of the development cycle, including building, testing, bundling, and deployment.
|
||||
The [Angular CLI](cli) is a command-line tool for managing the Angular development cycle. Use it to create the initial filesystem scaffolding for a [workspace](guide/glossary#workspace) or [project](guide/glossary#project), and to run [schematics](guide/glossary#schematic) that add and modify code for initial generic versions of various elements. The CLI supports all stages of the development cycle, including building, testing, bundling, and deployment.
|
||||
|
||||
* To begin using the CLI for a new project, see [QuickStart](guide/quickstart).
|
||||
* To learn more about the full capabilities of the CLI, see the [Angular CLI documentation](https://github.com/angular/angular-cli/wiki).
|
||||
* To begin using the CLI for a new project, see [Getting Started](guide/quickstart).
|
||||
* To learn more about the full capabilities of the CLI, see the [CLI command reference](cli).
|
||||
|
||||
{@a component}
|
||||
|
||||
@ -621,8 +621,7 @@ For more information, see [Routing and Navigation](guide/router).
|
||||
A scaffolding library that defines how to generate or transform a programming project by creating, modifying, refactoring, or moving files and code.
|
||||
The Angular [CLI](guide/glossary#cli) uses schematics to generate and modify [Angular projects](guide/glossary#project) and parts of projects.
|
||||
|
||||
* Angular provides a set of schematics for use with the CLI.
|
||||
For details, see [Angular CLI documentation](https://github.com/angular/angular-cli/wiki).
|
||||
* Angular provides a set of schematics for use with the CLI. See the [Angular CLI command reference](cli). The [`ng add`](cli/add) command runs schematics as part of adding a library to your project. The [`ng generate`](cli/generate) command runs schematics to create apps, libraries, and Angular code constructs.
|
||||
|
||||
* Library developers can create schematics that enable the CLI to generate their published libraries.
|
||||
For more information, see [devkit documentation](https://www.npmjs.com/package/@angular-devkit/schematics).
|
||||
@ -636,7 +635,7 @@ NgModules are delivered within scoped packages whose names begin with the Angula
|
||||
|
||||
Import a scoped package in the same way that you import a normal package.
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" linenums="false" title="architecture/src/app/app.component.ts (import)" region="import">
|
||||
<code-example path="architecture/src/app/app.component.ts" linenums="false" header="architecture/src/app/app.component.ts (import)" region="import">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -785,7 +784,7 @@ See [custom element](guide/glossary#custom-element).
|
||||
## workspace
|
||||
|
||||
In Angular, a folder that contains [projects](guide/glossary#project) (that is, apps and libraries).
|
||||
The [CLI](guide/glossary#cli) `new` command creates a workspace to contain projects.
|
||||
The [CLI](guide/glossary#cli) `ng new` command creates a workspace to contain projects.
|
||||
Commands that create or operate on apps and libraries (such as `add` and `generate`) must be executed from within a workspace folder.
|
||||
|
||||
{@a X}
|
||||
|
@ -23,9 +23,9 @@ When you specify providers in the `@Injectable()` decorator of the service itsel
|
||||
|
||||
* Learn more about [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers).
|
||||
|
||||
You're likely to inject `UserService` in many places throughout the app and will want to inject the same service instance every time. Providing `UserService` through the `root` injector is a good choice, and is the default that the CLI uses when you generate a service for your app.
|
||||
You're likely to inject `UserService` in many places throughout the app and will want to inject the same service instance every time. Providing `UserService` through the `root` injector is a good choice, and is the default that the [Angular CLI](cli) uses when you generate a service for your app.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
<div class="alert is-helpful">
|
||||
<header>Platform injector</header>
|
||||
|
||||
When you use `providedIn:'root'`, you are configuring the root injector for the _app_, which is the injector for `AppModule`.
|
||||
@ -75,7 +75,7 @@ When an injectable class provides its own service to the `root` injector, the se
|
||||
|
||||
The following example configures a provider for `HeroService` using the `@Injectable()` decorator on the class.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" title="src/app/heroes/heroes.service.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" header="src/app/heroes/heroes.service.ts" linenums="false"> </code-example>
|
||||
|
||||
This configuration tells Angular that the app's root injector is responsible for creating an
|
||||
instance of `HeroService` by invoking its constructor,
|
||||
@ -92,7 +92,7 @@ Instead of specifying the `root` injector, you can set `providedIn` to a specifi
|
||||
For example, in the following excerpt, the `@Injectable()` decorator configures a provider
|
||||
that is available in any injector that includes the `HeroModule`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.4.ts" title="src/app/heroes/hero.service.ts" linenums="false"> </code-example>
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.4.ts" header="src/app/heroes/hero.service.ts" linenums="false"> </code-example>
|
||||
|
||||
This is generally no different from configuring the injector of the NgModule itself,
|
||||
except that the service is tree-shakable if the NgModule doesn't use it.
|
||||
@ -117,7 +117,7 @@ Here is an example of the case where the component router configuration includes
|
||||
a non-default [location strategy](guide/router#location-strategy) by listing its provider
|
||||
in the `providers` list of the `AppModule`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/app.module.ts" region="providers" title="src/app/app.module.ts (providers)" linenums="false">
|
||||
<code-example path="dependency-injection-in-action/src/app/app.module.ts" region="providers" header="src/app/app.module.ts (providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -132,7 +132,7 @@ by configuring the provider at the component level using the `@Component` metada
|
||||
|
||||
The following example is a revised `HeroesComponent` that specifies `HeroService` in its `providers` array. `HeroService` can provide heroes to instances of this component, or to any child component instances.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" title="src/app/heroes/heroes.component.ts" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Element injectors
|
||||
@ -207,7 +207,7 @@ that would make the `VillainsService` available everywhere in the application, i
|
||||
Instead, you can provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/villains-list.component.ts" linenums="false" title="src/app/villains-list.component.ts (metadata)" region="metadata">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/villains-list.component.ts" linenums="false" header="src/app/villains-list.component.ts (metadata)" region="metadata">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -250,14 +250,14 @@ It caches a single `HeroTaxReturn`, tracks changes to that return, and can save
|
||||
It also delegates to the application-wide singleton `HeroService`, which it gets by injection.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts" title="src/app/hero-tax-return.service.ts">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts" header="src/app/hero-tax-return.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here is the `HeroTaxReturnComponent` that makes use of it.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" title="src/app/hero-tax-return.component.ts">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" header="src/app/hero-tax-return.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -273,7 +273,7 @@ Every component would share the same service instance, and each component would
|
||||
To prevent this, we configure the component-level injector of `HeroTaxReturnComponent` to provide the service, using the `providers` property in the component metadata.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" linenums="false" title="src/app/hero-tax-return.component.ts (providers)" region="providers">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" linenums="false" header="src/app/hero-tax-return.component.ts (providers)" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -28,7 +28,7 @@ Most apps do so in the root `AppModule`.
|
||||
<code-example
|
||||
path="http/src/app/app.module.ts"
|
||||
region="sketch"
|
||||
title="app/app.module.ts (excerpt)" linenums="false">
|
||||
header="app/app.module.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Having imported `HttpClientModule` into the `AppModule`, you can inject the `HttpClient`
|
||||
@ -37,7 +37,7 @@ into an application class as shown in the following `ConfigService` example.
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="proto"
|
||||
title="app/config/config.service.ts (excerpt)" linenums="false">
|
||||
header="app/config/config.service.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Getting JSON data
|
||||
@ -48,7 +48,7 @@ that specifies resource URLs.
|
||||
|
||||
<code-example
|
||||
path="http/src/assets/config.json"
|
||||
title="assets/config.json" linenums="false">
|
||||
header="assets/config.json" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `ConfigService` fetches this file with a `get()` method on `HttpClient`.
|
||||
@ -56,7 +56,7 @@ The `ConfigService` fetches this file with a `get()` method on `HttpClient`.
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="getConfig_1"
|
||||
title="app/config/config.service.ts (getConfig v.1)" linenums="false">
|
||||
header="app/config/config.service.ts (getConfig v.1)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
A component, such as `ConfigComponent`, injects the `ConfigService` and calls
|
||||
@ -65,7 +65,7 @@ the `getConfig` service method.
|
||||
<code-example
|
||||
path="http/src/app/config/config.component.ts"
|
||||
region="v1"
|
||||
title="app/config/config.component.ts (showConfig v.1)" linenums="false">
|
||||
header="app/config/config.component.ts (showConfig v.1)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Because the service method returns an `Observable` of configuration data,
|
||||
@ -116,7 +116,7 @@ Then, specify that interface as the `HttpClient.get()` call's type parameter in
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="getConfig_2"
|
||||
title="app/config/config.service.ts (getConfig v.2)" linenums="false">
|
||||
header="app/config/config.service.ts (getConfig v.2)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The callback in the updated component method receives a typed data object, which is
|
||||
@ -125,7 +125,7 @@ easier and safer to consume:
|
||||
<code-example
|
||||
path="http/src/app/config/config.component.ts"
|
||||
region="v2"
|
||||
title="app/config/config.component.ts (showConfig v.2)" linenums="false">
|
||||
header="app/config/config.component.ts (showConfig v.2)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Reading the full response
|
||||
@ -146,7 +146,7 @@ The component's `showConfigResponse()` method displays the response headers as w
|
||||
<code-example
|
||||
path="http/src/app/config/config.component.ts"
|
||||
region="showConfigResponse"
|
||||
title="app/config/config.component.ts (showConfigResponse)"
|
||||
header="app/config/config.component.ts (showConfigResponse)"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -161,7 +161,7 @@ You _could_ handle in the component by adding a second callback to the `.subscri
|
||||
<code-example
|
||||
path="http/src/app/config/config.component.ts"
|
||||
region="v3"
|
||||
title="app/config/config.component.ts (showConfig v.3 with error handling)"
|
||||
header="app/config/config.component.ts (showConfig v.3 with error handling)"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -188,7 +188,7 @@ You might first devise an error handler like this one:
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="handleError"
|
||||
title="app/config/config.service.ts (handleError)" linenums="false">
|
||||
header="app/config/config.service.ts (handleError)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Notice that this handler returns an RxJS [`ErrorObservable`](#rxjs) with a user-friendly error message.
|
||||
@ -201,7 +201,7 @@ and _pipe them through_ to the error handler.
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="getConfig_3"
|
||||
title="app/config/config.service.ts (getConfig v.3 with error handler)" linenums="false">
|
||||
header="app/config/config.service.ts (getConfig v.3 with error handler)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### `retry()`
|
||||
@ -218,7 +218,7 @@ _Pipe_ it onto the `HttpClient` method result just before the error handler.
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="getConfig"
|
||||
title="app/config/config.service.ts (getConfig with retry)" linenums="false">
|
||||
header="app/config/config.service.ts (getConfig with retry)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a rxjs}
|
||||
@ -239,7 +239,7 @@ If you're following along with these code snippets, note that you must import th
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="rxjs-imports"
|
||||
title="app/config/config.service.ts (RxJS imports)" linenums="false">
|
||||
header="app/config/config.service.ts (RxJS imports)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Requesting non-JSON data
|
||||
@ -252,7 +252,7 @@ as an `Observable<string>`.
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.service.ts"
|
||||
region="getTextFile"
|
||||
title="app/downloader/downloader.service.ts (getTextFile)" linenums="false">
|
||||
header="app/downloader/downloader.service.ts (getTextFile)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
`HttpClient.get()` returns a string rather than the default JSON because of the `responseType` option.
|
||||
@ -264,7 +264,7 @@ A `download()` method in the `DownloaderComponent` initiates the request by subs
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.component.ts"
|
||||
region="download"
|
||||
title="app/downloader/downloader.component.ts (download)" linenums="false">
|
||||
header="app/downloader/downloader.component.ts (download)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Sending data to the server
|
||||
@ -289,7 +289,7 @@ to every `HttpClient` save method.
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="http-options"
|
||||
title="app/heroes/heroes.service.ts (httpOptions)" linenums="false">
|
||||
header="app/heroes/heroes.service.ts (httpOptions)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Making a POST request
|
||||
@ -300,7 +300,7 @@ In the following example, the `HeroesService` posts when adding a hero to the da
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="addHero"
|
||||
title="app/heroes/heroes.service.ts (addHero)" linenums="false">
|
||||
header="app/heroes/heroes.service.ts (addHero)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `HttpClient.post()` method is similar to `get()` in that it has a type parameter
|
||||
@ -320,7 +320,7 @@ the `Observable` returned by this service method.
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.component.ts"
|
||||
region="add-hero-subscribe"
|
||||
title="app/heroes/heroes.component.ts (addHero)" linenums="false">
|
||||
header="app/heroes/heroes.component.ts (addHero)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
When the server responds successfully with the newly added hero, the component adds
|
||||
@ -334,7 +334,7 @@ in the request URL.
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="deleteHero"
|
||||
title="app/heroes/heroes.service.ts (deleteHero)" linenums="false">
|
||||
header="app/heroes/heroes.service.ts (deleteHero)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `HeroesComponent` initiates the actual DELETE operation by subscribing to
|
||||
@ -343,7 +343,7 @@ the `Observable` returned by this service method.
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.component.ts"
|
||||
region="delete-hero-subscribe"
|
||||
title="app/heroes/heroes.component.ts (deleteHero)" linenums="false">
|
||||
header="app/heroes/heroes.component.ts (deleteHero)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The component isn't expecting a result from the delete operation, so it subscribes without a callback. Even though you are not using the result, you still have to subscribe. Calling the `subscribe()` method _executes_ the observable, which is what initiates the DELETE request.
|
||||
@ -403,7 +403,7 @@ The following `HeroesService` example is just like the POST example.
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="updateHero"
|
||||
title="app/heroes/heroes.service.ts (updateHero)" linenums="false">
|
||||
header="app/heroes/heroes.service.ts (updateHero)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
For the reasons [explained above](#always-subscribe), the caller (`HeroesComponent.update()` in this case) must `subscribe()` to the observable returned from the `HttpClient.put()`
|
||||
@ -464,7 +464,7 @@ Here's a pertinent excerpt from the template:
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.html"
|
||||
region="search"
|
||||
title="app/package-search/package-search.component.html (search)">
|
||||
header="app/package-search/package-search.component.html (search)">
|
||||
</code-example>
|
||||
|
||||
The `(keyup)` event binding sends every keystroke to the component's `search()` method.
|
||||
@ -476,7 +476,7 @@ That's easy to implement with RxJS operators, as shown in this excerpt.
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.ts"
|
||||
region="debounce"
|
||||
title="app/package-search/package-search.component.ts (excerpt))">
|
||||
header="app/package-search/package-search.component.ts (excerpt))">
|
||||
</code-example>
|
||||
|
||||
The `searchText$` is the sequence of search-box values coming from the user.
|
||||
@ -543,7 +543,7 @@ To implement an interceptor, declare a class that implements the `intercept()` m
|
||||
Here is a do-nothing _noop_ interceptor that simply passes the request through without touching it:
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/noop-interceptor.ts"
|
||||
title="app/http-interceptors/noop-interceptor.ts"
|
||||
header="app/http-interceptors/noop-interceptor.ts"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -608,7 +608,7 @@ Consider creating a "barrel" file that gathers all the interceptor providers int
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/index.ts"
|
||||
region="interceptor-providers"
|
||||
title="app/http-interceptors/index.ts" linenums="false">
|
||||
header="app/http-interceptors/index.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Then import and add it to the `AppModule` _providers array_ like this:
|
||||
@ -616,7 +616,7 @@ Then import and add it to the `AppModule` _providers array_ like this:
|
||||
<code-example
|
||||
path="http/src/app/app.module.ts"
|
||||
region="interceptor-providers"
|
||||
title="app/app.module.ts (interceptor providers)" linenums="false">
|
||||
header="app/app.module.ts (interceptor providers)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
As you create new interceptors, add them to the `httpInterceptorProviders` array and
|
||||
@ -672,7 +672,7 @@ You can clone and modify the request in a single step as in this example.
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/ensure-https-interceptor.ts"
|
||||
region="excerpt"
|
||||
title="app/http-interceptors/ensure-https-interceptor.ts (excerpt)" linenums="false">
|
||||
header="app/http-interceptors/ensure-https-interceptor.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `clone()` method's hash argument allows you to mutate specific properties of the request while copying the others.
|
||||
@ -692,7 +692,7 @@ If you must mutate the request body, copy it first, change the copy,
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/trim-name-interceptor.ts"
|
||||
region="excerpt"
|
||||
title="app/http-interceptors/trim-name-interceptor.ts (excerpt)" linenums="false">
|
||||
header="app/http-interceptors/trim-name-interceptor.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
##### Clearing the request body
|
||||
@ -718,7 +718,7 @@ adds an authorization header with that token to every outgoing request:
|
||||
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/auth-interceptor.ts"
|
||||
title="app/http-interceptors/auth-interceptor.ts">
|
||||
header="app/http-interceptors/auth-interceptor.ts">
|
||||
</code-example>
|
||||
|
||||
The practice of cloning a request to set new headers is so common that
|
||||
@ -747,7 +747,7 @@ with the injected `MessageService`.
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/logging-interceptor.ts"
|
||||
region="excerpt"
|
||||
title="app/http-interceptors/logging-interceptor.ts)">
|
||||
header="app/http-interceptors/logging-interceptor.ts)">
|
||||
</code-example>
|
||||
|
||||
The RxJS `tap` operator captures whether the request succeed or failed.
|
||||
@ -768,7 +768,7 @@ The `CachingInterceptor` demonstrates this approach.
|
||||
<code-example
|
||||
path="http/src/app/http-interceptors/caching-interceptor.ts"
|
||||
region="v1"
|
||||
title="app/http-interceptors/caching-interceptor.ts)" linenums="false">
|
||||
header="app/http-interceptors/caching-interceptor.ts)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `isCachable()` function determines if the request is cachable.
|
||||
@ -858,7 +858,7 @@ with the `reportProgress` option set true to enable tracking of progress events.
|
||||
<code-example
|
||||
path="http/src/app/uploader/uploader.service.ts"
|
||||
region="upload-request"
|
||||
title="app/uploader/uploader.service.ts (upload request)">
|
||||
header="app/uploader/uploader.service.ts (upload request)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
@ -873,7 +873,7 @@ returns an `Observable` of `HttpEvents`, the same events processed by intercepto
|
||||
<code-example
|
||||
path="http/src/app/uploader/uploader.service.ts"
|
||||
region="upload-body"
|
||||
title="app/uploader/uploader.service.ts (upload body)" linenums="false">
|
||||
header="app/uploader/uploader.service.ts (upload body)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `getEventMessage` method interprets each type of `HttpEvent` in the event stream.
|
||||
@ -881,7 +881,7 @@ The `getEventMessage` method interprets each type of `HttpEvent` in the event st
|
||||
<code-example
|
||||
path="http/src/app/uploader/uploader.service.ts"
|
||||
region="getEventMessage"
|
||||
title="app/uploader/uploader.service.ts (getEventMessage)" linenums="false">
|
||||
header="app/uploader/uploader.service.ts (getEventMessage)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -936,7 +936,7 @@ setting up such mocking straightforward.
|
||||
### Mocking philosophy
|
||||
|
||||
Angular's HTTP testing library is designed for a pattern of testing wherein
|
||||
the the app executes code and makes requests first.
|
||||
the app executes code and makes requests first.
|
||||
|
||||
Then a test expects that certain requests have or have not been made,
|
||||
performs assertions against those requests,
|
||||
@ -964,7 +964,7 @@ along with the other symbols your tests require.
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="imports"
|
||||
title="app/testing/http-client.spec.ts (imports)" linenums="false">
|
||||
header="app/testing/http-client.spec.ts (imports)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Then add the `HttpClientTestingModule` to the `TestBed` and continue with
|
||||
@ -973,7 +973,7 @@ the setup of the _service-under-test_.
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="setup"
|
||||
title="app/testing/http-client.spec.ts(setup)" linenums="false">
|
||||
header="app/testing/http-client.spec.ts(setup)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Now requests made in the course of your tests will hit the testing backend instead of the normal backend.
|
||||
@ -988,7 +988,7 @@ Now you can write a test that expects a GET Request to occur and provides a mock
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="get-test"
|
||||
title="app/testing/http-client.spec.ts(httpClient.get)" linenums="false">
|
||||
header="app/testing/http-client.spec.ts(httpClient.get)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The last step, verifying that no requests remain outstanding, is common enough for you to move it into an `afterEach()` step:
|
||||
|
@ -19,7 +19,7 @@ Angular simplifies the following aspects of internationalization:
|
||||
* Handling plural forms of words.
|
||||
* Handling alternative text.
|
||||
|
||||
For localization, you can use the [**Angular CLI**](https://cli.angular.io/) to generate most of the boilerplate necessary to create files for translators, and to publish your app in multiple languages.
|
||||
For localization, you can use the [Angular CLI](cli) to generate most of the boilerplate necessary to create files for translators, and to publish your app in multiple languages.
|
||||
After you have set up your app to use i18n, the CLI can help you with the following steps:
|
||||
* Extracting localizable text into a file that you can send out to be translated.
|
||||
* Building and serving the app for a given locale, using the translated text.
|
||||
@ -57,7 +57,7 @@ To set your app's locale to another value, use the CLI parameter `--configuratio
|
||||
|
||||
If you use JIT, you also need to define the `LOCALE_ID` provider in your main module:
|
||||
|
||||
<code-example path="i18n/doc-files/app.module.ts" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.module.ts" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ The CLI imports the locale data for you when you use the parameter `--configurat
|
||||
|
||||
If you want to import locale data for other languages, you can do it manually:
|
||||
|
||||
<code-example path="i18n/doc-files/app.locale_data.ts" region="import-locale" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.locale_data.ts" region="import-locale" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The first parameter is an object containing the locale data imported from `@angular/common/locales`.
|
||||
@ -112,7 +112,7 @@ The files in `@angular/common/locales` contain most of the locale data that you
|
||||
need, but some advanced formatting options might only be available in the extra dataset that you can
|
||||
import from `@angular/common/locales/extra`. An error message informs you when this is the case.
|
||||
|
||||
<code-example path="i18n/doc-files/app.locale_data_extra.ts" region="import-locale-extra" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.locale_data_extra.ts" region="import-locale-extra" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -158,12 +158,12 @@ text is to be translated.
|
||||
|
||||
In the example below, an `<h1>` tag displays a simple English language greeting, "Hello i18n!"
|
||||
|
||||
<code-example path="i18n/doc-files/app.component.html" region="greeting" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.component.html" region="greeting" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
To mark the greeting for translation, add the `i18n` attribute to the `<h1>` tag.
|
||||
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -183,7 +183,7 @@ To translate a text message accurately, the translator may need additional infor
|
||||
You can add a description of the text message as the value of the `i18n` attribute, as shown in the
|
||||
example below:
|
||||
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute-desc" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute-desc" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The translator may also need to know the meaning or intent of the text message within this particular
|
||||
@ -192,7 +192,7 @@ app context.
|
||||
You add context by beginning the `i18n` attribute value with the _meaning_ and
|
||||
separating it from the _description_ with the `|` character: `<meaning>|<description>`
|
||||
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute-meaning" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-attribute-meaning" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
All occurrences of a text message that have the same meaning will have the same translation.
|
||||
@ -220,7 +220,7 @@ You must then update the translation file with the new id.
|
||||
Alternatively, you can specify a custom id in the `i18n` attribute by using the prefix `@@`.
|
||||
The example below defines the custom id `introductionHeader`:
|
||||
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-solo-id' title='app/app.component.html' linenums="false">
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-solo-id' header='app/app.component.html' linenums="false">
|
||||
</code-example>
|
||||
|
||||
When you specify a custom id, the extractor tool and compiler generate a translation unit with that
|
||||
@ -238,12 +238,12 @@ You can use a custom id in combination with a description by including both in t
|
||||
`i18n` attribute. In the example below, the `i18n` attribute value includes a description, followed
|
||||
by the custom `id`:
|
||||
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-id' title='app/app.component.html' linenums="false">
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-id' header='app/app.component.html' linenums="false">
|
||||
</code-example>
|
||||
|
||||
You also can add a meaning, as shown in this example:
|
||||
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-meaning-and-id' title='app/app.component.html' linenums="false">
|
||||
<code-example path='i18n/doc-files/app.component.html' region='i18n-attribute-meaning-and-id' header='app/app.component.html' linenums="false">
|
||||
</code-example>
|
||||
|
||||
#### Define unique custom ids
|
||||
@ -287,7 +287,7 @@ However, if you don't want to create a new DOM element merely to facilitate tran
|
||||
you can wrap the text in an `<ng-container>` element.
|
||||
The `<ng-container>` is transformed into an html comment:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-ng-container" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-ng-container" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a translate-attributes}
|
||||
@ -296,14 +296,14 @@ The `<ng-container>` is transformed into an html comment:
|
||||
Displayed text is sometimes supplied as the value of an attribute, rather than the content of tag.
|
||||
For example, if your template has an image with a `title` attribute, the text value of the `title` attribute needs to be translated.
|
||||
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-title" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.component.html" region="i18n-title" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
To mark an attribute for translation, add an attribute in the form of `i18n-x`,
|
||||
where `x` is the name of the attribute to translate. The following example shows how to mark the
|
||||
`title` attribute for translation by adding the `i18n-title` attribute on the `img` tag:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-title-translate" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-title-translate" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
This technique works for any attribute of any element.
|
||||
@ -328,7 +328,7 @@ Other languages might express the cardinality differently.
|
||||
The example below shows how to use a `plural` ICU expression to display one of those three options
|
||||
based on when the update occurred:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-plural" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-plural" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
* The first parameter is the key. It is bound to the component property (`minutes`), which determines
|
||||
@ -359,7 +359,7 @@ Pluralization categories include (depending on the language):
|
||||
After the pluralization category, put the default English text in braces (`{}`).
|
||||
|
||||
In the example above, the three options are specified according to that pluralization pattern. For
|
||||
talking about about zero minutes, you use `=0 {just now}`. For one minute, you use `=1 {one minute}`.
|
||||
talking about zero minutes, you use `=0 {just now}`. For one minute, you use `=1 {one minute}`.
|
||||
Any unmatched cardinality uses `other {{{minutes}} minutes ago}`. You could choose to add patterns
|
||||
for two, three, or any other number if the pluralization rules were different. For the example of
|
||||
"minute", only these three patterns are necessary in English.
|
||||
@ -384,7 +384,7 @@ The following format message in the component template binds to the component's
|
||||
which outputs one of the following string values: "m", "f" or "o".
|
||||
The message maps those values to the appropriate translations:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-select" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-select" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a nesting-ICUS}
|
||||
@ -392,7 +392,7 @@ The message maps those values to the appropriate translations:
|
||||
|
||||
You can also nest different ICU expressions together, as shown in this example:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-nested" title="src/app/app.component.html">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-nested" header="src/app/app.component.html">
|
||||
</code-example>
|
||||
|
||||
{@a ng-xi18n}
|
||||
@ -413,7 +413,7 @@ By default, the command creates a file named `messages.xlf` in your `src/` folde
|
||||
|
||||
If you don't use the CLI, you have two options:
|
||||
* You can use the `ng-xi18n` tool directly from the `@angular/compiler-cli` package.
|
||||
For more information, see [i18n in the CLI documentation](https://github.com/angular/angular-cli/wiki/xi18n).
|
||||
For more information, see the [`ng xi18n` command documentation](cli/xi18n).
|
||||
* You can use the CLI Webpack plugin `AngularCompilerPlugin` from the `@ngtools/webpack` package.
|
||||
Set the parameters `i18nOutFile` and `i18nOutFormat` to trigger the extraction.
|
||||
For more information, see the [Angular Ahead-of-Time Webpack Plugin documentation](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack).
|
||||
@ -530,7 +530,7 @@ This sample file is easy to translate without a special editor or knowledge of F
|
||||
|
||||
1. Open `messages.fr.xlf` and find the first `<trans-unit>` section:
|
||||
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-hello-before" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-hello-before" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
> This XML element represents the translation of the `<h1>` greeting tag that you marked with the
|
||||
@ -542,16 +542,16 @@ This sample file is easy to translate without a special editor or knowledge of F
|
||||
|
||||
|
||||
2. Duplicate the `<source/>` tag, rename it `target`, and then replace its content with the French
|
||||
greeting. If you were working with a more complex translation, you could use the the information
|
||||
greeting. If you were working with a more complex translation, you could use the information
|
||||
and context provided by the source, description, and meaning elements to guide your selection of
|
||||
the appropriate French translation.
|
||||
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-hello" title="src/locale/messages.fr.xlf (<trans-unit>, after translation)" linenums="false">
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-hello" header="src/locale/messages.fr.xlf (<trans-unit>, after translation)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
3. Translate the other text nodes the same way:
|
||||
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-other-nodes" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
> <code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-other-nodes" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
@ -578,7 +578,7 @@ must be just below the translation unit for the logo.
|
||||
|
||||
To translate a `plural`, translate its ICU format match values:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-plural" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-plural" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You can add or remove plural cases, with each language having its own cardinality. (See
|
||||
@ -589,7 +589,7 @@ You can add or remove plural cases, with each language having its own cardinalit
|
||||
|
||||
Below is the content of our example `select` ICU expression in the component template:
|
||||
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-select" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="i18n/src/app/app.component.html" region="i18n-select" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The extraction tool broke that into two translation units because ICU expressions are extracted
|
||||
@ -600,18 +600,18 @@ In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `
|
||||
Translate the text and move around the placeholder if necessary, but don't remove it. If you remove
|
||||
the placeholder, the ICU expression will not be present in your translated app.
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-select-1" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-select-1" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The second translation unit, immediately below the first one, contains the `select` message.
|
||||
Translate that as well.
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-select-2" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-select-2" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Here they are together, after translation:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-select" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translated-select" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a translate-nested}
|
||||
@ -620,17 +620,17 @@ Here they are together, after translation:
|
||||
A nested expression is similar to the previous examples. As in the previous example, there are
|
||||
two translation units. The first one contains the text outside of the nested expression:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested-1" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested-1" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The second unit contains the complete nested expression:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested-2" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested-2" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
And both together:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested" title="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="translate-nested" header="src/locale/messages.fr.xlf (<trans-unit>)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The entire template translation is complete. The next section describes how to load that translation
|
||||
@ -642,15 +642,15 @@ into the app.
|
||||
The sample app and its translation file are now as follows:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="src/app/app.component.html" path="i18n/src/app/app.component.html">
|
||||
<code-pane header="src/app/app.component.html" path="i18n/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane title="src/app/app.component.ts" path="i18n/src/app/app.component.ts">
|
||||
<code-pane header="src/app/app.component.ts" path="i18n/src/app/app.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="src/app/app.module.ts" path="i18n/src/app/app.module.ts">
|
||||
<code-pane header="src/app/app.module.ts" path="i18n/src/app/app.module.ts">
|
||||
</code-pane>
|
||||
<code-pane title="src/main.ts" path="i18n/doc-files/main.1.ts">
|
||||
<code-pane header="src/main.ts" path="i18n/doc-files/main.1.ts">
|
||||
</code-pane>
|
||||
<code-pane title="src/locale/messages.fr.xlf" path="i18n/doc-files/messages.fr.xlf.html">
|
||||
<code-pane header="src/locale/messages.fr.xlf" path="i18n/doc-files/messages.fr.xlf.html">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
@ -790,12 +790,12 @@ while compiling the app:
|
||||
The Angular `bootstrapModule` method has a second `compilerOptions` parameter that can influence the
|
||||
behavior of the compiler. You can use it to specify the translation providers:
|
||||
|
||||
<code-example path="i18n/doc-files/main.2.ts" title="src/main.ts">
|
||||
<code-example path="i18n/doc-files/main.2.ts" header="src/main.ts">
|
||||
</code-example>
|
||||
|
||||
Then provide the `LOCALE_ID` in the main module:
|
||||
|
||||
<code-example path="i18n/doc-files/app.module.ts" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="i18n/doc-files/app.module.ts" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -826,7 +826,7 @@ If you use the JIT compiler, specify the warning level in the compiler config at
|
||||
the 'MissingTranslationStrategy' property. The example below shows how to set the warning level to
|
||||
error:
|
||||
|
||||
<code-example path="i18n/doc-files/main.3.ts" title="src/main.ts">
|
||||
<code-example path="i18n/doc-files/main.3.ts" header="src/main.ts">
|
||||
</code-example>
|
||||
|
||||
### Build for multiple locales
|
||||
|
@ -98,7 +98,7 @@ placeholder markup in `app.component.html` with a custom nav
|
||||
so you can easily navigate to your modules in the browser:
|
||||
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app.component.html" region="app-component-template" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app.component.html" region="app-component-template" header="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -138,19 +138,19 @@ Each feature module acts as a doorway via the router. In the `AppRoutingModule`,
|
||||
In `AppRoutingModule`, update the `routes` array with the following:
|
||||
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app-routing.module.ts" region="const-routes" title="src/app/app-routing.module.ts" linenums="false">
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app-routing.module.ts" region="const-routes" header="src/app/app-routing.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
The import statements stay the same. The first two paths are the routes to the `CustomersModule` and the `OrdersModule` respectively. Notice that the lazy loading syntax uses `loadChildren` followed by a string that is the path to the module, a hash mark or `#`, and the module’s class name.
|
||||
The import statements stay the same. The first two paths are the routes to the `CustomersModule` and the `OrdersModule` respectively. Notice that the lazy loading syntax uses `loadChildren` followed by a string that is the relative path to the module, a hash mark or `#`, and the module’s class name.
|
||||
|
||||
### Inside the feature module
|
||||
|
||||
Next, take a look at `customers.module.ts`. If you’re using the CLI and following the steps outlined in this page, you don’t have to do anything here. The feature module is like a connector between the `AppRoutingModule` and the feature routing module. The `AppRoutingModule` imports the feature module, `CustomersModule`, and `CustomersModule` in turn imports the `CustomersRoutingModule`.
|
||||
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers.module.ts" region="customers-module" title="src/app/customers/customers.module.ts" linenums="false">
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers.module.ts" region="customers-module" header="src/app/customers/customers.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -163,7 +163,7 @@ The `customers.module.ts` file imports the `CustomersRoutingModule` and `Custome
|
||||
|
||||
The next step is in `customers-routing.module.ts`. First, import the component at the top of the file with the other JavaScript import statements. Then, add the route to `CustomerListComponent`.
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers-routing.module.ts" region="customers-routing-module" title="src/app/customers/customers-routing.module.ts" linenums="false">
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers-routing.module.ts" region="customers-routing-module" header="src/app/customers/customers-routing.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -172,7 +172,7 @@ Notice that the `path` is set to an empty string. This is because the path in `A
|
||||
|
||||
Repeat this last step of importing the `OrdersListComponent` and configuring the Routes array for the `orders-routing.module.ts`:
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/orders/orders-routing.module.ts" region="orders-routing-module-detail" title="src/app/orders/orders-routing.module.ts (excerpt)" linenums="false">
|
||||
<code-example path="lazy-loading-ngmodules/src/app/orders/orders-routing.module.ts" region="orders-routing-module-detail" header="src/app/orders/orders-routing.module.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -23,7 +23,7 @@ Each interface has a single hook method whose name is the interface name prefixe
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit()`
|
||||
that Angular calls shortly after creating the component:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" title="peek-a-boo.component.ts (excerpt)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" header="peek-a-boo.component.ts (excerpt)" linenums="false"></code-example>
|
||||
|
||||
No directive or component will implement all of the lifecycle hooks.
|
||||
Angular only calls a directive/component hook method *if it is defined*.
|
||||
@ -339,13 +339,13 @@ The heroes will never know they're being watched.
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit()` and `ngOnDestroy()` hooks
|
||||
that log messages to the parent via an injected `LoggerService`.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/spy.directive.ts" region="spy-directive" title="src/app/spy.directive.ts" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/spy.directive.ts" region="spy-directive" header="src/app/spy.directive.ts" linenums="false"></code-example>
|
||||
|
||||
You can apply the spy to any native or component element and it'll be initialized and destroyed
|
||||
at the same time as that element.
|
||||
Here it is attached to the repeated hero `<div>`:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/spy.component.html" region="template" title="src/app/spy.component.html" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/spy.component.html" region="template" header="src/app/spy.component.html" linenums="false"></code-example>
|
||||
|
||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||
with an entry in the *Hook Log* as seen here:
|
||||
@ -425,7 +425,7 @@ You risk memory leaks if you neglect to do so.
|
||||
Angular calls its `ngOnChanges()` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
This example monitors the `OnChanges` hook.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="ng-on-changes" title="on-changes.component.ts (excerpt)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="ng-on-changes" header="on-changes.component.ts (excerpt)" linenums="false"></code-example>
|
||||
|
||||
The `ngOnChanges()` method takes an object that maps each changed property name to a
|
||||
[SimpleChange](api/core/SimpleChange) object holding the current and previous property values.
|
||||
@ -433,11 +433,11 @@ This hook iterates over the changed properties and logs them.
|
||||
|
||||
The example component, `OnChangesComponent`, has two input properties: `hero` and `power`.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="inputs" title="src/app/on-changes.component.ts" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="inputs" header="src/app/on-changes.component.ts" linenums="false"></code-example>
|
||||
|
||||
The host `OnChangesParentComponent` binds to them like this:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes-parent.component.html" region="on-changes" title="src/app/on-changes-parent.component.html"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes-parent.component.html" region="on-changes" header="src/app/on-changes-parent.component.html"></code-example>
|
||||
|
||||
Here's the sample in action as the user makes changes.
|
||||
|
||||
@ -468,7 +468,7 @@ Use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch
|
||||
|
||||
The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck()` hook:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/do-check.component.ts" region="ng-do-check" title="DoCheckComponent (ngDoCheck)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/do-check.component.ts" region="ng-do-check" header="DoCheckComponent (ngDoCheck)" linenums="false"></code-example>
|
||||
|
||||
This code inspects certain _values of interest_, capturing and comparing their current state against previous values.
|
||||
It writes a special message to the log when there are no substantive changes to the `hero` or the `power`
|
||||
@ -497,25 +497,25 @@ The *AfterView* sample explores the `AfterViewInit()` and `AfterViewChecked()` h
|
||||
|
||||
Here's a child view that displays a hero's name in an `<input>`:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="child-view" title="ChildComponent" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="child-view" header="ChildComponent" linenums="false"></code-example>
|
||||
|
||||
The `AfterViewComponent` displays this child view *within its template*:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="template" title="AfterViewComponent (template)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="template" header="AfterViewComponent (template)" linenums="false"></code-example>
|
||||
|
||||
The following hooks take action based on changing values *within the child view*,
|
||||
which can only be reached by querying for the child view via the property decorated with
|
||||
[@ViewChild](api/core/ViewChild).
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="hooks" title="AfterViewComponent (class excerpts)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="hooks" header="AfterViewComponent (class excerpts)" linenums="false"></code-example>
|
||||
|
||||
{@a wait-a-tick}
|
||||
|
||||
### Abide by the unidirectional data flow rule
|
||||
The `doSomething()` method updates the screen when the hero name exceeds 10 characters.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="do-something" title="AfterViewComponent (doSomething)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="do-something" header="AfterViewComponent (doSomething)" linenums="false"></code-example>
|
||||
|
||||
Why does the `doSomething()` method wait a tick before updating `comment`?
|
||||
|
||||
@ -559,7 +559,7 @@ Consider this variation on the [previous _AfterView_](guide/lifecycle-hooks#afte
|
||||
This time, instead of including the child view within the template, it imports the content from
|
||||
the `AfterContentComponent`'s parent. Here's the parent's template:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="parent-template" title="AfterContentParentComponent (template excerpt)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="parent-template" header="AfterContentParentComponent (template excerpt)" linenums="false"></code-example>
|
||||
|
||||
Notice that the `<app-child>` tag is tucked between the `<after-content>` tags.
|
||||
Never put content between a component's element tags *unless you intend to project that content
|
||||
@ -567,7 +567,7 @@ into the component*.
|
||||
|
||||
Now look at the component's template:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="template" title="AfterContentComponent (template)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="template" header="AfterContentComponent (template)" linenums="false"></code-example>
|
||||
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
It tells Angular where to insert that content.
|
||||
@ -603,7 +603,7 @@ The following *AfterContent* hooks take action based on changing values in a *co
|
||||
which can only be reached by querying for them via the property decorated with
|
||||
[@ContentChild](api/core/ContentChild).
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="hooks" title="AfterContentComponent (class excerpts)" linenums="false"></code-example>
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="hooks" header="AfterContentComponent (class excerpts)" linenums="false"></code-example>
|
||||
|
||||
{@a no-unidirectional-flow-worries}
|
||||
|
||||
|
@ -480,7 +480,7 @@ You can throw an error or take other remedial action.
|
||||
Certain NgModules, such as `BrowserModule`, implement such a guard.
|
||||
Here is a custom constructor for an NgModule called `CoreModule`.
|
||||
|
||||
<code-example path="ngmodule-faq/src/app/core/core.module.ts" region="ctor" title="src/app/core/core.module.ts (Constructor)" linenums="false">
|
||||
<code-example path="ngmodule-faq/src/app/core/core.module.ts" region="ctor" header="src/app/core/core.module.ts (Constructor)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<hr/>
|
||||
|
@ -29,7 +29,7 @@ JavaScript modules help you namespace, preventing accidental global variables.
|
||||
<!-- KW-- perMisko: let's discuss. This does not answer the question why it is different. Also, last sentence is confusing.-->
|
||||
NgModules are classes decorated with `@NgModule`. The `@NgModule` decorator’s `imports` array tells Angular what other NgModules the current module needs. The modules in the `imports` array are different than JavaScript modules because they are NgModules rather than regular JavaScript modules. Classes with an `@NgModule` decorator are by convention kept in their own files, but what makes them an `NgModule` isn’t being in their own file, like JavaScript modules; it’s the presence of `@NgModule` and its metadata.
|
||||
|
||||
The `AppModule` generated from the Angular CLI demonstrates both kinds of modules in action:
|
||||
The `AppModule` generated from the [Angular CLI](cli) demonstrates both kinds of modules in action:
|
||||
|
||||
```typescript
|
||||
/* These are JavaScript import statements. Angular doesn’t know anything about these. */
|
||||
|
@ -57,9 +57,9 @@ You then import these modules into the root module.
|
||||
|
||||
## The basic NgModule
|
||||
|
||||
The CLI generates the following basic app module when creating a new app.
|
||||
The [Angular CLI](cli) generates the following basic app module when creating a new app.
|
||||
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="whole-ngmodule" title="src/app/app.module.ts" linenums="false">
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="whole-ngmodule" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
At the top are the import statements. The next section is where you configure the `@NgModule` by stating what components and directives belong to it (`declarations`) as well as which other modules it uses (`imports`). This page builds on [Bootstrapping](guide/bootstrapping), which covers the structure of an NgModule in detail. If you need more information on the structure of an `@NgModule`, be sure to read [Bootstrapping](guide/bootstrapping).
|
||||
|
@ -16,7 +16,7 @@ A good example of usage can be found on the [EventEmitter](https://angular.io/ap
|
||||
|
||||
Here is the component definition:
|
||||
|
||||
<code-example path="observables-in-angular/src/main.ts" title="EventEmitter" region="eventemitter"></code-example>
|
||||
<code-example path="observables-in-angular/src/main.ts" header="EventEmitter" region="eventemitter"></code-example>
|
||||
|
||||
## HTTP
|
||||
Angular’s `HttpClient` returns observables from HTTP method calls. For instance, `http.get(‘/api’)` returns an observable. This provides several advantages over promise-based HTTP APIs:
|
||||
@ -32,20 +32,20 @@ The [AsyncPipe](https://angular.io/api/common/AsyncPipe) subscribes to an observ
|
||||
|
||||
The following example binds the `time` observable to the component's view. The observable continuously updates the view with the current time.
|
||||
|
||||
<code-example path="observables-in-angular/src/main.ts" title="Using async pipe" region="pipe"></code-example>
|
||||
<code-example path="observables-in-angular/src/main.ts" header="Using async pipe" region="pipe"></code-example>
|
||||
|
||||
## Router
|
||||
|
||||
[`Router.events`](https://angular.io/api/router/Router#events) provides events as observables. You can use the `filter()` operator from RxJS to look for events of interest, and subscribe to them in order to make decisions based on the sequence of events in the navigation process. Here's an example:
|
||||
|
||||
<code-example path="observables-in-angular/src/main.ts" title="Router events" region="router"></code-example>
|
||||
<code-example path="observables-in-angular/src/main.ts" header="Router events" region="router"></code-example>
|
||||
|
||||
The [ActivatedRoute](https://angular.io/api/router/ActivatedRoute) is an injected router service that makes use of observables to get information about a route path and parameters. For example, `ActivateRoute.url` contains an observable that reports the route path or paths. Here's an example:
|
||||
|
||||
<code-example path="observables-in-angular/src/main.ts" title="ActivatedRoute" region="activated_route"></code-example>
|
||||
<code-example path="observables-in-angular/src/main.ts" header="ActivatedRoute" region="activated_route"></code-example>
|
||||
|
||||
## Reactive forms
|
||||
|
||||
Reactive forms have properties that use observables to monitor form control values. The [`FormControl`](https://angular.io/api/forms/FormControl) properties `valueChanges` and `statusChanges` contain observables that raise change events. Subscribing to an observable form-control property is a way of triggering application logic within the component class. For example:
|
||||
|
||||
<code-example path="observables-in-angular/src/main.ts" title="Reactive forms" region="forms"></code-example>
|
||||
<code-example path="observables-in-angular/src/main.ts" header="Reactive forms" region="forms"></code-example>
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
# Observables
|
||||
|
||||
Observables provide support for passing messages between publishers and subscribers in your application. Observables offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values.
|
||||
@ -16,7 +17,7 @@ To execute the observable you have created and begin receiving notifications, yo
|
||||
|
||||
Here's an example that demonstrates the basic usage model by showing how an observable could be used to provide geolocation updates.
|
||||
|
||||
<code-example path="observables/src/geolocation.ts" title="Observe geolocation updates"></code-example>
|
||||
<code-example path="observables/src/geolocation.ts" header="Observe geolocation updates"></code-example>
|
||||
|
||||
## Defining observers
|
||||
|
||||
@ -36,10 +37,10 @@ An `Observable` instance begins publishing values only when someone subscribes t
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In order to show how subscribing works, we need to create a new observable. There is a constructor that you use to create new instances, but for illustration, we can use some static methods on the `Observable` class that create simple observables of frequently used types:
|
||||
In order to show how subscribing works, we need to create a new observable. There is a constructor that you use to create new instances, but for illustration, we can use some methods from the RxJS library that create simple observables of frequently used types:
|
||||
|
||||
* `Observable.of(...items)`—Returns an `Observable` instance that synchronously delivers the values provided as arguments.
|
||||
* `Observable.from(iterable)`—Converts its argument to an `Observable` instance. This method is commonly used to convert an array to an observable.
|
||||
* `of(...items)`—Returns an `Observable` instance that synchronously delivers the values provided as arguments.
|
||||
* `from(iterable)`—Converts its argument to an `Observable` instance. This method is commonly used to convert an array to an observable.
|
||||
|
||||
</div>
|
||||
|
||||
@ -48,11 +49,11 @@ Here's an example of creating and subscribing to a simple observable, with an ob
|
||||
<code-example
|
||||
path="observables/src/subscribing.ts"
|
||||
region="observer"
|
||||
title="Subscribe using observer"></code-example>
|
||||
header="Subscribe using observer"></code-example>
|
||||
|
||||
Alternatively, the `subscribe()` method can accept callback function definitions in line, for `next`, `error`, and `complete` handlers. For example, the following `subscribe()` call is the same as the one that specifies the predefined observer:
|
||||
|
||||
<code-example path="observables/src/subscribing.ts" region="sub_fn" title="Subscribe with positional arguments"></code-example>
|
||||
<code-example path="observables/src/subscribing.ts" region="sub_fn" header="Subscribe with positional arguments"></code-example>
|
||||
|
||||
In either case, a `next` handler is required. The `error` and `complete` handlers are optional.
|
||||
|
||||
@ -62,17 +63,17 @@ Note that a `next()` function could receive, for instance, message strings, or e
|
||||
|
||||
Use the `Observable` constructor to create an observable stream of any type. The constructor takes as its argument the subscriber function to run when the observable’s `subscribe()` method executes. A subscriber function receives an `Observer` object, and can publish values to the observer's `next()` method.
|
||||
|
||||
For example, to create an observable equivalent to the `Observable.of(1, 2, 3)` above, you could do something like this:
|
||||
For example, to create an observable equivalent to the `of(1, 2, 3)` above, you could do something like this:
|
||||
|
||||
<code-example path="observables/src/creating.ts" region="subscriber" title="Create observable with constructor"></code-example>
|
||||
<code-example path="observables/src/creating.ts" region="subscriber" header="Create observable with constructor"></code-example>
|
||||
|
||||
To take this example a little further, we can create an observable that publishes events. In this example, the subscriber function is defined inline.
|
||||
|
||||
<code-example path="observables/src/creating.ts" region="fromevent" title="Create with custom fromEvent function"></code-example>
|
||||
<code-example path="observables/src/creating.ts" region="fromevent" header="Create with custom fromEvent function"></code-example>
|
||||
|
||||
Now you can use this function to create an observable that publishes keydown events:
|
||||
|
||||
<code-example path="observables/src/creating.ts" region="fromevent_use" title="Use custom fromEvent function"></code-example>
|
||||
<code-example path="observables/src/creating.ts" region="fromevent_use" header="Use custom fromEvent function"></code-example>
|
||||
|
||||
## Multicasting
|
||||
|
||||
@ -86,15 +87,15 @@ When creating an observable you should determine how you want that observable to
|
||||
|
||||
Let’s look at an example that counts from 1 to 3, with a one-second delay after each number emitted.
|
||||
|
||||
<code-example path="observables/src/multicasting.ts" region="delay_sequence" title="Create a delayed sequence"></code-example>
|
||||
<code-example path="observables/src/multicasting.ts" region="delay_sequence" header="Create a delayed sequence"></code-example>
|
||||
|
||||
Notice that if you subscribe twice, there will be two separate streams, each emitting values every second. It looks something like this:
|
||||
|
||||
<code-example path="observables/src/multicasting.ts" region="subscribe_twice" title="Two subscriptions"></code-example>
|
||||
<code-example path="observables/src/multicasting.ts" region="subscribe_twice" header="Two subscriptions"></code-example>
|
||||
|
||||
Changing the observable to be multicasting could look something like this:
|
||||
|
||||
<code-example path="observables/src/multicasting.ts" region="multicast_sequence" title="Create a multicast subscriber"></code-example>
|
||||
<code-example path="observables/src/multicasting.ts" region="multicast_sequence" header="Create a multicast subscriber"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Multicasting observables take a bit more setup, but they can be useful for certain applications. Later we will look at tools that simplify the process of multicasting, allowing you to take any observable and make it multicasting.
|
||||
|
@ -26,7 +26,7 @@ In this page, you'll use pipes to transform a component's birthday property into
|
||||
a human-friendly date.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday1.component.ts" title="src/app/hero-birthday1.component.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/hero-birthday1.component.ts" header="src/app/hero-birthday1.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -35,7 +35,7 @@ a human-friendly date.
|
||||
Focus on the component's template.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="hero-birthday-template" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="pipes/src/app/app.component.html" region="hero-birthday-template" header="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -79,7 +79,7 @@ Modify the birthday template to give the date pipe a format parameter.
|
||||
After formatting the hero's April 15th birthday, it renders as **<samp>04/15/88</samp>**:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="format-birthday" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="pipes/src/app/app.component.html" region="format-birthday" header="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -95,7 +95,7 @@ Write a second component that *binds* the pipe's format parameter
|
||||
to the component's `format` property. Here's the template for that component:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="template" title="src/app/hero-birthday2.component.ts (template)" linenums="false">
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="template" header="src/app/hero-birthday2.component.ts (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -106,7 +106,7 @@ That method toggles the component's `format` property between a short form
|
||||
(`'shortDate'`) and a longer form (`'fullDate'`).
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="class" title="src/app/hero-birthday2.component.ts (class)" linenums="false">
|
||||
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="class" header="src/app/hero-birthday2.component.ts (class)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -143,7 +143,7 @@ the birthday is chained to the `DatePipe` and on to the `UpperCasePipe`.
|
||||
The birthday displays as **<samp>APR 15, 1988</samp>**.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-birthday" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-birthday" header="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -153,7 +153,7 @@ This example—which displays **<samp>FRIDAY, APRIL 15, 1988</samp>**—
|
||||
the same pipes as above, but passes in a parameter to `date` as well.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-parameter-birthday" title="src/app/app.component.html" linenums="false">
|
||||
<code-example path="pipes/src/app/app.component.html" region="chained-parameter-birthday" header="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -166,7 +166,7 @@ You can write your own custom pipes.
|
||||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/exponential-strength.pipe.ts" title="src/app/exponential-strength.pipe.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/exponential-strength.pipe.ts" header="src/app/exponential-strength.pipe.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -200,7 +200,7 @@ Technically, it's optional; Angular looks for and executes the `transform` metho
|
||||
|
||||
Now you need a component to demonstrate the pipe.
|
||||
|
||||
<code-example path="pipes/src/app/power-booster.component.ts" title="src/app/power-booster.component.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/power-booster.component.ts" header="src/app/power-booster.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<figure>
|
||||
@ -223,7 +223,7 @@ Note the following:
|
||||
|
||||
You must register custom pipes.
|
||||
If you don't, Angular reports an error.
|
||||
Angular CLI's generator registers the pipe automatically.
|
||||
The [Angular CLI's](cli) generator registers the pipe automatically.
|
||||
|
||||
|
||||
</div>
|
||||
@ -240,7 +240,7 @@ Upgrade the example to a "Power Boost Calculator" that combines
|
||||
your pipe and two-way data binding with `ngModel`.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/power-boost-calculator.component.ts" title="src/app/power-boost-calculator.component.ts">
|
||||
<code-example path="pipes/src/app/power-boost-calculator.component.ts" header="src/app/power-boost-calculator.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -270,7 +270,7 @@ In the next example, the component uses the default, aggressive change detection
|
||||
its display of every hero in the `heroes` array. Here's the template:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-1" title="src/app/flying-heroes.component.html (v1)" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-1" header="src/app/flying-heroes.component.html (v1)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -278,7 +278,7 @@ its display of every hero in the `heroes` array. Here's the template:
|
||||
|
||||
The companion component class provides heroes, adds heroes into the array, and can reset the array.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="v1" title="src/app/flying-heroes.component.ts (v1)" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="v1" header="src/app/flying-heroes.component.ts (v1)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -292,7 +292,7 @@ If you added the ability to remove or change a hero, Angular would detect those
|
||||
|
||||
Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-flying-heroes" title="src/app/flying-heroes.component.html (flyers)" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-flying-heroes" header="src/app/flying-heroes.component.html (flyers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -300,7 +300,7 @@ Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroe
|
||||
|
||||
Here's the `FlyingHeroesPipe` implementation, which follows the pattern for custom pipes described earlier.
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pure" title="src/app/flying-heroes.pipe.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pure" header="src/app/flying-heroes.pipe.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -314,7 +314,7 @@ It's just using a different change-detection algorithm that ignores changes to t
|
||||
|
||||
Notice how a hero is added:
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="push" title="src/app/flying-heroes.component.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.component.ts" region="push" header="src/app/flying-heroes.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -364,7 +364,7 @@ You make a pipe impure by setting its pure flag to false. You could make the `Fl
|
||||
impure like this:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pipe-decorator" title="src/app/flying-heroes.pipe.ts" linenums="false">
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pipe-decorator" header="src/app/flying-heroes.pipe.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -422,11 +422,11 @@ The complete implementation is as follows:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="FlyingHeroesImpurePipe" path="pipes/src/app/flying-heroes.pipe.ts" region="impure">
|
||||
<code-pane header="FlyingHeroesImpurePipe" path="pipes/src/app/flying-heroes.pipe.ts" region="impure">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="FlyingHeroesPipe" path="pipes/src/app/flying-heroes.pipe.ts" region="pure">
|
||||
<code-pane header="FlyingHeroesPipe" path="pipes/src/app/flying-heroes.pipe.ts" region="pure">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -440,7 +440,7 @@ The only difference is the `pure` flag in the pipe metadata.
|
||||
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" linenums="false" title="src/app/flying-heroes.pipe.ts (filter)" region="filter">
|
||||
<code-example path="pipes/src/app/flying-heroes.pipe.ts" linenums="false" header="src/app/flying-heroes.pipe.ts (filter)" region="filter">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -449,7 +449,7 @@ This is a good candidate for an impure pipe because the `transform` function is
|
||||
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/flying-heroes-impure.component.html" linenums="false" title="src/app/flying-heroes-impure.component.html (excerpt)" region="template-flying-heroes">
|
||||
<code-example path="pipes/src/app/flying-heroes-impure.component.html" linenums="false" header="src/app/flying-heroes-impure.component.html (excerpt)" region="template-flying-heroes">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -476,7 +476,7 @@ This next example binds an `Observable` of message strings
|
||||
(`message$`) to a view with the `async` pipe.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-async-message.component.ts" title="src/app/hero-async-message.component.ts">
|
||||
<code-example path="pipes/src/app/hero-async-message.component.ts" header="src/app/hero-async-message.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -499,7 +499,7 @@ In the following code, the pipe only calls the server when the request URL chang
|
||||
The code uses the [Angular http](guide/http) client to retrieve data:
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/fetch-json.pipe.ts" title="src/app/fetch-json.pipe.ts">
|
||||
<code-example path="pipes/src/app/fetch-json.pipe.ts" header="src/app/fetch-json.pipe.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -509,7 +509,7 @@ Now demonstrate it in a harness component whose template defines two bindings to
|
||||
both requesting the heroes from the `heroes.json` file.
|
||||
|
||||
|
||||
<code-example path="pipes/src/app/hero-list.component.ts" title="src/app/hero-list.component.ts">
|
||||
<code-example path="pipes/src/app/hero-list.component.ts" header="src/app/hero-list.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -14,10 +14,10 @@ Observables can simplify the implementation of type-ahead suggestions. Typically
|
||||
|
||||
Writing this in full JavaScript can be quite involved. With observables, you can use a simple series of RxJS operators:
|
||||
|
||||
<code-example path="practical-observable-usage/src/typeahead.ts" title="Typeahead"></code-example>
|
||||
<code-example path="practical-observable-usage/src/typeahead.ts" header="Typeahead"></code-example>
|
||||
|
||||
## Exponential backoff
|
||||
|
||||
Exponential backoff is a technique in which you retry an API after failure, making the time in between retries longer after each consecutive failure, with a maximum number of retries after which the request is considered to have failed. This can be quite complex to implement with promises and other methods of tracking AJAX calls. With observables, it is very easy:
|
||||
|
||||
<code-example path="practical-observable-usage/src/backoff.ts" title="Exponential backoff"></code-example>
|
||||
<code-example path="practical-observable-usage/src/backoff.ts" header="Exponential backoff"></code-example>
|
||||
|
@ -13,7 +13,7 @@ A provider is an instruction to the DI system on how to obtain a value for a dep
|
||||
|
||||
## Providing a service
|
||||
|
||||
If you already have a CLI generated app, create a service using the following CLI command in the root project directory. Replace _User_ with the name of your service.
|
||||
If you already have an app that was created with the [Angular CLI](cli), you can create a service using the [`ng generate`](cli/generate) CLI command in the root project directory. Replace _User_ with the name of your service.
|
||||
|
||||
```sh
|
||||
ng generate service User
|
||||
@ -21,7 +21,7 @@ ng generate service User
|
||||
|
||||
This command creates the following `UserService` skeleton:
|
||||
|
||||
<code-example path="providers/src/app/user.service.0.ts" title="src/app/user.service.0.ts" linenums="false"> </code-example>
|
||||
<code-example path="providers/src/app/user.service.0.ts" header="src/app/user.service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
You can now inject `UserService` anywhere in your application.
|
||||
|
||||
@ -38,15 +38,15 @@ You should always provide your service in the root injector unless there is a ca
|
||||
|
||||
It's also possible to specify that a service should be provided in a particular `@NgModule`. For example, if you don't want `UserService` to be available to applications unless they import a `UserModule` you've created, you can specify that the service should be provided in the module:
|
||||
|
||||
<code-example path="providers/src/app/user.service.1.ts" title="src/app/user.service.1.ts" linenums="false"> </code-example>
|
||||
<code-example path="providers/src/app/user.service.1.ts" header="src/app/user.service.1.ts" linenums="false"> </code-example>
|
||||
|
||||
The example above shows the preferred way to provide a service in a module. This method is preferred because it enables tree-shaking of the service if nothing injects it. If it's not possible to specify in the service which module should provide it, you can also declare a provider for the service within the module:
|
||||
|
||||
<code-example path="providers/src/app/user.module.ts" title="src/app/user.module.ts" linenums="false"> </code-example>
|
||||
<code-example path="providers/src/app/user.module.ts" header="src/app/user.module.ts" linenums="false"> </code-example>
|
||||
|
||||
## Limiting provider scope by lazy loading modules
|
||||
|
||||
In the basic CLI generated app, modules are eagerly loaded which means that they are all loaded when the app launches. Angular uses an injector system to make things available between modules. In an eagerly loaded app, the root application injector makes all of the providers in all of the modules available throughout the app.
|
||||
In the basic CLI-generated app, modules are eagerly loaded which means that they are all loaded when the app launches. Angular uses an injector system to make things available between modules. In an eagerly loaded app, the root application injector makes all of the providers in all of the modules available throughout the app.
|
||||
|
||||
This behavior necessarily changes when you use lazy loading. Lazy loading is when you load modules only when you need them; for example, when routing. They aren’t loaded right away like with eagerly loaded modules. This means that any services listed in their provider arrays aren’t available because the root injector doesn’t know about these modules.
|
||||
|
||||
@ -67,7 +67,7 @@ method is helpful for when you want to eagerly load a module that needs a servic
|
||||
Providing a service in the component limits the service only to that component (other components in
|
||||
the same module can’t access it.)
|
||||
|
||||
<code-example path="providers/src/app/app.component.ts" region="component-providers" title="src/app/app.component.ts" linenums="false">
|
||||
<code-example path="providers/src/app/app.component.ts" region="component-providers" header="src/app/app.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
|
@ -1,46 +1,51 @@
|
||||
# QuickStart
|
||||
# Getting started
|
||||
|
||||
Good tools make application development quicker and easier to maintain than
|
||||
if you did everything by hand.
|
||||
Welcome to Angular! Angular helps you build modern applications for the web, mobile, or desktop.
|
||||
|
||||
The [**Angular CLI**](https://cli.angular.io/) is a **_command line interface_** tool
|
||||
that can create a project, add files, and perform a variety of ongoing development tasks such
|
||||
as testing, bundling, and deployment.
|
||||
|
||||
The goal in this guide is to build and run a simple Angular
|
||||
application in TypeScript, using the Angular CLI
|
||||
while adhering to the [Style Guide](guide/styleguide) recommendations that
|
||||
This guide shows you how to build and run a simple Angular
|
||||
app. You'll use the [Angular CLI tool](cli "CLI command reference") to accelerate development,
|
||||
while adhering to the [Style Guide](guide/styleguide "Angular style guide") recommendations that
|
||||
benefit _every_ Angular project.
|
||||
|
||||
By the end of the chapter, you'll have a basic understanding of development with the CLI
|
||||
and a foundation for both these documentation samples and for real world applications.
|
||||
|
||||
And you can also <a href="generated/zips/cli-quickstart/cli-quickstart.zip" target="_blank">download the example.</a>
|
||||
|
||||
<h2 id='devenv'>
|
||||
Step 1. Set up the Development Environment
|
||||
</h2>
|
||||
This guide takes less than 30 minutes to complete.
|
||||
At the end of this guide—as part of final code review—there is a link to download a copy of the final application code. (If you don't execute the commands in this guide, you can still download the final application code.)
|
||||
|
||||
|
||||
{@a devenv}
|
||||
{@a prerequisites}
|
||||
## Prerequisites
|
||||
|
||||
You need to set up your development environment before you can do anything.
|
||||
Before you begin, make sure your development environment includes `Node.js®` and an npm package manager.
|
||||
|
||||
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
||||
if they are not already on your machine.
|
||||
{@a nodejs}
|
||||
### Node.js
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Angular requires `Node.js` version 8.x or 10.x.
|
||||
|
||||
* To check your version, run `node -v` in a terminal/console window.
|
||||
|
||||
* To get `Node.js`, go to [nodejs.org](https://nodejs.org "Nodejs.org").
|
||||
|
||||
{@a npm}
|
||||
### npm package manager
|
||||
|
||||
Angular, the Angular CLI, and Angular apps depend on features and functionality provided by libraries that are available as [npm packages](https://docs.npmjs.com/getting-started/what-is-npm). To download and install npm packages, you must have an npm package manager.
|
||||
|
||||
This Quick Start uses the [npm client](https://docs.npmjs.com/cli/install) command line interface, which is installed with `Node.js` by default.
|
||||
|
||||
To check that you have the npm client installed, run `npm -v` in a terminal/console window.
|
||||
|
||||
|
||||
{@a install-cli}
|
||||
|
||||
**Verify that you are running at least Node.js version `8.x` or greater and npm version `5.x` or greater**
|
||||
by running `node -v` and `npm -v` in a terminal/console window.
|
||||
Older versions produce errors, but newer versions are fine.
|
||||
## Step 1: Install the Angular CLI
|
||||
|
||||
</div>
|
||||
You use the Angular CLI
|
||||
to create projects, generate application and library code, and perform a variety of ongoing development tasks such as testing, bundling, and deployment.
|
||||
|
||||
Install the Angular CLI globally.
|
||||
|
||||
|
||||
Then install the [Angular CLI](https://github.com/angular/angular-cli) globally.
|
||||
To install the CLI using `npm`, open a terminal/console window and enter the following command:
|
||||
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
@ -50,592 +55,130 @@ Then install the [Angular CLI](https://github.com/angular/angular-cli) globally.
|
||||
|
||||
|
||||
|
||||
{@a create-proj}
|
||||
|
||||
<h2 id='create-proj'>
|
||||
Step 2. Create a new project
|
||||
</h2>
|
||||
## Step 2: Create a workspace and initial application
|
||||
|
||||
You develop apps in the context of an Angular [**workspace**](guide/glossary#workspace). A workspace contains the files for one or more [**projects**](guide/glossary/#project). A project is the set of files that comprise an app, a library, or end-to-end (e2e) tests.
|
||||
|
||||
To create a new workspace and initial app project:
|
||||
|
||||
Open a terminal window.
|
||||
1. Run the CLI command `ng new` and provide the name `my-app`, as shown here:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng new my-app
|
||||
|
||||
Generate a new project and default app by running the following command:
|
||||
</code-example>
|
||||
|
||||
2. The `ng new` command prompts you for information about features to include in the initial app project. Accept the defaults by pressing the Enter or Return key.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng new my-app
|
||||
The Angular CLI installs the necessary Angular npm packages and other dependencies. This can take a few minutes.
|
||||
|
||||
</code-example>
|
||||
It also creates the following workspace and starter project files:
|
||||
|
||||
* A new workspace, with a root folder named `my-app`
|
||||
* An initial skeleton app project, also called `my-app` (in the `src` subfolder)
|
||||
* An end-to-end test project (in the `e2e` subfolder)
|
||||
* Related configuration files
|
||||
|
||||
The Angular CLI installs the necessary npm packages, creates the project files, and populates the project with a simple default app. This can take some time.
|
||||
The initial app project contains a simple Welcome app, ready to run.
|
||||
|
||||
{@a serve}
|
||||
|
||||
## Step 3: Serve the application
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Angular includes a server, so that you can easily build and serve your app locally.
|
||||
|
||||
1. Go to the workspace folder (`my-app`).
|
||||
|
||||
|
||||
You can add pre-packaged functionality to a new project by using the `ng add` command. The `ng add` command transforms a project by applying the schematics in the specified package.
|
||||
For more information, see the [Angular CLI documentation.](https://github.com/angular/angular-cli/wiki/add "Angular CLI documentation")
|
||||
|
||||
Angular Material provides schematics for typical app layouts.
|
||||
See the [Angular Material documentation](https://material.angular.io/guides "Angular Material documentation") for details.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h2 id='serve'>
|
||||
Step 3: Serve the application
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
Go to the project directory and launch the server.
|
||||
|
||||
1. Launch the server by using the CLI command `ng serve`, with the `--open` option.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
cd my-app
|
||||
ng serve --open
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `ng serve` command launches the server, watches your files,
|
||||
and rebuilds the app as you make changes to those files.
|
||||
|
||||
Using the `--open` (or just `-o`) option will automatically open your browser
|
||||
on `http://localhost:4200/`.
|
||||
The `--open` (or just `-o`) option automatically opens your browser
|
||||
to `http://localhost:4200/`.
|
||||
|
||||
Your app greets you with a message:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src='generated/images/guide/cli-quickstart/app-works.png' alt="The app works!">
|
||||
<img src='generated/images/guide/cli-quickstart/app-works.png' alt="Welcome to my-app!">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
{@a first-component}
|
||||
|
||||
<h2 id='first-component'>
|
||||
Step 4: Edit your first Angular component
|
||||
</h2>
|
||||
## Step 4: Edit your first Angular component
|
||||
|
||||
[**_Components_**](guide/glossary#component) are the fundamental building blocks of Angular applications.
|
||||
They display data on the screen, listen for user input, and take action based on that input.
|
||||
|
||||
As part of the initial app, the CLI created the first Angular component for you. It is the _root component_, and it is named `app-root`.
|
||||
|
||||
The CLI created the first Angular component for you.
|
||||
This is the _root component_ and it is named `app-root`.
|
||||
You can find it in `./src/app/app.component.ts`.
|
||||
1. Open `./src/app/app.component.ts`.
|
||||
|
||||
2. Change the `title` property from `'my-app'` to `'My First Angular App'`.
|
||||
|
||||
Open the component file and change the `title` property from `'app'` to `'My First Angular App!'`.
|
||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="component" header="src/app/app.component.ts" linenums="false"></code-example>
|
||||
|
||||
The browser reloads automatically with the revised title. That's nice, but it could look better.
|
||||
|
||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false"></code-example>
|
||||
|
||||
|
||||
|
||||
The browser reloads automatically with the revised title. That's nice, but it could look better.
|
||||
|
||||
Open `src/app/app.component.css` and give the component some style.
|
||||
|
||||
|
||||
<code-example path="cli-quickstart/src/app/app.component.css" title="src/app/app.component.css" linenums="false"></code-example>
|
||||
3. Open `./src/app/app.component.css` and give the component some style.
|
||||
|
||||
<code-example path="cli-quickstart/src/app/app.component.css" header="src/app/app.component.css" linenums="false"></code-example>
|
||||
|
||||
Looking good!
|
||||
|
||||
<figure>
|
||||
<img src='generated/images/guide/cli-quickstart/my-first-app.png' alt="Output of QuickStart app">
|
||||
<img src='generated/images/guide/cli-quickstart/my-first-app.png' alt="Output of Getting Started app">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Looking good!
|
||||
|
||||
{@a project-file-review}
|
||||
|
||||
## Final code review
|
||||
|
||||
## What's next?
|
||||
That's about all you'd expect to do in a "Hello, World" app.
|
||||
You can <a href="generated/zips/cli-quickstart/cli-quickstart.zip" target="_blank">download an example</a> of the app that you created in this Getting Started guide.
|
||||
|
||||
You're ready to take the [Tour of Heroes Tutorial](tutorial) and build
|
||||
a small application that demonstrates the great things you can build with Angular.
|
||||
|
||||
Or you can stick around a bit longer to learn about the files in your brand new project.
|
||||
|
||||
|
||||
|
||||
## Project file review
|
||||
|
||||
An Angular CLI project is the foundation for both quick experiments and enterprise solutions.
|
||||
|
||||
The first file you should check out is `README.md`.
|
||||
It has some basic information on how to use CLI commands.
|
||||
Whenever you want to know more about how Angular CLI works make sure to visit
|
||||
[the Angular CLI repository](https://github.com/angular/angular-cli) and
|
||||
[Wiki](https://github.com/angular/angular-cli/wiki).
|
||||
|
||||
Some of the generated files might be unfamiliar to you.
|
||||
|
||||
|
||||
|
||||
### The `src` folder
|
||||
Your app lives in the `src` folder.
|
||||
All Angular components, templates, styles, images, and anything else your app needs go here.
|
||||
Any files outside of this folder are meant to support building your app.
|
||||
|
||||
|
||||
<div class='filetree'>
|
||||
<div class='file'>src</div>
|
||||
<div class='children'>
|
||||
<div class='file'>app</div>
|
||||
<div class='children'>
|
||||
<div class='file'>app.component.css</div>
|
||||
<div class='file'>app.component.html</div>
|
||||
<div class="file">app.component.spec.ts</div>
|
||||
<div class="file">app.component.ts</div>
|
||||
<div class="file">app.module.ts</div>
|
||||
</div>
|
||||
<div class="file">assets</div>
|
||||
<div class='children'>
|
||||
<div class="file">.gitkeep</div>
|
||||
</div>
|
||||
<div class="file">environments</div>
|
||||
<div class='children'>
|
||||
<div class="file">environment.prod.ts</div>
|
||||
<div class="file">environment.ts</div>
|
||||
</div>
|
||||
<div class="file">browserslist</div>
|
||||
<div class="file">favicon.ico</div>
|
||||
<div class="file">index.html</div>
|
||||
<div class="file">karma.conf.js</div>
|
||||
<div class="file">main.ts</div>
|
||||
<div class="file">polyfills.ts</div>
|
||||
<div class="file">styles.css</div>
|
||||
<div class="file">test.ts</div>
|
||||
<div class="file">tsconfig.app.json</div>
|
||||
<div class="file">tsconfig.spec.json</div>
|
||||
<div class="file">tslint.json</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<table width="100%">
|
||||
<col width="20%">
|
||||
</col>
|
||||
<col width="80%">
|
||||
</col>
|
||||
<tr>
|
||||
<th>
|
||||
File
|
||||
</th>
|
||||
<th>
|
||||
Purpose
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`app/app.component.{ts,html,css,spec.ts}`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Defines the `AppComponent` along with an HTML template, CSS stylesheet, and a unit test.
|
||||
It is the **root** component of what will become a tree of nested components
|
||||
as the application evolves.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`app/app.module.ts`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Defines `AppModule`, the [root module](guide/bootstrapping "AppModule: the root module") that tells Angular how to assemble the application.
|
||||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`assets/*`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
A folder where you can put images and anything else to be copied wholesale
|
||||
when you build your application.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`environments/*`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
This folder contains one file for each of your destination environments,
|
||||
each exporting simple configuration variables to use in your application.
|
||||
The files are replaced on-the-fly when you build your app.
|
||||
You might use a different API endpoint for development than you do for production
|
||||
or maybe different analytics tokens.
|
||||
You might even use some mock services.
|
||||
Either way, the CLI has you covered.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`browserslist`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
A configuration file to share [target browsers](https://github.com/browserslist/browserslist) between different front-end tools.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`favicon.ico`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Every site wants to look good on the bookmark bar.
|
||||
Get started with your very own Angular icon.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`index.html`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The main HTML page that is served when someone visits your site.
|
||||
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
|
||||
never need to add any `<script>` or `<link>` tags here manually.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`karma.conf.js`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Unit test configuration for the [Karma test runner](https://karma-runner.github.io),
|
||||
used when running `ng test`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`main.ts`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The main entry point for your app.
|
||||
Compiles the application with the [JIT compiler](guide/glossary#jit)
|
||||
and bootstraps the application's root module (`AppModule`) to run in the browser.
|
||||
You can also use the [AOT compiler](guide/aot-compiler)
|
||||
without changing any code by appending the`--aot` flag to the `ng build` and `ng serve` commands.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`polyfills.ts`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Different browsers have different levels of support of the web standards.
|
||||
Polyfills help normalize those differences.
|
||||
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
|
||||
the [Browser Support guide](guide/browser-support) for more information.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`styles.css`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Your global styles go here.
|
||||
Most of the time you'll want to have local styles in your components for easier maintenance,
|
||||
but styles that affect all of your app need to be in a central place.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`test.ts`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
This is the main entry point for your unit tests.
|
||||
It has some custom configuration that might be unfamiliar, but it's not something you'll
|
||||
need to edit.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`tsconfig.{app|spec}.json`
|
||||
</td>
|
||||
<td>
|
||||
|
||||
TypeScript compiler configuration for the Angular app (`tsconfig.app.json`)
|
||||
and for the unit tests (`tsconfig.spec.json`).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`tslint.json`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Additional Linting configuration for [TSLint](https://palantir.github.io/tslint/) together with
|
||||
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
||||
Linting helps keep your code style consistent.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### The root folder
|
||||
|
||||
The `src/` folder is just one of the items inside the project's root folder.
|
||||
Other files help you build, test, maintain, document, and deploy the app.
|
||||
These files go in the root folder next to `src/`.
|
||||
|
||||
|
||||
<div class='filetree'>
|
||||
<div class="file">my-app</div>
|
||||
<div class='children'>
|
||||
<div class="file">e2e</div>
|
||||
<div class='children'>
|
||||
<div class="file">src</div>
|
||||
<div class='children'>
|
||||
<div class="file">app.e2e-spec.ts</div>
|
||||
<div class="file">app.po.ts</div>
|
||||
</div>
|
||||
<div class="file">tsconfig.e2e.json</div>
|
||||
<div class="file">protractor.conf.js</div>
|
||||
</div>
|
||||
<div class="file">node_modules/...</div>
|
||||
<div class="file">src/...</div>
|
||||
<div class='children'>
|
||||
<div class="file">karma.conf.js</div>
|
||||
</div>
|
||||
<div class="file">.editorconfig</div>
|
||||
<div class="file">.gitignore</div>
|
||||
<div class="file">angular.json</div>
|
||||
<div class="file">package.json</div>
|
||||
<div class="file">README.md</div>
|
||||
<div class="file">tsconfig.json</div>
|
||||
<div class="file">tslint.json</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<table width="100%">
|
||||
<col width="20%">
|
||||
</col>
|
||||
<col width="80%">
|
||||
</col>
|
||||
<tr>
|
||||
<th>
|
||||
File
|
||||
</th>
|
||||
<th>
|
||||
Purpose
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`e2e/`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Inside `e2e/` live the end-to-end tests.
|
||||
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
||||
just so happens to test your main app.
|
||||
That's also why they have their own `tsconfig.e2e.json`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`node_modules/`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`Node.js` creates this folder and puts all third party modules listed in
|
||||
`package.json` inside of it.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`.editorconfig`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Simple configuration for your editor to make sure everyone that uses your project
|
||||
has the same basic configuration.
|
||||
Most editors support an `.editorconfig` file.
|
||||
See http://editorconfig.org for more information.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`.gitignore`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Git configuration to make sure autogenerated files are not committed to source control.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`angular.json`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Configuration for Angular CLI.
|
||||
In this file you can set several defaults and also configure what files are included
|
||||
when your project is built.
|
||||
Check out the official documentation if you want to know more.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`package.json`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`npm` configuration listing the third party packages your project uses.
|
||||
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`protractor.conf.js`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
End-to-end test configuration for [Protractor](http://www.protractortest.org/),
|
||||
used when running `ng e2e`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`README.md`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Basic documentation for your project, pre-filled with CLI command information.
|
||||
Make sure to enhance it with project documentation so that anyone
|
||||
checking out the repo can build your app!
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`tsconfig.json`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
TypeScript compiler configuration for your IDE to pick up and give you helpful tooling.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`tslint.json`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Linting configuration for [TSLint](https://palantir.github.io/tslint/) together with
|
||||
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
||||
Linting helps keep your code style consistent.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
### Next Step
|
||||
**Tip:** Most Angular guides include links to download example files and run live examples in [Stackblitz](http://www.stackblitz.com), so that you can see Angular concepts and code in action.
|
||||
|
||||
If you're new to Angular, continue with the
|
||||
[tutorial](tutorial "Tour of Heroes tutorial").
|
||||
You can skip the "Setup" step since you're already using the Angular CLI setup.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
For more information about Angular project files and the file structure, see [Workspace and project file struture](guide/file-structure).
|
||||
|
||||
|
||||
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've seen the essentials of an Angular app and the Angular CLI, continue with these other introductory materials:
|
||||
|
||||
* The [Tour of Heroes tutorial](tutorial "Tour of Heroes tutorial") provides additional hands-on learning. It walks you through the steps to build an app that helps a staffing agency manage a group of superhero employees.
|
||||
It has many of the features you'd expect to find in a data-driven application:
|
||||
|
||||
- Acquiring and displaying a list of items
|
||||
|
||||
- Editing a selected item's detail
|
||||
|
||||
- Navigating among different views of the data
|
||||
|
||||
|
||||
* The [Architecture guide](guide/architecture "Architecture guide") describes key concepts such as modules, components, services, and dependency injection (DI). It provides a foundation for more in-depth guides about specific Angular concepts and features.
|
||||
|
||||
After the Tutorial and Architecture guide, you'll be ready to continue exploring Angular on your own through the other guides and references in this documentation set, focusing on the features most important for your apps.
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user