mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9b3d2072b | |||
| c5db867ddc | |||
| f9511bf6e8 | |||
| 40d8821dde | |||
| 9e02af8c7c | |||
| dc29e21b14 | |||
| e05d6db2cd | |||
| 32caa8b669 | |||
| 6790d3ceb1 | |||
| fff8056e7f | |||
| 0dd6102c79 | |||
| 78e72db19c | |||
| e2be52671e | |||
| e717980d77 | |||
| e9ac16ad02 | |||
| b8562eaf01 | |||
| 9aeede60cb | |||
| b96e571897 | |||
| 45a5aa8e01 | |||
| 33f9dba686 | |||
| b9aa77d1d0 | |||
| 7f74e35ffc | |||
| 8e9ab02444 | |||
| e5de9a0722 | |||
| 7c5ede6552 | |||
| d14603fdb5 | |||
| 591068afc7 | |||
| 482b6119c2 | |||
| 1142a1026b | |||
| 3f548610dd | |||
| c83e861a3f |
@@ -24,20 +24,20 @@ version: 2.1
|
||||
# **NOTE 2 **: If you change the cache key prefix, also sync the cache_key_fallback to match.
|
||||
# **NOTE 3 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
|
||||
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
|
||||
var_3: &cache_key v7-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_3: &cache_key v8-angular-node-16-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "aio/yarn.lock" }}
|
||||
# We invalidate the cache if the Bazel version changes because otherwise the `bazelisk` cache
|
||||
# folder will contain all previously used versions and ultimately cause the cache restoring to
|
||||
# be slower due to its growing size.
|
||||
var_4: &cache_key_fallback v7-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
var_4: &cache_key_fallback v8-angular-node-16-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Windows needs its own cache key because binaries in node_modules are different.
|
||||
var_3_win: &cache_key_win v9-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}
|
||||
var_4_win: &cache_key_win_fallback v9-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
var_3_win: &cache_key_win v10-angular-win-node-16-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}
|
||||
var_4_win: &cache_key_win_fallback v10-angular-win-node-16-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||
var_5: &components_repo_unit_tests_cache_key v1-angular-components-{{ checksum "month.txt" }}-7a24e95bafbdeb697f74a48e275c2442bcbefc74
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v1-angular-components-{{ checksum "month.txt" }}
|
||||
var_5: &components_repo_unit_tests_cache_key v2-angular-components-{{ checksum "month.txt" }}-7a24e95bafbdeb697f74a48e275c2442bcbefc74
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v2-angular-components-{{ checksum "month.txt" }}
|
||||
|
||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages`.
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
|
||||
+3
-1
@@ -266,6 +266,8 @@ groups:
|
||||
'aio/content/images/guide/frequent-ngmodules/**/{*,.*}',
|
||||
'aio/content/guide/hierarchical-dependency-injection.md',
|
||||
'aio/content/examples/hierarchical-dependency-injection/**/{*,.*}',
|
||||
'aio/content/guide/image-directive.md',
|
||||
'aio/content/guide/image-directive-setup.md',
|
||||
'aio/content/examples/providers-viewproviders/**/{*,.*}',
|
||||
'aio/content/examples/resolution-modifiers/**/{*,.*}',
|
||||
'aio/content/guide/lazy-loading-ngmodules.md',
|
||||
@@ -1096,7 +1098,7 @@ groups:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
contains_any_globs(files.exclude('.pullapprove.yml'), [
|
||||
'{*,.*}',
|
||||
'.circleci/**/{*,.*}',
|
||||
'.devcontainer/**/{*,.*}',
|
||||
|
||||
+70
-85
@@ -1,3 +1,57 @@
|
||||
<a name="14.2.0"></a>
|
||||
# 14.2.0 (2022-08-25)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d5f7da2120](https://github.com/angular/angular/commit/d5f7da21207f7808dffb3932fa61d22614e6bd57) | feat | introduce the NgOptimizedImage directive ([#47082](https://github.com/angular/angular/pull/47082)) |
|
||||
| [e2ab99b95e](https://github.com/angular/angular/commit/e2ab99b95efd893c49d15c02cccd72ef82ea1cae) | fix | allow null/undefined to be passed to ngClass input ([#39280](https://github.com/angular/angular/pull/39280)) ([#46906](https://github.com/angular/angular/pull/46906)) |
|
||||
| [bedf537951](https://github.com/angular/angular/commit/bedf537951e64c55dde9b38936e451daa4a4bde9) | fix | allow null/undefined to be passed to ngStyle input ([#47069](https://github.com/angular/angular/pull/47069)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d1e83e1b30](https://github.com/angular/angular/commit/d1e83e1b30f2cea9f2ed16bff2d3b969335072ab) | feat | add `createComponent` function ([#46685](https://github.com/angular/angular/pull/46685)) |
|
||||
| [10becab70e](https://github.com/angular/angular/commit/10becab70e0f2afee29da7b3358eedcc22f655a2) | feat | add `reflectComponentType` function ([#46685](https://github.com/angular/angular/pull/46685)) |
|
||||
| [4b377d3a6d](https://github.com/angular/angular/commit/4b377d3a6d069da23ec577f8deca761b01d4e2f5) | feat | introduce createApplication API ([#46475](https://github.com/angular/angular/pull/46475)) |
|
||||
| [31429eaccc](https://github.com/angular/angular/commit/31429eaccc973672efb4ed98a628cf9842e24b02) | feat | support TypeScript 4.8 ([#47038](https://github.com/angular/angular/pull/47038)) |
|
||||
| [796840209c](https://github.com/angular/angular/commit/796840209cd38aacc5061a31701efe7eda1f6587) | fix | align TestBed interfaces and implementation ([#46635](https://github.com/angular/angular/pull/46635)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [426af91a42](https://github.com/angular/angular/commit/426af91a42104d6ce227ab52c8c4db3e218f5e03) | feat | add `FormBuilder.record()` method ([#46485](https://github.com/angular/angular/pull/46485)) |
|
||||
| [b302797de4](https://github.com/angular/angular/commit/b302797de47409b988ad77e87f766a5f18374ded) | fix | Correctly infer `FormBuilder` types involving `[value, validators]` shorthand in more cases. ([#47034](https://github.com/angular/angular/pull/47034)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [598b72bd05](https://github.com/angular/angular/commit/598b72bd05ea33468c70ac35b8ea5286439f41d9) | feat | support fix the component missing member ([#46764](https://github.com/angular/angular/pull/46764)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [07606e3181](https://github.com/angular/angular/commit/07606e3181fc7aa9f9185eb771fc9c44f01fe344) | feat | add `isEmpty` method to the `TransferState` class ([#46915](https://github.com/angular/angular/pull/46915)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2b4d7f6733](https://github.com/angular/angular/commit/2b4d7f6733e10e45e3ca8a448307bbc2834fc4a1) | feat | support document reference in render functions ([#47032](https://github.com/angular/angular/pull/47032)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0abb67af59](https://github.com/angular/angular/commit/0abb67af59a92a2b29082a259aa9f4ea3fbaab7d) | feat | allow guards and resolvers to be plain functions ([#46684](https://github.com/angular/angular/pull/46684)) |
|
||||
| [75df404467](https://github.com/angular/angular/commit/75df4044675c61d2b646437cfe64fe828a39b3a0) | feat | Create APIs for using Router without RouterModule ([#47010](https://github.com/angular/angular/pull/47010)) |
|
||||
| [10289f1f6e](https://github.com/angular/angular/commit/10289f1f6e3d5935304a38d4300d4dadfea66150) | feat | expose resolved route title ([#46826](https://github.com/angular/angular/pull/46826)) |
|
||||
| [8600732b09](https://github.com/angular/angular/commit/8600732b090a4c253eb89ffde6db3858e4a2021c) | feat | Expose the default matcher for `Routes` used by the `Router` ([#46913](https://github.com/angular/angular/pull/46913)) |
|
||||
| [422323cee0](https://github.com/angular/angular/commit/422323cee00192239d1ab279e6a7c3a808098169) | feat | improve typings for RouterLink boolean inputs ([#47101](https://github.com/angular/angular/pull/47101)) |
|
||||
| [26ea97688c](https://github.com/angular/angular/commit/26ea97688cba5850588d8da83abaf8b539577241) | feat | Make router directives standalone ([#46758](https://github.com/angular/angular/pull/46758)) |
|
||||
| [2a43beec15](https://github.com/angular/angular/commit/2a43beec159990b8c4bafc7f189e2bd85002dc7a) | fix | Fix route recognition behavior with some versions of rxjs ([#47098](https://github.com/angular/angular/pull/47098)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [383090858c](https://github.com/angular/angular/commit/383090858caf04c27ee8b320cceca2da0ae54bb3) | feat | support `sendRequest` as a `notificationclick` action ([#46912](https://github.com/angular/angular/pull/46912)) |
|
||||
| [3f548610dd](https://github.com/angular/angular/commit/3f548610dd05f484cd33fa2cee4e3693ed731126) | fix | export NoNewVersionDetectedEvent ([#47044](https://github.com/angular/angular/pull/47044)) |
|
||||
| [482b6119c2](https://github.com/angular/angular/commit/482b6119c2f3fff5ec0623e101d308a49c416b42) | fix | update golden `index.md` ([#47044](https://github.com/angular/angular/pull/47044)) |
|
||||
## Special Thanks
|
||||
Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Bob Watson, Cédric Exbrayat, Dylan Hunn, Emmanuel Roux, FatalMerlin, George Kalpakas, Ilia Mirkin, Jan Kuehle, Jeremy Elbourn, Jessica Janiuk, JiaLiPassion, Kalbarczyk, Kara Erickson, Katie Hempenius, Kristiyan Kostadinov, Merlin, Paul Gschwendtner, Pawel Kozlowski, Tristan Sprößer, Victor Porof, angular-robot[bot], dario-piotrowicz, ivanwonder and vyom
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.1.3"></a>
|
||||
# 14.1.3 (2022-08-17)
|
||||
### compiler
|
||||
@@ -24,74 +78,6 @@ AlirezaEbrahimkhani, Alma Eyre, Andrew Scott, Bob Watson, George Kalpakas, Kalba
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.2.0-next.1"></a>
|
||||
# 14.2.0-next.1 (2022-08-10)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bedf537951](https://github.com/angular/angular/commit/bedf537951e64c55dde9b38936e451daa4a4bde9) | fix | allow null/undefined to be passed to ngStyle input ([#47069](https://github.com/angular/angular/pull/47069)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ea89677c12](https://github.com/angular/angular/commit/ea89677c125c16654e3e521998b575687c2bd20c) | feat | support more recent version of `tsickle` ([#47018](https://github.com/angular/angular/pull/47018)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dbed2cf079](https://github.com/angular/angular/commit/dbed2cf079c52825ce0aa67fc2a46bc3d38f408f) | fix | check if transplanted views are attached to change detector ([#46974](https://github.com/angular/angular/pull/46974)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2b4d7f6733](https://github.com/angular/angular/commit/2b4d7f6733e10e45e3ca8a448307bbc2834fc4a1) | feat | support document reference in render functions ([#47032](https://github.com/angular/angular/pull/47032)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0abb67af59](https://github.com/angular/angular/commit/0abb67af59a92a2b29082a259aa9f4ea3fbaab7d) | feat | allow guards and resolvers to be plain functions ([#46684](https://github.com/angular/angular/pull/46684)) |
|
||||
| [2a43beec15](https://github.com/angular/angular/commit/2a43beec159990b8c4bafc7f189e2bd85002dc7a) | fix | Fix route recognition behavior with some versions of rxjs ([#47098](https://github.com/angular/angular/pull/47098)) |
|
||||
## Special Thanks
|
||||
4javier, Andrew Kushnir, Andrew Scott, AntonioCardenas, Bob Watson, Bruno Barbosa, Eduardo Speroni, Edward, George Kalpakas, Ilia Mirkin, Jan Melcher, Jessica Janiuk, JiaLiPassion, Kristiyan Kostadinov, Mladen Jakovljević, Paul Gschwendtner, Pawel Kozlowski, Roman Matusevich, Victor Porof, Vovch, ashide2729, ileil and onrails
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.2.0-next.0"></a>
|
||||
# 14.2.0-next.0 (2022-08-03)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e2ab99b95e](https://github.com/angular/angular/commit/e2ab99b95efd893c49d15c02cccd72ef82ea1cae) | fix | allow null/undefined to be passed to ngClass input ([#39280](https://github.com/angular/angular/pull/39280)) ([#46906](https://github.com/angular/angular/pull/46906)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d1e83e1b30](https://github.com/angular/angular/commit/d1e83e1b30f2cea9f2ed16bff2d3b969335072ab) | feat | add `createComponent` function ([#46685](https://github.com/angular/angular/pull/46685)) |
|
||||
| [10becab70e](https://github.com/angular/angular/commit/10becab70e0f2afee29da7b3358eedcc22f655a2) | feat | add `reflectComponentType` function ([#46685](https://github.com/angular/angular/pull/46685)) |
|
||||
| [4b377d3a6d](https://github.com/angular/angular/commit/4b377d3a6d069da23ec577f8deca761b01d4e2f5) | feat | introduce createApplication API ([#46475](https://github.com/angular/angular/pull/46475)) |
|
||||
| [796840209c](https://github.com/angular/angular/commit/796840209cd38aacc5061a31701efe7eda1f6587) | fix | align TestBed interfaces and implementation ([#46635](https://github.com/angular/angular/pull/46635)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [426af91a42](https://github.com/angular/angular/commit/426af91a42104d6ce227ab52c8c4db3e218f5e03) | feat | add `FormBuilder.record()` method ([#46485](https://github.com/angular/angular/pull/46485)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [598b72bd05](https://github.com/angular/angular/commit/598b72bd05ea33468c70ac35b8ea5286439f41d9) | feat | support fix the component missing member ([#46764](https://github.com/angular/angular/pull/46764)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [07606e3181](https://github.com/angular/angular/commit/07606e3181fc7aa9f9185eb771fc9c44f01fe344) | feat | add `isEmpty` method to the `TransferState` class ([#46915](https://github.com/angular/angular/pull/46915)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [10289f1f6e](https://github.com/angular/angular/commit/10289f1f6e3d5935304a38d4300d4dadfea66150) | feat | expose resolved route title ([#46826](https://github.com/angular/angular/pull/46826)) |
|
||||
| [8600732b09](https://github.com/angular/angular/commit/8600732b090a4c253eb89ffde6db3858e4a2021c) | feat | Expose the default matcher for `Routes` used by the `Router` ([#46913](https://github.com/angular/angular/pull/46913)) |
|
||||
| [26ea97688c](https://github.com/angular/angular/commit/26ea97688cba5850588d8da83abaf8b539577241) | feat | Make router directives standalone ([#46758](https://github.com/angular/angular/pull/46758)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [383090858c](https://github.com/angular/angular/commit/383090858caf04c27ee8b320cceca2da0ae54bb3) | feat | support `sendRequest` as a `notificationclick` action ([#46912](https://github.com/angular/angular/pull/46912)) |
|
||||
## Special Thanks
|
||||
Andrew Kushnir, Andrew Scott, Cédric Exbrayat, Emmanuel Roux, George Kalpakas, Ilia Mirkin, Jessica Janiuk, Paul Gschwendtner, Pawel Kozlowski, Tristan Sprößer, angular-robot[bot], dario-piotrowicz and ivanwonder
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.1.1"></a>
|
||||
# 14.1.1 (2022-08-03)
|
||||
### core
|
||||
@@ -166,7 +152,7 @@ Alan Agius, Andrew Kushnir, Andrew Quinn, Andrew Scott, Aristeidis Bampakos, Asa
|
||||
| [ce20ed067f](https://github.com/angular/angular/commit/ce20ed067f12714ee6488ffe07f1a1c18bb60a15) | fix | Ensure Route injector is created before running CanMatch guards ([#46394](https://github.com/angular/angular/pull/46394)) |
|
||||
| [6a7b818d94](https://github.com/angular/angular/commit/6a7b818d94fdd13d81a01285e3e4be953a29cc06) | fix | Ensure target `RouterStateSnapshot` is defined in `NavigationError` ([#46842](https://github.com/angular/angular/pull/46842)) |
|
||||
| [f94c6f433d](https://github.com/angular/angular/commit/f94c6f433dba3924b79f137cfcc49d2dfd4d679c) | fix | Expose CanMatchFn as public API ([#46394](https://github.com/angular/angular/pull/46394)) |
|
||||
| [e8ae0fe3e9](https://github.com/angular/angular/commit/e8ae0fe3e91e2e805146fad0d2f0976b1f886112) | fix | Fix cancellation code for canLoad rejections ([#46752](https://github.com/angular/angular/pull/46752)) |
|
||||
| [e8ae0fe3e9](https://github.com/angular/angular/commit/e8ae0fe3e91e2e805146fad0d2f0976b1f886112) | fix | Fix cancelation code for canLoad rejections ([#46752](https://github.com/angular/angular/pull/46752)) |
|
||||
### upgrade
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
@@ -380,7 +366,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
|
||||
This tightens parameter types of `TransferState` usage, and is a minor breaking change which may reveal existing problematic calls.
|
||||
### router
|
||||
- The type of `Route.pathMatch` is now more strict. Places that use
|
||||
- The type of `Route.pathMatch` is now stricter. Places that use
|
||||
`pathMatch` will likely need to be updated to have an explicit
|
||||
`Route`/`Routes` type so that TypeScript does not infer the type as
|
||||
`string`.
|
||||
@@ -620,7 +606,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ec0a0e0669](https://github.com/angular/angular/commit/ec0a0e0669ef5d2f524fbf9a540eb2b306b2e24a) | feat | add `cacheOpaqueResponses` option for data-groups ([#44723](https://github.com/angular/angular/pull/44723)) |
|
||||
| [bd04fbc05b](https://github.com/angular/angular/commit/bd04fbc05b0397de966a493a863e1054203c86d9) | feat | emit a notification when the service worker is already up to date after check ([#45216](https://github.com/angular/angular/pull/45216)) |
|
||||
| [bd04fbc05b](https://github.com/angular/angular/commit/bd04fbc05b0397de966a493a863e1054203c86d9) | feat | emit a notification when the service worker is already up-to-date after check ([#45216](https://github.com/angular/angular/pull/45216)) |
|
||||
## Special Thanks
|
||||
Adrian Kunz, Alan Agius, AleksanderBodurri, Alex Rickabaugh, AlirezaEbrahimkhani, Amir Rustamzadeh, Andrew Kushnir, Andrew Scott, Chabbey François, Charles Lyding, Cédric Exbrayat, Daan De Smedt, David Schmidt, Derek Cormier, Dmitrij Kuba, Doug Parker, Dylan Hunn, Emma Twersky, George Kalpakas, George Looshch, Jan Kuehle, Jessica Janiuk, JiaLiPassion, JimMorrison723, Joe Martin (Crowdstaffing), Joey Perrott, JoostK, Kristiyan Kostadinov, Krzysztof Platis, Leosvel Pérez Espinosa, Maddie Klein, Mark Whitfeld, Martin Sikora, Michael-Doner, Michal Materowski, Minko Gechev, Paul Gschwendtner, Pawel Kozlowski, Payam Shahidi, Pusztai Tibor, Ricardo Mattiazzi Baumgartner, Roy Dorombozi, Ruslan Lekhman, Samuel Littley, Sergej Grilborzer, Sumit Arora, Tobias Speicher, Virginia Dooley, Zack Elliott, alirezaghey, ananyahs96, arturovt, cexbrayat, dario-piotrowicz, ivanwonder, kamikopi, markostanimirovic, markwhitfeld, mgechev, renovate[bot], twerske and zverbeta
|
||||
|
||||
@@ -1040,7 +1026,7 @@ Aristeidis Bampakos, Dylan Hunn, George Kalpakas, JoostK, Kristiyan Kostadinov,
|
||||
- The `downgradeModule` function calls with NgModule factories are deprecated. Please use NgModule class based `downgradeModule` calls instead.
|
||||
### common
|
||||
- `TestRequest` from `@angular/common/http/testing` no longer
|
||||
accepts `ErrorEvent` when simulating XHR errors. Instead instances of
|
||||
accepts `ErrorEvent` when simulating XHR errors. Instead, instances of
|
||||
`ProgressEvent` should be passed, matching with the native browser behavior.
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
@@ -1262,11 +1248,11 @@ return type.
|
||||
### core
|
||||
- Angular no longer requires component factories to dynamically create components. The factory-based signature of the `ViewContainerRef.createComponent` function is deprecated in favor of a different signature that allows passing component classes instead.
|
||||
|
||||
- The `getModuleFactory` function is deprecated in favor of the `getNgModuleById` one. With Ivy it's possible to work with NgModule classes directly, without retrieving corresponding factories, so the `getNgModuleById` should be used instead.
|
||||
- The `getModuleFactory` function is deprecated in favor of the `getNgModuleById` one. With Ivy, it's possible to work with NgModule classes directly, without retrieving corresponding factories, so the `getNgModuleById` should be used instead.
|
||||
|
||||
- Ivy made it possible to avoid the need to resolve Component and NgModule factories. Framework APIs allow to use Component and NgModule Types directly. As a result, the `PlatformRef.bootstrapModuleFactory` and a factory-based signature of the `ApplicationRef.bootstrap` method are now obsolete and are now deprecated. The `PlatformRef.bootstrapModuleFactory` calls can be replaced with `PlatformRef.bootstrapModule` ones. The `ApplicationRef.bootstrap` method allows to provide Component Type, so this can be used a replacement for the factory-based calls.
|
||||
|
||||
- In ViewEngine, [JIT compilation](https://angular.io/guide/glossary#jit) required special providers (like `Compiler`, `CompilerFactory`, etc) to be injected in the app and corresponding methods to be invoked. With Ivy, JIT compilation takes place implicitly if the Component, NgModule, etc have not already been [AOT compiled](https://angular.io/guide/glossary#aot). Those special providers were made available in Ivy for backwards-compatibility with ViewEngine to make the transition to Ivy smoother. Since ViewEngine is deprecated and will soon be removed, those symbols are now deprecated as well:
|
||||
- In ViewEngine, [JIT compilation](https://angular.io/guide/glossary#jit) required special providers (like `Compiler`, `CompilerFactory`, etc) to be injected in the app and corresponding methods to be invoked. With Ivy, JIT compilation takes place implicitly if the Component, NgModule, etc. have not already been [AOT compiled](https://angular.io/guide/glossary#aot). Those special providers were made available in Ivy for backwards-compatibility with ViewEngine to make the transition to Ivy smoother. Since ViewEngine is deprecated and will soon be removed, those symbols are now deprecated as well:
|
||||
|
||||
- `ModuleWithComponentFactories`
|
||||
- `Compiler`
|
||||
@@ -2207,7 +2193,7 @@ Alex Rickabaugh, Daniel Díaz, David Shevitz, Dylan Hunn, Front-end developer, G
|
||||
* **router:** update type castings for JSON.parse usage ([#40710](https://github.com/angular/angular/issues/40710)) ([350dada](https://github.com/angular/angular/commit/350dada))
|
||||
* **service-worker:** update supported range of node versions to only include LTS versions ([#41822](https://github.com/angular/angular/issues/41822)) ([6b823d7](https://github.com/angular/angular/commit/6b823d7071d59234ab52bdf7eaa248df6b7d9faa))
|
||||
* **service-worker:** update type castings for JSON.parse usage ([#40710](https://github.com/angular/angular/issues/40710)) ([4f7ff96](https://github.com/angular/angular/commit/4f7ff96))
|
||||
* **upgrade:** preserve $interval.flush when ngMocks is being used ([#30229](https://github.com/angular/angular/issues/30229)) ([87dc851](https://github.com/angular/angular/commit/87dc8511ccb9c75d78866ebd2c250ea96fc91bd0))
|
||||
* **upgrade:** preserve $interval flush when ngMocks is being used ([#30229](https://github.com/angular/angular/issues/30229)) ([87dc851](https://github.com/angular/angular/commit/87dc8511ccb9c75d78866ebd2c250ea96fc91bd0))
|
||||
* **upgrade:** update supported range of node versions to only include LTS versions ([#41822](https://github.com/angular/angular/issues/41822)) ([10c4523](https://github.com/angular/angular/commit/10c45239a68e82ea52c1601fae09953aa7843351))
|
||||
|
||||
|
||||
@@ -2223,7 +2209,7 @@ Alex Rickabaugh, Daniel Díaz, David Shevitz, Dylan Hunn, Front-end developer, G
|
||||
* **bazel:** update supported range of node versions ([#41544](https://github.com/angular/angular/issues/41544)) ([d583d92](https://github.com/angular/angular/commit/d583d926db537cc74f9235cad51189dbe83fbb44))
|
||||
* **common:** update supported range of node versions ([#41544](https://github.com/angular/angular/issues/41544)) ([e0250e5](https://github.com/angular/angular/commit/e0250e567ae47ed7b77e9d88a3289727c056a6f1))
|
||||
* **common:** add `historyGo` method to `Location` service ([#38890](https://github.com/angular/angular/issues/38890)) ([e05a6f3](https://github.com/angular/angular/commit/e05a6f3bb3048e9a94a4b154526221dea290312d))
|
||||
* **common:** support ICU standard "stand alone day of week" with `DatePipe` ([#40766](https://github.com/angular/angular/issues/40766)) ([c56ecab](https://github.com/angular/angular/commit/c56ecab515c28d2ad45c630080bfbf549675528a)), closes [#26922](https://github.com/angular/angular/issues/26922)
|
||||
* **common:** support ICU standard "stand-alone day of week" with `DatePipe` ([#40766](https://github.com/angular/angular/issues/40766)) ([c56ecab](https://github.com/angular/angular/commit/c56ecab515c28d2ad45c630080bfbf549675528a)), closes [#26922](https://github.com/angular/angular/issues/26922)
|
||||
* **common:** implement `appendAll()` method on `HttpParams` ([#20930](https://github.com/angular/angular/issues/20930)) ([575a2d1](https://github.com/angular/angular/commit/575a2d1)), closes [#20798](https://github.com/angular/angular/issues/20798)
|
||||
* **compiler:** support nullish coalescing in templates ([#41437](https://github.com/angular/angular/issues/41437)) ([ec27bd4](https://github.com/angular/angular/commit/ec27bd4ed1cc086d76d8142fadf87354b38aa61a)), closes [#36528](https://github.com/angular/angular/issues/36528)
|
||||
* **compiler:** update supported range of node versions ([#41544](https://github.com/angular/angular/issues/41544)) ([75cc813](https://github.com/angular/angular/commit/75cc8133ad8efd6453694f222e2cc6789c4de7b2))
|
||||
@@ -2307,7 +2293,7 @@ migrate their message ids using the `localize-migrate` command line tool.
|
||||
* **core:** Angular no longer maintains support for node v10
|
||||
* **core:** Previously the `ng.getDirectives` function threw an error in case a
|
||||
given DOM node had no Angular context associated with it (for example
|
||||
if a function was called for a DOM element outside of an Angular app).
|
||||
if a function was called for a DOM element outside an Angular app).
|
||||
This behavior was inconsistent with other debugging utilities under `ng`
|
||||
namespace, which handled this situation without raising an exception.
|
||||
Now calling the `ng.getDirectives` function for such DOM nodes would
|
||||
@@ -2341,8 +2327,7 @@ export class QueryCompWithStrictChangeEmitParent {
|
||||
}
|
||||
```
|
||||
For backward compatibility before v12
|
||||
`emitDistinctChangesOnlyDefaultValue` was set to `false`. This change
|
||||
changes the default to `true`.
|
||||
`emitDistinctChangesOnlyDefaultValue` was set to `false`. This change the default to `true`.
|
||||
* **core:** The type of the `APP_INITIALIZER` token has been changed to more accurately
|
||||
reflect the types of return values that are handled by Angular. Previously,
|
||||
each initializer callback was typed to return `any`, this is now
|
||||
@@ -2353,7 +2338,7 @@ the stricter type.
|
||||
|
||||
Additionally, TypeScript may report the TS2742 error if the `APP_INITIALIZER`
|
||||
token is used in an expression of which its inferred type has to be emitted
|
||||
into a .d.ts file. To workaround this, an explicit type annotation is needed,
|
||||
into a .d.ts file. To work around this, an explicit type annotation is needed,
|
||||
which would typically be `Provider` or `Provider[]`.
|
||||
* **core:** Minimum supported `zone.js` version is `0.11.4`
|
||||
* **forms:** The `emitEvent` option was added to the following `FormArray` and `FormGroup` methods:
|
||||
@@ -2438,7 +2423,7 @@ Alex Rickabaugh, Andrew J Asche, Georgii Dolzhykov, Joey Perrott, Joost Koehoorn
|
||||
* **core:** Angular no longer maintains support for node v10
|
||||
* **core:** Previously the `ng.getDirectives` function threw an error in case a
|
||||
given DOM node had no Angular context associated with it (for example
|
||||
if a function was called for a DOM element outside of an Angular app).
|
||||
if a function was called for a DOM element outside an Angular app).
|
||||
This behavior was inconsistent with other debugging utilities under `ng`
|
||||
namespace, which handled this situation without raising an exception.
|
||||
Now calling the `ng.getDirectives` function for such DOM nodes would
|
||||
@@ -2782,7 +2767,7 @@ This release contains the same set the of changes as 11.2.0-next.1.
|
||||
* **core:** Allow passing AbstractType to the inject function ([#37958](https://github.com/angular/angular/issues/37958)) ([a1b6ad0](https://github.com/angular/angular/commit/a1b6ad0))
|
||||
* **core:** Ensure OnPush ancestors are marked dirty when events occur ([#39833](https://github.com/angular/angular/issues/39833)) ([68d4a74](https://github.com/angular/angular/commit/68d4a74)), closes [#39832](https://github.com/angular/angular/issues/39832)
|
||||
* **core:** meta addTag() adds incorrect attribute for httpEquiv ([#32531](https://github.com/angular/angular/issues/32531)) ([ff0a90e](https://github.com/angular/angular/commit/ff0a90e))
|
||||
* **core:** migration error if program contains files outside of the project ([#39790](https://github.com/angular/angular/issues/39790)) ([1a26f6d](https://github.com/angular/angular/commit/1a26f6d)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** migration error if program contains files outside the project ([#39790](https://github.com/angular/angular/issues/39790)) ([1a26f6d](https://github.com/angular/angular/commit/1a26f6d)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** not invoking object's toString when rendering to the DOM ([#39843](https://github.com/angular/angular/issues/39843)) ([11cd37f](https://github.com/angular/angular/commit/11cd37f)), closes [#38839](https://github.com/angular/angular/issues/38839)
|
||||
* **core:** remove duplicated noop function ([#39761](https://github.com/angular/angular/issues/39761)) ([066126a](https://github.com/angular/angular/commit/066126a))
|
||||
* **core:** support `Attribute` DI decorator in `deps` section of a token ([#37085](https://github.com/angular/angular/issues/37085)) ([f5cbf0b](https://github.com/angular/angular/commit/f5cbf0b)), closes [#36479](https://github.com/angular/angular/issues/36479)
|
||||
@@ -2980,7 +2965,7 @@ This release contains the same set the of changes as 11.2.0-next.1.
|
||||
* **compiler-cli:** report error when a reference target is missing instead of crashing ([#39805](https://github.com/angular/angular/issues/39805)) ([8634611](https://github.com/angular/angular/commit/8634611)), closes [#38618](https://github.com/angular/angular/issues/38618) [#39744](https://github.com/angular/angular/issues/39744)
|
||||
* **core:** Ensure OnPush ancestors are marked dirty when events occur ([#39833](https://github.com/angular/angular/issues/39833)) ([01c1bfd](https://github.com/angular/angular/commit/01c1bfd)), closes [#39832](https://github.com/angular/angular/issues/39832)
|
||||
* **core:** meta addTag() adds incorrect attribute for httpEquiv ([#32531](https://github.com/angular/angular/issues/32531)) ([3114b0a](https://github.com/angular/angular/commit/3114b0a))
|
||||
* **core:** migration error if program contains files outside of the project ([#39790](https://github.com/angular/angular/issues/39790)) ([7dcc212](https://github.com/angular/angular/commit/7dcc212)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** migration error if program contains files outside the project ([#39790](https://github.com/angular/angular/issues/39790)) ([7dcc212](https://github.com/angular/angular/commit/7dcc212)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** not invoking object's toString when rendering to the DOM ([#39843](https://github.com/angular/angular/issues/39843)) ([75e22ab](https://github.com/angular/angular/commit/75e22ab)), closes [#38839](https://github.com/angular/angular/issues/38839)
|
||||
* **core:** remove duplicated noop function ([#39761](https://github.com/angular/angular/issues/39761)) ([26a1337](https://github.com/angular/angular/commit/26a1337))
|
||||
* **core:** support `Attribute` DI decorator in `deps` section of a token ([#37085](https://github.com/angular/angular/issues/37085)) ([aaa3111](https://github.com/angular/angular/commit/aaa3111)), closes [#36479](https://github.com/angular/angular/issues/36479)
|
||||
@@ -3183,7 +3168,7 @@ This release contains the same set the of changes as 11.2.0-next.1.
|
||||
`CollectionChangeRecord` has been removed, use `IterableChangeRecord` instead.
|
||||
- **[c8f056b](https://github.com/angular/angular/commit/c8f056beb696d176530ee4e86f64964c3b834c9a):**
|
||||
If you call `TestBed.overrideProvider` after TestBed initialization, provider overrides are not applied.
|
||||
This behavior is consistent with other override methods (such as `TestBed.overrideDirective`, etc) but they throw an error to indicate that, when the check was missing in the `TestBed.overrideProvider` function.
|
||||
This behavior is consistent with other override methods (such as `TestBed.overrideDirective`, etc.) but they throw an error to indicate that, when the check was missing in the `TestBed.overrideProvider` function.
|
||||
Now calling `TestBed.overrideProvider` after `TestBed` initialization also triggers an error, thus there is a chance that some tests (where `TestBed.overrideProvider` is called after `TestBed` initialization) will start to fail and require updates to move `TestBed.overrideProvider` calls before `TestBed` initialization is completed.
|
||||
- **[4ca1c73](https://github.com/angular/angular/commit/4ca1c736bb020de166b6a1fbfb9fe264602ccd5c):**
|
||||
In v10, IE 9, 10, and IE mobile support was deprecated.
|
||||
@@ -3209,7 +3194,7 @@ This release contains the same set the of changes as 11.2.0-next.1.
|
||||
In an unlikely case your code was testing the parent against `undefined` with strict equality, you'll need to change this to `=== null` instead, since the parent is now explicitly initialized with `null` instead of being left `undefined`.
|
||||
* **platform-server:**
|
||||
- **[b4e8399](https://github.com/angular/angular/commit/b4e8399144ae4b6d4f0da9ff2a8e1fa73643f766):**
|
||||
If you use `useAbsoluteUrl` to setup `platform-server`, you now need to also specify `baseUrl`.
|
||||
If you use `useAbsoluteUrl` to set up `platform-server`, you now need to also specify `baseUrl`.
|
||||
We are intentionally making this a breaking change in a minor release, because if `useAbsoluteUrl` is set to `true` then the behavior of the application could be unpredictable, resulting in issues that are hard to discover but could be affecting production environments.
|
||||
* **platform-webworker:**
|
||||
- **[93c3d8f](https://github.com/angular/angular/commit/93c3d8f9fd79014bf700a1447503f5ee6f170bf9):**
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an safe and welcoming environment, we as
|
||||
In the interest of fostering a safe and welcoming environment, we as
|
||||
the Angular team pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity, gender expression,
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ A minimal reproduction allows us to quickly confirm a bug (or point out a coding
|
||||
|
||||
We require a minimal reproduction to save maintainers' time and ultimately be able to fix more bugs.
|
||||
Often, developers find coding problems themselves while preparing a minimal reproduction.
|
||||
We understand that sometimes it might be hard to extract essential bits of code from a larger codebase but we really need to isolate the problem before we can fix it.
|
||||
We understand that sometimes it might be hard to extract essential bits of code from a larger codebase, but we really need to isolate the problem before we can fix it.
|
||||
|
||||
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you, we are going to close an issue that doesn't have enough info to be reproduced.
|
||||
|
||||
|
||||
@@ -158,10 +158,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "85b34f2e5e227aa586b6e0fd6942262e17b3cface596891eeef5a29a0b7467e3",
|
||||
strip_prefix = "rules_sass-697e5a08bdd39aee49f2c6f4eb621ef59e677e49",
|
||||
sha256 = "926cdd6133926a69e557172cb3232d5dac64ec6b8f167b7dceecd96cc4c6f19b",
|
||||
strip_prefix = "rules_sass-6802532fce57dee2a17be221d82cf441a923aeb7",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/697e5a08bdd39aee49f2c6f4eb621ef59e677e49.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/6802532fce57dee2a17be221d82cf441a923aeb7.zip",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# Setting up `NgOptimizedImage`
|
||||
|
||||
This tutorial explains how to setup the `NgOptimizedImage`. For information on using `NgOptimizedImage`, see [Getting Started with NgOptimizedImage](/guide/image-directive).
|
||||
|
||||
## Import `NgOptimizedImage`
|
||||
|
||||
You can import `NgOptimizedImage` from the `@angular/common` module. The directive is defined as a [standalone directive](/guide/standalone-components), so components should import it directly.
|
||||
|
||||
## Configure an `ImageLoader`
|
||||
|
||||
A "loader" is a function that generates the [image transformation URL](https://web.dev/image-cdns/#how-image-cdns-use-urls-to-indicate-optimization-options) for a given image file. When appropriate, `NgOptimzedImage` sets the size, format, and image quality transformations for an image.
|
||||
|
||||
`NgOptimizedImage` provides a generic loader as well as loaders for various third-party image services; it also supports writing your own custom loader.
|
||||
|
||||
| Loader type| Behavior |
|
||||
|:--- |:--- |
|
||||
| Generic loader | The URL returned by the generic loader will always match the value of `src`. In other words, this loader applies no transformations. Sites that use Angular to serve images are the primary intended use case for this loader.|
|
||||
| Loaders for third-party image services | The URL returned by the loaders for third-party image services will follow API conventions used by that particular image service. |
|
||||
| Custom loaders | A custom loader's behavior is defined by its developer. You should use a custom loader if your image service isn't supported by the loaders that come preconfigured with `NgOptimzedImage`.|
|
||||
|
||||
Based on the image services commonly used with Angular applications, `NgOptimizedImage` provides loaders preconfigured to work with the following image services:
|
||||
|
||||
| Image Service | Angular API | Documentation |
|
||||
|:--- |:--- |:--- |
|
||||
| Cloudflare Image Resizing | `provideCloudflareLoader` | [Documentation](https://developers.cloudflare.com/images/image-resizing/) |
|
||||
| Cloudinary | `provideCloudinaryLoader` | [Documentation](https://developers.cloudflare.com/images/image-resizing/) | |
|
||||
| ImageKit | `provideImageKitLoader` | [Documentation](https://docs.imagekit.io/) |
|
||||
| Imgix | `provideImgixLoader` | [Documentation](https://docs.imgix.com/) |
|
||||
|
||||
You must configure an image loader to use `NgOptimizedImage`.
|
||||
|
||||
These instructions explain how to setup an image loader for use with the `NgOptimizedImage`.
|
||||
|
||||
1. Import the `NgOptimizedImage` directive into the application by adding it to the `imports` section of an NgModule or a standalone Component.
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
import { NgOptimizedImage } from '@angular/common';
|
||||
// Include NgOptimizedImage in the appropriate NgModule
|
||||
@NgModule({
|
||||
imports: [
|
||||
// ... other imports
|
||||
NgOptimizedImage,
|
||||
],
|
||||
})
|
||||
|
||||
class AppModule {}
|
||||
</code-example>
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
// ... other imports
|
||||
NgOptimizedImage,
|
||||
],
|
||||
})
|
||||
|
||||
class MyStandaloneComponent {}
|
||||
</code-example>
|
||||
|
||||
2. Configure a loader that you want to use.
|
||||
|
||||
To use the **generic loader**: no additional code changes are necessary.
|
||||
|
||||
To use an existing loader for a **third-party image service**: add the provider factory for your chosen service to the `providers` array. In the example below, the Imgix loader is used:
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
providers: [
|
||||
provideImgixLoader('https://my.base.url/'),
|
||||
],
|
||||
</code-example>
|
||||
|
||||
The base URL for your image assets should be passed to the provider factory as an argument. For most sites, this base URL should match one of the following patterns:
|
||||
|
||||
* https://yoursite.yourcdn.com
|
||||
* https://subdomain.yoursite.com
|
||||
* https://subdomain.yourcdn.com/yoursite
|
||||
|
||||
You can learn more about the base URL structure in the docs of a corresponding CDN provider.
|
||||
|
||||
To use a **custom loader**: provide your loader function as a value for the `IMAGE_LOADER` DI token. In the example below, the custom loader function returns a URL starting with `https://example.com` that includes `src` and `width` as URL parameters.
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
providers: [
|
||||
{
|
||||
provide: IMAGE_LOADER,
|
||||
useValue: (config: ImageLoaderConfig) => {
|
||||
return `https://example.com/images?src=${config.src}&width=${config.width}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
</code-example>
|
||||
|
||||
A loader function for the `NgOptimizedImage` directive takes an object with the `ImageLoaderConfig` type (from `@angular/common`) as its argument and returns the absolute URL of the image asset. The `ImageLoaderConfig` object contains the `src`and `width` properties.
|
||||
|
||||
Note: a custom loader must support requesting images at various widths in order for `rawSrcset` to work properly.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Getting started with NgOptimizedImage
|
||||
|
||||
Note: the `NgOptimizedImage` directive is currently in the [“Developer Preview” mode](https://angular.io/guide/releases#developer-preview). The Angular team will stabilize the APIs based on the feedback and will make an announcement once the APIs are fully stable.
|
||||
|
||||
The `NgOptimizedImage` directive makes it easy to adopt performance best practices for loading images.
|
||||
|
||||
The `NgOptimizedImage` directive ensures that the loading of the [Largest Contentful Paint](http://web.dev/lcp) image is prioritized by:
|
||||
|
||||
* Automatically setting the `fetchpriority` attribute on the `<img>` tag
|
||||
* Lazy loading other images by default
|
||||
* Asserting that there is a corresponding preconnect link tag in the document head
|
||||
|
||||
In addition to optimizing the loading of the LCP image, `NgOptimizedImage` enforces a number of image best practices:
|
||||
|
||||
* Using [image URLs to apply image optimizations](https://web.dev/image-cdns/#how-image-cdns-use-urls-to-indicate-optimization-options)
|
||||
* Requires that `width` and `height` are set
|
||||
* Warns if `width` or `height` have been set incorrectly
|
||||
* Warns if the image will be visually distorted when rendered
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need to import the directive into your application. In addition, you will need to setup an image loader. These steps are explained in the [Setting up `NgOptimizedImage`](/guide/image-directive-setup) tutorial.
|
||||
|
||||
## Usage in a template
|
||||
|
||||
### Overview
|
||||
|
||||
To activate the `NgOptimizedImage` directive, replace your image's `src` attribute with `rawSrc`.
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
<img rawSrc=”cat.jpg" width="400" height="200">
|
||||
</code-example>
|
||||
|
||||
The built-in third-party loaders prepend a shared base URL to `src`. If you're using one of these loaders (or any other loader that does this), make sure to omit the shared base URL path from `src` to prevent unnecessary duplication.
|
||||
|
||||
You must also set the `width` and `height` attributes. This is done to prevent [image-related layout shifts](https://web.dev/css-web-vitals/#images-and-layout-shifts). The `width` and `height` attributes should reflect the [intrinsic size](https://developer.mozilla.org/en-US/docs/Glossary/Intrinsic_Size) of the image. During development, the `NgOptimizedImage` warns if it detects that the `width` and `height` attributes have been set incorrectly.
|
||||
|
||||
### Marking images as `priority`
|
||||
|
||||
Always mark the [LCP image](https://web.dev/lcp/#what-elements-are-considered) on your page as `priority` to prioritize its loading.
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
<img rawSrc="cat.jpg" width="400" height="200" priority>
|
||||
</code-example>
|
||||
|
||||
Marking an image as `priority` applies the following optimizations:
|
||||
|
||||
* Sets `fetchpriority=high` (read more about priority hints [here](https://web.dev/priority-hints/))
|
||||
* Sets `loading=eager` (read more about native lazy loading [here](https://web.dev/browser-level-image-lazy-loading/))
|
||||
|
||||
Angular displays a warning during development if the LCP element is an image that does not have the `priority` attribute. A page’s LCP element can vary based on a number of factors - such as the dimensions of a user's screen. A page may have multiple images that should be marked `priority`. See [CSS for Web Vitals](https://web.dev/css-web-vitals/#images-and-largest-contentful-paint-lcp) for more details.
|
||||
|
||||
### Adding resource hints
|
||||
|
||||
You can add a [`preconnect` resource hint](https://web.dev/preconnect-and-dns-prefetch/) for your image origin to ensure that the LCP image loads as quickly as possible. Always put resource hints in the `<head>` of the document.
|
||||
|
||||
<code-example format="html" language="html">
|
||||
<link rel="preconnect" href="https://my.cdn.origin" />
|
||||
</code-example>
|
||||
|
||||
By default, if you use a loader for a third-party image service, the `NgOptimizedImage` directive will warn during development if it detects that there is no `preconnect` resource hint for the origin that serves the LCP image.
|
||||
|
||||
To disable these warnings, add `{ensurePreconnect: false}` to the arguments passed to the provider factory for your chosen image service:
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
providers: [
|
||||
provideImgixLoader('https://my.base.url', {ensurePreconnect: false})
|
||||
],
|
||||
</code-example>
|
||||
|
||||
### Adjusting image styling
|
||||
|
||||
Depending on the image's styling, adding `width` and `height` attributes may cause the image to render differently. `NgOptimizedImage` warns you if your image styling renders the image at a distorted aspect ratio.
|
||||
|
||||
You can typically fix this by adding `height: auto` or `width: auto` to your image styles. For more information, see the [web.dev article on the `<img>` tag](https://web.dev/patterns/web-vitals-patterns/images/img-tag/).
|
||||
|
||||
### Handling `srcset` attributes
|
||||
|
||||
If your `<img>` tag defines a `srcset` attribute, replace it with `rawSrcset`.
|
||||
|
||||
<code-example format="html" language="html">
|
||||
<img rawSrc="hero.jpg" rawSrcset="100w, 200w, 300w">
|
||||
</code-example>
|
||||
|
||||
If the `rawSrcset` attribute is present, `NgOptimizedImage` generates and sets the [`srcset` attribute](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset) using the configured image loader. Do not include image file names in `rawSrcset` - the directive infers this information from `rawSrc`. The directive supports both width descriptors (e.g. `100w`) and density descriptors (e.g. `1x`) are supported.
|
||||
|
||||
You can also use `rawSrcset` with the standard image [`sizes` attribute](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes).
|
||||
|
||||
<code-example format="html" language="html">
|
||||
<img rawSrc="hero.jpg" rawSrcset="100w, 200w, 300w" sizes=”50vw”>
|
||||
</code-example>
|
||||
|
||||
### Disabling image lazy loading
|
||||
|
||||
By default, `NgOptimizedImage` sets `loading=lazy` for all images that are not marked `priority`. You can disable this behavior for non-priority images by setting the `loading` attribute. This attribute accepts values: `eager`, `auto`, and `lazy`. [See the documentation for the standard image `loading` attribute for details](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading#value).
|
||||
|
||||
<code-example format="html" language="html">
|
||||
<img rawSrc="cat.jpg" width="400" height="200" loading="eager">
|
||||
</code-example>
|
||||
@@ -40,7 +40,7 @@ Use them to perform the following kinds of operations.
|
||||
|
||||
| Hook method | Purpose | Timing |
|
||||
|:--- |:--- |:--- |
|
||||
| `ngOnChanges()` | Respond when Angular sets or resets data-bound input properties. The method receives a `SimpleChanges` object of current and previous property values. <br /> <div class="alert is-helpful"> **NOTE**: <br /> This happens very frequently, so any operation you perform here impacts performance significantly. </div> See details in [Using change detection hooks](#onchanges) in this document. | Called before `ngOnInit()` \(if the component has bound inputs\) and whenever one or more data-bound input properties change. <br /> <div class="alert is-helpful"> **NOTE**: <br /> If your component has no inputs or you use it without providing any inputs, the framework will not call `ngOnChanges()`. </div> |
|
||||
| `ngOnChanges()` | Respond when Angular sets or resets data-bound input properties. The method receives a `SimpleChanges` object of current and previous property values. <br /> <div class="alert is-helpful"> **NOTE**: <br /> This happens frequently, so any operation you perform here impacts performance significantly. </div> See details in [Using change detection hooks](#onchanges) in this document. | Called before `ngOnInit()` \(if the component has bound inputs\) and whenever one or more data-bound input properties change. <br /> <div class="alert is-helpful"> **NOTE**: <br /> If your component has no inputs or you use it without providing any inputs, the framework will not call `ngOnChanges()`. </div> |
|
||||
| `ngOnInit()` | Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component's input properties. See details in [Initializing a component or directive](#oninit) in this document. | Called once, after the first `ngOnChanges()`. `ngOnInit()` is still called even when `ngOnChanges()` is not \(which is the case when there are no template-bound inputs\). |
|
||||
| `ngDoCheck()` | Detect and act upon changes that Angular can't or won't detect on its own. See details and example in [Defining custom change detection](#docheck) in this document. | Called immediately after `ngOnChanges()` on every change detection run, and immediately after `ngOnInit()` on the first run. |
|
||||
| `ngAfterContentInit()` | Respond after Angular projects external content into the component's view, or into the view that a directive is in. <br /> See details and example in [Responding to changes in content](#aftercontent) in this document. | Called *once* after the first `ngDoCheck()`. |
|
||||
@@ -121,12 +121,16 @@ The sequence of log messages follows the prescribed hook calling order:
|
||||
|:--- |:--- |
|
||||
| 1 | `OnChanges` |
|
||||
| 2 | `OnInit` |
|
||||
| 3-5 | `DoCheck` |
|
||||
| 6 | `AfterContentInit` |
|
||||
| 7-9 | `AfterContentChecked` |
|
||||
| 10 | `AfterViewInit` |
|
||||
| 11-13 | `AfterViewChecked` |
|
||||
| 14 | `OnDestroy` |
|
||||
| 3 | `DoCheck` |
|
||||
| 4 | `AfterContentInit` |
|
||||
| 5 | `AfterContentChecked` |
|
||||
| 6 | `AfterViewInit` |
|
||||
| 7 | `AfterViewChecked` |
|
||||
| 8 | `DoCheck` |
|
||||
| 9 | `AfterContentChecked` |
|
||||
| 10 | `AfterViewChecked` |
|
||||
| 11 | `OnDestroy` |
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@@ -151,8 +155,8 @@ The example does not perform any initialization or clean-up.
|
||||
It just tracks the appearance and disappearance of an element in the view by recording when the directive itself is instantiated and destroyed.
|
||||
|
||||
A spy directive like this can provide insight into a DOM object that you cannot change directly.
|
||||
You can't touch the implementation of a built-in `<div>`, or modify a third party component.
|
||||
You can, however watch these elements with a directive.
|
||||
You can't access the implementation of a built-in `<div>`, or modify a third party component.
|
||||
You do have the option to watch these elements with a directive.
|
||||
|
||||
The directive defines `ngOnInit()` and `ngOnDestroy()` hooks
|
||||
that log messages to the parent using an injected `LoggerService`.
|
||||
@@ -253,8 +257,8 @@ The `LoggerService.tick_then()` statement postpones the log update for one turn
|
||||
|
||||
#### Write lean hook methods to avoid performance problems
|
||||
|
||||
When you run the *AfterView* sample, notice how frequently Angular calls `AfterViewChecked()`-often when there are no changes of interest.
|
||||
Be very careful about how much logic or computation you put into one of these methods.
|
||||
When you run the *AfterView* sample, notice how frequently Angular calls `AfterViewChecked()` - often when there are no changes of interest.
|
||||
Be careful about how much logic or computation you put into one of these methods.
|
||||
|
||||
<div class="lightbox">
|
||||
|
||||
@@ -284,7 +288,7 @@ AngularJS developers know this technique as *transclusion*.
|
||||
The *AfterContent* sample explores the `AfterContentInit()` and `AfterContentChecked()` hooks that Angular calls *after* Angular projects external content into the component.
|
||||
|
||||
Consider this variation on the [previous *AfterView*](#afterview) example.
|
||||
This time, instead of including the child view within the template, it imports the content from the `AfterContentComponent`'s parent.
|
||||
This time, instead of including the child view within the template, it imports the content from the `AfterContentComponent` hook's parent.
|
||||
The following is the parent's template.
|
||||
|
||||
<code-example header="AfterContentParentComponent (template excerpt)" path="lifecycle-hooks/src/app/after-content-parent.component.ts" region="parent-template"></code-example>
|
||||
@@ -354,7 +358,7 @@ The results are illuminating.
|
||||
|
||||
</div>
|
||||
|
||||
While the `ngDoCheck()` hook can detect when the hero's `name` has changed, it is very expensive.
|
||||
While the `ngDoCheck()` hook can detect when the hero's `name` has changed, it is an expensive hook.
|
||||
This hook is called with enormous frequency —after *every* change detection cycle no matter where the change occurred.
|
||||
It's called over twenty times in this example before the user can do anything.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 50 KiB |
@@ -169,6 +169,15 @@
|
||||
"bio": "Brian is a software engineer and GDE in Angular with a passion for learning, writing, speaking, teaching and mentoring. Brian has been building web applications for over 20 years and has long been a fanboy of JavaScript. When not in front of his Macbook Pro Brian is in the Rocky Mountains skiing or hiking.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"bobwatson": {
|
||||
"name": "Bob Watson",
|
||||
"lead": "mgechev",
|
||||
"picture": "bob-watson.jpg",
|
||||
"twitter": "bobwatsonphd",
|
||||
"website": "https://docsbydesign.com",
|
||||
"bio": "Bob is the documentation lead for angular.io. When he's not obsessing over the i-dotting and t-crossing of documentation, he enjoys seeing the world from up in the air and below the sea.",
|
||||
"groups": ["Angular"]
|
||||
},
|
||||
"brocco": {
|
||||
"name": "Mike Brocchi",
|
||||
"picture": "mike-brocchi.jpg",
|
||||
@@ -916,12 +925,6 @@
|
||||
"bio": "Andrew is a software engineer using Angular and .NET. He spends most of his spare time staying up-to-date, helping other people, and experimenting with web tech.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"tmdavisgoogle": {
|
||||
"name": "Tiffany Davis",
|
||||
"bio": "Tiffany is an editor working on Angular documentation.",
|
||||
"groups": ["Angular"],
|
||||
"lead": "mgechev"
|
||||
},
|
||||
"toddmotto": {
|
||||
"name": "Todd Motto",
|
||||
"picture": "toddmotto.jpg",
|
||||
|
||||
@@ -84,6 +84,18 @@
|
||||
"title": "Angular Rocks",
|
||||
"url": "https://angularrocks.com"
|
||||
},
|
||||
"angular-master-podcast": {
|
||||
"desc": "Listen / Code / Repeat. Everything you need to fall in love with Angular for a long time.",
|
||||
"logo": "https://ng-poland.pl/images/podcast/angular-master-podcast-logo.png",
|
||||
"title": "Angular Master Podcast",
|
||||
"url": "https://anchor.fm/angular-master/"
|
||||
},
|
||||
"javascript-master-podcast": {
|
||||
"desc": "Listen / Code / Repeat. Everything you need to know to become an informed JavaScript developer.",
|
||||
"logo": "https://ng-poland.pl/images/podcast/javascript-master-podcast-logo.png",
|
||||
"title": "JavaScript Master Podcast",
|
||||
"url": "https://anchor.fm/javascript-master/"
|
||||
},
|
||||
"happy-angular": {
|
||||
"desc": "A weekly German podcast for Angular on the go",
|
||||
"logo": "",
|
||||
@@ -816,6 +828,11 @@
|
||||
"title": "Angular University",
|
||||
"url": "https://angular-university.io/"
|
||||
},
|
||||
"angular-master-dev": {
|
||||
"desc": "Angular Architecture: Strategic Design with Nx - Online Video Course",
|
||||
"title": "Angular Master",
|
||||
"url": "https://angularmaster.dev/"
|
||||
},
|
||||
"-acceleb": {
|
||||
"desc": "Customized, Instructor-Led Angular Training",
|
||||
"title": "Accelebrate",
|
||||
|
||||
@@ -637,6 +637,11 @@
|
||||
"url": "guide/prerendering",
|
||||
"title": "Prerendering",
|
||||
"tooltip": "Prerender HTML server-side with Angular Universal."
|
||||
},
|
||||
{
|
||||
"url": "guide/image-directive",
|
||||
"title": "Image Directive",
|
||||
"tooltip": "Performant images with the Angular image directive."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -816,7 +821,12 @@
|
||||
"tooltip": "Create a template-driven form using directives and Angular template syntax."
|
||||
},
|
||||
{
|
||||
"title": "Angular libraries",
|
||||
"url": "guide/image-directive-setup",
|
||||
"title": "Setting up NgOptimzedImage",
|
||||
"tooltip": "Setup the Angular image directive"
|
||||
},
|
||||
{
|
||||
"title": "Angular Libraries",
|
||||
"tooltip": "Extending Angular with shared libraries.",
|
||||
"children": [
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
|
||||
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
|
||||
</a>
|
||||
<aio-top-menu *ngIf="showTopMenu" [nodes]="topMenuNodes" [currentNode]="currentNodes?.TopBar"></aio-top-menu>
|
||||
<aio-top-menu *ngIf="showTopMenu" [nodes]="topMenuNodes" [currentNode]="currentNodes.TopBar"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event, true)"></aio-search-box>
|
||||
<aio-theme-toggle #themeToggle></aio-theme-toggle>
|
||||
<div #externalIcons class="toolbar-external-icons-container">
|
||||
@@ -60,8 +60,8 @@
|
||||
<mat-sidenav-container class="sidenav-container" [class.no-animations]="disableAnimations" [class.has-floating-toc]="hasFloatingToc">
|
||||
|
||||
<mat-sidenav [ngClass]="{'collapsed': !dockSideNav}" #sidenav class="sidenav" [mode]="mode" [opened]="isOpened" (openedChange)="updateHostClasses()">
|
||||
<aio-nav-menu *ngIf="!showTopMenu" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="dockSideNav" navLabel="primary"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" [isWide]="dockSideNav" navLabel="guides and docs"></aio-nav-menu>
|
||||
<aio-nav-menu *ngIf="!showTopMenu" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes.TopBarNarrow" [isWide]="dockSideNav" navLabel="primary"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes.SideNav" [isWide]="dockSideNav" navLabel="guides and docs"></aio-nav-menu>
|
||||
|
||||
<div class="doc-version">
|
||||
<aio-nav-menu [nodes]="docVersions" [isWide]="true" [currentNode]="currentDocsVersionNode" navLabel="docs versions"></aio-nav-menu>
|
||||
|
||||
@@ -90,7 +90,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
currentDocsVersionNode?: NavigationNode;
|
||||
|
||||
versionInfo: VersionInfo;
|
||||
versionInfo: VersionInfo | undefined;
|
||||
|
||||
get isOpened() { return this.dockSideNav && this.isSideNavDoc; }
|
||||
get mode() { return this.isOpened ? 'side' : 'over'; }
|
||||
|
||||
@@ -8,5 +8,5 @@ import { NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
})
|
||||
export class FooterComponent {
|
||||
@Input() nodes: NavigationNode[];
|
||||
@Input() versionInfo: VersionInfo;
|
||||
@Input() versionInfo: VersionInfo | undefined;
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ import { VersionInfo } from 'app/navigation/navigation.service';
|
||||
})
|
||||
export class ModeBannerComponent {
|
||||
@Input() mode: string;
|
||||
@Input() version: VersionInfo;
|
||||
@Input() version: VersionInfo | undefined;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(keydown)="handleKeydown($event)"
|
||||
[attr.aria-expanded]="showOptions"
|
||||
[attr.aria-activedescendant]="currentOptionIdx > -1 && showOptions ? listBoxId + '-option-' + currentOptionIdx : null"
|
||||
[attr.aria-label]="label + (selected?.title ?? '')"
|
||||
[attr.aria-label]="label + (selected?.title ?? '')"
|
||||
[class.disabled]="disabled"
|
||||
tabindex="0"
|
||||
>
|
||||
|
||||
@@ -230,9 +230,9 @@ class HostComponent {
|
||||
onChange = jasmine.createSpy('onChange');
|
||||
options: Option[];
|
||||
selected: Option;
|
||||
label: string;
|
||||
showSymbol: boolean;
|
||||
disabled: boolean;
|
||||
label = '';
|
||||
showSymbol = false;
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
function getButton(): HTMLButtonElement {
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Option {
|
||||
export class SelectComponent implements OnInit {
|
||||
static instancesCounter = 0;
|
||||
|
||||
@Input() selected: Option;
|
||||
@Input() selected: Option | undefined;
|
||||
|
||||
@Input() options: Option[];
|
||||
|
||||
@@ -23,7 +23,7 @@ export class SelectComponent implements OnInit {
|
||||
|
||||
@Input() showSymbol = false;
|
||||
|
||||
@Input() label!: string;
|
||||
@Input() label = '';
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@@ -81,7 +81,7 @@ export class SelectComponent implements OnInit {
|
||||
const runOrOpenOptions = (fn: () => void): void => {
|
||||
if(!this.showOptions) {
|
||||
this.showOptions = true;
|
||||
const indexOfSelected = (this.options ?? []).indexOf(this.selected);
|
||||
const indexOfSelected = (!this.options || !this.selected) ? -1 : this.options.indexOf(this.selected);
|
||||
this.currentOptionIdx = indexOfSelected > 0 ? indexOfSelected : 0;
|
||||
} else {
|
||||
fn();
|
||||
|
||||
@@ -48,6 +48,7 @@ aio-contributor {
|
||||
width: calc(100% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
clip-path: $angular-shape-polygon;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.contributor-social-links {
|
||||
|
||||
+15
-13
@@ -4,9 +4,9 @@
|
||||
* - A symbol could have multiple constructors
|
||||
* - It is possible to disambiguate multiple different signatures from a single polymorphic constructor
|
||||
* - The return type of the overridden constructor can differ (e.g. `Foo<T|null>` vs `Foo<T>`)
|
||||
*
|
||||
*
|
||||
* This looks like the following:
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @overriddenImplementation FooCtor
|
||||
@@ -14,21 +14,21 @@
|
||||
* export interface Foo {
|
||||
* bar();
|
||||
* }
|
||||
*
|
||||
*
|
||||
* type FooInterface = Foo;
|
||||
*
|
||||
*
|
||||
* export class FooImpl { ... }
|
||||
*
|
||||
*
|
||||
* export interface FooCtor {
|
||||
* new(): Foo;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* export const Foo: FooCtor =
|
||||
(class Foo implements FooInterface { ... }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* This processor will extend the docs for symbol `Foo` by copying all documented constructor overrides from `FooCtor`.
|
||||
*
|
||||
*
|
||||
* In order to use this processor, annotate the exported interface with `@overriddenImplementation`. Place the desired
|
||||
* documentation on the interface and constructor signatures.
|
||||
*/
|
||||
@@ -42,7 +42,7 @@
|
||||
],
|
||||
$process(docs) {
|
||||
docs.forEach(doc => {
|
||||
if (doc.overriddenImplementation) {
|
||||
if (doc.overriddenImplementation) {
|
||||
// Convert the specified name into a doc.
|
||||
const ctorDocArray = getDocFromAlias(doc.overriddenImplementation);
|
||||
if (ctorDocArray.length === 0) {
|
||||
@@ -51,18 +51,20 @@
|
||||
if (ctorDocArray.length >= 2) {
|
||||
throw new Error(`@overriddenImplementation found multiple docs for ${doc.overriddenImplementation}. You may only have one documented symbol for each.`);
|
||||
}
|
||||
|
||||
|
||||
const ctorDoc = ctorDocArray[0];
|
||||
|
||||
// Copy the constructor overrides from the constructor doc, if any are present.
|
||||
if (!ctorDoc.members || ctorDoc.members.length === 0 || !ctorDoc.members[0].name.includes('new')) {
|
||||
throw new Error(`@overriddenImplementation requires that the provided constructor ${ctorDoc} have a member called "new", possibly with multiple overrides.`);
|
||||
throw new Error(`@overriddenImplementation requires that the provided constructor ${ctorDoc.id} have a member called "new", possibly with multiple overrides.`);
|
||||
}
|
||||
doc.constructorDoc = ctorDoc.members[0];
|
||||
|
||||
// Mark the constructor doc internal.
|
||||
if (!ctorDoc.internal) {
|
||||
log.warn(`Constructor doc ${ctorDoc} was not marked '@internal'; adding this annotation.`);
|
||||
if (!ctorDoc.internal && !ctorDoc.privateExport) {
|
||||
log.warn(
|
||||
`Constructor doc ${ctorDoc.id} was not marked as internal (either via an ` +
|
||||
'\'@internal\' annotation or a \'ɵ\' prefix). Marking it as internal.');
|
||||
ctorDoc.internal = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,17 @@ import * as i0 from '@angular/core';
|
||||
import { ModuleWithProviders } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// @public
|
||||
export interface NoNewVersionDetectedEvent {
|
||||
// (undocumented)
|
||||
type: 'NO_NEW_VERSION_DETECTED';
|
||||
// (undocumented)
|
||||
version: {
|
||||
hash: string;
|
||||
appData?: Object;
|
||||
};
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class ServiceWorkerModule {
|
||||
static register(script: string, opts?: SwRegistrationOptions): ModuleWithProviders<ServiceWorkerModule>;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "14.2.0-next.1",
|
||||
"version": "14.2.0",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
|
||||
@@ -75,7 +75,7 @@ export function createImageLoader(
|
||||
buildUrlFn: (path: string, config: ImageLoaderConfig) => string, exampleUrls?: string[]) {
|
||||
return function provideImageLoader(
|
||||
path: string, options: {ensurePreconnect?: boolean} = {ensurePreconnect: true}) {
|
||||
if (ngDevMode && !isValidPath(path)) {
|
||||
if (!isValidPath(path)) {
|
||||
throwInvalidPathError(path, exampleUrls || []);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function createImageLoader(
|
||||
path = normalizePath(path);
|
||||
|
||||
const loaderFn = (config: ImageLoaderConfig) => {
|
||||
if (ngDevMode && isAbsoluteUrl(config.src)) {
|
||||
if (isAbsoluteUrl(config.src)) {
|
||||
// Image loader functions expect an image file name (e.g. `my-image.png`)
|
||||
// or a relative path + a file name (e.g. `/a/b/c/my-image.png`) as an input,
|
||||
// so the final absolute URL can be constructed.
|
||||
@@ -106,19 +106,22 @@ export function createImageLoader(
|
||||
}
|
||||
|
||||
function throwInvalidPathError(path: unknown, exampleUrls: string[]): never {
|
||||
const exampleUrlsMsg = exampleUrls.join(' or ');
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_LOADER_ARGUMENTS,
|
||||
`Image loader has detected an invalid path (\`${path}\`). ` +
|
||||
`To fix this, supply a path using one of the following formats: ${exampleUrlsMsg}`);
|
||||
ngDevMode &&
|
||||
`Image loader has detected an invalid path (\`${path}\`). ` +
|
||||
`To fix this, supply a path using one of the following formats: ${
|
||||
exampleUrls.join(' or ')}`);
|
||||
}
|
||||
|
||||
function throwUnexpectedAbsoluteUrlError(path: string, url: string): never {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_LOADER_ARGUMENTS,
|
||||
`Image loader has detected a \`<img>\` tag with an invalid \`rawSrc\` attribute: ${url}. ` +
|
||||
`This image loader expects \`rawSrc\` to be a relative URL - ` +
|
||||
`however the provided value is an absolute URL. ` +
|
||||
`To fix this, provide \`rawSrc\` as a path relative to the base URL ` +
|
||||
`configured for this loader (\`${path}\`).`);
|
||||
ngDevMode &&
|
||||
`Image loader has detected a \`<img>\` tag with an invalid \`rawSrc\` attribute: ${
|
||||
url}. ` +
|
||||
`This image loader expects \`rawSrc\` to be a relative URL - ` +
|
||||
`however the provided value is an absolute URL. ` +
|
||||
`To fix this, provide \`rawSrc\` as a path relative to the base URL ` +
|
||||
`configured for this loader (\`${path}\`).`);
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ const VALID_WIDTH_DESCRIPTOR_SRCSET = /^((\s*\d+w\s*(,|$)){1,})$/;
|
||||
|
||||
/**
|
||||
* RegExpr to determine whether a src in a srcset is using density descriptors.
|
||||
* Should match something like: "1x, 2x".
|
||||
* Should match something like: "1x, 2x, 50x". Also supports decimals like "1.5x, 1.50x".
|
||||
*/
|
||||
const VALID_DENSITY_DESCRIPTOR_SRCSET = /^((\s*\d(\.\d)?x\s*(,|$)){1,})$/;
|
||||
const VALID_DENSITY_DESCRIPTOR_SRCSET = /^((\s*\d+(\.\d+)?x\s*(,|$)){1,})$/;
|
||||
|
||||
/**
|
||||
* Srcset values with a density descriptor higher than this value will actively
|
||||
@@ -110,7 +110,7 @@ const OVERSIZED_IMAGE_TOLERANCE = 1000;
|
||||
*
|
||||
* To use the **default loader**: no additional code changes are necessary. The URL returned by the
|
||||
* generic loader will always match the value of "src". In other words, this loader applies no
|
||||
* transformations to thr resource URL and the value of the `rawSrc` attribute will be used as is.
|
||||
* transformations to the resource URL and the value of the `rawSrc` attribute will be used as is.
|
||||
*
|
||||
* To use an existing loader for a **third-party image service**: add the provider factory for your
|
||||
* chosen service to the `providers` array. In the example below, the Imgix loader is used:
|
||||
@@ -169,6 +169,9 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
private imgElement: HTMLImageElement = inject(ElementRef).nativeElement;
|
||||
private injector = inject(Injector);
|
||||
|
||||
// a LCP image observer - should be injected only in the dev mode
|
||||
private lcpObserver = ngDevMode ? this.injector.get(LCPImageObserver) : null;
|
||||
|
||||
/**
|
||||
* Calculate the rewritten `src` once and store it.
|
||||
* This is needed to avoid repetitive calculations and make sure the directive cleanup in the
|
||||
@@ -277,10 +280,12 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
// Monitor whether an image is an LCP element only in case
|
||||
// the `priority` attribute is missing. Otherwise, an image
|
||||
// has the necessary settings and no extra checks are required.
|
||||
invokeLCPImageObserverCallback(
|
||||
this.injector,
|
||||
(observer: LCPImageObserver) =>
|
||||
observer.registerImage(this.getRewrittenSrc(), this.rawSrc));
|
||||
if (this.lcpObserver !== null) {
|
||||
const ngZone = this.injector.get(NgZone);
|
||||
ngZone.runOutsideAngular(() => {
|
||||
this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.rawSrc);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setHostAttributes();
|
||||
@@ -344,10 +349,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
ngOnDestroy() {
|
||||
if (ngDevMode) {
|
||||
if (!this.priority && this._renderedSrc !== null) {
|
||||
invokeLCPImageObserverCallback(
|
||||
this.injector,
|
||||
(observer: LCPImageObserver) => observer.unregisterImage(this._renderedSrc!));
|
||||
if (!this.priority && this._renderedSrc !== null && this.lcpObserver !== null) {
|
||||
this.lcpObserver.unregisterImage(this._renderedSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,24 +376,6 @@ function inputToBoolean(value: unknown): boolean {
|
||||
return value != null && `${value}` !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a function, passing an instance of the `LCPImageObserver` as an argument.
|
||||
*
|
||||
* Notes:
|
||||
* - the `LCPImageObserver` is a tree-shakable provider, provided in 'root',
|
||||
* thus it's a singleton within this application
|
||||
* - the process of `LCPImageObserver` creation and an actual operation are invoked outside of the
|
||||
* NgZone to make sure none of the calls inside the `LCPImageObserver` class trigger unnecessary
|
||||
* change detection
|
||||
*/
|
||||
function invokeLCPImageObserverCallback(
|
||||
injector: Injector, operation: (observer: LCPImageObserver) => void): void {
|
||||
const ngZone = injector.get(NgZone);
|
||||
return ngZone.runOutsideAngular(() => {
|
||||
const observer = injector.get(LCPImageObserver);
|
||||
operation(observer);
|
||||
});
|
||||
}
|
||||
|
||||
/***** Assert functions *****/
|
||||
|
||||
@@ -506,7 +491,7 @@ function assertUnderDensityCap(dir: NgOptimizedImage, value: string) {
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}x but supports image densities up to ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
`greater than ${RECOMMENDED_SRCSET_DENSITY_CAP}x - which makes them unnecessary for ` +
|
||||
`most use cases. Images that will be pinch-zoomed are typically the primary use case for` +
|
||||
`most use cases. Images that will be pinch-zoomed are typically the primary use case for ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x images. Please remove the high density descriptor and try again.`);
|
||||
}
|
||||
}
|
||||
@@ -628,10 +613,11 @@ function assertNoImageDistortion(
|
||||
RuntimeErrorCode.OVERSIZED_IMAGE,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the intrinsic image is significantly ` +
|
||||
`larger than necessary. ` +
|
||||
`Rendered image size: ${renderedWidth}w x ${renderedHeight}h. ` +
|
||||
`Intrinsic image size: ${intrinsicWidth}w x ${intrinsicHeight}h. ` +
|
||||
`Recommended intrinsic image size: ${recommendedWidth}w x ${recommendedHeight}h. ` +
|
||||
`Note: Recommended intrinsic image size is calculated assuming a maximum DPR of ` +
|
||||
`\nRendered image size: ${renderedWidth}w x ${renderedHeight}h. ` +
|
||||
`\nIntrinsic image size: ${intrinsicWidth}w x ${intrinsicHeight}h. ` +
|
||||
`\nRecommended intrinsic image size: ${recommendedWidth}w x ${
|
||||
recommendedHeight}h. ` +
|
||||
`\nNote: Recommended intrinsic image size is calculated assuming a maximum DPR of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}. To improve loading time, resize the image ` +
|
||||
`or consider using the "rawSrcset" and "sizes" attributes.`));
|
||||
}
|
||||
|
||||
@@ -17,19 +17,6 @@ export function isAbsoluteUrl(src: string): boolean {
|
||||
return /^https?:\/\//.test(src);
|
||||
}
|
||||
|
||||
// Assembles directive details string, useful for error messages.
|
||||
export function imgDirectiveDetails(rawSrc: string, includeRawSrc = true) {
|
||||
const rawSrcInfo =
|
||||
includeRawSrc ? `(activated on an <img> element with the \`rawSrc="${rawSrc}"\`) ` : '';
|
||||
return `The NgOptimizedImage directive ${rawSrcInfo}has detected that`;
|
||||
}
|
||||
|
||||
// Invokes a callback for each element in the array. Also invokes a callback
|
||||
// recursively for each nested array.
|
||||
export function deepForEach<T>(input: (T|any[])[], fn: (value: T) => void): void {
|
||||
input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
|
||||
}
|
||||
|
||||
// Given a URL, extract the hostname part.
|
||||
// If a URL is a relative one - the URL is returned as is.
|
||||
export function extractHostname(url: string): string {
|
||||
|
||||
@@ -367,7 +367,36 @@ describe('Image directive', () => {
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
`greater than ${
|
||||
RECOMMENDED_SRCSET_DENSITY_CAP}x - which makes them unnecessary for ` +
|
||||
`most use cases. Images that will be pinch-zoomed are typically the primary use case for` +
|
||||
`most use cases. Images that will be pinch-zoomed are typically the primary use case for ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x images. Please remove the high density descriptor and try again.`);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if rawSrcset exceeds the density cap with multiple digits', () => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => {
|
||||
const width = config.width ? `-${config.width}` : ``;
|
||||
return window.location.origin + `/path/${config.src}${width}.png`;
|
||||
};
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img" rawSrcset="1x, 200x" width="100" height="50">
|
||||
`;
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
`NG0${
|
||||
RuntimeErrorCode
|
||||
.INVALID_INPUT}: The NgOptimizedImage directive (activated on an <img> element with the \`rawSrc="img"\`) ` +
|
||||
`has detected that the \`rawSrcset\` contains an unsupported image density:` +
|
||||
`\`1x, 200x\`. NgOptimizedImage generally recommends a max image density of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}x but supports image densities up to ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
`greater than ${
|
||||
RECOMMENDED_SRCSET_DENSITY_CAP}x - which makes them unnecessary for ` +
|
||||
`most use cases. Images that will be pinch-zoomed are typically the primary use case for ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x images. Please remove the high density descriptor and try again.`);
|
||||
});
|
||||
|
||||
@@ -966,7 +995,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="1x, 2.5x, 3x" width="100" height="50">
|
||||
<img rawSrc="img.png" rawSrcset="1.75x, 2.5x, 3x" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -976,7 +1005,7 @@ describe('Image directive', () => {
|
||||
expect(img.src).toBe(`${IMG_BASE_URL}/img.png`);
|
||||
expect(img.srcset)
|
||||
.toBe(
|
||||
`${IMG_BASE_URL}/img.png?w=100 1x, ` +
|
||||
`${IMG_BASE_URL}/img.png?w=175 1.75x, ` +
|
||||
`${IMG_BASE_URL}/img.png?w=250 2.5x, ` +
|
||||
`${IMG_BASE_URL}/img.png?w=300 3x`);
|
||||
});
|
||||
|
||||
@@ -444,6 +444,7 @@ export class ComponentDecoratorHandler implements
|
||||
rawImports,
|
||||
resolvedImports,
|
||||
schemas,
|
||||
decorator: decorator?.node as ts.Decorator | null ?? null,
|
||||
},
|
||||
diagnostics,
|
||||
};
|
||||
@@ -483,6 +484,7 @@ export class ComponentDecoratorHandler implements
|
||||
imports: analysis.resolvedImports,
|
||||
animationTriggerNames: analysis.animationTriggerNames,
|
||||
schemas: analysis.schemas,
|
||||
decorator: analysis.decorator,
|
||||
});
|
||||
|
||||
this.resourceRegistry.registerResources(analysis.resources, node);
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface ComponentAnalysisData {
|
||||
resolvedImports: Reference<ClassDeclaration>[]|null;
|
||||
|
||||
schemas: SchemaMetadata[]|null;
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
export type ComponentResolutionData =
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface DirectiveHandlerData {
|
||||
outputs: ClassPropertyMapping;
|
||||
isPoisoned: boolean;
|
||||
isStructural: boolean;
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
export class DirectiveDecoratorHandler implements
|
||||
@@ -115,6 +116,7 @@ export class DirectiveDecoratorHandler implements
|
||||
providersRequiringFactory,
|
||||
isPoisoned: false,
|
||||
isStructural: directiveResult.isStructural,
|
||||
decorator: decorator?.node as ts.Decorator | null ?? null,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -149,6 +151,7 @@ export class DirectiveDecoratorHandler implements
|
||||
isStandalone: analysis.meta.isStandalone,
|
||||
imports: null,
|
||||
schemas: null,
|
||||
decorator: analysis.decorator,
|
||||
});
|
||||
|
||||
this.injectableRegistry.registerInjectable(node);
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface NgModuleAnalysis {
|
||||
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||
providers: ts.Expression|null;
|
||||
remoteScopesMayRequireCycleProtection: boolean;
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
export interface NgModuleResolution {
|
||||
@@ -442,6 +443,7 @@ export class NgModuleDecoratorHandler implements
|
||||
node, this.reflector, this.isCore, this.annotateForClosureCompiler),
|
||||
factorySymbolName: node.name.text,
|
||||
remoteScopesMayRequireCycleProtection,
|
||||
decorator: decorator?.node as ts.Decorator | null ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -464,6 +466,7 @@ export class NgModuleDecoratorHandler implements
|
||||
rawDeclarations: analysis.rawDeclarations,
|
||||
rawImports: analysis.rawImports,
|
||||
rawExports: analysis.rawExports,
|
||||
decorator: analysis.decorator,
|
||||
});
|
||||
|
||||
if (this.factoryTracker !== null) {
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface PipeHandlerData {
|
||||
meta: R3PipeMetadata;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
pipeNameExpr: ts.Expression;
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +144,7 @@ export class PipeDecoratorHandler implements
|
||||
},
|
||||
classMetadata: extractClassMetadata(clazz, this.reflector, this.isCore),
|
||||
pipeNameExpr,
|
||||
decorator: decorator?.node as ts.Decorator | null ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -159,6 +161,7 @@ export class PipeDecoratorHandler implements
|
||||
name: analysis.meta.pipeName,
|
||||
nameExpr: analysis.pipeNameExpr,
|
||||
isStandalone: analysis.meta.isStandalone,
|
||||
decorator: analysis.decorator,
|
||||
});
|
||||
|
||||
this.injectableRegistry.registerInjectable(node);
|
||||
|
||||
@@ -25,6 +25,11 @@ export interface TestOnlyOptions {
|
||||
*/
|
||||
_useHostForImportGeneration?: boolean;
|
||||
|
||||
/**
|
||||
* Enable the Language Service APIs for template type-checking for tests.
|
||||
*/
|
||||
_enableTemplateTypeChecker?: boolean;
|
||||
|
||||
/**
|
||||
* An option to enable ngtsc's internal performance tracing.
|
||||
*
|
||||
|
||||
@@ -256,6 +256,7 @@ export class NgCompiler {
|
||||
private cycleAnalyzer: CycleAnalyzer;
|
||||
readonly ignoreForDiagnostics: Set<ts.SourceFile>;
|
||||
readonly ignoreForEmit: Set<ts.SourceFile>;
|
||||
readonly enableTemplateTypeChecker: boolean;
|
||||
|
||||
/**
|
||||
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||
@@ -314,10 +315,12 @@ export class NgCompiler {
|
||||
readonly programDriver: ProgramDriver,
|
||||
readonly incrementalStrategy: IncrementalBuildStrategy,
|
||||
readonly incrementalCompilation: IncrementalCompilation,
|
||||
readonly enableTemplateTypeChecker: boolean,
|
||||
enableTemplateTypeChecker: boolean,
|
||||
readonly usePoisonedData: boolean,
|
||||
private livePerfRecorder: ActivePerfRecorder,
|
||||
) {
|
||||
this.enableTemplateTypeChecker =
|
||||
enableTemplateTypeChecker || (options._enableTemplateTypeChecker ?? false);
|
||||
this.constructionDiagnostics.push(
|
||||
...this.adapter.constructionDiagnostics, ...verifyCompatibleTypeCheckOptions(this.options));
|
||||
|
||||
@@ -1064,8 +1067,8 @@ export class NgCompiler {
|
||||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
this.inputProgram, notifyingDriver, traitCompiler, this.getTypeCheckingConfig(), refEmitter,
|
||||
reflector, this.adapter, this.incrementalCompilation, scopeReader, typeCheckScopeRegistry,
|
||||
this.delegatingPerfRecorder);
|
||||
reflector, this.adapter, this.incrementalCompilation, metaReader, scopeReader,
|
||||
typeCheckScopeRegistry, this.delegatingPerfRecorder);
|
||||
|
||||
// Only construct the extended template checker if the configuration is valid and usable.
|
||||
const extendedTemplateChecker = this.constructionDiagnostics.length === 0 ?
|
||||
|
||||
@@ -48,6 +48,13 @@ export interface NgModuleMeta {
|
||||
* because the module came from a .d.ts file).
|
||||
*/
|
||||
rawExports: ts.Expression|null;
|
||||
|
||||
/**
|
||||
* The primary decorator associated with this `ngModule`.
|
||||
*
|
||||
* If this is `null`, no decorator exists, meaning it's probably from a .d.ts file.
|
||||
*/
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,6 +168,13 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta {
|
||||
* For standalone components, the list of schemas declared.
|
||||
*/
|
||||
schemas: SchemaMetadata[]|null;
|
||||
|
||||
/**
|
||||
* The primary decorator associated with this directive.
|
||||
*
|
||||
* If this is `null`, no decorator exists, meaning it's probably from a .d.ts file.
|
||||
*/
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +205,7 @@ export interface PipeMeta {
|
||||
name: string;
|
||||
nameExpr: ts.Expression|null;
|
||||
isStandalone: boolean;
|
||||
decorator: ts.Decorator|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,6 +120,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||
imports: null,
|
||||
// The same goes for schemas.
|
||||
schemas: null,
|
||||
decorator: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +155,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||
name,
|
||||
nameExpr: null,
|
||||
isStandalone,
|
||||
decorator: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scope = scopeRegistry.getScopeOfModule(Module.node) as LocalModuleScope;
|
||||
@@ -77,6 +78,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -88,6 +90,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -99,6 +102,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) as LocalModuleScope;
|
||||
@@ -119,6 +123,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -130,6 +135,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) as LocalModuleScope;
|
||||
@@ -150,6 +156,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -161,6 +168,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -172,6 +180,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scope = scopeRegistry.getScopeOfModule(ModuleA.node) as LocalModuleScope;
|
||||
@@ -199,6 +208,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scope = scopeRegistry.getScopeOfModule(Module.node) as LocalModuleScope;
|
||||
@@ -218,6 +228,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -229,6 +240,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) as LocalModuleScope;
|
||||
@@ -248,6 +260,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
metaRegistry.registerNgModuleMetadata({
|
||||
kind: MetaKind.NgModule,
|
||||
@@ -259,6 +272,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
rawDeclarations: null,
|
||||
rawImports: null,
|
||||
rawExports: null,
|
||||
decorator: null,
|
||||
});
|
||||
|
||||
expect(scopeRegistry.getScopeOfModule(ModuleA.node)!.compilation.isPoisoned).toBeTrue();
|
||||
@@ -297,6 +311,7 @@ function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||
isStandalone: false,
|
||||
imports: null,
|
||||
schemas: null,
|
||||
decorator: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -308,6 +323,7 @@ function fakePipe(ref: Reference<ClassDeclaration>): PipeMeta {
|
||||
name,
|
||||
nameExpr: null,
|
||||
isStandalone: false,
|
||||
decorator: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,18 @@ export interface TemplateTypeChecker {
|
||||
*/
|
||||
getPotentialElementTags(component: ts.ClassDeclaration): Map<string, DirectiveInScope|null>;
|
||||
|
||||
/**
|
||||
* Get the primary decorator for an Angular class (such as @Component). This does not work for
|
||||
* `@Injectable`.
|
||||
*/
|
||||
getPrimaryAngularDecorator(target: ts.ClassDeclaration): ts.Decorator|null;
|
||||
|
||||
/**
|
||||
* Get the class of the NgModule that owns this Angular trait. If the result is `null`, that
|
||||
* probably means the provided component is standalone.
|
||||
*/
|
||||
getOwningNgModule(component: ts.ClassDeclaration): ts.ClassDeclaration|null;
|
||||
|
||||
/**
|
||||
* Retrieve any potential DOM bindings for the given element.
|
||||
*
|
||||
@@ -188,8 +200,8 @@ export interface TemplateTypeChecker {
|
||||
*/
|
||||
export enum OptimizeFor {
|
||||
/**
|
||||
* Indicates that a consumer of a `TemplateTypeChecker` is only interested in results for a given
|
||||
* file, and wants them as fast as possible.
|
||||
* Indicates that a consumer of a `TemplateTypeChecker` is only interested in results for a
|
||||
* given file, and wants them as fast as possible.
|
||||
*
|
||||
* Calling `TemplateTypeChecker` methods successively for multiple files while specifying
|
||||
* `OptimizeFor.SingleFile` can result in significant unnecessary overhead overall.
|
||||
@@ -197,8 +209,8 @@ export enum OptimizeFor {
|
||||
SingleFile,
|
||||
|
||||
/**
|
||||
* Indicates that a consumer of a `TemplateTypeChecker` intends to query for results pertaining to
|
||||
* the entire user program, and so the type-checker should internally optimize for this case.
|
||||
* Indicates that a consumer of a `TemplateTypeChecker` intends to query for results pertaining
|
||||
* to the entire user program, and so the type-checker should internally optimize for this case.
|
||||
*
|
||||
* Initial calls to retrieve type-checking information may take longer, but repeated calls to
|
||||
* gather information for the whole user program will be significantly faster with this mode of
|
||||
|
||||
@@ -13,7 +13,7 @@ import {ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
||||
import {Reference, ReferenceEmitter} from '../../imports';
|
||||
import {IncrementalBuild} from '../../incremental/api';
|
||||
import {MetaKind} from '../../metadata';
|
||||
import {MetadataReader, MetaKind} from '../../metadata';
|
||||
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
|
||||
import {ProgramDriver, UpdateMode} from '../../program_driver';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
@@ -85,6 +85,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
||||
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost,
|
||||
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
||||
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
|
||||
private readonly metaReader: MetadataReader,
|
||||
private readonly componentScopeReader: ComponentScopeReader,
|
||||
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry,
|
||||
private readonly perf: PerfRecorder) {}
|
||||
@@ -613,6 +614,50 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
||||
return REGISTRY.allKnownEventsOfElement(tagName);
|
||||
}
|
||||
|
||||
getPrimaryAngularDecorator(target: ts.ClassDeclaration): ts.Decorator|null {
|
||||
this.ensureAllShimsForOneFile(target.getSourceFile());
|
||||
|
||||
if (!isNamedClassDeclaration(target)) {
|
||||
return null;
|
||||
}
|
||||
const ref = new Reference(target);
|
||||
const dirMeta = this.metaReader.getDirectiveMetadata(ref);
|
||||
if (dirMeta !== null) {
|
||||
return dirMeta.decorator;
|
||||
}
|
||||
|
||||
const pipeMeta = this.metaReader.getPipeMetadata(ref);
|
||||
if (pipeMeta !== null) {
|
||||
return pipeMeta.decorator;
|
||||
}
|
||||
|
||||
const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
|
||||
if (ngModuleMeta !== null) {
|
||||
return ngModuleMeta.decorator;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getOwningNgModule(component: ts.ClassDeclaration): ts.ClassDeclaration|null {
|
||||
if (!isNamedClassDeclaration(component)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dirMeta = this.metaReader.getDirectiveMetadata(new Reference(component));
|
||||
if (dirMeta !== null && dirMeta.isStandalone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scope = this.componentScopeReader.getScopeForComponent(component);
|
||||
if (scope === null || scope.kind !== ComponentScopeKind.NgModule ||
|
||||
!isNamedClassDeclaration(scope.ngModule)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return scope.ngModule;
|
||||
}
|
||||
|
||||
private getScopeData(component: ts.ClassDeclaration): ScopeData|null {
|
||||
if (this.scopeCache.has(component)) {
|
||||
return this.scopeCache.get(component)!;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {AST, ASTWithSource, BindingPipe, Call, ParseSourceSpan, PropertyRead, Pr
|
||||
import ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ComponentScopeKind, ComponentScopeReader} from '../../scope';
|
||||
import {isAssignment, isSymbolWithValueDeclaration} from '../../util/src/typescript';
|
||||
|
||||
@@ -554,7 +554,8 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
|
||||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host,
|
||||
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry, NOOP_PERF_RECORDER);
|
||||
NOOP_INCREMENTAL_BUILD, fakeMetadataReader, fakeScopeReader, typeCheckScopeRegistry,
|
||||
NOOP_PERF_RECORDER);
|
||||
return {
|
||||
templateTypeChecker,
|
||||
program,
|
||||
@@ -665,6 +666,7 @@ function makeScope(program: ts.Program, sf: ts.SourceFile, decls: TestDeclaratio
|
||||
isStandalone: false,
|
||||
imports: null,
|
||||
schemas: null,
|
||||
decorator: null,
|
||||
});
|
||||
} else if (decl.type === 'pipe') {
|
||||
scope.dependencies.push({
|
||||
@@ -673,6 +675,7 @@ function makeScope(program: ts.Program, sf: ts.SourceFile, decls: TestDeclaratio
|
||||
name: decl.pipeName,
|
||||
nameExpr: null,
|
||||
isStandalone: false,
|
||||
decorator: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"//packages/compiler-cli/test:test_utils",
|
||||
|
||||
@@ -20,6 +20,7 @@ import {IndexedComponent} from '../../src/ngtsc/indexer';
|
||||
import {NgtscProgram} from '../../src/ngtsc/program';
|
||||
import {DeclarationNode} from '../../src/ngtsc/reflection';
|
||||
import {NgtscTestCompilerHost} from '../../src/ngtsc/testing';
|
||||
import {TemplateTypeChecker} from '../../src/ngtsc/typecheck/api';
|
||||
import {setWrapHostForTest} from '../../src/transformers/compiler_host';
|
||||
|
||||
type TsConfigOptionsValue =
|
||||
@@ -271,6 +272,17 @@ export class NgtscTestEnvironment {
|
||||
return defaultGatherDiagnostics(program as api.Program) as ts.Diagnostic[];
|
||||
}
|
||||
|
||||
driveTemplateTypeChecker(): {program: ts.Program, checker: TemplateTypeChecker} {
|
||||
const {rootNames, options} = readNgcCommandLineAndConfiguration(this.commandLineArgs);
|
||||
const host = createCompilerHost({options});
|
||||
const program = createProgram({rootNames, host, options});
|
||||
const checker = (program as NgtscProgram).compiler.getTemplateTypeChecker();
|
||||
return {
|
||||
program: program.getTsProgram(),
|
||||
checker,
|
||||
};
|
||||
}
|
||||
|
||||
driveIndexer(): Map<DeclarationNode, IndexedComponent> {
|
||||
const {rootNames, options} = readNgcCommandLineAndConfiguration(this.commandLineArgs);
|
||||
const host = createCompilerHost({options});
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 ts from 'typescript';
|
||||
|
||||
import {DiagnosticCategoryLabel} from '../../src/ngtsc/core/api';
|
||||
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {absoluteFrom as _, getSourceFileOrError} from '../../src/ngtsc/file_system';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {expectCompleteReuse, getSourceCodeForDiagnostic, loadStandardTestFiles} from '../../src/ngtsc/testing';
|
||||
import {factory as invalidBananaInBoxFactory} from '../../src/ngtsc/typecheck/extended/checks/invalid_banana_in_box';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
import {getClass} from './util';
|
||||
|
||||
const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true});
|
||||
|
||||
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('full type checking', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig({strictTemplates: true, _enableTemplateTypeChecker: true});
|
||||
});
|
||||
|
||||
describe('supports `getPrimaryAngularDecorator()` ', () => {
|
||||
it('for components', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'test-cmp',
|
||||
template: '<div></div>',
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const decorator = checker.getPrimaryAngularDecorator(getClass(sf!, 'TestCmp'));
|
||||
expect(decorator?.getText()).toContain(`selector: 'test-cmp'`);
|
||||
});
|
||||
|
||||
it('for pipes', () => {
|
||||
env.write('test.ts', `
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@Pipe({name: 'expPipe'})
|
||||
export class ExpPipe implements PipeTransform {
|
||||
transform(value: number, exponent = 1): number {
|
||||
return Math.pow(value, exponent);
|
||||
}
|
||||
}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const decorator = checker.getPrimaryAngularDecorator(getClass(sf!, 'ExpPipe'));
|
||||
expect(decorator?.getText()).toContain(`name: 'expPipe'`);
|
||||
});
|
||||
|
||||
it('for NgModules', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [],
|
||||
providers: [],
|
||||
bootstrap: []
|
||||
})
|
||||
export class AppModule {}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const decorator = checker.getPrimaryAngularDecorator(getClass(sf!, 'AppModule'));
|
||||
expect(decorator?.getText()).toContain(`declarations: []`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supports `getOwningNgModule()` ', () => {
|
||||
it('for components', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppCmp],
|
||||
imports: [],
|
||||
providers: [],
|
||||
bootstrap: [AppCmp]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: '<div></div>',
|
||||
})
|
||||
export class AppCmp {}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const ngModuleKnownClass = getClass(sf!, 'AppModule');
|
||||
expect(ngModuleKnownClass).not.toBeNull();
|
||||
const ngModuleRetrievedClass = checker.getOwningNgModule(getClass(sf!, 'AppCmp'));
|
||||
expect(ngModuleRetrievedClass).toEqual(ngModuleKnownClass);
|
||||
});
|
||||
|
||||
it('for standalone components (which should be null)', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppCmp],
|
||||
imports: [],
|
||||
providers: [],
|
||||
bootstrap: [AppCmp]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@Component({
|
||||
selector: 'app-cmp',
|
||||
template: '<div></div>',
|
||||
standalone: true,
|
||||
})
|
||||
export class AppCmp {}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const ngModuleKnownClass = getClass(sf!, 'AppModule');
|
||||
expect(ngModuleKnownClass).not.toBeNull();
|
||||
const ngModuleRetrievedClass = checker.getOwningNgModule(getClass(sf!, 'AppCmp'));
|
||||
expect(ngModuleRetrievedClass).toBe(null);
|
||||
});
|
||||
|
||||
it('for pipes', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, NgModule, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ExpPipe],
|
||||
imports: [],
|
||||
providers: [],
|
||||
})
|
||||
export class PipeModule {}
|
||||
|
||||
@Pipe({name: 'expPipe'})
|
||||
export class ExpPipe implements PipeTransform {
|
||||
transform(value: number, exponent = 1): number {
|
||||
return Math.pow(value, exponent);
|
||||
}
|
||||
}
|
||||
`);
|
||||
const {program, checker} = env.driveTemplateTypeChecker();
|
||||
const sf = program.getSourceFile(_('/test.ts'));
|
||||
expect(sf).not.toBeNull();
|
||||
const ngModuleKnownClass = getClass(sf!, 'PipeModule');
|
||||
expect(ngModuleKnownClass).not.toBeNull();
|
||||
const ngModuleRetrievedClass = checker.getOwningNgModule(getClass(sf!, 'ExpPipe'));
|
||||
expect(ngModuleRetrievedClass).toEqual(ngModuleKnownClass);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {ClassDeclaration, isNamedClassDeclaration} from '@angular/compiler-cli/src/ngtsc/reflection';
|
||||
import ts from 'typescript';
|
||||
|
||||
export function getClass(sf: ts.SourceFile, name: string): ClassDeclaration<ts.ClassDeclaration> {
|
||||
for (const stmt of sf.statements) {
|
||||
if (isNamedClassDeclaration(stmt) && stmt.name.text === name) {
|
||||
return stmt;
|
||||
}
|
||||
}
|
||||
throw new Error(`Class ${name} not found in file: ${sf.fileName}: ${sf.text}`);
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export class ShadowCss {
|
||||
// `:host-context(.one):host-context(.two)`.
|
||||
// Execute `_cssColonHostContextRe` over and over until we have extracted all the
|
||||
// `:host-context` selectors from this selector.
|
||||
let match: RegExpMatchArray|null;
|
||||
let match: RegExpExecArray|null;
|
||||
while (match = _cssColonHostContextRe.exec(selectorText)) {
|
||||
// `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>]
|
||||
|
||||
|
||||
@@ -33,18 +33,11 @@
|
||||
*
|
||||
* This regular expression was taken from the Closure sanitization library.
|
||||
*/
|
||||
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
|
||||
|
||||
/* A pattern that matches safe srcset values */
|
||||
const SAFE_SRCSET_PATTERN = /^(?:(?:https?|file):|[^&:/?#]*(?:[/?#]|$))/gi;
|
||||
|
||||
/** A pattern that matches safe data URLs. Only matches image, video and audio types. */
|
||||
const DATA_URL_PATTERN =
|
||||
/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+\/]+=*$/i;
|
||||
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|data|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
|
||||
|
||||
export function _sanitizeUrl(url: string): string {
|
||||
url = String(url);
|
||||
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url;
|
||||
if (url.match(SAFE_URL_PATTERN)) return url;
|
||||
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
console.warn(`WARNING: sanitizing unsafe URL value ${url} (see https://g.co/ng/security#xss)`);
|
||||
|
||||
@@ -110,9 +110,6 @@
|
||||
{
|
||||
"name": "Console"
|
||||
},
|
||||
{
|
||||
"name": "DATA_URL_PATTERN"
|
||||
},
|
||||
{
|
||||
"name": "DEFAULT_SERIALIZER"
|
||||
},
|
||||
|
||||
@@ -66,11 +66,6 @@ import {_sanitizeUrl, sanitizeSrcset} from '../../src/sanitization/url_sanitizer
|
||||
'javascript:',
|
||||
'jav	ascript:alert();',
|
||||
'jav\u0000ascript:alert();',
|
||||
'data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:text/javascript;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:application/x-msdownload;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
];
|
||||
for (const url of invalidUrls) {
|
||||
it(`valid ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
|
||||
|
||||
@@ -94,3 +94,38 @@ export function collectMemberMethods(
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an existing array literal expression, update it by pushing a new expression.
|
||||
*/
|
||||
export function addElementToArrayLiteral(
|
||||
arr: ts.ArrayLiteralExpression, elem: ts.Expression): ts.ArrayLiteralExpression {
|
||||
return ts.factory.updateArrayLiteralExpression(arr, [...arr.elements, elem]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an ObjectLiteralExpression node, extract and return the PropertyAssignment corresponding to
|
||||
* the given key. `null` if no such key exists.
|
||||
*/
|
||||
export function objectPropertyAssignmentForKey(
|
||||
obj: ts.ObjectLiteralExpression, key: string): ts.PropertyAssignment|null {
|
||||
const matchingProperty = obj.properties.filter(
|
||||
a => a.name !== undefined && ts.isIdentifier(a.name) && a.name.escapedText === key)[0];
|
||||
return matchingProperty && ts.isPropertyAssignment(matchingProperty) ? matchingProperty : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an ObjectLiteralExpression node, create or update the specified key, using the provided
|
||||
* callback to generate the new value (possibly based on an old value).
|
||||
*/
|
||||
export function updateObjectValueForKey(
|
||||
obj: ts.ObjectLiteralExpression, key: string,
|
||||
newValueFn: (oldValue?: ts.Expression) => ts.Expression): ts.ObjectLiteralExpression {
|
||||
const existingProp = objectPropertyAssignmentForKey(obj, key);
|
||||
const newProp = ts.factory.createPropertyAssignment(
|
||||
ts.factory.createIdentifier(key), newValueFn(existingProp?.initializer));
|
||||
return ts.factory.updateObjectLiteralExpression(obj, [
|
||||
...obj.properties.filter(p => p !== existingProp),
|
||||
newProp,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {collectMemberMethods, findTightestNode} from '../src/ts_utils';
|
||||
import {addElementToArrayLiteral, collectMemberMethods, findTightestNode, objectPropertyAssignmentForKey, updateObjectValueForKey} from '../src/ts_utils';
|
||||
import {LanguageServiceTestEnv, OpenBuffer, Project} from '../testing';
|
||||
|
||||
describe('ts utils', () => {
|
||||
describe('TS util', () => {
|
||||
describe('collectMemberMethods', () => {
|
||||
beforeEach(() => {
|
||||
initMockFileSystem('Native');
|
||||
@@ -84,4 +84,82 @@ describe('ts utils', () => {
|
||||
.sort();
|
||||
}
|
||||
});
|
||||
|
||||
describe('AST method', () => {
|
||||
let printer: ts.Printer;
|
||||
let sourceFile: ts.SourceFile;
|
||||
|
||||
function print(node: ts.Node): string {
|
||||
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
printer = ts.createPrinter();
|
||||
sourceFile =
|
||||
ts.createSourceFile('placeholder.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
|
||||
});
|
||||
|
||||
describe('addElementToArrayLiteral', () => {
|
||||
it('transforms an empty array literal expression', () => {
|
||||
const oldArr = ts.factory.createArrayLiteralExpression([], false);
|
||||
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('a'));
|
||||
expect(print(newArr)).toEqual('["a"]');
|
||||
});
|
||||
|
||||
it('transforms an existing array literal expression', () => {
|
||||
const oldArr =
|
||||
ts.factory.createArrayLiteralExpression([ts.factory.createStringLiteral('a')], false);
|
||||
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('b'));
|
||||
expect(print(newArr)).toEqual('["a", "b"]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectPropertyAssignmentForKey', () => {
|
||||
let oldObj: ts.ObjectLiteralExpression;
|
||||
|
||||
beforeEach(() => {
|
||||
oldObj = ts.factory.createObjectLiteralExpression(
|
||||
[ts.factory.createPropertyAssignment(
|
||||
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
|
||||
false);
|
||||
});
|
||||
|
||||
it('returns null when no property exists', () => {
|
||||
const prop = objectPropertyAssignmentForKey(oldObj, 'oops');
|
||||
expect(prop).toBeNull();
|
||||
});
|
||||
|
||||
it('returns the requested property assignment', () => {
|
||||
const prop = objectPropertyAssignmentForKey(oldObj, 'foo');
|
||||
expect(print(prop!)).toEqual('foo: "bar"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateObjectValueForKey', () => {
|
||||
let oldObj: ts.ObjectLiteralExpression;
|
||||
|
||||
const valueAppenderFn = (oldValue?: ts.Expression) => {
|
||||
if (!oldValue) return ts.factory.createStringLiteral('baz');
|
||||
if (!ts.isStringLiteral(oldValue)) return oldValue;
|
||||
return ts.factory.createStringLiteral(oldValue.text + 'baz');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
oldObj = ts.factory.createObjectLiteralExpression(
|
||||
[ts.factory.createPropertyAssignment(
|
||||
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
|
||||
false);
|
||||
});
|
||||
|
||||
it('creates a non-existant property', () => {
|
||||
const obj = updateObjectValueForKey(oldObj, 'newKey', valueAppenderFn);
|
||||
expect(print(obj)).toBe('{ foo: "bar", newKey: "baz" }');
|
||||
});
|
||||
|
||||
it('updates an existing property', () => {
|
||||
const obj = updateObjectValueForKey(oldObj, 'foo', valueAppenderFn);
|
||||
expect(print(obj)).toBe('{ foo: "barbaz" }');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {UnrecoverableStateEvent, UpdateActivatedEvent, UpdateAvailableEvent, VersionDetectedEvent, VersionEvent, VersionInstallationFailedEvent, VersionReadyEvent,} from './low_level';
|
||||
export {NoNewVersionDetectedEvent, UnrecoverableStateEvent, UpdateActivatedEvent, UpdateAvailableEvent, VersionDetectedEvent, VersionEvent, VersionInstallationFailedEvent, VersionReadyEvent} from './low_level';
|
||||
export {ServiceWorkerModule, SwRegistrationOptions} from './module';
|
||||
export {SwPush} from './push';
|
||||
export {SwUpdate} from './update';
|
||||
|
||||
Reference in New Issue
Block a user