Compare commits
331 Commits
6.1.0-beta
...
6.0.7
Author | SHA1 | Date | |
---|---|---|---|
2880cf9ef1 | |||
64a8584a92 | |||
97897ab738 | |||
9378f44d6d | |||
afa46af4c6 | |||
dbffdcc442 | |||
06d68a1b9f | |||
75dd3c5ca5 | |||
23f56198a5 | |||
69167e4519 | |||
10feafcf27 | |||
3f3fed95be | |||
94433f3b9e | |||
aa753878e6 | |||
3513ae40cc | |||
979e7b6086 | |||
41f008d109 | |||
c950410ee1 | |||
b0fa504d39 | |||
426f9710a0 | |||
ae01c70bba | |||
3341a97154 | |||
2056e1f05c | |||
3938a8be75 | |||
393db94b8d | |||
65744e4ae1 | |||
56bc86992c | |||
9426c02648 | |||
0b356d4163 | |||
a886659444 | |||
3649958595 | |||
8919577c54 | |||
84eff4219e | |||
c11e84ee18 | |||
1ce5a495d3 | |||
1f532aaa5a | |||
e81982ef90 | |||
776bc38999 | |||
7e7ef8ee25 | |||
dd90d92573 | |||
1b862820e9 | |||
1a642231cd | |||
138e0d79cd | |||
f0a43716e2 | |||
2be27fb888 | |||
74f07f40e0 | |||
aa66e84e44 | |||
e1bc3f5c1a | |||
f5b366147b | |||
3147a92ee9 | |||
ffedbde0b3 | |||
e543c734ab | |||
fbe6871a94 | |||
f43cb9398c | |||
1e04df9ac7 | |||
840ca05fe8 | |||
facc9d498d | |||
9d0999027f | |||
c00dd5a3f3 | |||
3908a63381 | |||
20f9e51b8f | |||
232203242a | |||
0ca634e697 | |||
7fd9918024 | |||
b0aacb81d4 | |||
6c850eb031 | |||
0df61ad107 | |||
fa985ac067 | |||
06f9197361 | |||
c2bad1249e | |||
9d69ff8ddd | |||
3fd33f8eb0 | |||
501a243b3f | |||
b91b9efc91 | |||
03718c95ce | |||
0834710c18 | |||
9a9a7de9a4 | |||
ff51691221 | |||
2ad90ab0d3 | |||
c5872e6782 | |||
d20877bf3f | |||
b3089b4963 | |||
880b03101e | |||
451d996414 | |||
ea3669e334 | |||
ea2987c7af | |||
5b823dedcc | |||
80f7c83bdd | |||
53072b10da | |||
30c2f560b1 | |||
18c4976f3b | |||
0139173c7c | |||
f3c9954a68 | |||
e8765352e8 | |||
9e61ad897e | |||
0d81151cbc | |||
70319722a1 | |||
cfa5b6567d | |||
41698bf5fd | |||
23c50e27fc | |||
0ae8ea254a | |||
19deca159b | |||
dc3e8aa0fb | |||
a9222c0ade | |||
ea3127e3f9 | |||
8e30f7b1aa | |||
46d81b48ac | |||
3e51a2cb26 | |||
faf60d9310 | |||
0639cb9de1 | |||
810d025488 | |||
efe49c141a | |||
a3d9878c9d | |||
10213d0ca0 | |||
0e1919c2db | |||
d579b8ce05 | |||
d69ba735ee | |||
2991b1b217 | |||
b18cf21e99 | |||
c17098dae6 | |||
43e3073687 | |||
f28878f92f | |||
a10bdefa8b | |||
2f377dbcdd | |||
e2e7b4943e | |||
b8a1363bb2 | |||
e9e6a58dd0 | |||
5932a79713 | |||
7c6a082afd | |||
8a2fe5b7c9 | |||
c9eb4910eb | |||
a634a5abbc | |||
41225026e4 | |||
e9f2203347 | |||
906b3ec8e7 | |||
1cb0c4644a | |||
9d28a27215 | |||
f04aef48f2 | |||
d249852f4a | |||
707dd59767 | |||
4d2570576a | |||
76e58e6cca | |||
a27066b9d2 | |||
29dfc8f3ac | |||
b5533e0ee5 | |||
b275d378df | |||
96655f7aac | |||
253f509493 | |||
5a02ae2f84 | |||
f860752902 | |||
efa126f157 | |||
715135b117 | |||
c30fc52d99 | |||
434eb971e4 | |||
6e31e22d41 | |||
8fef926cd2 | |||
7698afedb1 | |||
a583d12660 | |||
a867d71ece | |||
84b43daf65 | |||
9b3423b50d | |||
d6041d83ec | |||
a638f504eb | |||
a29c756251 | |||
f206eb94bb | |||
3d6e82eccd | |||
154289305e | |||
469b1e4a9a | |||
7fee1fd442 | |||
0ee5b7efa5 | |||
afff84c03f | |||
23f2a7069f | |||
42260702f7 | |||
eec231d21e | |||
95344d6039 | |||
1c9839e91d | |||
bc1a66e907 | |||
2a83dbb0d8 | |||
4007d00403 | |||
61e32ac3c4 | |||
43ee10fbbd | |||
eb90039ea1 | |||
5d318ff234 | |||
f3361abdd7 | |||
52cbe894e9 | |||
41c2030534 | |||
daee41a40a | |||
68bd45ba87 | |||
0f6407750b | |||
95fca24fd8 | |||
a8f6542115 | |||
c6b618d020 | |||
ad6052e397 | |||
e9d1709156 | |||
b5d3de50cc | |||
734d37b231 | |||
bc2063807c | |||
2a528fcb15 | |||
752b83ac81 | |||
56be3375ec | |||
1b83b3fb15 | |||
14d4625ede | |||
fd880a8de4 | |||
bd3dddce4b | |||
1336a9451f | |||
d280077412 | |||
2b578f5c61 | |||
12dcb313af | |||
f576851ecc | |||
ca6cb66c32 | |||
484233f6c1 | |||
3d8799b3a2 | |||
8733843c11 | |||
f1097914c5 | |||
06776d1d10 | |||
2b31b6dc3f | |||
2254ac23e4 | |||
38c678fdcd | |||
1a655836cb | |||
2e466f4bea | |||
7b06fa88ab | |||
3d712894ae | |||
75b8edae03 | |||
fe7f48c1d5 | |||
29600cbb19 | |||
5581e97d2a | |||
19262d9f90 | |||
1eb1c6315d | |||
3807599a4d | |||
2ed41d9e38 | |||
5eb9c01bb6 | |||
09d9662386 | |||
844cbd9774 | |||
373a47dda9 | |||
83f12f3047 | |||
bbc416cdcd | |||
07f2098655 | |||
9b53a6e779 | |||
65f8505fb6 | |||
5a5ea45c40 | |||
52a3657b48 | |||
3824e3f858 | |||
05aa5e0179 | |||
4ddeb030e7 | |||
afe6380429 | |||
947ea17a09 | |||
a190c45a64 | |||
902781803f | |||
0d480ac0dc | |||
6934bf4863 | |||
7e7ea33fb0 | |||
5bd8c6887e | |||
d28ab372c8 | |||
d0ccf5f169 | |||
ecde15298a | |||
983e5f2d7e | |||
5fc4299e0a | |||
1823d5dd1c | |||
91d4da0d2f | |||
22eb8e26fc | |||
f6002c1702 | |||
14138f6382 | |||
f11daa2031 | |||
31a435ef5b | |||
3e92b22258 | |||
2e5457c824 | |||
1ab5fba92e | |||
e1e57ddaa7 | |||
ee7cb48877 | |||
a30c57090a | |||
8a49ec4f27 | |||
697b6c040c | |||
4008e36e80 | |||
e47bb52084 | |||
ac2b530f4b | |||
64bf6edf00 | |||
adf6235479 | |||
04c18ac1aa | |||
1b26dd8cdb | |||
f721b06bde | |||
0bc8443e12 | |||
db17231597 | |||
540626a3a6 | |||
391bfcede5 | |||
d8de6488dd | |||
151fb66848 | |||
02424ff0d0 | |||
f0925d9705 | |||
212b806eda | |||
b9431e88fb | |||
7790cfa0d0 | |||
41b5149509 | |||
06f865640d | |||
824f74f27b | |||
5301c43eed | |||
f280d1aef1 | |||
5e741d42a6 | |||
0035d41030 | |||
08f447ceec | |||
8953f123e3 | |||
6274007e3b | |||
ee76be7783 | |||
a8c720bc3a | |||
ac47a3cd93 | |||
041458a3d2 | |||
2cb74b748f | |||
cb0bfe7a43 | |||
8ee11aeaa6 | |||
bf89fcb361 | |||
4f70c5b6f7 | |||
ad3ebec2a5 | |||
3f5d61f2dd | |||
11ada7f78b | |||
0b96bc7456 | |||
fefadadff3 | |||
946057ae29 | |||
d4293aaaaa | |||
e3dcc227f6 | |||
c9230dd90e | |||
234af9ba59 | |||
7204028d3e | |||
f7c55952bf | |||
e38e3bd135 | |||
cd20c01ba1 | |||
12a191ef3f | |||
65e67b3c3a | |||
32e57f6197 | |||
7de69ba29b | |||
44193c0b94 | |||
6db8241ffa | |||
33c594516c |
@ -85,7 +85,7 @@ jobs:
|
||||
# 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
|
||||
- run: bazel query --output=label //... | xargs bazel test
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
@ -111,42 +111,6 @@ jobs:
|
||||
paths:
|
||||
- "node_modules"
|
||||
- "~/bazel_repository_cache"
|
||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||
test_ivy_jit:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *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
|
||||
- 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:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *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
|
||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
@ -176,8 +140,6 @@ jobs:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
- packages-dist-ivy-jit
|
||||
- packages-dist-ivy-local
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# They are a separate workflow job so that they can be easily re-run.
|
||||
@ -242,8 +204,6 @@ workflows:
|
||||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
@ -256,8 +216,6 @@ workflows:
|
||||
requires:
|
||||
# Only publish if tests and integration tests pass
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- integration_test
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
|
@ -163,7 +163,7 @@ groups:
|
||||
files:
|
||||
- "packages/compiler/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- chuckjaz #primary
|
||||
- vicb
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
86
CHANGELOG.md
86
CHANGELOG.md
@ -1,12 +1,11 @@
|
||||
<a name="6.1.0-beta.2"></a>
|
||||
# [6.1.0-beta.2](https://github.com/angular/angular/compare/6.1.0-beta.1...6.1.0-beta.2) (2018-06-20)
|
||||
<a name="6.0.7"></a>
|
||||
## [6.0.7](https://github.com/angular/angular/compare/6.0.6...6.0.7) (2018-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363)
|
||||
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d))
|
||||
|
||||
* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4))
|
||||
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
|
||||
|
||||
|
||||
<a name="6.0.6"></a>
|
||||
@ -24,40 +23,8 @@
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
|
||||
<a name="6.1.0-beta.1"></a>
|
||||
# [6.1.0-beta.1](https://github.com/angular/angular/compare/6.1.0-beta.0...6.1.0-beta.1) (2018-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0))
|
||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351))
|
||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([8be6892](https://github.com/angular/angular/commit/8be6892)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||
* **ivy:** compute transitive scopes from NgModuleDef only ([#24334](https://github.com/angular/angular/issues/24334)) ([1135563](https://github.com/angular/angular/commit/1135563))
|
||||
* **ivy:** correctly handle queries with embedded views ([#24418](https://github.com/angular/angular/issues/24418)) ([014949f](https://github.com/angular/angular/commit/014949f))
|
||||
* **ivy:** remove debugger statement ([#24480](https://github.com/angular/angular/issues/24480)) ([70ef061](https://github.com/angular/angular/commit/70ef061))
|
||||
* **ivy:** special case [style] and [class] bindings for future use ([#23232](https://github.com/angular/angular/issues/23232)) ([1b253e1](https://github.com/angular/angular/commit/1b253e1))
|
||||
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
|
||||
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7))
|
||||
* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c))
|
||||
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
|
||||
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
|
||||
* **ivy:** a generic visitor which allows prefixing nodes for ngtsc ([#24230](https://github.com/angular/angular/issues/24230)) ([ca79e11](https://github.com/angular/angular/commit/ca79e11))
|
||||
* **ivy:** add support of ApplicationRef.bootstrapModuleFactory ([#23811](https://github.com/angular/angular/issues/23811)) ([e3759f7](https://github.com/angular/angular/commit/e3759f7))
|
||||
* **ivy:** namespaced attributes added to output instructions ([#24386](https://github.com/angular/angular/issues/24386)) ([82c5313](https://github.com/angular/angular/commit/82c5313))
|
||||
* **ivy:** now supports SVG and MathML elements ([#24377](https://github.com/angular/angular/issues/24377)) ([8c1ac28](https://github.com/angular/angular/commit/8c1ac28))
|
||||
* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595)
|
||||
|
||||
|
||||
|
||||
<a name="6.0.5"></a>
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
|
||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
|
||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||
@ -66,51 +33,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="6.1.0-beta.0"></a>
|
||||
## [6.1.0-beta.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0-beta.0) (2018-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687))
|
||||
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e))
|
||||
* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25))
|
||||
* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4))
|
||||
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d))
|
||||
* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41))
|
||||
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
|
||||
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
|
||||
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
|
||||
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
|
||||
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
|
||||
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
|
||||
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
|
||||
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36))
|
||||
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
|
||||
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8))
|
||||
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
|
||||
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
|
||||
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
|
||||
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
|
||||
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
|
||||
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526)
|
||||
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e))
|
||||
* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67))
|
||||
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
|
||||
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
|
||||
* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb))
|
||||
* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202)
|
||||
|
||||
|
||||
<a name="6.0.4"></a>
|
||||
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
||||
|
||||
|
2
aio/content/examples/.gitignore
vendored
2
aio/content/examples/.gitignore
vendored
@ -60,6 +60,8 @@ dist/
|
||||
!rollup-config.js
|
||||
aot-compiler/**/*.d.ts
|
||||
aot-compiler/**/*.factory.d.ts
|
||||
upgrade-phonecat-2-hybrid/aot/**/*
|
||||
!upgrade-phonecat-2-hybrid/aot/index.html
|
||||
|
||||
# i18n
|
||||
!i18n/src/systemjs-text-plugin.js
|
||||
|
@ -16,6 +16,7 @@ describe('Form Validation Tests', function () {
|
||||
|
||||
tests('Template-Driven Form');
|
||||
bobTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
|
||||
describe('Reactive form', () => {
|
||||
@ -25,6 +26,7 @@ describe('Form Validation Tests', function () {
|
||||
|
||||
tests('Reactive Form');
|
||||
bobTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
});
|
||||
|
||||
@ -42,7 +44,8 @@ let page: {
|
||||
powerOption: ElementFinder,
|
||||
errorMessages: ElementArrayFinder,
|
||||
heroFormButtons: ElementArrayFinder,
|
||||
heroSubmitted: ElementFinder
|
||||
heroSubmitted: ElementFinder,
|
||||
crossValidationErrorMessage: ElementFinder,
|
||||
};
|
||||
|
||||
function getPage(sectionTag: string) {
|
||||
@ -59,7 +62,8 @@ function getPage(sectionTag: string) {
|
||||
powerOption: section.element(by.css('#power option')),
|
||||
errorMessages: section.all(by.css('div.alert')),
|
||||
heroFormButtons: buttons,
|
||||
heroSubmitted: section.element(by.css('.submitted-message'))
|
||||
heroSubmitted: section.element(by.css('.submitted-message')),
|
||||
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
|
||||
};
|
||||
}
|
||||
|
||||
@ -172,3 +176,29 @@ function bobTests() {
|
||||
expectFormIsValid();
|
||||
});
|
||||
}
|
||||
|
||||
function crossValidationTests() {
|
||||
const emsg = 'Name cannot match alter ego.';
|
||||
|
||||
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, function () {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Batman');
|
||||
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Batman');
|
||||
|
||||
expectFormIsInvalid();
|
||||
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with different values', function () {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Batman');
|
||||
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Superman');
|
||||
|
||||
expectFormIsValid();
|
||||
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
|
||||
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
|
||||
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
||||
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||
|
||||
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -19,7 +19,8 @@ import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||
AppComponent,
|
||||
HeroFormTemplateComponent,
|
||||
HeroFormReactiveComponent,
|
||||
ForbiddenValidatorDirective
|
||||
ForbiddenValidatorDirective,
|
||||
IdentityRevealedValidatorDirective
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
|
@ -0,0 +1,42 @@
|
||||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form-reactive',
|
||||
templateUrl: './hero-form-reactive.component.html',
|
||||
styleUrls: ['./hero-form-reactive.component.css'],
|
||||
})
|
||||
export class HeroFormReactiveComponent implements OnInit {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
|
||||
heroForm: FormGroup;
|
||||
|
||||
// #docregion form-group
|
||||
ngOnInit(): void {
|
||||
// #docregion custom-validator
|
||||
this.heroForm = new FormGroup({
|
||||
'name': new FormControl(this.hero.name, [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
});
|
||||
// #enddocregion custom-validator
|
||||
|
||||
}
|
||||
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
get power() { return this.heroForm.get('power'); }
|
||||
// #enddocregion form-group
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,5 @@
|
||||
/* #docregion cross-validation-error-css */
|
||||
.cross-validation-error input {
|
||||
border-left: 5px solid red;
|
||||
}
|
||||
/* #enddocregion cross-validation-error-css */
|
@ -7,33 +7,41 @@
|
||||
|
||||
<div [hidden]="formDir.submitted">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
|
||||
<div class="form-group">
|
||||
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input id="name" class="form-control"
|
||||
formControlName="name" required >
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input id="name" class="form-control"
|
||||
formControlName="name" required >
|
||||
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
formControlName="alterEgo" >
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
formControlName="alterEgo" >
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
||||
Name cannot match alter ego.
|
||||
</div>
|
||||
<!-- #enddocregion cross-validation-error-message -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -1,40 +1,36 @@
|
||||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form-reactive',
|
||||
templateUrl: './hero-form-reactive.component.html'
|
||||
templateUrl: './hero-form-reactive.component.html',
|
||||
styleUrls: ['./hero-form-reactive.component.css'],
|
||||
})
|
||||
export class HeroFormReactiveComponent implements OnInit {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
|
||||
|
||||
heroForm: FormGroup;
|
||||
|
||||
// #docregion form-group
|
||||
ngOnInit(): void {
|
||||
// #docregion custom-validator
|
||||
this.heroForm = new FormGroup({
|
||||
'name': new FormControl(this.hero.name, [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
|
||||
forbiddenNameValidator(/bob/i)
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
});
|
||||
// #enddocregion custom-validator
|
||||
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
|
||||
}
|
||||
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
get power() { return this.heroForm.get('power'); }
|
||||
// #enddocregion form-group
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -0,0 +1,25 @@
|
||||
// #docregion
|
||||
import { Directive } from '@angular/core';
|
||||
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
|
||||
|
||||
// #docregion cross-validation-validator
|
||||
/** A hero's name can't match the hero's alter ego */
|
||||
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
|
||||
const name = control.get('name');
|
||||
const alterEgo = control.get('alterEgo');
|
||||
|
||||
return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
|
||||
};
|
||||
// #enddocregion cross-validation-validator
|
||||
|
||||
// #docregion cross-validation-directive
|
||||
@Directive({
|
||||
selector: '[appIdentityRevealed]',
|
||||
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
|
||||
})
|
||||
export class IdentityRevealedValidatorDirective implements Validator {
|
||||
validate(control: AbstractControl): ValidationErrors {
|
||||
return identityRevealedValidator(control)
|
||||
}
|
||||
}
|
||||
// #enddocregion cross-validation-directive
|
@ -0,0 +1,4 @@
|
||||
/* #docregion */
|
||||
.cross-validation-error input {
|
||||
border-left: 5px solid red;
|
||||
}
|
@ -2,41 +2,48 @@
|
||||
<div class="container">
|
||||
|
||||
<h1>Template-Driven Form</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form #heroForm="ngForm">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<!-- #docregion cross-validation-register-validator -->
|
||||
<form #heroForm="ngForm" appIdentityRevealed>
|
||||
<!-- #enddocregion cross-validation-register-validator -->
|
||||
<div [hidden]="heroForm.submitted">
|
||||
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<!-- #docregion name-input -->
|
||||
<input id="name" name="name" class="form-control"
|
||||
required minlength="4" appForbiddenName="bob"
|
||||
[(ngModel)]="hero.name" #name="ngModel" >
|
||||
<!-- #enddocregion name-input -->
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<!-- #docregion name-input -->
|
||||
<input id="name" name="name" class="form-control"
|
||||
required minlength="4" appForbiddenName="bob"
|
||||
[(ngModel)]="hero.name" #name="ngModel" >
|
||||
<!-- #enddocregion name-input -->
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
||||
Name cannot match alter ego.
|
||||
</div>
|
||||
<!-- #enddocregion cross-validation-error-message -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@ -62,5 +69,4 @@
|
||||
<button (click)="heroForm.resetForm({})">Add new hero</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
@ -3,9 +3,11 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion component
|
||||
@Component({
|
||||
selector: 'app-hero-form-template',
|
||||
templateUrl: './hero-form-template.component.html'
|
||||
templateUrl: './hero-form-template.component.html',
|
||||
styleUrls: ['./hero-form-template.component.css'],
|
||||
})
|
||||
export class HeroFormTemplateComponent {
|
||||
|
||||
@ -14,3 +16,4 @@ export class HeroFormTemplateComponent {
|
||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -2,6 +2,7 @@
|
||||
"description": "Validation",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
]
|
||||
}
|
||||
|
@ -1,16 +1,36 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroSearchComponent } from '../hero-search/hero-search.component';
|
||||
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { HEROES } from '../mock-heroes';
|
||||
import { HeroService } from '../hero.service';
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent;
|
||||
let fixture: ComponentFixture<DashboardComponent>;
|
||||
let heroService;
|
||||
let getHeroesSpy;
|
||||
|
||||
beforeEach(async(() => {
|
||||
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
|
||||
getHeroesSpy = heroService.getHeroes.and.returnValue( of(HEROES) );
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DashboardComponent ]
|
||||
declarations: [
|
||||
DashboardComponent,
|
||||
HeroSearchComponent
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
{ provide: HeroService, useValue: heroService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -22,4 +42,17 @@ describe('DashboardComponent', () => {
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display "Top Heroes" as headline', () => {
|
||||
expect(fixture.nativeElement.querySelector('h3').textContent).toEqual('Top Heroes');
|
||||
});
|
||||
|
||||
it('should call heroService', async(() => {
|
||||
expect(getHeroesSpy.calls.any()).toBe(true);
|
||||
}));
|
||||
|
||||
it('should display 4 links', async(() => {
|
||||
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
|
||||
import { HeroSearchComponent } from './hero-search.component';
|
||||
|
||||
|
||||
describe('HeroSearchComponent', () => {
|
||||
let component: HeroSearchComponent;
|
||||
let fixture: ComponentFixture<HeroSearchComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HeroSearchComponent ]
|
||||
declarations: [ HeroSearchComponent ],
|
||||
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
|
||||
describe('HeroesComponent', () => {
|
||||
let component: HeroesComponent;
|
||||
@ -8,7 +9,8 @@ describe('HeroesComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HeroesComponent ]
|
||||
declarations: [ HeroesComponent ],
|
||||
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
@ -8,6 +8,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,8 @@
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": [
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docregion
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
|
||||
import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory';
|
||||
import { AppModuleNgFactory } from './app.module.ngfactory';
|
||||
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
@ -21,7 +21,7 @@ function xyzPhoneData(): PhoneData {
|
||||
|
||||
class MockPhone {
|
||||
get(id: string): Observable<PhoneData> {
|
||||
return Observable.of(xyzPhoneData());
|
||||
return of(xyzPhoneData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
|
||||
@ -15,7 +15,7 @@ class ActivatedRouteMock {
|
||||
|
||||
class MockPhone {
|
||||
query(): Observable<PhoneData[]> {
|
||||
return Observable.of([
|
||||
return of([
|
||||
{name: 'Nexus S', snippet: '', images: []},
|
||||
{name: 'Motorola DROID', snippet: '', images: []}
|
||||
]);
|
||||
|
@ -9,10 +9,8 @@
|
||||
"lib": ["es2015", "dom"],
|
||||
"removeComments": false,
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
|
||||
"files": [
|
||||
@ -21,7 +19,6 @@
|
||||
],
|
||||
|
||||
"angularCompilerOptions": {
|
||||
"genDir": "aot",
|
||||
"skipMetadataEmit" : true
|
||||
"skipMetadataEmit" : true
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,8 @@
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": [
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
@ -21,7 +21,7 @@ function xyzPhoneData(): PhoneData {
|
||||
|
||||
class MockPhone {
|
||||
get(id: string): Observable<PhoneData> {
|
||||
return Observable.of(xyzPhoneData());
|
||||
return of(xyzPhoneData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion routestuff
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
|
||||
@ -17,7 +17,7 @@ class ActivatedRouteMock {
|
||||
|
||||
class MockPhone {
|
||||
query(): Observable<PhoneData[]> {
|
||||
return Observable.of([
|
||||
return of([
|
||||
{name: 'Nexus S', snippet: '', images: []},
|
||||
{name: 'Motorola DROID', snippet: '', images: []}
|
||||
]);
|
||||
|
@ -8,10 +8,8 @@
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": [
|
||||
|
@ -895,7 +895,7 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
### lowercase
|
||||
|
||||
<code-example hideCopy>
|
||||
<div>{{movie.title | lowercase}}</div>
|
||||
<td>{{movie.title | lowercase}}</td>
|
||||
</code-example>
|
||||
|
||||
|
||||
|
@ -97,6 +97,9 @@ You can control your app compilation by providing template compiler options in t
|
||||
}
|
||||
}
|
||||
```
|
||||
### *enableResourceInlining*
|
||||
This options tell the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
|
||||
When enabled, the `.js` output of ngc will have no lazy-loaded `templateUrl` or `styleUrls`.
|
||||
|
||||
### *skipMetadataEmit*
|
||||
|
||||
@ -236,14 +239,14 @@ Tells the compiler to generate all the possible generated files even if they are
|
||||
how `bazel` rules track file dependencies. It is not recommended to use this option outside of the `bazel`
|
||||
rules.
|
||||
|
||||
### *enableIvy*
|
||||
### *enableIvy*
|
||||
|
||||
Tells the compiler to generate definitions using the Render3 style code generation. This option defaults to `false`.
|
||||
Tells the compiler to generate definitions using the Render3 style code generation. This option defaults to `false`.
|
||||
|
||||
Not all features are supported with this option enabled. It is only supported
|
||||
for experimentation and testing of Render3 style code generation.
|
||||
Not all features are supported with this option enabled. It is only supported
|
||||
for experimentation and testing of Render3 style code generation.
|
||||
|
||||
*Note*: Is it not recommended to use this option as it is not yet feature complete with the Render2 code generation.
|
||||
*Note*: Is it not recommended to use this option as it is not yet feature complete with the Render2 code generation.
|
||||
|
||||
|
||||
## Angular Metadata and AOT
|
||||
|
@ -152,7 +152,7 @@ Install `source-map-explorer`:
|
||||
Build your app for production _including the source maps_
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
ng build --prod --sourcemaps
|
||||
ng build --prod --source-map
|
||||
</code-example>
|
||||
|
||||
List the generated bundles in the `dist/` folder.
|
||||
|
@ -31,6 +31,8 @@ Here are a few essential commands for guide page authors.
|
||||
|
||||
1. http://localhost:4200/ — browse to the app running locally.
|
||||
|
||||
You can combine `yarn docs-watch` and `yarn start` into one command with `yarn serve-and-sync`.
|
||||
|
||||
## Guide pages
|
||||
|
||||
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.
|
||||
|
@ -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.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" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Note that:
|
||||
@ -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.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" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Adding to template-driven forms
|
||||
@ -208,5 +208,80 @@ set the color of each form control's border.
|
||||
|
||||
</code-example>
|
||||
|
||||
## Cross field validation
|
||||
This section shows how to perform cross field validation. It assumes some basic knowledge of creating custom validators.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
If you haven't created custom validators before, start by reviewing the [custom validators section](guide/form-validation#custom-validators).
|
||||
|
||||
</div>
|
||||
|
||||
In the following section, we will make sure that our heroes do not reveal their true identities by filling out the Hero Form. We will do that by validating that the hero names and alter egos do not match.
|
||||
|
||||
### Adding to reactive forms
|
||||
|
||||
The form has the following structure:
|
||||
|
||||
```javascript
|
||||
const heroForm = new FormGroup({
|
||||
'name': new FormControl(),
|
||||
'alterEgo': new FormControl(),
|
||||
'power': new FormControl()
|
||||
});
|
||||
```
|
||||
|
||||
Notice that the name and alterEgo are sibling controls. To evaluate both controls in a single custom validator, we should perform the validation in a common ancestor control: the `FormGroup`. That way, we can query the `FormGroup` for the child controls which will allow us to compare their values.
|
||||
|
||||
To add a validator to the `FormGroup`, pass the new validator in as the second argument on creation.
|
||||
|
||||
```javascript
|
||||
const heroForm = new FormGroup({
|
||||
'name': new FormControl(),
|
||||
'alterEgo': new FormControl(),
|
||||
'power': new FormControl()
|
||||
}, { validators: identityRevealedValidator });
|
||||
```
|
||||
|
||||
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>
|
||||
|
||||
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.
|
||||
|
||||
First we retrieve the child controls by calling the `FormGroup`'s [get](api/forms/AbstractControl#get) method. Then we simply compare the values of the `name` and `alterEgo` controls.
|
||||
|
||||
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>
|
||||
|
||||
Note that we check if:
|
||||
- the `FormGroup` has the cross validation error returned by the `identityRevealed` validator,
|
||||
- the user is yet to [interact](guide/form-validation#why-check-dirty-and-touched) with the form.
|
||||
|
||||
### 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>
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
|
||||
Note that we check if:
|
||||
- the form has the cross validation error returned by the `identityRevealed` validator,
|
||||
- the user is yet to [interact](guide/form-validation#why-check-dirty-and-touched) with the form.
|
||||
|
||||
This completes the cross validation example. We managed to:
|
||||
- validate the form based on the values of two sibling controls,
|
||||
- show a descriptive error message after the user interacted with the form and the validation failed.
|
||||
|
||||
**You can run the <live-example></live-example> to see the complete reactive and template-driven example code.**
|
||||
|
352
aio/content/guide/upgrade-performance.md
Normal file
352
aio/content/guide/upgrade-performance.md
Normal file
@ -0,0 +1,352 @@
|
||||
# Upgrading for Performance
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Angular_ is the name for the Angular of today and tomorrow.<br />
|
||||
_AngularJS_ is the name for all 1.x versions of Angular.
|
||||
|
||||
</div>
|
||||
|
||||
This guide describes some of the built-in tools for efficiently migrating AngularJS projects over to
|
||||
the Angular platform, one piece at a time. It is very similar to
|
||||
[Upgrading from AngularJS](guide/upgrade) with the exception that this one uses the {@link
|
||||
downgradeModule downgradeModule()} helper function instead of the {@link UpgradeModule
|
||||
UpgradeModule} class. This affects how the app is bootstrapped and how change detection is
|
||||
propagated between the two frameworks. It allows you to upgrade incrementally while improving the
|
||||
speed of your hybrid apps and leveraging the latest of Angular in AngularJS apps early in the
|
||||
process of upgrading.
|
||||
|
||||
|
||||
|
||||
## Preparation
|
||||
|
||||
Before discussing how you can use `downgradeModule()` to create hybrid apps, there are things that
|
||||
you can do to ease the upgrade process even before you begin upgrading. Because the steps are the
|
||||
same regardless of how you upgrade, refer to the [Preparation](guide/upgrade#preparation) section of
|
||||
[Upgrading from AngularJS](guide/upgrade).
|
||||
|
||||
|
||||
## Upgrading with `ngUpgrade`
|
||||
|
||||
With the `ngUpgrade` library in Angular you can upgrade an existing AngularJS app incrementally by
|
||||
building a hybrid app where you can run both frameworks side-by-side. In these hybrid apps you can
|
||||
mix and match AngularJS and Angular components and services and have them interoperate seamlessly.
|
||||
That means you don't have to do the upgrade work all at once as there is a natural coexistence
|
||||
between the two frameworks during the transition period.
|
||||
|
||||
|
||||
### How `ngUpgrade` Works
|
||||
|
||||
Regardless of whether you choose `downgradeModule()` or `UpgradeModule`, the basic principles of
|
||||
upgrading, the mental model behind hybrid apps, and how you use the {@link upgrade/static
|
||||
upgrade/static} utilities remain the same. For more information, see the
|
||||
[How `ngUpgrade` Works](guide/upgrade#how-ngupgrade-works) section of
|
||||
[Upgrading from AngularJS](guide/upgrade).
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The [Change Detection](guide/upgrade#change-detection) section of
|
||||
[Upgrading from AngularJS](guide/upgrade) only applies to apps that use `UpgradeModule`. Though
|
||||
you handle change detection differently with `downgradeModule()`, which is the focus of this
|
||||
guide, reading the [Change Detection](guide/upgrade#change-detection) section provides helpful
|
||||
context for what follows.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
#### Change Detection with `downgradeModule()`
|
||||
|
||||
As mentioned before, one of the key differences between `downgradeModule()` and `UpgradeModule` has
|
||||
to do with change detection and how it is propagated between the two frameworks.
|
||||
|
||||
With `UpgradeModule`, the two change detection systems are tied together more tightly. Whenever
|
||||
something happens in the AngularJS part of the app, change detection is automatically triggered on
|
||||
the Angular part and vice versa. This is convenient as it ensures that neither framework misses an
|
||||
important change. Most of the time, though, these extra change detection runs are unnecessary.
|
||||
|
||||
`downgradeModule()`, on the other side, avoids explicitly triggering change detection unless it
|
||||
knows the other part of the app is interested in the changes. For example, if a downgraded component
|
||||
defines an `@Input()`, chances are that the app needs to be aware when that value changes. Thus,
|
||||
`downgradeComponent()` automatically triggers change detection on that component.
|
||||
|
||||
In most cases, though, the changes made locally in a particular component are of no interest to the
|
||||
rest of the app. For example, if the user clicks a button that submits a form, the component usually
|
||||
handles the result of this action. That being said, there _are_ cases where you want to propagate
|
||||
changes to some other part of the app that may be controlled by the other framework. In such cases,
|
||||
you are responsible for notifying the interested parties by manually triggering change detection.
|
||||
|
||||
If you want a particular piece of code to trigger change detection in the AngularJS part of the app,
|
||||
you need to wrap it in
|
||||
[scope.$apply()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply). Similarly, for
|
||||
triggering change detection in Angular you would use {@link NgZone#run ngZone.run()}.
|
||||
|
||||
In many cases, a few extra change detection runs may not matter much. However, on larger or
|
||||
change-detection-heavy apps they can have a noticeable impact. By giving you more fine-grained
|
||||
control over the change detection propagation, `downgradeModule()` allows you to achieve better
|
||||
performance for your hybrid apps.
|
||||
|
||||
|
||||
## Using `downgradeModule()`
|
||||
|
||||
Both AngularJS and Angular have their own concept of modules to help organize an app into cohesive
|
||||
blocks of functionality.
|
||||
|
||||
Their details are quite different in architecture and implementation. In AngularJS, you create a
|
||||
module by specifying its name and dependencies with
|
||||
[angular.module()](https://docs.angularjs.org/api/ng/function/angular.module). Then you can add
|
||||
assets using its various methods. In Angular, you create a class adorned with an {@link NgModule
|
||||
NgModule} decorator that describes assets in metadata.
|
||||
|
||||
In a hybrid app you run both frameworks at the same time. This means that you need at least one
|
||||
module each from both AngularJS and Angular.
|
||||
|
||||
For the most part, you specify the modules in the same way you would for a regular app. Then, you
|
||||
use the `upgrade/static` helpers to let the two frameworks know about assets they can use from each
|
||||
other. This is known as "upgrading" and "downgrading".
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<b>Definitions:</b>
|
||||
|
||||
- _Upgrading_: The act of making an AngularJS asset, such as a component or service, available to
|
||||
the Angular part of the app.
|
||||
- _Downgrading_: The act of making an Angular asset, such as a component or service, available to
|
||||
the AngularJS part of the app.
|
||||
|
||||
</div>
|
||||
|
||||
An important part of inter-linking dependencies is linking the two main modules together. This is
|
||||
where `downgradeModule()` comes in. Use it to create an AngularJS module—one that you can use
|
||||
as a dependency in your main AngularJS module—that will bootstrap your main Angular module and
|
||||
kick off the Angular part of the hybrid app. In a sense, it "downgrades" an Angular module to an
|
||||
AngularJS module.
|
||||
|
||||
There are a few things to note, though:
|
||||
|
||||
1. You don't pass the Angular module directly to `downgradeModule()`. All `downgradeModule()` needs
|
||||
is a "recipe", for example, a factory function, to create an instance for your module.
|
||||
|
||||
2. The Angular module is not instantiated until the app actually needs it.
|
||||
|
||||
The following is an example of how you can use `downgradeModule()` to link the two modules.
|
||||
|
||||
```ts
|
||||
// Import `downgradeModule()`.
|
||||
import { downgradeModule } from '@angular/upgrade/static';
|
||||
|
||||
// Use it to downgrade the Angular module to an AngularJS module.
|
||||
const downgradedModule = downgradeModule(MainAngularModuleFactory);
|
||||
|
||||
// Use the downgraded module as a dependency to the main AngularJS module.
|
||||
angular.module('mainAngularJsModule', [
|
||||
downgradedModule
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
#### Specifying a factory for the Angular module
|
||||
|
||||
As mentioned earlier, `downgradeModule()` needs to know how to instantiate the Angular module. It
|
||||
needs a recipe. You define that recipe by providing a factory function that can create an instance
|
||||
of the Angular module. `downgradeModule()` accepts two types of factory functions:
|
||||
|
||||
1. `NgModuleFactory`
|
||||
2. `(extraProviders: StaticProvider[]) => Promise<NgModuleRef>`
|
||||
|
||||
When you pass an `NgModuleFactory`, `downgradeModule()` uses it to instantiate the module using
|
||||
{@link platformBrowser platformBrowser}'s {@link PlatformRef#bootstrapModuleFactory
|
||||
bootstrapModuleFactory()}, which is compatible with ahead-of-time (AOT) compilation. AOT compilation
|
||||
helps make your apps load faster. For more about AOT and how to create an `NgModuleFactory`, see the
|
||||
[Ahead-of-Time Compilation](guide/aot-compiler) guide.
|
||||
|
||||
Alternatively, you can pass a plain function, which is expected to return a promise resolving to an
|
||||
{@link NgModuleRef NgModuleRef} (i.e. an instance of your Angular module). The function is called
|
||||
with an array of extra {@link StaticProvider Providers} that are expected to be available on the
|
||||
returned `NgModuleRef`'s {@link Injector Injector}. For example, if you are using {@link
|
||||
platformBrowser platformBrowser} or {@link platformBrowserDynamic platformBrowserDynamic}, you can
|
||||
pass the `extraProviders` array to them:
|
||||
|
||||
```ts
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) => {
|
||||
const platformRef = platformBrowserDynamic(extraProviders);
|
||||
return platformRef.bootstrapModule(MainAngularModule);
|
||||
};
|
||||
// or
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) => {
|
||||
const platformRef = platformBrowser(extraProviders);
|
||||
return platformRef.bootstrapModuleFactory(MainAngularModuleFactory);
|
||||
};
|
||||
```
|
||||
|
||||
Using an `NgModuleFactory` requires less boilerplate and is a good default option as it supports AOT
|
||||
out-of-the-box. Using a custom function requires slightly more code, but gives you greater
|
||||
flexibility.
|
||||
|
||||
|
||||
#### Instantiating the Angular module on-demand
|
||||
|
||||
Another key difference between `downgradeModule()` and `UpgradeModule` is that the latter requires
|
||||
you to instantiate both the AngularJS and Angular modules up-front. This means that you have to pay
|
||||
the cost of instantiating the Angular part of the app, even if you don't use any Angular assets
|
||||
until later. `downgradeModule()` is again less aggressive. It will only instantiate the Angular part
|
||||
when it is required for the first time; that is, as soon as it needs to create a downgraded
|
||||
component.
|
||||
|
||||
You could go a step further and not even download the code for the Angular part of the app to the
|
||||
user's browser until it is needed. This is especially useful when you use Angular on parts of the
|
||||
hybrid app that are not necessary for the initial rendering or that the user doesn't reach.
|
||||
|
||||
|
||||
A few examples are:
|
||||
|
||||
- You use Angular on specific routes only and you don't need it until/if a user visits such a route.
|
||||
- You use Angular for features that are only visible to specific types of users; for example,
|
||||
logged-in users, administrators, or VIP members. You don't need to load Angular until a user is
|
||||
authenticated.
|
||||
- You use Angular for a feature that is not critical for the initial rendering of the app and you
|
||||
can afford a small delay in favor of better initial load performance.
|
||||
|
||||
|
||||
### Bootstrapping with `downgradeModule()`
|
||||
|
||||
As you might have guessed, you don't need to change anything in the way you bootstrap your existing
|
||||
AngularJS app. Unlike `UpgradeModule`—which requires some extra steps—
|
||||
`downgradeModule()` is able to take care of bootstrapping the Angular module, as long as you provide
|
||||
the recipe.
|
||||
|
||||
In order to start using any `upgrade/static` APIs, you still need to load the Angular framework as
|
||||
you would in a normal Angular app. You can see how this can be done with SystemJS by following the
|
||||
instructions in the [Setup](guide/setup) guide, selectively copying code from the
|
||||
[QuickStart github repository](https://github.com/angular/quickstart).
|
||||
|
||||
You also need to install the `@angular/upgrade` package via `npm install @angular/upgrade --save`
|
||||
and add a mapping for the `@angular/upgrade/static` package:
|
||||
|
||||
<code-example title="system.config.js">
|
||||
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
</code-example>
|
||||
|
||||
Next, create an `app.module.ts` file and add the following `NgModule` class:
|
||||
|
||||
<code-example title="app.module.ts">
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
]
|
||||
})
|
||||
export class MainAngularModule {
|
||||
// Empty placeholder method to satisfy the `Compiler`.
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
This bare minimum `NgModule` imports `BrowserModule`, the module every Angular browser-based app
|
||||
must have. It also defines an empty `ngDoBootstrap()` method, to prevent the {@link Compiler
|
||||
Compiler} from returning errors. This is necessary because the module will not have a `bootstrap`
|
||||
declaration on its `NgModule` decorator.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
You do not add a `bootstrap` declaration to the `NgModule` decorator since AngularJS owns the root
|
||||
template of the app and `ngUpgrade` bootstraps the necessary components.
|
||||
|
||||
</div>
|
||||
|
||||
You can now link the AngularJS and Angular modules together using `downgradeModule()`.
|
||||
|
||||
<code-example title="app.module.ts">
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { downgradeModule } from '@angular/upgrade/static';
|
||||
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) => {
|
||||
const platformRef = platformBrowserDynamic(extraProviders);
|
||||
return platformRef.bootstrapModule(MainAngularModule);
|
||||
};
|
||||
const downgradedModule = downgradeModule(bootstrapFn);
|
||||
|
||||
angular.module('mainAngularJsModule', [
|
||||
downgradedModule
|
||||
]);
|
||||
</code-example>
|
||||
|
||||
The existing AngularJS code works as before _and_ you are ready to start adding Angular code.
|
||||
|
||||
|
||||
### Using Components and Injectables
|
||||
|
||||
The differences between `downgradeModule()` and `UpgradeModule` end here. The rest of the
|
||||
`upgrade/static` APIs and concepts work in the exact same way for both types of hybrid apps.
|
||||
See [Upgrading from AngularJS](guide/upgrade) to learn about:
|
||||
|
||||
- [Using Angular Components from AngularJS Code](guide/upgrade#using-angular-components-from-angularjs-code).
|
||||
- [Using AngularJS Component Directives from Angular Code](guide/upgrade#using-angularjs-component-directives-from-angular-code).
|
||||
- [Projecting AngularJS Content into Angular Components](guide/upgrade#projecting-angularjs-content-into-angular-components).
|
||||
- [Transcluding Angular Content into AngularJS Component Directives](guide/upgrade#transcluding-angular-content-into-angularjs-component-directives).
|
||||
- [Making AngularJS Dependencies Injectable to Angular](guide/upgrade#making-angularjs-dependencies-injectable-to-angular).
|
||||
- [Making Angular Dependencies Injectable to AngularJS](guide/upgrade#making-angular-dependencies-injectable-to-angularjs).
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
While it is possible to downgrade injectables, downgraded injectables will not be available until
|
||||
the Angular module is instantiated. In order to be safe, you need to ensure that the downgraded
|
||||
injectables are not used anywhere _outside_ the part of the app that is controlled by Angular.
|
||||
|
||||
For example, it is _OK_ to use a downgraded service in an upgraded component that is only used
|
||||
from Angular components, but it is _not OK_ to use it in an AngularJS component that may be used
|
||||
independently of Angular.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Using ahead-of-time compilation with hybrid apps
|
||||
|
||||
You can take advantage of ahead-of-time (AOT) compilation in hybrid apps just like in any other
|
||||
Angular app. The setup for a hybrid app is mostly the same as described in the
|
||||
[Ahead-of-Time Compilation](guide/aot-compiler) guide save for differences in `index.html` and
|
||||
`main-aot.ts`.
|
||||
|
||||
AOT needs to load any AngularJS files that are in the `<script>` tags in the AngularJS `index.html`.
|
||||
An easy way to copy them is to add each to the `copy-dist-files.js`file.
|
||||
|
||||
You also need to pass the generated `MainAngularModuleFactory` to `downgradeModule()` instead of the
|
||||
custom bootstrap function:
|
||||
|
||||
<code-example title="app/main-aot.ts">
|
||||
import { downgradeModule } from '@angular/upgrade/static';
|
||||
import { MainAngularModuleNgFactory } from '../aot/app/app.module.ngfactory';
|
||||
|
||||
const downgradedModule = downgradeModule(MainAngularModuleNgFactory);
|
||||
|
||||
angular.module('mainAngularJsModule', [
|
||||
downgradedModule
|
||||
]);
|
||||
</code-example>
|
||||
|
||||
And that is all you need to do to get the full benefit of AOT for hybrid Angular apps.
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
This page covered how to use the {@link upgrade/static upgrade/static} package to incrementally
|
||||
upgrade existing AngularJS apps at your own pace and without impeding further development of the app
|
||||
for the duration of the upgrade process.
|
||||
|
||||
Specifically, this guide showed how you can achieve better performance and greater flexibility in
|
||||
your hybrid apps by using {@link downgradeModule downgradeModule()} instead of {@link UpgradeModule
|
||||
UpgradeModule}.
|
||||
|
||||
To summarize, the key differentiating factors of `downgradeModule()` are:
|
||||
|
||||
1. It allows instantiating or even loading the Angular part lazily, which improves the initial
|
||||
loading time. In some cases this may waive the cost of running a second framework altogether.
|
||||
2. It improves performance by avoiding unnecessary change detection runs while giving the developer
|
||||
greater ability to customize.
|
||||
3. It does not require you to change how you bootstrap your AngularJS app.
|
||||
|
||||
Using `downgradeModule()` is a good option for hybrid apps when you want to keep the AngularJS and
|
||||
Angular parts less coupled. You can still mix and match components and services from both
|
||||
frameworks, but you might need to manually propagate change detection. In return,
|
||||
`downgradeModule()` offers more control and better performance.
|
@ -1,7 +1,7 @@
|
||||
# Upgrading from AngularJS to Angular
|
||||
|
||||
_Angular_ is the name for the Angular of today and tomorrow.
|
||||
_AngularJS_ is the name for all v1.x versions of Angular.
|
||||
_Angular_ is the name for the Angular of today and tomorrow.<br />
|
||||
_AngularJS_ is the name for all 1.x versions of Angular.
|
||||
|
||||
AngularJS apps are great.
|
||||
Always consider the business case before moving to Angular.
|
||||
@ -195,7 +195,7 @@ transition period.
|
||||
|
||||
### How ngUpgrade Works
|
||||
|
||||
The primary tool provided by ngUpgrade is called the `UpgradeModule`.
|
||||
One of the primary tools provided by ngUpgrade is called the `UpgradeModule`.
|
||||
This is a module that contains utilities for bootstrapping and managing hybrid
|
||||
applications that support both Angular and AngularJS code.
|
||||
|
||||
@ -252,7 +252,7 @@ frameworks in how it actually works.
|
||||
</table>
|
||||
|
||||
Even accounting for these differences you can still have dependency injection
|
||||
interoperability. The `UpgradeModule` resolves the differences and makes
|
||||
interoperability. `upgrade/static` resolves the differences and makes
|
||||
everything work seamlessly:
|
||||
|
||||
* You can make AngularJS services available for injection to Angular code
|
||||
@ -569,7 +569,7 @@ So, you can write an Angular component and then use it from AngularJS
|
||||
code. This is useful when you start to migrate from lower-level
|
||||
components and work your way up. But in some cases it is more convenient
|
||||
to do things in the opposite order: To start with higher-level components
|
||||
and work your way down. This too can be done using the `UpgradeModule`.
|
||||
and work your way down. This too can be done using the `upgrade/static`.
|
||||
You can *upgrade* AngularJS component directives and then use them from
|
||||
Angular.
|
||||
|
||||
@ -710,7 +710,7 @@ and then provide the input and output using Angular template syntax:
|
||||
When you are using a downgraded Angular component from an AngularJS
|
||||
template, the need may arise to *transclude* some content into it. This
|
||||
is also possible. While there is no such thing as transclusion in Angular,
|
||||
there is a very similar concept called *content projection*. The `UpgradeModule`
|
||||
there is a very similar concept called *content projection*. `upgrade/static`
|
||||
is able to make these two features interoperate.
|
||||
|
||||
Angular components that support content projection make use of an `<ng-content>`
|
||||
|
BIN
aio/content/images/bios/elanaolson.jpg
Normal file
BIN
aio/content/images/bios/elanaolson.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@ -645,5 +645,13 @@
|
||||
"website": "https://kmaida.io/",
|
||||
"bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"elanaolson": {
|
||||
"name": "Elana Olson",
|
||||
"picture": "elanaolson.jpg",
|
||||
"twitter": "elanathellama",
|
||||
"bio": "Elana is a Developer Relations intern on the Angular team at Google. She is working on migration paths from AngularJS to Angular and would love to chat about your experience with upgrading.",
|
||||
"group": "Angular"
|
||||
}
|
||||
}
|
||||
|
@ -490,6 +490,13 @@
|
||||
"rev": true,
|
||||
"title": "Angular-Buch (German)",
|
||||
"url": "https://angular-buch.com/"
|
||||
},
|
||||
"wishtack-guide-angular": {
|
||||
"desc": "The free, open-source and up-to-date Angular guide. This pragmatic guide is focused on best practices and will drive you from scratch to cloud.",
|
||||
"logo": "https://raw.githubusercontent.com/wishtack/gitbook-guide-angular/master/.gitbook/assets/wishtack-logo-with-text.png",
|
||||
"rev": true,
|
||||
"title": "The Angular Guide by Wishtack (Français)",
|
||||
"url": "https://guide-angular.wishtack.io/"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -514,6 +514,11 @@
|
||||
"title": "Upgrading Instructions",
|
||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular."
|
||||
},
|
||||
{
|
||||
"url": "guide/upgrade-performance",
|
||||
"title": "Upgrading for Performance",
|
||||
"tooltip": "Upgrade from AngularJS to Angular in a more flexible way."
|
||||
},
|
||||
{
|
||||
"url": "guide/ajs-quick-reference",
|
||||
"title": "AngularJS-Angular Concepts",
|
||||
|
@ -15,7 +15,7 @@
|
||||
// A random bad indexed page that used `api/api`
|
||||
{"type": 301, "source": "/api/api/:rest*", "destination": "/api/:rest*"},
|
||||
|
||||
// Guide renames
|
||||
// Guide renames/removals
|
||||
{"type": 301, "source": "/docs/*/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
|
||||
{"type": 301, "source": "/docs/*/latest/glossary.html", "destination": "/guide/glossary"},
|
||||
{"type": 301, "source": "/docs/*/latest/quickstart.html", "destination": "/guide/quickstart"},
|
||||
@ -25,6 +25,7 @@
|
||||
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
|
||||
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
|
||||
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"},
|
||||
{"type": 301, "source": "/guide/webpack", "destination": "https://v5.angular.io/guide/webpack"},
|
||||
|
||||
// some top level guide pages on old site were moved below the guide folder
|
||||
{"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
|
||||
|
@ -18,7 +18,7 @@
|
||||
"routing": {
|
||||
"index": "/index.html",
|
||||
"routes": {
|
||||
"^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing/|api/|animate/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?(?:\\?.*)?$|.*\\.[^\/.]+$)": {
|
||||
"^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular|webpack)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing/|api/|animate/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?(?:\\?.*)?$|.*\\.[^\/.]+$)": {
|
||||
"match": "regex"
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
"test-pwa-score": "node scripts/test-pwa-score",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
|
||||
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
||||
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
||||
"deploy-preview": "scripts/deploy-preview.sh",
|
||||
|
@ -27,6 +27,12 @@
|
||||
</a>
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
<div class="toolbar-external-icons-container">
|
||||
<a href="https://twitter.com/angular" title="Twitter">
|
||||
<img src="assets/images/logos/twitter-icon.svg"></a>
|
||||
<a href="https://github.com/angular/angular" title="GitHub">
|
||||
<img src="assets/images/logos/github-icon.svg"></a>
|
||||
</div>
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
|
||||
|
14
aio/src/assets/images/logos/github-icon.svg
Normal file
14
aio/src/assets/images/logos/github-icon.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 51.8 50.4" style="enable-background:new 0 0 51.8 50.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M25.9,0.2C11.8,0.2,0.3,11.7,0.3,25.8c0,11.3,7.3,20.9,17.5,24.3c1.3,0.2,1.7-0.6,1.7-1.2c0-0.6,0-2.6,0-4.8
|
||||
c-7.1,1.5-8.6-3-8.6-3c-1.2-3-2.8-3.7-2.8-3.7c-2.3-1.6,0.2-1.6,0.2-1.6c2.6,0.2,3.9,2.6,3.9,2.6c2.3,3.9,6,2.8,7.5,2.1
|
||||
c0.2-1.7,0.9-2.8,1.6-3.4c-5.7-0.6-11.7-2.8-11.7-12.7c0-2.8,1-5.1,2.6-6.9c-0.3-0.7-1.1-3.3,0.3-6.8c0,0,2.1-0.7,7,2.6
|
||||
c2-0.6,4.2-0.9,6.4-0.9c2.2,0,4.4,0.3,6.4,0.9c4.9-3.3,7-2.6,7-2.6c1.4,3.5,0.5,6.1,0.3,6.8c1.6,1.8,2.6,4.1,2.6,6.9
|
||||
c0,9.8-6,12-11.7,12.6c0.9,0.8,1.7,2.4,1.7,4.7c0,3.4,0,6.2,0,7c0,0.7,0.5,1.5,1.8,1.2c10.2-3.4,17.5-13,17.5-24.3
|
||||
C51.5,11.7,40.1,0.2,25.9,0.2z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
13
aio/src/assets/images/logos/twitter-icon.svg
Normal file
13
aio/src/assets/images/logos/twitter-icon.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 49.7" style="enable-background:new 0 0 50 49.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M50,9.3c-1.8,0.8-3.8,1.4-5.9,1.6c2.1-1.3,3.7-3.3,4.5-5.7c-2,1.2-4.2,2-6.5,2.5c-1.9-2-4.5-3.2-7.5-3.2
|
||||
c-5.7,0-10.3,4.6-10.3,10.3c0,0.8,0.1,1.6,0.3,2.3C16.1,16.7,8.5,12.6,3.5,6.4c-0.9,1.5-1.4,3.3-1.4,5.2c0,3.6,1.8,6.7,4.6,8.5
|
||||
C5,20,3.4,19.6,2,18.8c0,0,0,0.1,0,0.1c0,5,3.5,9.1,8.2,10.1c-0.9,0.2-1.8,0.4-2.7,0.4c-0.7,0-1.3-0.1-1.9-0.2
|
||||
c1.3,4.1,5.1,7,9.6,7.1c-3.5,2.8-7.9,4.4-12.7,4.4c-0.8,0-1.6,0-2.4-0.1c4.5,2.9,9.9,4.6,15.7,4.6c18.9,0,29.2-15.6,29.2-29.2
|
||||
c0-0.4,0-0.9,0-1.3C46.9,13.2,48.6,11.4,50,9.3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 937 B |
@ -37,5 +37,9 @@
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.short-description {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
$hamburgerShownMargin: 0 8px 0 0;
|
||||
$hamburgerHiddenMargin: 0 16px 0 -88px;
|
||||
|
||||
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
mat-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
@ -10,7 +9,7 @@ mat-toolbar.mat-toolbar {
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3);
|
||||
|
||||
mat-toolbar-row {
|
||||
padding: 0 16px 0 0;
|
||||
@ -21,7 +20,6 @@ mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar {
|
||||
background-color: $blue;
|
||||
@ -29,12 +27,11 @@ aio-shell.page-home mat-toolbar.mat-toolbar {
|
||||
@media (min-width: 481px) {
|
||||
&:not(.transitioning) {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear;
|
||||
transition: background-color 0.2s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-features mat-toolbar.mat-toolbar,
|
||||
@ -48,7 +45,6 @@ aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DOCS PAGES OVERRIDE: HAMBURGER
|
||||
aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
@ -62,7 +58,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HAMBURGER BUTTON
|
||||
.hamburger.mat-button {
|
||||
height: 100%;
|
||||
@ -70,9 +65,9 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
padding: 0;
|
||||
|
||||
&:not(.starting) {
|
||||
transition-duration: .4s;
|
||||
transition-duration: 0.4s;
|
||||
transition-property: color, margin;
|
||||
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
|
||||
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
@ -91,7 +86,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME NAV-LINK
|
||||
.nav-link.home {
|
||||
cursor: pointer;
|
||||
@ -104,7 +98,7 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
top: 12px;
|
||||
height: 40px;
|
||||
|
||||
@media(max-width: 992px) {
|
||||
@media (max-width: 992px) {
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
@ -112,7 +106,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TOP MENU
|
||||
aio-top-menu {
|
||||
display: flex;
|
||||
@ -158,7 +151,6 @@ aio-top-menu {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SEARCH BOX
|
||||
aio-search-box.search-container {
|
||||
display: flex;
|
||||
@ -181,7 +173,7 @@ aio-search-box.search-container {
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@include bp(big) {
|
||||
@ -196,3 +188,25 @@ aio-search-box.search-container {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EXTERNAL LINK ICONS
|
||||
.app-toolbar {
|
||||
.toolbar-external-icons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 16px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +178,7 @@
|
||||
/guide/service-worker-getstart /guide/service-worker-getting-started
|
||||
/guide/service-worker-comm /guide/service-worker-communications
|
||||
/guide/service-worker-configref /guide/service-worker-config
|
||||
/guide/webpack https://v5.angular.io/guide/webpack
|
||||
/news https://blog.angular.io/
|
||||
/news.html https://blog.angular.io/
|
||||
/testing /guide/testing
|
||||
|
@ -18,7 +18,6 @@ const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
const IGNORED_EXAMPLES = [ // temporary ignores
|
||||
'quickstart',
|
||||
'setup',
|
||||
'upgrade-p'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -8,10 +8,8 @@
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"../node_modules/@types/"
|
||||
]
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": [
|
||||
|
@ -45,16 +45,15 @@
|
||||
"@angular/compiler-cli": "^6.0.0",
|
||||
"@angular/language-service": "^6.0.0",
|
||||
"@angular/platform-server": "^6.0.0",
|
||||
"@types/angular": "^1.5.16",
|
||||
"@types/angular-animate": "^1.5.5",
|
||||
"@types/angular-cookies": "^1.4.2",
|
||||
"@types/angular-mocks": "^1.5.5",
|
||||
"@types/angular-resource": "^1.5.6",
|
||||
"@types/angular-route": "^1.3.2",
|
||||
"@types/angular-sanitize": "^1.3.3",
|
||||
"@types/angular": "^1.6.47",
|
||||
"@types/angular-animate": "^1.5.10",
|
||||
"@types/angular-mocks": "^1.6.0",
|
||||
"@types/angular-resource": "^1.5.14",
|
||||
"@types/angular-route": "^1.3.5",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/jasmine": "~2.8.0",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/jquery": "^3.3.4",
|
||||
"@types/node": "^6.0.45",
|
||||
"canonical-path": "0.0.2",
|
||||
"concurrently": "^3.0.0",
|
||||
|
@ -240,45 +240,33 @@
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
|
||||
"@types/angular-animate@^1.5.5":
|
||||
version "1.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-animate/-/angular-animate-1.5.8.tgz#578e058ee0ca5539e1795421a91ae2f52581dc8f"
|
||||
"@types/angular-animate@^1.5.10":
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-animate/-/angular-animate-1.5.10.tgz#b94b45358c61163f1478768e8b081c76439c515f"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular-cookies@^1.4.2":
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-cookies/-/angular-cookies-1.4.5.tgz#f5ccf5f42a7b9f4d13e77afb8722034ea9f40bd3"
|
||||
"@types/angular-mocks@^1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.6.0.tgz#bd32f55b678c239880d2d0d9a3a79b5cad45547e"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular-mocks@^1.5.5":
|
||||
version "1.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.5.11.tgz#d5bbefbf742f2196071bda0fe051878b6f4fd72c"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular-resource@^1.5.6":
|
||||
"@types/angular-resource@^1.5.14":
|
||||
version "1.5.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-resource/-/angular-resource-1.5.14.tgz#902f34e8c98f708ae99493c6d416b39b4a22d9fe"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular-route@^1.3.2":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-route/-/angular-route-1.3.4.tgz#10d3f7eb313fb8a4b832041f9401869803dcd4df"
|
||||
"@types/angular-route@^1.3.5":
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-route/-/angular-route-1.3.5.tgz#78b8e0b069d5efe55ec7072461f4e2f6ae20767b"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular-sanitize@^1.3.3":
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-sanitize/-/angular-sanitize-1.3.6.tgz#fec2bd040d38708e46f02e66fba5199e8a64b22e"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular@*", "@types/angular@^1.5.16":
|
||||
version "1.6.36"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.36.tgz#15e73d632274b5655a391470844863548c7755f4"
|
||||
"@types/angular@*", "@types/angular@^1.6.47":
|
||||
version "1.6.47"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.47.tgz#f7a31279a02c0892ed9aa76aae2da1b17791bacd"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.16.7"
|
||||
@ -315,6 +303,10 @@
|
||||
dependencies:
|
||||
"@types/jasmine" "*"
|
||||
|
||||
"@types/jquery@^3.3.4":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.4.tgz#f1850fb9a70041a14ace4f81a7ed782db8548317"
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
|
||||
|
@ -32,6 +32,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
.processor(require('./processors/computeSearchTitle'))
|
||||
.processor(require('./processors/simplifyMemberAnchors'))
|
||||
.processor(require('./processors/computeStability'))
|
||||
.processor(require('./processors/removeInjectableConstructors'))
|
||||
|
||||
/**
|
||||
* These are the API doc types that will be rendered to actual files.
|
||||
|
18
aio/tools/transforms/angular-api-package/processors/removeInjectableConstructors.js
vendored
Normal file
18
aio/tools/transforms/angular-api-package/processors/removeInjectableConstructors.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = function removeInjectableConstructors() {
|
||||
return {
|
||||
$runAfter: ['processing-docs', 'splitDescription'],
|
||||
$runBefore: ['docs-processed'],
|
||||
injectableDecorators: ['Injectable', 'Directive', 'Component', 'Pipe', 'NgModule'],
|
||||
$process(docs) {
|
||||
docs.forEach(doc => {
|
||||
if (
|
||||
doc.constructorDoc &&
|
||||
!doc.constructorDoc.shortDescription &&
|
||||
doc.decorators &&
|
||||
doc.decorators.some(decorator => this.injectableDecorators.indexOf(decorator.name) !== -1)) {
|
||||
delete doc.constructorDoc;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
const processorFactory = require('./removeInjectableConstructors');
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('removeInjectableConstructors processor', () => {
|
||||
|
||||
it('should be available on the injector', () => {
|
||||
const dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
const processor = injector.get('removeInjectableConstructors');
|
||||
expect(processor.$process).toBeDefined();
|
||||
expect(processor.$runAfter).toEqual(['processing-docs', 'splitDescription']);
|
||||
expect(processor.$runBefore).toEqual(['docs-processed']);
|
||||
});
|
||||
|
||||
it('should remove undocumented constructors from docs that have an "Injectable" decorator on them', () => {
|
||||
const processor = processorFactory();
|
||||
const docs = [
|
||||
{ constructorDoc: {} },
|
||||
{ constructorDoc: {}, decorators: [] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Injectable' }] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Component' }] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Directive' }] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Pipe' }] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Other' }, { name: 'Injectable' }] },
|
||||
{ constructorDoc: {}, decorators: [{ name: 'Other' }] },
|
||||
|
||||
{ constructorDoc: { shortDescription: 'Blah' } },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Injectable' }] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Component' }] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Directive' }] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Pipe' }] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Other' }, { name: 'Injectable' }] },
|
||||
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Other' }] },
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(docs[0].constructorDoc).toBeDefined();
|
||||
expect(docs[1].constructorDoc).toBeDefined();
|
||||
expect(docs[2].constructorDoc).toBeUndefined();
|
||||
expect(docs[3].constructorDoc).toBeUndefined();
|
||||
expect(docs[4].constructorDoc).toBeUndefined();
|
||||
expect(docs[5].constructorDoc).toBeUndefined();
|
||||
expect(docs[6].constructorDoc).toBeUndefined();
|
||||
expect(docs[7].constructorDoc).toBeDefined();
|
||||
|
||||
expect(docs[8].constructorDoc).toBeDefined();
|
||||
expect(docs[9].constructorDoc).toBeDefined();
|
||||
expect(docs[10].constructorDoc).toBeDefined();
|
||||
expect(docs[11].constructorDoc).toBeDefined();
|
||||
expect(docs[12].constructorDoc).toBeDefined();
|
||||
expect(docs[13].constructorDoc).toBeDefined();
|
||||
expect(docs[14].constructorDoc).toBeDefined();
|
||||
expect(docs[15].constructorDoc).toBeDefined();
|
||||
});
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
{% import "lib/memberHelpers.html" as memberHelper -%}
|
||||
|
||||
<section class="{$ doc.docType $}-overview">
|
||||
<code-example language="ts" hideCopy="true">
|
||||
<code-example language="ts" hideCopy="true" linenums="false">
|
||||
{% if doc.isAbstract %}abstract {% endif%}{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $}
|
||||
}
|
||||
</code-example>
|
||||
|
@ -40,13 +40,13 @@
|
||||
|
||||
{%- macro renderOverloadInfo(overload, cssClass, method) -%}
|
||||
|
||||
<code-example language="ts" hideCopy="true" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
|
||||
|
||||
{% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %}
|
||||
<div class="short-description">
|
||||
{$ overload.shortDescription | marked $}
|
||||
</div>{% endif %}
|
||||
|
||||
<code-example language="ts" hideCopy="true" linenums="false" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
|
||||
|
||||
<h4 class="no-anchor">Parameters</h4>
|
||||
{$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $}
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
# Supported Public API Surface of Angular
|
||||
|
||||
Our semver, timed-release cycle and deprecation policy currently applies to these npm packages:
|
||||
Our SemVer, timed-release cycle and deprecation policy currently applies to these npm packages:
|
||||
|
||||
- `@angular/animations`
|
||||
- `@angular/core`
|
||||
- `@angular/common`
|
||||
- `@angular/elements`
|
||||
- `@angular/forms`
|
||||
- `@angular/http`
|
||||
- `@angular/platform-browser`
|
||||
- `@angular/platform-browser-dynamic`
|
||||
- `@angular/platform-server`
|
||||
@ -15,13 +12,12 @@ Our semver, timed-release cycle and deprecation policy currently applies to thes
|
||||
- `@angular/platform-webworker-dynamic`
|
||||
- `@angular/upgrade`
|
||||
- `@angular/router`
|
||||
- `@angular/service-worker`
|
||||
- `@angular/forms`
|
||||
- `@angular/http`
|
||||
|
||||
|
||||
One intentional omission from this list is `@angular/compiler`, which is currently considered a low level api and is subject to internal changes. These changes will not affect any applications or libraries using the higher-level apis (the command line interface or JIT compilation via `@angular/platform-browser-dynamic`). Only very specific use-cases require direct access to the compiler API (mostly tooling integration for IDEs, linters, etc). If you are working on this kind of integration, please reach out to us first.
|
||||
|
||||
Package `@angular/bazel` is currently an Angular Labs project and not covered by the public API guarantees.
|
||||
|
||||
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
|
||||
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, will be covered by these or similar guarantees in the future as they mature.
|
||||
@ -35,7 +31,7 @@ Within the supported packages, we provide guarantees for:
|
||||
|
||||
We explicitly don't consider the following to be our public API surface:
|
||||
|
||||
- any file/import paths within our package except for the `/`, `/testing` and `/bundles/*` and other documented package entry-points.
|
||||
- any file/import paths within our package except for the `/`, `/testing` and `/bundles/*`
|
||||
- constructors of injectable classes (services and directives) - please use DI to obtain instances of these classes
|
||||
- any class members or symbols marked as `private`, or prefixed with underscore (`_`) and [barred latin o](https://en.wikipedia.org/wiki/%C6%9F) (`ɵ`)
|
||||
- extending any of our classes unless the support for this is specifically documented in the API docs
|
||||
|
@ -3,13 +3,13 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod --progress false",
|
||||
"e2e": "ng e2e",
|
||||
"test": "ng test && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
|
||||
"start": "ng serve",
|
||||
"test": "ng test && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false"
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 2.7 can type-check an Angular application",
|
||||
"description": "Assert that users with TypeScript 2.6 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as core from '@angular/core';
|
||||
import * as coreTesting from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as http from '@angular/http';
|
||||
import * as httpTesting from '@angular/http/testing';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
|
||||
export default {
|
||||
compiler,
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
forms,
|
||||
http,
|
||||
httpTesting,
|
||||
platformBrowser,
|
||||
platformBrowserTesting,
|
||||
platformBrowserDynamic,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 2.8 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/http": "file:../../dist/packages-dist/http",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"typescript": "2.8.x",
|
||||
"zone.js": "file:../../node_modules/zone.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tsc"
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../dist/typings_test_ts28/",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.iterable",
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
@ -65,8 +65,6 @@ module.exports = function(config) {
|
||||
'dist/all/@angular/**/*node_only_spec.js',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler-cli/src/ngtsc/**',
|
||||
'dist/all/@angular/compiler-cli/test/ngtsc/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/compiler/test/render3/**',
|
||||
'dist/all/@angular/core/test/bundling/**',
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {TableCell, buildTable, emptyTable} from '../util';
|
||||
|
||||
@ -15,7 +15,7 @@ export class LargeTableComponent {
|
||||
data: TableCell[][] = emptyTable;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<LargeTableComponent> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
|
||||
type: LargeTableComponent,
|
||||
selectors: [['largetable']],
|
||||
template: function(rf: RenderFlags, ctx: LargeTableComponent) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
|
||||
@ -35,7 +35,7 @@ export class TreeComponent {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<TreeComponent> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
|
||||
type: TreeComponent,
|
||||
selectors: [['tree']],
|
||||
template: function(rf: RenderFlags, ctx: TreeComponent) {
|
||||
@ -95,7 +95,7 @@ export class TreeFunction {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<TreeFunction> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
|
||||
type: TreeFunction,
|
||||
selectors: [['tree']],
|
||||
template: function(rf: RenderFlags, ctx: TreeFunction) {
|
||||
|
@ -20,7 +20,7 @@
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "es5",
|
||||
"types": ["angularjs"]
|
||||
"types": ["angular"]
|
||||
},
|
||||
"exclude": [
|
||||
"angular1_router",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "6.1.0-beta.2",
|
||||
"version": "6.0.7",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -40,7 +40,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bazel/ibazel": "^0.1.1",
|
||||
"@types/angularjs": "1.5.14-alpha",
|
||||
"@types/angular": "^1.6.47",
|
||||
"@types/base64-js": "1.2.5",
|
||||
"@types/chai": "^4.1.2",
|
||||
"@types/chokidar": "1.7.3",
|
||||
@ -65,7 +65,7 @@
|
||||
"canonical-path": "0.0.2",
|
||||
"chokidar": "1.7.0",
|
||||
"clang-format": "1.0.41",
|
||||
"cldr": "4.8.0",
|
||||
"cldr": "4.10.0",
|
||||
"cldr-data-downloader": "0.3.2",
|
||||
"cldrjs": "0.5.0",
|
||||
"conventional-changelog": "1.1.0",
|
||||
@ -114,7 +114,7 @@
|
||||
"tslint": "5.7.0",
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"tsutils": "2.20.0",
|
||||
"typescript": "2.8.x",
|
||||
"typescript": "2.7.x",
|
||||
"uglify-es": "^3.3.9",
|
||||
"universal-analytics": "0.4.15",
|
||||
"vlq": "0.2.2",
|
||||
|
@ -24,10 +24,7 @@ ng_package(
|
||||
"//packages/animations/browser/testing:package.json",
|
||||
],
|
||||
entry_point = "packages/animations/index.js",
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [
|
||||
":animations",
|
||||
"//packages/animations/browser",
|
||||
|
@ -15,7 +15,12 @@ const DEFAULT_FILL_MODE = 'forwards';
|
||||
const DEFAULT_EASING = 'linear';
|
||||
const ANIMATION_END_EVENT = 'animationend';
|
||||
|
||||
export const enum AnimatorControlState {INITIALIZED = 1, STARTED = 2, FINISHED = 3, DESTROYED = 4}
|
||||
export enum AnimatorControlState {
|
||||
INITIALIZED = 1,
|
||||
STARTED = 2,
|
||||
FINISHED = 3,
|
||||
DESTROYED = 4
|
||||
}
|
||||
|
||||
export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
@ -29,8 +34,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
public readonly totalTime: number;
|
||||
public readonly easing: string;
|
||||
public currentSnapshot: {[key: string]: string} = {};
|
||||
|
||||
private _state: AnimatorControlState = 0;
|
||||
public state = 0;
|
||||
|
||||
constructor(
|
||||
public readonly element: any, public readonly keyframes: {[key: string]: string | number}[],
|
||||
@ -50,8 +54,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
destroy() {
|
||||
this.init();
|
||||
if (this._state >= AnimatorControlState.DESTROYED) return;
|
||||
this._state = AnimatorControlState.DESTROYED;
|
||||
if (this.state >= AnimatorControlState.DESTROYED) return;
|
||||
this.state = AnimatorControlState.DESTROYED;
|
||||
this._styler.destroy();
|
||||
this._flushStartFns();
|
||||
this._flushDoneFns();
|
||||
@ -71,8 +75,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
finish() {
|
||||
this.init();
|
||||
if (this._state >= AnimatorControlState.FINISHED) return;
|
||||
this._state = AnimatorControlState.FINISHED;
|
||||
if (this.state >= AnimatorControlState.FINISHED) return;
|
||||
this.state = AnimatorControlState.FINISHED;
|
||||
this._styler.finish();
|
||||
this._flushStartFns();
|
||||
this._flushDoneFns();
|
||||
@ -82,10 +86,10 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
getPosition(): number { return this._styler.getPosition(); }
|
||||
|
||||
hasStarted(): boolean { return this._state >= AnimatorControlState.STARTED; }
|
||||
hasStarted(): boolean { return this.state >= AnimatorControlState.STARTED; }
|
||||
init(): void {
|
||||
if (this._state >= AnimatorControlState.INITIALIZED) return;
|
||||
this._state = AnimatorControlState.INITIALIZED;
|
||||
if (this.state >= AnimatorControlState.INITIALIZED) return;
|
||||
this.state = AnimatorControlState.INITIALIZED;
|
||||
const elm = this.element;
|
||||
this._styler.apply();
|
||||
if (this._delay) {
|
||||
@ -97,7 +101,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this.init();
|
||||
if (!this.hasStarted()) {
|
||||
this._flushStartFns();
|
||||
this._state = AnimatorControlState.STARTED;
|
||||
this.state = AnimatorControlState.STARTED;
|
||||
}
|
||||
this._styler.resume();
|
||||
}
|
||||
@ -133,7 +137,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this.init();
|
||||
const styles: {[key: string]: string} = {};
|
||||
if (this.hasStarted()) {
|
||||
const finished = this._state >= AnimatorControlState.FINISHED;
|
||||
const finished = this.state >= AnimatorControlState.FINISHED;
|
||||
Object.keys(this._finalStyles).forEach(prop => {
|
||||
if (prop != 'offset') {
|
||||
styles[prop] = finished ? this._finalStyles[prop] : computeStyle(this.element, prop);
|
||||
|
@ -14,6 +14,10 @@ export function isBrowser() {
|
||||
return (typeof window !== 'undefined' && typeof window.document !== 'undefined');
|
||||
}
|
||||
|
||||
export function isNode() {
|
||||
return (typeof process !== 'undefined');
|
||||
}
|
||||
|
||||
export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
|
||||
switch (players.length) {
|
||||
case 0:
|
||||
@ -142,11 +146,14 @@ let _query: (element: any, selector: string, multi: boolean) => any[] =
|
||||
return [];
|
||||
};
|
||||
|
||||
if (isBrowser()) {
|
||||
// Define utility methods for browsers and platform-server(domino) where Element
|
||||
// and utility methods exist.
|
||||
const _isNode = isNode();
|
||||
if (_isNode || typeof Element !== 'undefined') {
|
||||
// this is well supported in all browsers
|
||||
_contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; };
|
||||
|
||||
if (Element.prototype.matches) {
|
||||
if (_isNode || Element.prototype.matches) {
|
||||
_matches = (element: any, selector: string) => element.matches(selector);
|
||||
} else {
|
||||
const proto = Element.prototype as any;
|
||||
|
@ -8,6 +8,7 @@
|
||||
import {AnimateTimings, AnimationMetadata, AnimationMetadataType, AnimationOptions, sequence, ɵStyleData} from '@angular/animations';
|
||||
import {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast';
|
||||
import {AnimationDslVisitor} from './dsl/animation_dsl_visitor';
|
||||
import {isNode} from './render/shared';
|
||||
|
||||
export const ONE_SECOND = 1000;
|
||||
|
||||
@ -125,12 +126,47 @@ export function copyStyles(
|
||||
return destination;
|
||||
}
|
||||
|
||||
function getStyleAttributeString(element: any, key: string, value: string) {
|
||||
// Return the key-value pair string to be added to the style attribute for the
|
||||
// given CSS style key.
|
||||
if (value) {
|
||||
return key + ':' + value + ';';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function writeStyleAttribute(element: any) {
|
||||
// Read the style property of the element and manually reflect it to the
|
||||
// style attribute. This is needed because Domino on platform-server doesn't
|
||||
// understand the full set of allowed CSS properties and doesn't reflect some
|
||||
// of them automatically.
|
||||
let styleAttrValue = '';
|
||||
for (let i = 0; i < element.style.length; i++) {
|
||||
const key = element.style.item(i);
|
||||
styleAttrValue += getStyleAttributeString(element, key, element.style.getPropertyValue(key));
|
||||
}
|
||||
for (const key in element.style) {
|
||||
// Skip internal Domino properties that don't need to be reflected.
|
||||
if (!element.style.hasOwnProperty(key) || key.startsWith('_')) {
|
||||
continue;
|
||||
}
|
||||
const dashKey = camelCaseToDashCase(key);
|
||||
styleAttrValue += getStyleAttributeString(element, dashKey, element.style[key]);
|
||||
}
|
||||
element.setAttribute('style', styleAttrValue);
|
||||
}
|
||||
|
||||
export function setStyles(element: any, styles: ɵStyleData) {
|
||||
if (element['style']) {
|
||||
Object.keys(styles).forEach(prop => {
|
||||
const camelProp = dashCaseToCamelCase(prop);
|
||||
element.style[camelProp] = styles[prop];
|
||||
});
|
||||
// On the server set the 'style' attribute since it's not automatically reflected.
|
||||
if (isNode()) {
|
||||
writeStyleAttribute(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +176,10 @@ export function eraseStyles(element: any, styles: ɵStyleData) {
|
||||
const camelProp = dashCaseToCamelCase(prop);
|
||||
element.style[camelProp] = '';
|
||||
});
|
||||
// On the server set the 'style' attribute since it's not automatically reflected.
|
||||
if (isNode()) {
|
||||
writeStyleAttribute(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,6 +271,10 @@ export function dashCaseToCamelCase(input: string): string {
|
||||
return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase());
|
||||
}
|
||||
|
||||
function camelCaseToDashCase(input: string): string {
|
||||
return input.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
|
||||
export function allowPreviousPlayerStylesMerge(duration: number, delay: number) {
|
||||
return duration === 0 || delay === 0;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
"files": [
|
||||
"public_api.ts",
|
||||
"../../../node_modules/@types/node/index.d.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts",
|
||||
"../../system.d.ts"
|
||||
],
|
||||
|
@ -40,11 +40,12 @@ export declare type AnimateTimings = {
|
||||
|
||||
/**
|
||||
* @description Options that control animation styling and timing.
|
||||
*
|
||||
* The following animation functions accept `AnimationOptions` data:
|
||||
*
|
||||
* - `transition()`
|
||||
* - `sequence()`
|
||||
* - `group()`
|
||||
* - `{@link animations/group group()}`
|
||||
* - `query()`
|
||||
* - `animation()`
|
||||
* - `useAnimation()`
|
||||
@ -100,7 +101,7 @@ export const enum AnimationMetadataType {
|
||||
Sequence = 2,
|
||||
/**
|
||||
* Contains a set of animation steps.
|
||||
* See `group()`
|
||||
* See `{@link animations/group group()}`
|
||||
*/
|
||||
Group = 3,
|
||||
/**
|
||||
@ -352,7 +353,7 @@ export interface AnimationSequenceMetadata extends AnimationMetadata {
|
||||
|
||||
/**
|
||||
* Encapsulates an animation group.
|
||||
* Instantiated and returned by the `group()` function.
|
||||
* Instantiated and returned by the `{@link animations/group group()}` function.
|
||||
*/
|
||||
export interface AnimationGroupMetadata extends AnimationMetadata {
|
||||
/**
|
||||
@ -579,7 +580,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
|
||||
* @returns An object that encapsulates the animation step.
|
||||
*
|
||||
* @usageNotes
|
||||
* Call within an animation `sequence()`, `group()`, or
|
||||
* Call within an animation `sequence()`, `{@link animations/group group()}`, or
|
||||
* `transition()` call to specify an animation step
|
||||
* that applies given style data to the parent animation for a given amount of time.
|
||||
*
|
||||
@ -676,9 +677,9 @@ export function group(
|
||||
* @usageNotes
|
||||
* When you pass an array of steps to a
|
||||
* `transition()` call, the steps run sequentially by default.
|
||||
* Compare this to the `group()` call, which runs animation steps in parallel.
|
||||
* Compare this to the `{@link animations/group group()}` call, which runs animation steps in parallel.
|
||||
*
|
||||
* When a sequence is used within a `group()` or a `transition()` call,
|
||||
* When a sequence is used within a `{@link animations/group group()}` or a `transition()` call,
|
||||
* execution continues to the next instruction only after each of the inner animation
|
||||
* steps have completed.
|
||||
*
|
||||
@ -863,7 +864,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* ...]
|
||||
* ```
|
||||
*
|
||||
* Note that when you call the `sequence()` function within a `group()`
|
||||
* Note that when you call the `sequence()` function within a `{@link animations/group group()}`
|
||||
* or a `transition()` call, execution does not continue to the next instruction
|
||||
* until each of the inner animation steps have completed.
|
||||
*
|
||||
|
@ -13,7 +13,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler-cli": "0.0.0-PLACEHOLDER",
|
||||
"typescript": ">=2.7.2 <2.9"
|
||||
"typescript": ">=2.7.2 <2.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -14,90 +14,6 @@ load(":rules_typescript.bzl",
|
||||
"ts_providers_dict_to_struct",
|
||||
)
|
||||
|
||||
def _compile_strategy(ctx):
|
||||
"""Detect which strategy should be used to implement ng_module.
|
||||
|
||||
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
|
||||
can be implemented in various ways. This function reads the configuration passed by the user and
|
||||
determines which mode is active.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx
|
||||
"""
|
||||
|
||||
strategy = 'legacy'
|
||||
if 'compile' in ctx.var:
|
||||
strategy = ctx.var['compile']
|
||||
|
||||
if strategy not in ['legacy', 'local', 'jit']:
|
||||
fail("Unknown --define=compile value '%s'" % strategy)
|
||||
|
||||
if strategy == 'legacy' and hasattr(ctx.attr, '_global_mode') and ctx.attr._global_mode:
|
||||
strategy = 'global'
|
||||
|
||||
return strategy
|
||||
|
||||
def _compiler_name(ctx):
|
||||
"""Selects a user-visible name depending on the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the name of the current compiler to be displayed in build output
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return 'ngc'
|
||||
elif strategy == 'global':
|
||||
return 'ngc.ivy'
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
|
||||
def _enable_ivy_value(ctx):
|
||||
"""Determines the value of the enableIvy option in the generated tsconfig.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return False
|
||||
elif strategy == 'global':
|
||||
return True
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
|
||||
def _include_ng_files(ctx):
|
||||
"""Determines whether Angular outputs will be produced by the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the current compilation strategy will produce View Engine compilation outputs (such as
|
||||
factory files), false otherwise
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
return strategy == 'legacy' or strategy == 'global'
|
||||
|
||||
def _basename_of(ctx, file):
|
||||
ext_len = len(".ts")
|
||||
if file.short_path.endswith(".ng.html"):
|
||||
@ -145,8 +61,6 @@ def _should_produce_flat_module_outs(ctx):
|
||||
# in the library. Most of these will be produced as empty files but it is
|
||||
# unknown, without parsing, which will be empty.
|
||||
def _expected_outs(ctx):
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
devmode_js_files = []
|
||||
closure_js_files = []
|
||||
declaration_files = []
|
||||
@ -164,7 +78,7 @@ def _expected_outs(ctx):
|
||||
|
||||
if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
|
||||
basename = short_path[len(package_prefix):-len(".ts")]
|
||||
if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set):
|
||||
if len(factory_basename_set) == 0 or basename in factory_basename_set:
|
||||
devmode_js = [
|
||||
".ngfactory.js",
|
||||
".ngsummary.js",
|
||||
@ -176,7 +90,7 @@ def _expected_outs(ctx):
|
||||
devmode_js = [".js"]
|
||||
summaries = []
|
||||
metadata = []
|
||||
elif include_ng_files and short_path.endswith(".css"):
|
||||
elif short_path.endswith(".css"):
|
||||
basename = short_path[len(package_prefix):-len(".css")]
|
||||
devmode_js = [
|
||||
".css.shim.ngstyle.js",
|
||||
@ -199,7 +113,7 @@ def _expected_outs(ctx):
|
||||
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]
|
||||
|
||||
# We do this just when producing a flat module index for a publishable ng_module
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
flat_module_out = _flat_module_out_file(ctx)
|
||||
devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
|
||||
closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out))
|
||||
@ -209,12 +123,7 @@ def _expected_outs(ctx):
|
||||
else:
|
||||
bundle_index_typings = None
|
||||
|
||||
# TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
|
||||
# when ngtsc can extract messages
|
||||
if include_ng_files:
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
else:
|
||||
i18n_messages_files = []
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
|
||||
return struct(
|
||||
closure_js = closure_js_files,
|
||||
@ -226,9 +135,14 @@ def _expected_outs(ctx):
|
||||
i18n_messages = i18n_messages_files,
|
||||
)
|
||||
|
||||
def _ivy_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs)
|
||||
|
||||
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs)
|
||||
|
||||
def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs):
|
||||
outs = _expected_outs(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
if "devmode_manifest" in kwargs:
|
||||
expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
else:
|
||||
@ -238,9 +152,8 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
"enableResourceInlining": ctx.attr.inline_resources,
|
||||
"generateCodeForLibraries": False,
|
||||
"allowEmptyCodegenFiles": True,
|
||||
# Summaries are only enabled if Angular outputs are to be produced.
|
||||
"enableSummariesForJit": include_ng_files,
|
||||
"enableIvy": _enable_ivy_value(ctx),
|
||||
"enableSummariesForJit": True,
|
||||
"enableIvy": enable_ivy,
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||
@ -303,10 +216,8 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
|
||||
the parameters of the compilation which will be used to replay the ngc action for i18N.
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
mnemonic = "AngularTemplateCompile"
|
||||
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
|
||||
progress_message = "Compiling Angular templates (ngc) %s" % label
|
||||
|
||||
if locale:
|
||||
mnemonic = "AngularI18NMerging"
|
||||
@ -340,7 +251,7 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
|
||||
},
|
||||
)
|
||||
|
||||
if include_ng_files and messages_out != None:
|
||||
if messages_out != None:
|
||||
ctx.actions.run(
|
||||
inputs = list(inputs),
|
||||
outputs = messages_out,
|
||||
@ -402,7 +313,7 @@ def _ts_expected_outs(ctx, label):
|
||||
_ignored = [label]
|
||||
return _expected_outs(ctx)
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
def ng_module_impl(ctx, ts_compile_actions, ivy = False):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
This is exposed so that google3 can have its own entry point that re-uses this
|
||||
@ -411,30 +322,29 @@ def ng_module_impl(ctx, ts_compile_actions):
|
||||
Args:
|
||||
ctx: the skylark rule context
|
||||
ts_compile_actions: generates all the actions to run an ngc compilation
|
||||
ivy: if True, run the compiler in Ivy mode (internal only)
|
||||
|
||||
Returns:
|
||||
the result of the ng_module rule as a dict, suitable for
|
||||
conversion by ts_providers_dict_to_struct
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
|
||||
|
||||
providers = ts_compile_actions(
|
||||
ctx, is_library=True, compile_action=_prodmode_compile_action,
|
||||
devmode_compile_action=_devmode_compile_action,
|
||||
tsc_wrapped_tsconfig=_ngc_tsconfig,
|
||||
tsc_wrapped_tsconfig=tsconfig,
|
||||
outputs = _ts_expected_outs)
|
||||
|
||||
outs = _expected_outs(ctx)
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files:
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
if len(outs.metadata) > 1:
|
||||
fail("expecting exactly one metadata output for " + str(ctx.label))
|
||||
|
||||
@ -450,6 +360,9 @@ def ng_module_impl(ctx, ts_compile_actions):
|
||||
def _ng_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
|
||||
|
||||
def _ivy_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True))
|
||||
|
||||
NG_MODULE_ATTRIBUTES = {
|
||||
"srcs": attr.label_list(allow_files = [".ts"]),
|
||||
|
||||
@ -516,16 +429,11 @@ ng_module = rule(
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
||||
|
||||
# TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information.
|
||||
# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used
|
||||
# instead, and should be removed once ngtsc is capable of fulfilling the same requirements.
|
||||
internal_global_ng_module = rule(
|
||||
implementation = _ng_module_impl,
|
||||
attrs = dict(NG_MODULE_RULE_ATTRS, **{
|
||||
"_global_mode": attr.bool(
|
||||
default = True,
|
||||
),
|
||||
}),
|
||||
# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
|
||||
# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
|
||||
# removed and its usages refactored to use ng_module() directly.
|
||||
internal_ivy_ng_module = rule(
|
||||
implementation = _ivy_module_impl,
|
||||
attrs = NG_MODULE_RULE_ATTRS,
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
@ -27,29 +27,6 @@ PLUGIN_CONFIG="{sideEffectFreeModules: [\n%s]}" % ",\n".join(
|
||||
BO_ROLLUP="angular_devkit/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js"
|
||||
BO_PLUGIN="require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG)
|
||||
|
||||
def _use_plain_rollup(ctx):
|
||||
"""Determine whether to use the Angular or upstream versions of the rollup_bundle rule.
|
||||
|
||||
In legacy mode, the Angular version of rollup is used. This runs build optimizer as part of its
|
||||
processing, which affects decorators and annotations.
|
||||
|
||||
In other modes, an emulation of the upstream rollup_bundle rule is used. This avoids running
|
||||
build optimizer on code which isn't designed to be optimized by it.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the Angular version of rollup with build optimizer should be used, false otherwise
|
||||
"""
|
||||
|
||||
if 'compile' not in ctx.var:
|
||||
return False
|
||||
|
||||
strategy = ctx.var['compile']
|
||||
return strategy != 'legacy'
|
||||
|
||||
|
||||
def run_brotli(ctx, input, output):
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._brotli,
|
||||
@ -58,41 +35,7 @@ def run_brotli(ctx, input, output):
|
||||
arguments = ["--output=%s" % output.path, input.path],
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs
|
||||
def _run_tsc(ctx, input, output):
|
||||
args = ctx.actions.args()
|
||||
args.add("--target", "es5")
|
||||
args.add("--allowJS")
|
||||
args.add(input)
|
||||
args.add("--outFile", output)
|
||||
|
||||
ctx.action(
|
||||
executable = ctx.executable._tsc,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = [args]
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs, with the addition of brotli compression output
|
||||
def _plain_rollup_bundle(ctx):
|
||||
rollup_config = write_rollup_config(ctx)
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es6)
|
||||
_run_tsc(ctx, ctx.outputs.build_es6, ctx.outputs.build_es5)
|
||||
source_map = run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min)
|
||||
run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True)
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html)
|
||||
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
files = [ctx.outputs.build_es5_min, source_map]
|
||||
return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files))
|
||||
|
||||
def _ng_rollup_bundle(ctx):
|
||||
# Escape and use the plain rollup rule if the compilation strategy requires it
|
||||
if _use_plain_rollup(ctx):
|
||||
return _plain_rollup_bundle(ctx)
|
||||
|
||||
# We don't expect anyone to make use of this bundle yet, but it makes this rule
|
||||
# compatible with rollup_bundle which allows them to be easily swapped back and
|
||||
# forth.
|
||||
|
@ -119,15 +119,6 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
compilerOpts.annotationsAs = 'static fields';
|
||||
}
|
||||
|
||||
// Detect from compilerOpts whether the entrypoint is being invoked in Ivy mode.
|
||||
const isInIvyMode = compilerOpts.enableIvy === 'ngtsc' || compilerOpts.enableIvy === 'tsc';
|
||||
|
||||
// Disable downleveling and Closure annotation if in Ivy mode.
|
||||
if (isInIvyMode) {
|
||||
compilerOpts.annotateForClosureCompiler = false;
|
||||
compilerOpts.annotationsAs = 'decorators';
|
||||
}
|
||||
|
||||
if (!compilerOpts.rootDirs) {
|
||||
throw new Error('rootDirs is not set!');
|
||||
}
|
||||
@ -181,12 +172,6 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
const bazelHost = new CompilerHost(
|
||||
files, compilerOpts, bazelOpts, tsHost, fileLoader, allowNonHermeticReads,
|
||||
generatedFileModuleResolver);
|
||||
|
||||
// Also need to disable decorator downleveling in the BazelHost in Ivy mode.
|
||||
if (isInIvyMode) {
|
||||
bazelHost.transformDecorators = false;
|
||||
}
|
||||
|
||||
// Prevent tsickle adding any types at all if we don't want closure compiler annotations.
|
||||
bazelHost.transformTypesToClosure = compilerOpts.annotateForClosureCompiler;
|
||||
const origBazelHostFileExist = bazelHost.fileExists;
|
||||
|
@ -51,12 +51,12 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
|
||||
gc() { return this._driver.executeScript('window.gc()'); }
|
||||
|
||||
async timeBegin(name: string): Promise<any> {
|
||||
timeBegin(name: string): Promise<any> {
|
||||
if (this._firstRun) {
|
||||
this._firstRun = false;
|
||||
// Before the first run, read out the existing performance logs
|
||||
// so that the chrome buffer does not fill up.
|
||||
await this._driver.logs('performance');
|
||||
this._driver.logs('performance');
|
||||
}
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
}
|
||||
|
@ -27,10 +27,7 @@ ng_package(
|
||||
],
|
||||
entry_point = "packages/common/index.js",
|
||||
packages = ["//packages/common/locales:package"],
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [
|
||||
"//packages/common",
|
||||
"//packages/common/http",
|
||||
|
@ -19,12 +19,13 @@ import {BrowserXhr, HttpXhrBackend, XhrFactory} from './xhr';
|
||||
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_HEADER_NAME} from './xsrf';
|
||||
|
||||
/**
|
||||
* An `HttpHandler` that applies a bunch of `HttpInterceptor`s
|
||||
* An injectable `HttpHandler` that applies multiple interceptors
|
||||
* to a request before passing it to the given `HttpBackend`.
|
||||
*
|
||||
* The interceptors are loaded lazily from the injector, to allow
|
||||
* interceptors to themselves inject classes depending indirectly
|
||||
* on `HttpInterceptingHandler` itself.
|
||||
* @see `HttpInterceptor`
|
||||
*/
|
||||
@Injectable()
|
||||
export class HttpInterceptingHandler implements HttpHandler {
|
||||
@ -42,6 +43,23 @@ export class HttpInterceptingHandler implements HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an `HttpHandler` that applies interceptors
|
||||
* to a request before passing it to the given `HttpBackend`.
|
||||
*
|
||||
* Use as a factory function within `HttpClientModule`.
|
||||
*
|
||||
*
|
||||
*/
|
||||
export function interceptingHandler(
|
||||
backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler {
|
||||
if (!interceptors) {
|
||||
return backend;
|
||||
}
|
||||
return interceptors.reduceRight(
|
||||
(next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function that determines where to store JSONP callbacks.
|
||||
*
|
||||
@ -58,14 +76,14 @@ export function jsonpCallbackContext(): Object {
|
||||
}
|
||||
|
||||
/**
|
||||
* `NgModule` which adds XSRF protection support to outgoing requests.
|
||||
* An NgModule that adds XSRF protection support to outgoing requests.
|
||||
*
|
||||
* Provided the server supports a cookie-based XSRF protection system, this
|
||||
* module can be used directly to configure XSRF protection with the correct
|
||||
* For a server that supports a cookie-based XSRF protection system,
|
||||
* use directly to configure XSRF protection with the correct
|
||||
* cookie and header names.
|
||||
*
|
||||
* If no such names are provided, the default is to use `X-XSRF-TOKEN` for
|
||||
* the header name and `XSRF-TOKEN` for the cookie name.
|
||||
* If no names are supplied, the default cookie name is `XSRF-TOKEN`
|
||||
* and the default header name is `X-XSRF-TOKEN`.
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ -92,8 +110,12 @@ export class HttpClientXsrfModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure XSRF protection to use the given cookie name or header name,
|
||||
* or the default names (as described above) if not provided.
|
||||
* Configure XSRF protection.
|
||||
* @param options An object that can specify either or both
|
||||
* cookie name or header name.
|
||||
* - Cookie name default is `XSRF-TOKEN`.
|
||||
* - Header name default is `X-XSRF-TOKEN`.
|
||||
*
|
||||
*/
|
||||
static withOptions(options: {
|
||||
cookieName?: string,
|
||||
@ -110,7 +132,7 @@ export class HttpClientXsrfModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* `NgModule` which provides the `HttpClient` and associated services.
|
||||
* An NgModule that provides the `HttpClient` and associated services.
|
||||
*
|
||||
* Interceptors can be added to the chain behind `HttpClient` by binding them
|
||||
* to the multiprovider for `HTTP_INTERCEPTORS`.
|
||||
@ -118,12 +140,18 @@ export class HttpClientXsrfModule {
|
||||
*
|
||||
*/
|
||||
@NgModule({
|
||||
/**
|
||||
* Optional configuration for XSRF protection.
|
||||
*/
|
||||
imports: [
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'XSRF-TOKEN',
|
||||
headerName: 'X-XSRF-TOKEN',
|
||||
}),
|
||||
],
|
||||
/**
|
||||
* The module provides `HttpClient` itself, and supporting services.
|
||||
*/
|
||||
providers: [
|
||||
HttpClient,
|
||||
{provide: HttpHandler, useClass: HttpInterceptingHandler},
|
||||
@ -137,7 +165,7 @@ export class HttpClientModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* `NgModule` which enables JSONP support in `HttpClient`.
|
||||
* An NgModule that enables JSONP support in `HttpClient`.
|
||||
*
|
||||
* Without this module, Jsonp requests will reach the backend
|
||||
* with method JSONP, where they'll be rejected.
|
||||
|
@ -2947,7 +2947,7 @@ export const locale_lv = [
|
||||
function plural_mk(n: number): number {
|
||||
let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length,
|
||||
f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0;
|
||||
if (v === 0 && i % 10 === 1 || f % 10 === 1) return 1;
|
||||
if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11)) return 1;
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ const u = undefined;
|
||||
function plural(n: number): number {
|
||||
let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length,
|
||||
f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0;
|
||||
if (v === 0 && i % 10 === 1 || f % 10 === 1) return 1;
|
||||
if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11)) return 1;
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,7 @@ export {parseCookieValue as ɵparseCookieValue} from './cookie';
|
||||
export {CommonModule, DeprecatedI18NPipesModule} from './common_module';
|
||||
export {NgClass, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
||||
export {DOCUMENT} from './dom_tokens';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||
export {DeprecatedDatePipe, DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './pipes/deprecated/index';
|
||||
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
|
||||
export {VERSION} from './version';
|
||||
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
|
||||
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, OnChanges, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
|
||||
|
||||
export class NgForOfContext<T> {
|
||||
constructor(
|
||||
@ -93,12 +93,8 @@ export class NgForOfContext<T> {
|
||||
*
|
||||
*/
|
||||
@Directive({selector: '[ngFor][ngForOf]'})
|
||||
export class NgForOf<T> implements DoCheck {
|
||||
@Input()
|
||||
set ngForOf(ngForOf: NgIterable<T>) {
|
||||
this._ngForOf = ngForOf;
|
||||
this._ngForOfDirty = true;
|
||||
}
|
||||
export class NgForOf<T> implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: NgIterable<T>;
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFunction<T>) {
|
||||
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
||||
@ -114,8 +110,6 @@ export class NgForOf<T> implements DoCheck {
|
||||
|
||||
get ngForTrackBy(): TrackByFunction<T> { return this._trackByFn; }
|
||||
|
||||
private _ngForOf: NgIterable<T>;
|
||||
private _ngForOfDirty: boolean = true;
|
||||
private _differ: IterableDiffer<T>|null = null;
|
||||
private _trackByFn: TrackByFunction<T>;
|
||||
|
||||
@ -133,11 +127,10 @@ export class NgForOf<T> implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (this._ngForOfDirty) {
|
||||
this._ngForOfDirty = false;
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('ngForOf' in changes) {
|
||||
// React on ngForOf changes only once all inputs have been initialized
|
||||
const value = this._ngForOf;
|
||||
const value = changes['ngForOf'].currentValue;
|
||||
if (!this._differ && value) {
|
||||
try {
|
||||
this._differ = this._differs.find(value).create(this.ngForTrackBy);
|
||||
@ -147,8 +140,11 @@ export class NgForOf<T> implements DoCheck {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (this._differ) {
|
||||
const changes = this._differ.diff(this._ngForOf);
|
||||
const changes = this._differ.diff(this.ngForOf);
|
||||
if (changes) this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
@ -159,7 +155,7 @@ export class NgForOf<T> implements DoCheck {
|
||||
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (item.previousIndex == null) {
|
||||
const view = this._viewContainer.createEmbeddedView(
|
||||
this._template, new NgForOfContext<T>(null !, this._ngForOf, -1, -1), currentIndex);
|
||||
this._template, new NgForOfContext<T>(null !, this.ngForOf, -1, -1), currentIndex);
|
||||
const tuple = new RecordViewTuple<T>(item, view);
|
||||
insertTuples.push(tuple);
|
||||
} else if (currentIndex == null) {
|
||||
|
File diff suppressed because one or more lines are too long
@ -15,11 +15,48 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Uses the function {@link formatDate} to format a date according to locale rules.
|
||||
* Formats a date value according to locale rules.
|
||||
*
|
||||
* The following tabled describes the formatting options.
|
||||
* Only the `en-US` locale data comes with Angular. To localize dates
|
||||
* in another language, you must import the corresponding locale data.
|
||||
* See the [I18n guide](guide/i18n#i18n-pipes) for more information.
|
||||
*
|
||||
* | Field Type | Format | Description | Example Value |
|
||||
* @see `formatDate()`
|
||||
*
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* The result of this pipe is not reevaluated when the input is mutated. To avoid the need to
|
||||
* reformat the date on every change-detection cycle, treat the date as an immutable object
|
||||
* and change the reference when the pipe needs to run again.
|
||||
*
|
||||
* ### Pre-defined format options
|
||||
*
|
||||
* Examples are given in `en-US` locale.
|
||||
*
|
||||
* - `'short'`: equivalent to `'M/d/yy, h:mm a'` (`6/15/15, 9:03 AM`).
|
||||
* - `'medium'`: equivalent to `'MMM d, y, h:mm:ss a'` (`Jun 15, 2015, 9:03:01 AM`).
|
||||
* - `'long'`: equivalent to `'MMMM d, y, h:mm:ss a z'` (`June 15, 2015 at 9:03:01 AM
|
||||
* GMT+1`).
|
||||
* - `'full'`: equivalent to `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (`Monday, June 15, 2015 at
|
||||
* 9:03:01 AM GMT+01:00`).
|
||||
* - `'shortDate'`: equivalent to `'M/d/yy'` (`6/15/15`).
|
||||
* - `'mediumDate'`: equivalent to `'MMM d, y'` (`Jun 15, 2015`).
|
||||
* - `'longDate'`: equivalent to `'MMMM d, y'` (`June 15, 2015`).
|
||||
* - `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` (`Monday, June 15, 2015`).
|
||||
* - `'shortTime'`: equivalent to `'h:mm a'` (`9:03 AM`).
|
||||
* - `'mediumTime'`: equivalent to `'h:mm:ss a'` (`9:03:01 AM`).
|
||||
* - `'longTime'`: equivalent to `'h:mm:ss a z'` (`9:03:01 AM GMT+1`).
|
||||
* - `'fullTime'`: equivalent to `'h:mm:ss a zzzz'` (`9:03:01 AM GMT+01:00`).
|
||||
*
|
||||
* ### Custom format options
|
||||
*
|
||||
* You can construct a format string using symbols to specify the components
|
||||
* of a date-time value, as described in the following table.
|
||||
* Format details depend on the locale.
|
||||
* Fields marked with (*) are only available in the extra data set for the given locale.
|
||||
*
|
||||
* | Field type | Format | Description | Example Value |
|
||||
* |--------------------|-------------|---------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | Era | G, GG & GGG | Abbreviated | AD |
|
||||
* | | GGGG | Wide | Anno Domini |
|
||||
@ -75,30 +112,40 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* | | O, OO & OOO | Short localized GMT format | GMT-8 |
|
||||
* | | OOOO | Long localized GMT format | GMT-08:00 |
|
||||
*
|
||||
* Note that timezone correction is not applied to an ISO string that has no time component, such as "2016-09-19"
|
||||
*
|
||||
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
|
||||
* applied and the formatted text will have the same day, month and year of the expression.
|
||||
* ### Format examples
|
||||
*
|
||||
* WARNINGS:
|
||||
* - this pipe has only access to en-US locale data by default. If you want to localize the dates
|
||||
* in another language, you will have to import data for other locales.
|
||||
* See the ["I18n guide"](guide/i18n#i18n-pipes) to know how to import additional locale
|
||||
* data.
|
||||
* - Fields suffixed with * are only available in the extra dataset.
|
||||
* See the ["I18n guide"](guide/i18n#i18n-pipes) to know how to import extra locale
|
||||
* data.
|
||||
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
|
||||
* Instead users should treat the date as an immutable object and change the reference when the
|
||||
* pipe needs to re-run (this is to avoid reformatting the date on every change detection run
|
||||
* which would be an expensive operation).
|
||||
* These examples transform a date into various formats,
|
||||
* assuming that `dateObj` is a JavaScript `Date` object for
|
||||
* year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11,
|
||||
* given in the local time for the `en-US` locale.
|
||||
*
|
||||
* ### Examples
|
||||
* ```
|
||||
* {{ dateObj | date }} // output is 'Jun 15, 2015'
|
||||
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
|
||||
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
|
||||
* {{ dateObj | date:'mmss' }} // output is '43:11'
|
||||
* ```
|
||||
*
|
||||
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
|
||||
* in the _local_ time and locale is 'en-US':
|
||||
* ### Usage example
|
||||
*
|
||||
* {@example common/pipes/ts/date_pipe.ts region='DatePipe'}
|
||||
* The following component uses a date pipe to display the current date in different formats.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: 'date-pipe',
|
||||
* template: `<div>
|
||||
* <p>Today is {{today | date}}</p>
|
||||
* <p>Or if you prefer, {{today | date:'fullDate'}}</p>
|
||||
* <p>The time is {{today | date:'h:mm a z'}}</p>
|
||||
* </div>`
|
||||
* })
|
||||
* // Get the current date and time as a date-time value.
|
||||
* export class DatePipeComponent {
|
||||
* today: number = Date.now();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
// clang-format on
|
||||
@ -107,29 +154,17 @@ export class DatePipe implements PipeTransform {
|
||||
constructor(@Inject(LOCALE_ID) private locale: string) {}
|
||||
|
||||
/**
|
||||
* @param value a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime).
|
||||
* @param format indicates which date/time components to include. The format can be predefined as
|
||||
* shown below (all examples are given for `en-US`) or custom as shown in the table.
|
||||
* - `'short'`: equivalent to `'M/d/yy, h:mm a'` (e.g. `6/15/15, 9:03 AM`).
|
||||
* - `'medium'`: equivalent to `'MMM d, y, h:mm:ss a'` (e.g. `Jun 15, 2015, 9:03:01 AM`).
|
||||
* - `'long'`: equivalent to `'MMMM d, y, h:mm:ss a z'` (e.g. `June 15, 2015 at 9:03:01 AM
|
||||
* GMT+1`).
|
||||
* - `'full'`: equivalent to `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (e.g. `Monday, June 15, 2015 at
|
||||
* 9:03:01 AM GMT+01:00`).
|
||||
* - `'shortDate'`: equivalent to `'M/d/yy'` (e.g. `6/15/15`).
|
||||
* - `'mediumDate'`: equivalent to `'MMM d, y'` (e.g. `Jun 15, 2015`).
|
||||
* - `'longDate'`: equivalent to `'MMMM d, y'` (e.g. `June 15, 2015`).
|
||||
* - `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` (e.g. `Monday, June 15, 2015`).
|
||||
* - `'shortTime'`: equivalent to `'h:mm a'` (e.g. `9:03 AM`).
|
||||
* - `'mediumTime'`: equivalent to `'h:mm:ss a'` (e.g. `9:03:01 AM`).
|
||||
* - `'longTime'`: equivalent to `'h:mm:ss a z'` (e.g. `9:03:01 AM GMT+1`).
|
||||
* - `'fullTime'`: equivalent to `'h:mm:ss a zzzz'` (e.g. `9:03:01 AM GMT+01:00`).
|
||||
* @param timezone to be used for formatting the time. It understands UTC/GMT and the continental
|
||||
* US time zone
|
||||
* abbreviations, but for general use, use a time zone offset (e.g. `'+0430'`).
|
||||
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
|
||||
* default).
|
||||
* @param value The date expression: a `Date` object, a number
|
||||
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
|
||||
* @param format The date/time components to include, using predefined options or a
|
||||
* custom format string.
|
||||
* @param timezone A timezone offset (such as `'+0430'`), or a standard
|
||||
* UTC/GMT or continental US timezone abbreviation. Default is
|
||||
* the local system timezone of the end-user's machine.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
* @returns A date string in the desired format.
|
||||
*/
|
||||
transform(value: any, format = 'mediumDate', timezone?: string, locale?: string): string|null {
|
||||
if (value == null || value === '' || value !== value) return null;
|
||||
|
@ -17,7 +17,6 @@ import {DatePipe} from './date_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {KeyValue, KeyValuePipe} from './keyvalue_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
|
||||
@ -26,8 +25,6 @@ export {
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
KeyValue,
|
||||
KeyValuePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe,
|
||||
JsonPipe,
|
||||
@ -35,7 +32,7 @@ export {
|
||||
PercentPipe,
|
||||
SlicePipe,
|
||||
TitleCasePipe,
|
||||
UpperCasePipe,
|
||||
UpperCasePipe
|
||||
};
|
||||
|
||||
|
||||
@ -55,5 +52,4 @@ export const COMMON_PIPES = [
|
||||
DatePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe,
|
||||
KeyValuePipe,
|
||||
];
|
||||
|
@ -12,14 +12,21 @@ import {Pipe, PipeTransform} from '@angular/core';
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Converts value into string using `JSON.stringify`. Useful for debugging.
|
||||
* Converts a value into its JSON-format representation. Useful for debugging.
|
||||
*
|
||||
* ### Example
|
||||
* @usageNotes
|
||||
*
|
||||
* The following component uses a JSON pipe to convert an object
|
||||
* to JSON format, and displays the string in both formats for comparison.
|
||||
|
||||
* {@example common/pipes/ts/json_pipe.ts region='JsonPipe'}
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Pipe({name: 'json', pure: false})
|
||||
export class JsonPipe implements PipeTransform {
|
||||
/**
|
||||
* @param value A value of any type to convert into a JSON-format string.
|
||||
*/
|
||||
transform(value: any): string { return JSON.stringify(value, null, 2); }
|
||||
}
|
||||
|
@ -1,110 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
function makeKeyValuePair<K, V>(key: K, value: V): KeyValue<K, V> {
|
||||
return {key: key, value: value};
|
||||
}
|
||||
|
||||
/**
|
||||
* A key value pair.
|
||||
* Usually used to represent the key value pairs from a Map or Object.
|
||||
*/
|
||||
export interface KeyValue<K, V> {
|
||||
key: K;
|
||||
value: V;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Transforms Object or Map into an array of key value pairs.
|
||||
*
|
||||
* The output array will be ordered by keys.
|
||||
* By default the comparator will be by Unicode point value.
|
||||
* You can optionally pass a compareFn if your keys are complex types.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* This examples show how an Object or a Map and be iterated by ngFor with the use of this keyvalue
|
||||
* pipe.
|
||||
*
|
||||
* {@example common/pipes/ts/keyvalue_pipe.ts region='KeyValuePipe'}
|
||||
*/
|
||||
@Pipe({name: 'keyvalue', pure: false})
|
||||
export class KeyValuePipe implements PipeTransform {
|
||||
constructor(private readonly differs: KeyValueDiffers) {}
|
||||
|
||||
private differ: KeyValueDiffer<any, any>;
|
||||
private keyValues: Array<KeyValue<any, any>>;
|
||||
|
||||
transform<K, V>(input: null, compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): null;
|
||||
transform<V>(
|
||||
input: {[key: string]: V}|Map<string, V>,
|
||||
compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>;
|
||||
transform<V>(
|
||||
input: {[key: number]: V}|Map<number, V>,
|
||||
compareFn?: (a: KeyValue<number, V>, b: KeyValue<number, V>) => number):
|
||||
Array<KeyValue<number, V>>;
|
||||
transform<K, V>(input: Map<K, V>, compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number):
|
||||
Array<KeyValue<K, V>>;
|
||||
transform<K, V>(
|
||||
input: null|{[key: string]: V, [key: number]: V}|Map<K, V>,
|
||||
compareFn: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number = defaultComparator):
|
||||
Array<KeyValue<K, V>>|null {
|
||||
if (!input || (!(input instanceof Map) && typeof input !== 'object')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.differ) {
|
||||
// make a differ for whatever type we've been passed in
|
||||
this.differ = this.differs.find(input).create();
|
||||
}
|
||||
|
||||
const differChanges: KeyValueChanges<K, V>|null = this.differ.diff(input as any);
|
||||
|
||||
if (differChanges) {
|
||||
this.keyValues = [];
|
||||
differChanges.forEachItem((r: KeyValueChangeRecord<K, V>) => {
|
||||
this.keyValues.push(makeKeyValuePair(r.key, r.currentValue !));
|
||||
});
|
||||
this.keyValues.sort(compareFn);
|
||||
}
|
||||
return this.keyValues;
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultComparator<K, V>(
|
||||
keyValueA: KeyValue<K, V>, keyValueB: KeyValue<K, V>): number {
|
||||
const a = keyValueA.key;
|
||||
const b = keyValueB.key;
|
||||
// if same exit with 0;
|
||||
if (a === b) return 0;
|
||||
// make sure that undefined are at the end of the sort.
|
||||
if (a === undefined) return 1;
|
||||
if (b === undefined) return -1;
|
||||
// make sure that nulls are at the end of the sort.
|
||||
if (a === null) return 1;
|
||||
if (b === null) return -1;
|
||||
if (typeof a == 'string' && typeof b == 'string') {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
if (typeof a == 'number' && typeof b == 'number') {
|
||||
return a - b;
|
||||
}
|
||||
if (typeof a == 'boolean' && typeof b == 'boolean') {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
// `a` and `b` are of different types. Compare their string values.
|
||||
const aString = String(a);
|
||||
const bString = String(b);
|
||||
return aString == bString ? 0 : aString < bString ? -1 : 1;
|
||||
}
|
@ -15,14 +15,19 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Uses the function {@link formatNumber} to format a number according to locale rules.
|
||||
* Transforms a number into a string,
|
||||
* formatted according to locale rules that determine group sizing and
|
||||
* separator, decimal-point character, and other locale-specific
|
||||
* configurations.
|
||||
*
|
||||
* Formats a number as text. Group sizing and separator and other locale-specific
|
||||
* configurations are based on the locale.
|
||||
* @see `formatNumber()`
|
||||
*
|
||||
* ### Example
|
||||
* @usageNotes
|
||||
* The following code shows how the pipe transforms numbers
|
||||
* into text strings, according to various format specifications,
|
||||
* where the caller's default locale is `en-US`.
|
||||
*
|
||||
* {@example common/pipes/ts/number_pipe.ts region='NumberPipe'}
|
||||
* <code-example path="common/pipes/ts/number_pipe.ts" region='NumberPipe'></code-example>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ -31,16 +36,19 @@ export class DecimalPipe implements PipeTransform {
|
||||
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
||||
|
||||
/**
|
||||
* @param value a number to be formatted.
|
||||
* @param digitsInfo a `string` which has a following format: <br>
|
||||
* @param value The number to be formatted.
|
||||
* @param digitsInfo Decimal representation options, specified by a string
|
||||
* in the following format:<br>
|
||||
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
|
||||
* - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`.
|
||||
* - `minFractionDigits` is the minimum number of digits after the decimal point. Defaults to
|
||||
* `0`.
|
||||
* - `maxFractionDigits` is the maximum number of digits after the decimal point. Defaults to
|
||||
* `3`.
|
||||
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
|
||||
* default).
|
||||
* - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
|
||||
* Default is `1`.
|
||||
* - `minFractionDigits`: The minimum number of digits after the decimal point.
|
||||
* Default is `0`.
|
||||
* - `maxFractionDigits`: The maximum number of digits after the decimal point.
|
||||
* Default is `3`.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(value: any, digitsInfo?: string, locale?: string): string|null {
|
||||
if (isEmpty(value)) return null;
|
||||
@ -60,12 +68,19 @@ export class DecimalPipe implements PipeTransform {
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Uses the function {@link formatPercent} to format a number as a percentage according
|
||||
* to locale rules.
|
||||
* Transforms a number to a percentage
|
||||
* string, formatted according to locale rules that determine group sizing and
|
||||
* separator, decimal-point character, and other locale-specific
|
||||
* configurations.
|
||||
*
|
||||
* ### Example
|
||||
* @see `formatPercent()`
|
||||
*
|
||||
* {@example common/pipes/ts/percent_pipe.ts region='PercentPipe'}
|
||||
* @usageNotes
|
||||
* The following code shows how the pipe transforms numbers
|
||||
* into text strings, according to various format specifications,
|
||||
* where the caller's default locale is `en-US`.
|
||||
*
|
||||
* <code-example path="common/pipes/ts/percent_pipe.ts" region='PercentPipe'></code-example>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ -75,10 +90,19 @@ export class PercentPipe implements PipeTransform {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value a number to be formatted as a percentage.
|
||||
* @param digitsInfo see {@link DecimalPipe} for more details.
|
||||
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
|
||||
* default).
|
||||
* @param value The number to be formatted as a percentage.
|
||||
* @param digitsInfo Decimal representation options, specified by a string
|
||||
* in the following format:<br>
|
||||
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
|
||||
* - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
|
||||
* Default is `1`.
|
||||
* - `minFractionDigits`: The minimum number of digits after the decimal point.
|
||||
* Default is `0`.
|
||||
* - `maxFractionDigits`: The maximum number of digits after the decimal point.
|
||||
* Default is `3`.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(value: any, digitsInfo?: string, locale?: string): string|null {
|
||||
if (isEmpty(value)) return null;
|
||||
@ -98,12 +122,19 @@ export class PercentPipe implements PipeTransform {
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Uses the functions {@link getCurrencySymbol} and {@link formatCurrency} to format a
|
||||
* number as currency using locale rules.
|
||||
* Transforms a number to a currency string, formatted according to locale rules
|
||||
* that determine group sizing and separator, decimal-point character,
|
||||
* and other locale-specific configurations.
|
||||
*
|
||||
* ### Example
|
||||
* @see `getCurrencySymbol()`
|
||||
* @see `formatCurrency()`
|
||||
*
|
||||
* {@example common/pipes/ts/currency_pipe.ts region='CurrencyPipe'}
|
||||
* @usageNotes
|
||||
* The following code shows how the pipe transforms numbers
|
||||
* into text strings, according to various format specifications,
|
||||
* where the caller's default locale is `en-US`.
|
||||
*
|
||||
* <code-example path="common/pipes/ts/currency_pipe.ts" region='CurrencyPipe'></code-example>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ -113,20 +144,31 @@ export class CurrencyPipe implements PipeTransform {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value a number to be formatted as currency.
|
||||
* @param currencyCodeis the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code,
|
||||
* @param value The number to be formatted as currency.
|
||||
* @param currencyCode The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code,
|
||||
* such as `USD` for the US dollar and `EUR` for the euro.
|
||||
* @param display indicates whether to show the currency symbol, the code or a custom value:
|
||||
* - `code`: use code (e.g. `USD`).
|
||||
* - `symbol`(default): use symbol (e.g. `$`).
|
||||
* - `symbol-narrow`: some countries have two symbols for their currency, one regular and one
|
||||
* narrow (e.g. the canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`).
|
||||
* - `string`: use this value instead of a code or a symbol.
|
||||
* - boolean (deprecated from v5): `true` for symbol and false for `code`.
|
||||
* If there is no narrow symbol for the chosen currency, the regular symbol will be used.
|
||||
* @param digitsInfo see {@link DecimalPipe} for more details.
|
||||
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
|
||||
* default).
|
||||
* @param display The format for the currency indicator. One of the following:
|
||||
* - `code`: Show the code (such as `USD`).
|
||||
* - `symbol`(default): Show the symbol (such as `$`).
|
||||
* - `symbol-narrow`: Use the narrow symbol for locales that have two symbols for their
|
||||
* currency.
|
||||
* For example, the Canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`. If the
|
||||
* locale has no narrow symbol, uses the standard symbol for the locale.
|
||||
* - String: Use the given string value instead of a code or a symbol.
|
||||
* - Boolean (marked deprecated in v5): `true` for symbol and false for `code`.
|
||||
*
|
||||
* @param digitsInfo Decimal representation options, specified by a string
|
||||
* in the following format:<br>
|
||||
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
|
||||
* - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
|
||||
* Default is `1`.
|
||||
* - `minFractionDigits`: The minimum number of digits after the decimal point.
|
||||
* Default is `0`.
|
||||
* - `maxFractionDigits`: The maximum number of digits after the decimal point.
|
||||
* Default is `3`.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(
|
||||
value: any, currencyCode?: string,
|
||||
@ -167,7 +209,7 @@ function isEmpty(value: any): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a string into a number (if needed)
|
||||
* Transforms a string into a number (if needed).
|
||||
*/
|
||||
function strToNumber(value: number | string): number {
|
||||
// Convert strings to numbers
|
||||
|
@ -1,183 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {defineInjectable, inject} from '@angular/core';
|
||||
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
|
||||
/**
|
||||
* @whatItDoes Manages the scroll position.
|
||||
*/
|
||||
export abstract class ViewportScroller {
|
||||
// De-sugared tree-shakable injection
|
||||
// See #23917
|
||||
/** @nocollapse */
|
||||
static ngInjectableDef = defineInjectable(
|
||||
{providedIn: 'root', factory: () => new BrowserViewportScroller(inject(DOCUMENT), window)});
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the top offset used when scrolling to an anchor.
|
||||
*
|
||||
* When given a tuple with two number, the service will always use the numbers.
|
||||
* When given a function, the service will invoke the function every time it restores scroll
|
||||
* position.
|
||||
*/
|
||||
abstract setOffset(offset: [number, number]|(() => [number, number])): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Returns the current scroll position.
|
||||
*/
|
||||
abstract getScrollPosition(): [number, number];
|
||||
|
||||
/**
|
||||
* @whatItDoes Sets the scroll position.
|
||||
*/
|
||||
abstract scrollToPosition(position: [number, number]): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Scrolls to the provided anchor.
|
||||
*/
|
||||
abstract scrollToAnchor(anchor: string): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Disables automatic scroll restoration provided by the browser.
|
||||
* See also [window.history.scrollRestoration
|
||||
* info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration)
|
||||
*/
|
||||
abstract setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Manages the scroll position.
|
||||
*/
|
||||
export class BrowserViewportScroller implements ViewportScroller {
|
||||
private offset: () => [number, number] = () => [0, 0];
|
||||
|
||||
constructor(private document: any, private window: any) {}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the top offset used when scrolling to an anchor.
|
||||
*
|
||||
* * When given a number, the service will always use the number.
|
||||
* * When given a function, the service will invoke the function every time it restores scroll
|
||||
* position.
|
||||
*/
|
||||
setOffset(offset: [number, number]|(() => [number, number])): void {
|
||||
if (Array.isArray(offset)) {
|
||||
this.offset = () => offset;
|
||||
} else {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Returns the current scroll position.
|
||||
*/
|
||||
getScrollPosition(): [number, number] {
|
||||
if (this.supportScrollRestoration()) {
|
||||
return [this.window.scrollX, this.window.scrollY];
|
||||
} else {
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Sets the scroll position.
|
||||
*/
|
||||
scrollToPosition(position: [number, number]): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
this.window.scrollTo(position[0], position[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Scrolls to the provided anchor.
|
||||
*/
|
||||
scrollToAnchor(anchor: string): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
const elSelectedById = this.document.querySelector(`#${anchor}`);
|
||||
if (elSelectedById) {
|
||||
this.scrollToElement(elSelectedById);
|
||||
return;
|
||||
}
|
||||
const elSelectedByName = this.document.querySelector(`[name='${anchor}']`);
|
||||
if (elSelectedByName) {
|
||||
this.scrollToElement(elSelectedByName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Disables automatic scroll restoration provided by the browser.
|
||||
*/
|
||||
setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
const history = this.window.history;
|
||||
if (history && history.scrollRestoration) {
|
||||
history.scrollRestoration = scrollRestoration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private scrollToElement(el: any): void {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const left = rect.left + this.window.pageXOffset;
|
||||
const top = rect.top + this.window.pageYOffset;
|
||||
const offset = this.offset();
|
||||
this.window.scrollTo(left - offset[0], top - offset[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only support scroll restoration when we can get a hold of window.
|
||||
* This means that we do not support this behavior when running in a web worker.
|
||||
*
|
||||
* Lifting this restriction right now would require more changes in the dom adapter.
|
||||
* Since webworkers aren't widely used, we will lift it once RouterScroller is
|
||||
* battle-tested.
|
||||
*/
|
||||
private supportScrollRestoration(): boolean {
|
||||
try {
|
||||
return !!this.window && !!this.window.scrollTo;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Provides an empty implementation of the viewport scroller. This will
|
||||
* live in @angular/common as it will be used by both platform-server and platform-webworker.
|
||||
*/
|
||||
export class NullViewportScroller implements ViewportScroller {
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
setOffset(offset: [number, number]|(() => [number, number])): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
getScrollPosition(): [number, number] { return [0, 0]; }
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
scrollToPosition(position: [number, number]): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
scrollToAnchor(anchor: string): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {}
|
||||
}
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {CommonModule, NgForOf} from '@angular/common';
|
||||
import {Component, Directive} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
@ -1,152 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {KeyValuePipe} from '@angular/common';
|
||||
import {EventEmitter, KeyValueDiffers, WrappedValue, ɵdefaultKeyValueDiffers as defaultKeyValueDiffers} from '@angular/core';
|
||||
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
import {defaultComparator} from '../../src/pipes/keyvalue_pipe';
|
||||
import {SpyChangeDetectorRef} from '../spies';
|
||||
|
||||
describe('KeyValuePipe', () => {
|
||||
it('should return null when given null', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
it('should return null when given undefined', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(undefined as any)).toEqual(null);
|
||||
});
|
||||
it('should return null for an unsupported type', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const fn = () => {};
|
||||
expect(pipe.transform(fn as any)).toEqual(null);
|
||||
});
|
||||
describe('object dictionary', () => {
|
||||
it('should transform a basic dictionary', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({1: 2})).toEqual([{key: '1', value: 2}]);
|
||||
});
|
||||
it('should order by alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({'b': 1, 'a': 1})).toEqual([
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({2: 1, 1: 1})).toEqual([{key: '1', value: 1}, {key: '2', value: 1}]);
|
||||
});
|
||||
it('should order by numerical and alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = {2: 1, 1: 1, 'b': 1, 0: 1, 3: 1, 'a': 1};
|
||||
expect(pipe.transform(input)).toEqual([
|
||||
{key: '0', value: 1}, {key: '1', value: 1}, {key: '2', value: 1}, {key: '3', value: 1},
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should return the same ref if nothing changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform({1: 2});
|
||||
const transform2 = pipe.transform({1: 2});
|
||||
expect(transform1 === transform2).toEqual(true);
|
||||
});
|
||||
it('should return a new ref if something changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform({1: 2});
|
||||
const transform2 = pipe.transform({1: 3});
|
||||
expect(transform1 !== transform2).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Map', () => {
|
||||
it('should transform a basic Map', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([[1, 2]]))).toEqual([{key: 1, value: 2}]);
|
||||
});
|
||||
it('should order by alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([['b', 1], ['a', 1]]))).toEqual([
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([[2, 1], [1, 1]]))).toEqual([
|
||||
{key: 1, value: 1}, {key: 2, value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical and alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = [[2, 1], [1, 1], ['b', 1], [0, 1], [3, 1], ['a', 1]];
|
||||
expect(pipe.transform(new Map(input as any))).toEqual([
|
||||
{key: 0, value: 1}, {key: 1, value: 1}, {key: 2, value: 1}, {key: 3, value: 1},
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by complex types with compareFn', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = new Map([[{id: 1}, 1], [{id: 0}, 1]]);
|
||||
expect(pipe.transform<{id: number}, number>(input, (a, b) => a.key.id > b.key.id ? 1 : -1))
|
||||
.toEqual([
|
||||
{key: {id: 0}, value: 1},
|
||||
{key: {id: 1}, value: 1},
|
||||
]);
|
||||
});
|
||||
it('should return the same ref if nothing changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform(new Map([[1, 2]]));
|
||||
const transform2 = pipe.transform(new Map([[1, 2]]));
|
||||
expect(transform1 === transform2).toEqual(true);
|
||||
});
|
||||
it('should return a new ref if something changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform(new Map([[1, 2]]));
|
||||
const transform2 = pipe.transform(new Map([[1, 3]]));
|
||||
expect(transform1 !== transform2).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultComparator', () => {
|
||||
it('should remain the same order when keys are equal', () => {
|
||||
const key = 1;
|
||||
const values = [{key, value: 2}, {key, value: 1}];
|
||||
expect(values.sort(defaultComparator)).toEqual(values);
|
||||
});
|
||||
it('should sort undefined keys to the end', () => {
|
||||
const values = [{key: 3, value: 1}, {key: undefined, value: 3}, {key: 1, value: 2}];
|
||||
expect(values.sort(defaultComparator)).toEqual([
|
||||
{key: 1, value: 2}, {key: 3, value: 1}, {key: undefined, value: 3}
|
||||
]);
|
||||
});
|
||||
it('should sort null keys to the end', () => {
|
||||
const values = [{key: 3, value: 1}, {key: null, value: 3}, {key: 1, value: 2}];
|
||||
expect(values.sort(defaultComparator)).toEqual([
|
||||
{key: 1, value: 2}, {key: 3, value: 1}, {key: null, value: 3}
|
||||
]);
|
||||
});
|
||||
it('should sort strings in alpha ascending', () => {
|
||||
const values = [{key: 'b', value: 1}, {key: 'a', value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 'a', value: 3}, {key: 'b', value: 1}]);
|
||||
});
|
||||
it('should sort numbers in numerical ascending', () => {
|
||||
const values = [{key: 2, value: 1}, {key: 1, value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 1, value: 3}, {key: 2, value: 1}]);
|
||||
});
|
||||
it('should sort boolean in false (0) -> true (1)', () => {
|
||||
const values = [{key: true, value: 3}, {key: false, value: 1}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: false, value: 1}, {key: true, value: 3}]);
|
||||
});
|
||||
it('should sort numbers as strings in numerical ascending', () => {
|
||||
const values = [{key: '2', value: 1}, {key: 1, value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 1, value: 3}, {key: '2', value: 1}]);
|
||||
});
|
||||
});
|
@ -25,8 +25,6 @@ ts_library(
|
||||
tsconfig = ":tsconfig",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
||||
|
||||
@ -35,9 +33,6 @@ npm_package(
|
||||
srcs = [
|
||||
"package.json",
|
||||
],
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [":compiler-cli"],
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ivy_ng_module")
|
||||
load("//tools:defaults.bzl", "ivy_ng_module", "ts_library")
|
||||
load("//packages/bazel/src:ng_rollup_bundle.bzl", "ng_rollup_bundle")
|
||||
|
||||
ivy_ng_module(
|
||||
|
@ -15,7 +15,7 @@
|
||||
"chokidar": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.7.2 <2.9",
|
||||
"typescript": ">=2.7.2 <2.8",
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"engines" : {
|
||||
|
@ -40,8 +40,7 @@ export function main(
|
||||
|
||||
|
||||
function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
|
||||
const transformDecorators = options.enableIvy !== 'ngtsc' && options.enableIvy !== 'tsc' &&
|
||||
options.annotationsAs !== 'decorators';
|
||||
const transformDecorators = options.annotationsAs !== 'decorators';
|
||||
const transformTypesToClosure = options.annotateForClosureCompiler;
|
||||
if (!transformDecorators && !transformTypesToClosure) {
|
||||
return undefined;
|
||||
|
@ -1,17 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "annotations",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {ComponentDecoratorHandler} from './src/component';
|
||||
export {DirectiveDecoratorHandler} from './src/directive';
|
||||
export {InjectableDecoratorHandler} from './src/injectable';
|
||||
export {NgModuleDecoratorHandler} from './src/ng_module';
|
||||
export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope';
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user