Compare commits

...

83 Commits

Author SHA1 Message Date
Dylan Hunn edc45d5b6f release: cut the v17.2.3 release 2024-02-27 16:23:40 -08:00
Sumit Parakh 79a32816dc fix(core): fix typo in injectors.svg file (#54596)
this commit fixes the unnecessary single quote in injectors.svg file

Closes #54592

PR Close #54596
2024-02-27 15:21:17 -08:00
Angular Robot 11a0a5d017 build: update io_bazel_rules_sass digest to 888dd9c (#54615)
See associated pull request for more information.

PR Close #54615
2024-02-27 14:06:34 -08:00
Doug Parker 89db58691f release: bump Angular DevTools version to 1.0.11 (#54631)
PR Close #54631
2024-02-27 14:02:20 -08:00
Doug Parker 624767c076 Revert "refactor(devtools): implement iframe support for Angular DevTools' browser code (#53934)" (#54629)
This reverts commit dd3dac9cc9.

PR Close #54629
2024-02-27 14:00:17 -08:00
Doug Parker 7b30a869e1 Revert "refactor(devtools): implement multiframe support in devtools page (#53934)" (#54629)
This reverts commit ebcdc8dc96.

PR Close #54629
2024-02-27 14:00:17 -08:00
Doug Parker 986b9c3bea refactor: add --spawn_strategy local to --config debug (#54167)
This seems to be necessary to debug Node tests.

PR Close #54167
2024-02-26 18:36:42 -08:00
Andrew Scott 69598c0c6c refactor(core): Internal render hooks trigger view refresh before executing user hooks (#54224)
This commit ensures that any internal render hooks that cause views to
become dirty again first refresh those dirty views before running user
render hooks. This ensures that user render hooks have the most complete
render state possible and stops them from needlessly executing multiple
times. This is important to maintain the goal of the public render
hooks, which is to provide the safest place to place code that depends
on DOM state, especially in ways that may force a browser paint.

PR Close #54224
2024-02-26 18:31:16 -08:00
Andrew Scott ffe3aa1169 refactor(core): Add helper function for queuing state updates (#54224)
This adds a helper function to defer application state updates to the
first possible "safe" moment. If application-wide change detection
(ApplicationRef.tick) is currently executing when this function is used,
the callback will execute as soon as all views attached to the
`ApplicationRef` have been refreshed. Refreshing the application views
will happen again before `checkNoChanges` executes.

When a change detection is _not_ running, this state update will execute
in the microtask queue.

This function is necessary as a replacement for current
`Promise.resolve().then(() => stateUpdate())` to be zoneless compatible
while ensuring those state updates are synchronized to the DOM before
the browser repaint. Without this, updates done in `Promise.resolve(...)` would
queue another round of change detection in zoneless applications, and
this change detection could happen in the next browser frame, and cause
noticeable flicker for the user.

Additionally, this function provides a way to perform state updates that
will run on the server as well as in the browser.

Last, current applications using `ngZone: 'noop'` may not be
calling `ApplicationRef.tick` at all so this function provides a
mechanism to ensure the state update still happens by racing
a microtask with `afterNextRender` (which might never execute).

PR Close #54224
2024-02-26 18:31:15 -08:00
cexbrayat 2aefed8763 fix(compiler-cli): catch function instance properties in interpolated signal diagnostic (#54325)
Currently, the following template compiles without error, even if the signal is not properly called:

```
<div>{{ mySignal.name }}</div>
```

This is because `name` is one of the instance properties of Function (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function#instance_properties).

The interpolated signal diagnostic is now extended to catch such issues.

PR Close #54325
2024-02-26 18:30:06 -08:00
Angular Robot 377442d90f build: update scorecard action dependencies (#54407)
See associated pull request for more information.

PR Close #54407
2024-02-26 18:29:39 -08:00
cexbrayat 20a549f1de docs: use components in model inputs guide (#54606)
The code example showcases a `@Directive` with a template instead of a `@Component`.

PR Close #54606
2024-02-26 18:28:15 -08:00
Kristiyan Kostadinov daf7c611b2 fix(compiler-cli): identify aliased initializer functions (#54609)
Fixes that initializer functions weren't being recognized if they are aliased (e.g. `import {model as alias} from '@angular/core';`).

To do this efficiently, I had to introduce the `ImportedSymbolsTracker` which scans the top-level imports of a file and allows them to be checked quickly, without having to go through the type checker. It will be useful in the future when verifying that that initializer APIs aren't used in unexpected places.

I've also introduced tests specifically for the `tryParseInitializerApiMember` function so that we can test it in isolation instead of going through the various functions that call into it.

PR Close #54609
2024-02-26 18:27:19 -08:00
Dylan Hunn c1163535d1 Revert "fix(compiler-cli): identify aliased initializer functions (#54480)" (#54595)
This reverts commit f04ecc0cda.

PR Close #54595
2024-02-26 08:36:50 -08:00
Andrew Kushnir 57123524a2 fix(core): collect providers from NgModules while rendering @defer block (#52881)
Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves #52876.

PR Close #52881
2024-02-23 12:30:06 -08:00
Pawel Kozlowski dcd6c8c79b refactor(core): use performance API for signals (#54521)
This commit adds a standard performance marker that can be viewed in Chrome dev tools and other tooling.
See more info at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #54521
2024-02-23 12:18:31 -08:00
Kristiyan Kostadinov fbb0398795 docs: add a guide for model inputs (#54514)
Adds a guide for `model()` inputs for angular.io. Will port this over to angular.dev once it's finalized.

PR Close #54514
2024-02-23 11:47:04 -08:00
Felix Geißler 56ad65afa7 docs: syntax error in signal input transform example (#54567)
PR Close #54567
2024-02-23 11:46:14 -08:00
loka shafeek b1b7bbffb8 docs: Update wildcard route component to PageNotFoundComponent (#54570)
This PR enhances the Angular documentation by replacing the placeholder `<component-name>` with the actual `PageNotFoundComponent` in the wildcard route configuration. This update ensures that the documentation accurately reflects the recommended practice for handling page-not-found scenarios. Additionally, it addresses the issue where HTML entities like `&lt;` were being displayed instead of the desired `<` symbol, resulting in clearer and more readable documentation for users.
PR Close #54570
2024-02-23 11:45:39 -08:00
Kristiyan Kostadinov 48aec63ee4 fix(compiler-cli): identify aliased initializer functions (#54480)
Fixes that initializer functions weren't being recognized if they are aliased (e.g. `import {model as alias} from '@angular/core';`).

To do this efficiently, I had to introduce the `ImportedSymbolsTracker` which scans the top-level imports of a file and allows them to be checked quickly, without having to go through the type checker. It will be useful in the future when verifying that that initializer APIs aren't used in unexpected places.

I've also introduced tests specifically for the `tryParseInitializerApiMember` function so that we can test it in isolation instead of going through the various functions that call into it.

PR Close #54480
2024-02-23 11:44:40 -08:00
Lukas Matta d68cf175a4 docs: remove mutate function mention (#54569)
PR Close #54569
2024-02-23 11:43:25 -08:00
Matthieu Riegler 89f8331b7e ci: adding Matthieu as approver for ADEV (#54575)
This is to help the team approve changes on the new doc site.

PR Close #54575
2024-02-23 11:42:46 -08:00
Kristiyan Kostadinov 84ade02df6 refactor(compiler-cli): changes to get signal diagnostic working internally (#54585)
The diagnostic for signals that haven't been invoked wasn't working internally, because the path to `@angular/core` is different. These changes resolve the issue and do some general cleanup.

PR Close #54585
2024-02-23 11:42:07 -08:00
Kristiyan Kostadinov dbe673b027 fix(migrations): resolve infinite loop for a single line element with a long tag name and angle bracket on a new line (#54588)
Fixes an edge case where a single-line elemnt with a long tag name a closing bracket on a new line was putting the control flow migration into an infinite loop.

Fixes #54587.

PR Close #54588
2024-02-23 11:40:31 -08:00
Andrew Scott 1a526f2881 perf(common): AsyncPipe should not call markForCheck on subscription (#54554)
This commit prevents `AsyncPipe` from calling `markForCheck` when values
are synchronously emit during subscription to an observable. This
prevents subscriptions to `Replay` observables from needlessly walking
up to the root of the view tree during template execution for each
new replay observable in the template.

PR Close #54554
2024-02-23 09:20:05 -08:00
Alex Rickabaugh 600a8cd59a release: cut the v17.2.2 release 2024-02-21 17:54:30 -08:00
Jessica Janiuk bb57d34110 fix(migrations): Fix cf migration regular expression to include underscores (#54533)
In rare cases people may use an underscore in their component names, which was not accounted for in the formatting portion of the migration.

fixes: #54532

PR Close #54533
2024-02-21 15:25:07 -08:00
Doug Parker ef94bb6bf6 docs(devtools): expand release docs (#54525)
This provides some more information on how to release and notably includes commands for zipping extension code and source code, which can otherwise be tricky to get exactly right.

PR Close #54525
2024-02-21 15:24:27 -08:00
Doug Parker e627c6eaef release: bump Angular DevTools version to 1.0.10 (#54523)
PR Close #54523
2024-02-21 15:23:37 -08:00
Kristiyan Kostadinov 65c9148c71 refactor(compiler-cli): move defer block tests into separate file (#54499)
Splits the tests for `@defer` blocks out into a separate file since the `ngtsc_spec.ts` is getting quite large.

PR Close #54499
2024-02-21 15:22:37 -08:00
Kristiyan Kostadinov 0a3edfb543 fix(compiler-cli): correctly detect deferred dependencies across scoped nodes (#54499)
This is based on an internal issue report.

An earlier change introduced a diagnostic to report cases where a symbol is in the `deferredImports` array, but is used eagerly. The check worked by looking through the deferred blocks in a scope, resolving the scope for each and checking if the element is within the scope. The problem is that resolving the scope won't work across scoped node boundaries. For example, if there's a control flow statement around the block or within the block but around the deferred dependency, it won't be able to resolve the scope since it isn't a direct child, e.g.

```
@if (true) {
  @defer {
   <deferred-dep/>
  }
}
```

To fix this the case where the deferred block is inside a scoped node, I've changed the `R3BoundTarget.deferBlocks` to be a `Map` holding both the deferred block and its corresponding scope. Then to resolve the case where the dependency is within a scoped node inside the deferred block, I've added a depth-first traversal through the scopes within the deferred block.

PR Close #54499
2024-02-21 15:22:37 -08:00
Kristiyan Kostadinov f00336c31f refactor(compiler-cli): add the ability to treat object literals as enums in docs (#54487)
We have a couple of cases now (#53753 and #54414) where we're forced to redefine enums as object literals. These literals aren't rendered in the best way in the docs so these changes introduce a new `object-literal-as-enum` tag that we can use to mark them so they're treated for documentation purposes.

PR Close #54487
2024-02-21 15:21:59 -08:00
Dean Davidson 8997260e29 docs: add closing tags to code examples (#54485)
There were two minor issues in the code examples that this addresses.
PR Close #54485
2024-02-21 15:20:17 -08:00
Lukas Matta 033608e17b docs: fix typos (#54475)
PR Close #54475
2024-02-21 15:19:40 -08:00
vladboisa 926063eb0a docs(docs-infra): fix tab shrinking (#54380)
Fix a behavior when block shrinks on Usage notes tab

Fixes #52818

PR Close #54380
2024-02-21 15:19:14 -08:00
cexbrayat 187b46a3a0 docs: view child static query differences formatting (#54370)
The docs were broken as the first line is the only one to be displayed in the table.

PR Close #54370
2024-02-21 15:18:48 -08:00
Dmitry Ivanitskiy 6447c0eecc fix(compiler): adding the inert property to the "SCHEMA" array (#53148)
This change allows template binding "inert" attribute with the following syntax: [inert]="isInert"

Fixes #51879

fixup! fix(compiler): adding the inert property to the "SCHEMA" array

revert: "fixup! fix(compiler): adding the inert property to the "SCHEMA" array"

This reverts commit b637b7ce646e8bab2f585339028a84018e8ea982.

This commit is being reverted because the inert property is safe as a boolean attribute

PR Close #53148
2024-02-21 15:13:56 -08:00
Matthieu Riegler ed8857e46f refactor(platform-browser): use performance API for async animations (#53963)
This commit adds a standard performance marker

PR Close #53963
2024-02-21 15:12:05 -08:00
Andrew Scott 3e31f1a34e fix(router): Clear internal transition when navigation finalizes (#54261)
This commit fixes a small memory issue in the router where a destroyed
component instance would be retained.

fixes #54241

PR Close #54261
2024-02-21 14:09:58 -08:00
cexbrayat d411ee735a docs: typos in signal queries guide (#54540)
PR Close #54540
2024-02-21 15:59:01 +00:00
Kristiyan Kostadinov d34e3298db fix(common): image placeholder not removed in OnPush component (#54515)
Fixes that the placeholder wasn't being removed when an optimized image is placed in an `OnPush` component.

Fixes #54478.

PR Close #54515
2024-02-20 09:50:00 -08:00
Pawel Kozlowski db35266021 test(core): more tests for queries as signals (#54508)
A couple of tests that illustrate combination of signal
and decorator queries in once component.

PR Close #54508
2024-02-20 09:49:20 -08:00
JoostK 790f4f7c26 fix(compiler-cli): use correct symbol name for default imported symbols in defer blocks (#54495)
This commit addresses a problem with PR #53695 that introduced support for default imports,
where the actual dynamic import used in the defer loading function continued to use the
symbol name, instead of `.default` for the dynamic import. This issue went unnoticed in the
testcase because a proper instance was being generated for the `ɵsetClassMetadataAsync` function,
but not the generated dependency loader function.

Fixes #54491

PR Close #54495
2024-02-20 09:45:33 -08:00
Jamie Couperwhite 5e80e6db1c docs: fix developer preview link in signal queries (#54492)
PR Close #54492
2024-02-20 09:44:43 -08:00
Alex Castle 5597e5d172 docs(common): add new faq entry to NgOptimizedImage docs (#54469)
add new FAQ entry explaining that we're not adding new loaders to the angular repo

PR Close #54469
2024-02-20 09:43:47 -08:00
Ahmed Hakeem f8a5b40489 docs: enhance Dependency injection description (#54464)
PR Close #54464
2024-02-20 09:29:54 -08:00
Pawel Kozlowski 3bd5860c74 fix(core): properly execute content queries for root components (#54457)
Prior to this fix an incorrect view instance (a dynamically created component
one instead of the root view) was passed to the content query function. Having
incorrect view instance meant that a component instance could not be found.

This is a pre-existing bug, introduction of signal-based queries just surfaced it.

Fixes #54450

PR Close #54457
2024-02-15 12:21:29 -08:00
Pawel Kozlowski b975ff1bc2 docs: add guide for signal queries (#54395)
Adding new guide for the signal-based queries.

PR Close #54395
2024-02-15 12:17:01 -08:00
Fabio Heer ba686cc43f docs: correct default application builder for new apps (#54386)
The documentation on angular.dev mentions build-angular:browser as default
application builder. This is not correct. Recent applications are setup
with build-angular:application by default.

PR Close #54386
2024-02-15 10:54:18 -08:00
AleksanderBodurri a7e4b99515 refactor(devtools): implement multiframe support in devtools page (#53934)
In the Angular DevTools Chrome DevTools page:

- Angular DevTools is able to ask the background script to list each frame that has been registered on a page.
- Angular Devtools is able to ask the background script to "enable" the connection on a particular frame. This enables the messaging between the content script <-> background script <-> devtools page
- Implements detection of non unique urls on the inspected page

Limitations:
- The `inspectedWindow.eval` API is only able to target frames by frameURL. This means some features that integrate with Chrome DevTools like inspect element and open source will not be available when inspecting frames that do not have a unique url on the page.

PR Close #53934
2024-02-14 17:15:26 -08:00
AleksanderBodurri 02fcd928b2 refactor(devtools): implement iframe support for Angular DevTools' browser code (#53934)
Modifies the messaging layer of devtools to allow for switching communication between frames on a page. When served as a browser extension.

Design:
- When a page renders, DevTools installs a content script onto it through it's manifest file. The all_frames option is used here to install this script onto every frame in a page.
- When Angular is detected, the content script will install a backend script into it's frame.
- Each content script / backend script pairing is kept track of in the background script. This pairing represents an angular devtools context in a particular frame.
- Angular DevTools is able to ask the background script to list each frame that has been registered on a page.
- Angular Devtools is able to ask the background script to "enable" the connection on a particular frame. This enables the messaging between the content script <-> background script <-> devtools page

Limitations:
- The `inspectedWindow.eval` API is only able to target frames by frameURL. This means some features that integrate with Chrome DevTools like inspect element and open source will not be available when inspecting frames that do not have a unique url on the page.

PR Close #53934
2024-02-14 17:15:26 -08:00
Angular Robot fba7fe175c build: update all non-major dependencies (#54406)
See associated pull request for more information.

PR Close #54406
2024-02-14 15:39:37 -08:00
Joey Perrott e5e8be4d72 ci: remove feature-request bot (#54442)
Remove feature request bot from usage.

PR Close #54442
2024-02-14 15:33:54 -08:00
Kisters-BS 8521188cd1 docs: fix typo (#54427)
PR Close #54427
2024-02-14 15:29:21 -08:00
Paul Gschwendtner 44a0ea4e2d docs: add guide for signal inputs (#54316)
Adds a guide for signal inputs.

We will port that to `angular.dev` once we finalized this version.

PR Close #54316
2024-02-14 15:22:49 -08:00
Andrew Kushnir 3eda005680 release: cut the v17.2.1 release 2024-02-14 14:55:28 -08:00
Alex Rickabaugh 7234824228 fix(compiler-cli): fix broken version detection condition (#54443)
The version detection condition for signal two-way bindings used an OR
instead of an AND, resulting in every `.0` patch version being considered
as supporting two-way bindings to signals.

This commit fixes the logic and adds additional parentheses to ensure the
meaning of the condition is more clear. Long term, we should switch to
semver version parsing instead.

PR Close #54443
2024-02-14 12:47:03 -08:00
Andrew Kushnir 0b95d141ab release: cut the v17.2.0 release 2024-02-14 10:55:44 -08:00
Kristiyan Kostadinov 78b48cc272 refactor(compiler-cli): do not emit signal unwrap calls in versions older than 17.2 (#54423)
In order to allow both signals and non-signals in two-way bindings, we have to pass the expression through `ɵunwrapWritableSignal`. The problem is that the language service uses a bundled compiler that is fairly new, but it may be compiling an older version of Angular that doesn't expose `ɵunwrapWritableSignal` (see https://github.com/angular/vscode-ng-language-service/issues/2001).

These changes add a `_angularCoreVersion` flag to the compiler which the language service can use to pass the parsed Angular version to the compiler which can then decide whether to emit the function.

PR Close #54423
2024-02-13 15:53:42 -08:00
Matthieu Riegler 1c536250b6 fix(http): Use string body to generate transfer cache key. (#54379)
This is particularly usefull for GraphQL queries where the string body might be the only discriminator.

Fixes #54377

PR Close #54379
2024-02-13 11:53:22 -08:00
Joey Perrott 674b6c36b0 fix(docs-infra): don't include prerender flag based on fast/full build in adev (#54400)
The serve command for architect does not support the `--prerender` flag

PR Close #54400
2024-02-13 11:52:40 -08:00
Lukas Matta 35925263bd docs: fix typo (#54403)
PR Close #54403
2024-02-13 11:51:56 -08:00
Kristiyan Kostadinov 501c159e05 build: update node (#54399)
Updates the repo to a newer version of Node to fix errors in the integration tests.

PR Close #54399
2024-02-12 15:36:19 -08:00
Kristiyan Kostadinov 277540c4fc build: reuse fake core in type checking tests (#54344) (#54398)
Currently we have two fake copies of `@angular/core` in the compiler tests which can be out of sync and cause inconsistent tests. These changes reuse a single copy instead.

PR Close #54344

PR Close #54398
2024-02-12 14:31:26 -08:00
Kristiyan Kostadinov 6d00115bf4 fix(core): show placeholder block on the server with immediate trigger (#54394)
Currently all triggers are set up to show the placeholder block on the server, except for `on immediate` which is basically a noop. These changes update `on immediate` to match the rest of the triggers.

Fixes #54385.

PR Close #54394
2024-02-12 11:02:41 -08:00
Kristiyan Kostadinov ef965ac87c test(language-service): add tests for model inputs (#54387)
Updates the language service tests to cover `model()` inputs.

PR Close #54387
2024-02-12 11:01:54 -08:00
Kristiyan Kostadinov 369e96c837 refactor(compiler-cli): split input and model tests (#54387)
Splits up the tests for `input()` and `model()` into separate files.

PR Close #54387
2024-02-12 11:01:54 -08:00
Kristiyan Kostadinov 2363e77002 refactor(compiler-cli): add diagnostic tests for models (#54387)
Sets up type checking diagnostic tests for model() inputs.

PR Close #54387
2024-02-12 11:01:54 -08:00
Kristiyan Kostadinov 1581988d2e test(core): add type tests for ɵunwrapWritableSignal (#54387)
Getting the typing for `ɵunwrapWritableSignal` just right was tricky so these changes add some tests to ensure that we don't regress.

Also reworks the type tester a bit to make it easier to find where to add new test files.

PR Close #54387
2024-02-12 11:01:54 -08:00
Kristiyan Kostadinov 0ecc2d12f1 refactor(core): reuse input signal node for models (#54387)
Reworks the model so that it reuses `INPUT_SIGNAL_NODE` instead of implementing its own.

PR Close #54387
2024-02-12 11:01:53 -08:00
Kristiyan Kostadinov 453e00c9b0 refactor(core): avoid wrapper around subscribe return value (#54387)
Reworks `ModelSignal.subscribe` so it doesn't have to wrap its value to look like a subscription.

PR Close #54387
2024-02-12 11:01:53 -08:00
arturovt 4d722a276f fix(zone.js): patch form-associated custom element callbacks (#50686)
This commit updates the implementation of the `customElements` patch and also
patches FACE callbacks (`formAssociatedCallback`, `formDisabledCallback`, `formResetCallback`
and `formStateRestoreCallback`). This now allows invoking those callbacks in the same zone
where the custom element has been defined.

PR Close #50686
2024-02-12 08:50:57 -08:00
Pawel Kozlowski 744cb1e561 fix(core): return the same children query results if there are no changes (#54392)
Assure that the same readonly array corresponding to the children query results
is returned for cases where a query is marked as dirty but there were no actual
changes to the content of the results array (this can happen if a view is added
and removed thus marking queries as dirty but not influencing final results).

Fixes #54376

PR Close #54392
2024-02-12 08:48:29 -08:00
Joey Perrott deb9ea07c5 refactor(docs-infra): create fast mode (#54364)
Creates a "fast mode" for building the adev site, currently only disabling prerender during fast build.
This is intended to be used for local development.

PR Close #54364
2024-02-09 10:59:08 -08:00
JoostK 62b87b4551 fix(core): do not crash for signal query that does not have any matches (#54353)
The newly introduced signal queries would error if no match exists, due to an
invalid read within the query internals.

This commit addresses the crash by allowing there to be no matches.

PR Close #54353
2024-02-09 14:59:51 +00:00
Hakeem 6c278a9069 docs(docs-infra): change deprecated modules' headers' style in the hover state (#54337)
PR Close #54337
2024-02-09 14:58:56 +00:00
Joey Perrott 5de985a565 fix(docs-infra): include manually defined api reference docs in adev (#54356)
Include the manual defined api reference docs in adev

PR Close #54356
2024-02-09 14:58:29 +00:00
Angular Robot 85c05514be build: update cross-repo angular dependencies (#54186)
See associated pull request for more information.

PR Close #54186
2024-02-09 14:57:46 +00:00
Paul Gschwendtner 7aa92fa8fe release: cut the v17.2.0-rc.1 release 2024-02-09 11:54:07 +01:00
Kristiyan Kostadinov 4b96f370ee fix(core): expose model signal subcribe for type checking purposes (#54357)
The `@internal` in the comment above `ModelSignal.subscribe` ended up marking the method as internal even though it wasn't meant to be.

PR Close #54357
2024-02-09 09:58:56 +00:00
Matthieu Riegler 9e2c1405d1 refactor(devtools): show tooltip for hydration icon on directive forest (#54326)
The MatTooltip was missing after migrating to standalone

PR Close #54326
2024-02-08 19:18:37 +00:00
Joey Perrott 9f05732656 refactor: migrate router to prettier formatting (#54318)
Migrate formatting to prettier for router from clang-format

PR Close #54318
2024-02-08 19:17:14 +00:00
Jessica Janiuk ed41bfcad7 release: cut the v17.2.0-rc.0 release 2024-02-08 10:38:52 -08:00
263 changed files with 21177 additions and 13554 deletions
+4 -1
View File
@@ -1,5 +1,5 @@
# Enable debugging tests with --config=debug
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --spawn_strategy=local
# Do not attempt to de-flake locally.
# On CI we might set this to `3` to run with deflaking.
@@ -177,6 +177,9 @@ test:saucelabs --flaky_test_attempts=1
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
# --adev_fast will run adev build/serve in a faster mode, skipping things like prerendering
# for local development.
build --flag_alias=fast_adev=//adev:fast_build_mode
####################################################
# User bazel configuration
+4 -4
View File
@@ -21,16 +21,16 @@ jobs:
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build --config=aio_local_deps //adev:build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
with:
bazelrc: ./.bazelrc.user
@@ -34,7 +34,7 @@ jobs:
# the number of concurrent actions is determined based on the host resources.
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
with:
workflow-artifact-name: 'aio'
pull-number: '${{github.event.pull_request.number}}'
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'aio'
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/branch-manager@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
with:
bazelrc: ./.bazelrc.user
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -31,7 +31,7 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Starting Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh run &
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
+28 -28
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
@@ -60,13 +60,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -78,13 +78,13 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
@@ -94,15 +94,15 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with upstream packages
@@ -115,11 +115,11 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
@@ -130,15 +130,15 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with local packages
@@ -153,15 +153,15 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Set the stable branch environment variable
@@ -197,13 +197,13 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -215,7 +215,7 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
@@ -223,9 +223,9 @@ jobs:
./packages/zone.js/node_modules
./packages/zone.js/test/typings/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -264,7 +264,7 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -272,9 +272,9 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/commit-message-based-labels@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/commit-message-based-labels@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/post-approval-changes@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/post-approval-changes@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
-19
View File
@@ -1,19 +0,0 @@
name: Feature request triage bot
on:
schedule:
# Run at 14:00 every day
- cron: '0 14 * * *'
# Declare default permissions as read only.
permissions:
contents: read
jobs:
feature_triage:
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/feature-request@f89362d698858f9674463e48fde514b097a15838
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/google-internal-tests@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/google-internal-tests@c83e99a12397014162531ca125c94549db55dd84
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@f89362d698858f9674463e48fde514b097a15838
- uses: angular/dev-infra/github-actions/unified-status-check@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -22,15 +22,15 @@ jobs:
version: ['https://next.angular.io/', 'https://angular.io/']
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules in aio
run: yarn install --frozen-lockfile --cwd aio
- name: Run basic e2e and deployment config tests.
@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
# Upload the results as artifacts.
- name: 'Upload artifact'
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: SARIF file
path: results.sarif
@@ -47,6 +47,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
with:
sarif_file: results.sarif
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
env:
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
with:
branch-prefix: update-cli-help
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- name: Generate `events.json`
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
with:
branch-prefix: docs-update-events
pr-title: 'docs: update events'
+3 -1
View File
@@ -20,6 +20,7 @@ export const format: FormatConfig = {
'packages/examples/**/*.{js,ts}',
'packages/misc/**/*.{js,ts}',
'packages/private/**/*.{js,ts}',
'packages/router/**/*.{js,ts}',
'packages/service-worker/**/*.{js,ts}',
'packages/upgrade/**/*.{js,ts}',
@@ -33,7 +34,7 @@ export const format: FormatConfig = {
},
'clang-format': {
'matchers': [
//'**/*.{js,ts}',
'**/*.{js,ts}',
// TODO: burn down format failures and remove aio and integration exceptions.
'!aio/**',
'!integration/**',
@@ -70,6 +71,7 @@ export const format: FormatConfig = {
'!packages/examples/**/*.{js,ts}',
'!packages/misc/**/*.{js,ts}',
'!packages/private/**/*.{js,ts}',
'!packages/router/**/*.{js,ts}',
'!packages/service-worker/**/*.{js,ts}',
'!packages/upgrade/**/*.{js,ts}',
],
+1 -1
View File
@@ -1 +1 @@
18.13.0
18.18.2
+1
View File
@@ -2,6 +2,7 @@
"printWidth": 100,
"tabWidth": 2,
"tabs": false,
"embeddedLanguageFormatting": "off",
"singleQuote": true,
"semicolon": true,
"quoteProps": "preserve",
+4
View File
@@ -322,7 +322,9 @@ groups:
'aio/content/guide/providers.md',
'aio/content/examples/providers/**/{*,.*}',
'aio/content/images/guide/providers/**/{*,.*}',
'aio/content/guide/signal-queries.md',
'aio/content/guide/singleton-services.md',
'aio/content/guide/signal-inputs.md',
'aio/content/guide/sharing-ngmodules.md',
'aio/content/guide/standalone-components.md',
'aio/content/guide/standalone-migration.md',
@@ -345,6 +347,7 @@ groups:
'aio/content/guide/defer.md',
'aio/content/guide/signals.md',
'aio/content/guide/control_flow.md',
'aio/content/guide/model-inputs.md',
'aio/content/examples/injection-token/**/{*,.*}',
])
reviewers:
@@ -1081,6 +1084,7 @@ groups:
- crisbeto
- atscott
- pkozlowski-opensource
- ~jeanmeche
# =========================================================
# Docs-infra
+173 -112
View File
@@ -1,3 +1,114 @@
<a name="17.2.3"></a>
# 17.2.3 (2024-02-27)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [1a526f2881](https://github.com/angular/angular/commit/1a526f28814429c05b003edbe40500b7069e1404) | perf | `AsyncPipe` should not call `markForCheck` on subscription ([#54554](https://github.com/angular/angular/pull/54554)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [2aefed8763](https://github.com/angular/angular/commit/2aefed87632293c5570e17653a7d270162ea35c3) | fix | catch function instance properties in interpolated signal diagnostic ([#54325](https://github.com/angular/angular/pull/54325)) |
| [48aec63ee4](https://github.com/angular/angular/commit/48aec63ee48fe9d20c1f1565b044ec359100736d) | fix | identify aliased initializer functions ([#54480](https://github.com/angular/angular/pull/54480)) |
| [daf7c611b2](https://github.com/angular/angular/commit/daf7c611b23797ba2dba24e20e26c344902e7bc7) | fix | identify aliased initializer functions ([#54609](https://github.com/angular/angular/pull/54609)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [57123524a2](https://github.com/angular/angular/commit/57123524a2e1481987eaf239d2ae7f1216291864) | fix | collect providers from NgModules while rendering `@defer` block ([#52881](https://github.com/angular/angular/pull/52881)) |
| [79a32816dc](https://github.com/angular/angular/commit/79a32816dcb57e9b02ba6e18261c6dccd14b0b23) | fix | fix typo in injectors.svg file ([#54596](https://github.com/angular/angular/pull/54596)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [dbe673b027](https://github.com/angular/angular/commit/dbe673b02751b8bf12f6f6f79b843dd268d965e1) | fix | resolve infinite loop for a single line element with a long tag name and angle bracket on a new line ([#54588](https://github.com/angular/angular/pull/54588)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.2"></a>
# 17.2.2 (2024-02-21)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [d34e3298db](https://github.com/angular/angular/commit/d34e3298db2da9d3aa8f8e2c49e1d1aed849e7d2) | fix | image placeholder not removed in OnPush component ([#54515](https://github.com/angular/angular/pull/54515)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [6447c0eecc](https://github.com/angular/angular/commit/6447c0eecc15800417bb5bde2d273865c559b7d4) | fix | adding the inert property to the "SCHEMA" array ([#53148](https://github.com/angular/angular/pull/53148)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [0a3edfb543](https://github.com/angular/angular/commit/0a3edfb5433e1b67f37d7b3f69a5c77b48eedce6) | fix | correctly detect deferred dependencies across scoped nodes ([#54499](https://github.com/angular/angular/pull/54499)) |
| [790f4f7c26](https://github.com/angular/angular/commit/790f4f7c26474af2b1d81a4f3c761047bf920edf) | fix | use correct symbol name for default imported symbols in defer blocks ([#54495](https://github.com/angular/angular/pull/54495)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [3bd5860c74](https://github.com/angular/angular/commit/3bd5860c749517c4d35850703c53a768138b7bde) | fix | properly execute content queries for root components ([#54457](https://github.com/angular/angular/pull/54457)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [bb57d34110](https://github.com/angular/angular/commit/bb57d34110ea8796f5c5f522b98dadf078e8ad8a) | fix | Fix cf migration regular expression to include underscores ([#54533](https://github.com/angular/angular/pull/54533)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [3e31f1a34e](https://github.com/angular/angular/commit/3e31f1a34ee97a802a4032cd9e7816d683a51d5e) | fix | Clear internal transition when navigation finalizes ([#54261](https://github.com/angular/angular/pull/54261)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.1"></a>
# 17.2.1 (2024-02-14)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [7234824228](https://github.com/angular/angular/commit/7234824228df11249a5ebe01a6dee381be74e02e) | fix | fix broken version detection condition ([#54443](https://github.com/angular/angular/pull/54443)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.0"></a>
# 17.2.0 (2024-02-14)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [03c3b3eb79](https://github.com/angular/angular/commit/03c3b3eb79ec061b0031d6ad7ba386d185c87d8d) | feat | add Netlify image loader ([#54311](https://github.com/angular/angular/pull/54311)) |
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
| [b774e22d8e](https://github.com/angular/angular/commit/b774e22d8e384f43e9cd8f5c55475d06e7f66988) | feat | make it configurable to generate alias reexports ([#53937](https://github.com/angular/angular/pull/53937)) |
| [3e1384048e](https://github.com/angular/angular/commit/3e1384048eb76c92532ae19aa2883318121c00e8) | feat | support host directives for local compilation mode ([#53877](https://github.com/angular/angular/pull/53877)) |
| [a592904c69](https://github.com/angular/angular/commit/a592904c691844d2c1aed00bd914edabef49f9b1) | fix | allow custom/duplicate decorators for `@Injectable` classes in local compilation mode ([#54139](https://github.com/angular/angular/pull/54139)) |
| [4b1d948b36](https://github.com/angular/angular/commit/4b1d948b36285ec6d80dbe93e0b92133f9d4be94) | fix | consider the case of duplicate Angular decorators in local compilation diagnostics ([#54139](https://github.com/angular/angular/pull/54139)) |
| [96bcf4fb12](https://github.com/angular/angular/commit/96bcf4fb1208d1f073784a2cde4a03553e905807) | fix | forbid custom/duplicate decorator when option `forbidOrphanComponents` is set ([#54139](https://github.com/angular/angular/pull/54139)) |
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
| [6c8b09468a](https://github.com/angular/angular/commit/6c8b09468a05a80cba3960861f0ab8d3bae80415) | fix | highlight the unresolved element in the @Component.styles array for the error LOCAL_COMPILATION_UNRESOLVED_CONST ([#54230](https://github.com/angular/angular/pull/54230)) |
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
| [f39cb06418](https://github.com/angular/angular/commit/f39cb064183d984254bdf4e41b61d3dc9379738a) | fix | show specific error for unresolved @Directive.exportAs in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
| [f3851b5945](https://github.com/angular/angular/commit/f3851b59459a1d9c214ace3db5a716d51c1f93c7) | fix | show specific error for unresolved @HostBinding's argument in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
| [39ddd884e8](https://github.com/angular/angular/commit/39ddd884e826cc0be63fd0f7d7de20d642877ef9) | fix | show specific error for unresolved @HostListener's event name in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
| [5d633240fd](https://github.com/angular/angular/commit/5d633240fd5927c4318a9240e60c3a30b2333cee) | fix | show the correct message for the error LOCAL_COMPILATION_UNRESOLVED_CONST when an unresolved symbol used for @Component.styles ([#54230](https://github.com/angular/angular/pull/54230)) |
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [702ab28b4c](https://github.com/angular/angular/commit/702ab28b4c07a903c403a20af2ca287348b6afd0) | feat | add support for model inputs ([#54252](https://github.com/angular/angular/pull/54252)) |
| [e95ef2cbc6](https://github.com/angular/angular/commit/e95ef2cbc6f850d8fe96218b74cff76cea947674) | feat | expose queries as signals ([#54283](https://github.com/angular/angular/pull/54283)) |
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
| [62b87b4551](https://github.com/angular/angular/commit/62b87b4551d77815f58af152d1921de3733621ba) | fix | do not crash for signal query that does not have any matches ([#54353](https://github.com/angular/angular/pull/54353)) |
| [4b96f370ee](https://github.com/angular/angular/commit/4b96f370eea08d2531cc54f65a651f94b504692d) | fix | expose model signal subcribe for type checking purposes ([#54357](https://github.com/angular/angular/pull/54357)) |
| [744cb1e561](https://github.com/angular/angular/commit/744cb1e561d9b0cbffd20ad612d5ea9e2cf6b2e7) | fix | return the same children query results if there are no changes ([#54392](https://github.com/angular/angular/pull/54392)) |
| [6d00115bf4](https://github.com/angular/angular/commit/6d00115bf45731b80178e11921df6ec1dc89efaf) | fix | show placeholder block on the server with immediate trigger ([#54394](https://github.com/angular/angular/pull/54394)) |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [1c536250b6](https://github.com/angular/angular/commit/1c536250b6112c67a68159de50c4afe0dbc440f8) | fix | Use string body to generate transfer cache key. ([#54379](https://github.com/angular/angular/pull/54379)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.3"></a>
# 17.1.3 (2024-02-08)
### compiler-cli
@@ -21,50 +132,9 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.0-next.1"></a>
# 17.2.0-next.1 (2024-01-31)
###
| Commit | Type | Description |
| -- | -- | -- |
| [dfc6c8d0c7](https://github.com/angular/angular/commit/dfc6c8d0c76380ee7beb6d904261e40857b375ed) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
### animations
| Commit | Type | Description |
| -- | -- | -- |
| [75aeae42b7](https://github.com/angular/angular/commit/75aeae42b7f512553262d515966f43d11e34d228) | fix | cleanup DOM elements when root view is removed with async animations ([#53033](https://github.com/angular/angular/pull/53033)) |
### common
| Commit | Type | Description |
| -- | -- | -- |
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
| [122213d37d](https://github.com/angular/angular/commit/122213d37d3e73fc0dcfd5a10a2c388dc573b6cf) | fix | The date pipe should return ISO format for week and week-year as intended in the unit test. ([#53879](https://github.com/angular/angular/pull/53879)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
| [eddf5dae5e](https://github.com/angular/angular/commit/eddf5dae5eb9e1aa3ca4c276c2cb2b897b73a9e0) | fix | Update type check block to fix control flow source mappings ([#53980](https://github.com/angular/angular/pull/53980)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
| [037b79b72e](https://github.com/angular/angular/commit/037b79b72ea18f08b3a74f9ad541bbdca183b1aa) | fix | change defer block fixture default behavior to playthrough ([#54088](https://github.com/angular/angular/pull/54088)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [28ad6fc4ad](https://github.com/angular/angular/commit/28ad6fc4ad518884bb5777fd20e9075d8969b27a) | fix | error in standalone migration when non-array value is used as declarations in TestBed ([#54122](https://github.com/angular/angular/pull/54122)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.2"></a>
# 17.1.2 (2024-01-31)
###
###
| Commit | Type | Description |
| -- | -- | -- |
| [ccddacf11d](https://github.com/angular/angular/commit/ccddacf11deaebeda12e1bdb6e93ec401397d352) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
@@ -91,15 +161,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.0-next.0"></a>
# 17.2.0-next.0 (2024-01-24)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.1"></a>
# 17.1.1 (2024-01-24)
### router
@@ -435,9 +496,9 @@ Live long and prosper 🖖🏻
[Blog post "Angular v17 is now available"](http://goo.gle/angular-v17).
## Breaking Changes
###
###
- Node.js v16 support has been removed and the minimum support version has been bumped to 18.13.0.
Node.js v16 is planned to be End-of-Life on 2023-09-11. Angular will stop supporting Node.js v16 in Angular v17. For Node.js release schedule details, please see: https://github.com/nodejs/release#release-schedule
### common
- the NgSwitch directive now defaults to the === equality operator,
@@ -451,30 +512,30 @@ Live long and prosper 🖖🏻
- The `mutate` method was removed from the `WritableSignal` interface and completely
dropped from the public API surface. As an alternative, please use the `update` method and
make immutable changes to the object.
Example before:
```typescript
items.mutate(itemsArray => itemsArray.push(newItem));
```
Example after:
```typescript
items.update(itemsArray => [itemsArray, …newItem]);
```
- The `mutate` method was removed from the `WritableSignal` interface and completely
dropped from the public API surface. As an alternative please use the update method and
make immutable changes to the object.
Example before:
```typescript
items.mutate(itemsArray => itemsArray.push(newItem));
```
Example after:
```typescript
items.update(itemsArray => [itemsArray, …newItem]);
```
@@ -490,7 +551,7 @@ Live long and prosper 🖖🏻
`detectChanges` on that component's `ChangeDetectorRef`.
### platform-browser
- `REMOVE_STYLES_ON_COMPONENT_DESTROY` default value is now `true`. This causes CSS of components to be removed from the DOM when destroyed. You retain the previous behaviour by providing the `REMOVE_STYLES_ON_COMPONENT_DESTROY` injection token.
```ts
import {REMOVE_STYLES_ON_COMPONENT_DESTROY} from '@angular/platform-browser';
...
@@ -513,13 +574,13 @@ Live long and prosper 🖖🏻
This should instead be configured through the provideRouter or RouterModule.forRoot APIs.
- The following Router properties have been removed from
the public API:
- canceledNavigationResolution
- paramsInheritanceStrategy
- titleStrategy
- urlUpdateStrategy
- malformedUriErrorHandler
These should instead be configured through the `provideRouter` or
`RouterModule.forRoot` APIs.
- The `setupTestingRouter` function has been removed. Use
@@ -530,7 +591,7 @@ Live long and prosper 🖖🏻
handled in the `UrlSerializer.parse` method.
### zone.js
- Deep and legacy `dist/` imports like `zone.js/bundles/zone-testing.js` and `zone.js/dist/zone` are no longer allowed. `zone-testing-bundle` and `zone-testing-node-bundle` are also no longer part of the package.
The proper way to import `zone.js` and `zone.js/testing` is:
```js
import 'zone.js';
@@ -541,7 +602,7 @@ Live long and prosper 🖖🏻
- The `AnimationDriver.NOOP` symbol is deprecated, use `NoopAnimationDriver` instead.
### core
- `ChangeDetectorRef.checkNoChanges` is deprecated.
Test code should use `ComponentFixture` instead of `ChangeDetectorRef`.
Application code should not call `ChangeDetectorRef.checkNoChanges` directly.
- Swapping out the context object for `EmbeddedViewRef`
@@ -550,12 +611,12 @@ Live long and prosper 🖖🏻
simple assignments in most cases, or `Object.assign` , or alternatively
still replace the full object by using a `Proxy` (see `NgTemplateOutlet`
as an example).
Also adds a warning if the deprecated
- NgProbeToken
The `NgProbeToken` is not used internally since the transition from View Engine to Ivy. The token has no utility and can be removed from applications and libraries.
###
###
| Commit | Type | Description |
| -- | -- | -- |
| [59aa0634f4](https://github.com/angular/angular/commit/59aa0634f4d4694203f2a69c40017fe5a3962514) | build | remove support for Node.js v16 ([#51755](https://github.com/angular/angular/pull/51755)) |
@@ -1169,7 +1230,7 @@ Live long and prosper 🖖🏻
[Blog post "Angular v16 is now available"](https://goo.gle/angular-v16).
## Breaking Changes
###
###
- Angular Compatibility Compiler (ngcc) has been removed and as a result Angular View Engine libraries will no longer work
- Deprecated `EventManager` method `addGlobalEventListener` has been removed as it is not used by Ivy.
### bazel
@@ -1192,14 +1253,14 @@ Live long and prosper 🖖🏻
The ideal fix would be to update the code to instead be compatible with
`MockPlatformLocation` instead.
- If the 'ngTemplateOutletContext' is different from the context, it will result in a compile-time error.
Before the change, the following template was compiling:
```typescript
interface MyContext {
$implicit: string;
}
@Component({
standalone: true,
imports: [NgTemplateOutlet],
@@ -1217,7 +1278,7 @@ Live long and prosper 🖖🏻
}
```
However, it does not compile now because the 'xxx' property does not exist in 'MyContext', resulting in the error: 'Type '{ $implicit: string; xxx: string; }' is not assignable to type 'MyContext'.'
The solution is either:
- add the 'xxx' property to 'MyContext' with the correct type or
- add '$any(...)' inside the template to make the error disappear. However, adding '$any(...)' does not correct the error but only preserves the previous behavior of the code.
@@ -1236,7 +1297,7 @@ Live long and prosper 🖖🏻
value differs from the previous call to `setInput`.
- `RendererType2.styles` no longer accepts a nested arrays.
- The `APP_ID` token value is no longer randomly generated. If you are bootstrapping multiple application on the same page you will need to set to provide the `APP_ID` yourself.
```ts
bootstrapApplication(ComponentA, {
providers: [
@@ -1247,21 +1308,21 @@ Live long and prosper 🖖🏻
```
- The `ReflectiveInjector` and related symbols were removed. Please update the code to avoid references to the `ReflectiveInjector` symbol. Use `Injector.create` as a replacement to create an injector instead.
- Node.js v14 support has been removed
Node.js v14 is planned to be End-of-Life on 2023-04-30. Angular will stop supporting Node.js v14 in Angular v16. Angular v16 will continue to officially support Node.js versions v16 and v18.
### platform-browser
- The deprecated `BrowserTransferStateModule` was removed, since it's no longer needed. The `TransferState` class can be injected without providing the module. The `BrowserTransferStateModule` was empty starting from v14 and you can just remove the reference to that module from your applications.
### platform-server
- Users that are using SSR with JIT mode will now need to add `import to @angular/compiler` before bootstrapping the application.
**NOTE:** this does not effect users using the Angular CLI.
- `renderApplication` method no longer accepts a root component as first argument. Instead, provide a bootstrapping function that returns a `Promise<ApplicationRef>`.
Before
```ts
const output: string = await renderApplication(RootComponent, options);
```
Now
```ts
const bootstrap = () => bootstrapApplication(RootComponent, appConfig);
@@ -1296,14 +1357,14 @@ Live long and prosper 🖖🏻
## Deprecations
### core
- `makeStateKey`, `StateKey` and `TransferState` exports have been moved from `@angular/platform-browser` to `@angular/core`. Please update the imports.
```diff
- import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
+ import {makeStateKey, StateKey, TransferState} from '@angular/core';
```
- `EnvironmentInjector.runInContext` is now deprecated, with
`runInInjectionContext` functioning as a direct replacement:
```typescript
// Previous method version (deprecated):
envInjector.runInContext(fn);
@@ -1316,7 +1377,7 @@ Live long and prosper 🖖🏻
### platform-browser
- `BrowserModule.withServerTransition` has been deprecated. `APP_ID` should be used instead to set the application ID.
NB: Unless, you render multiple Angular applications on the same page, setting an application ID is not necessary.
Before:
```ts
imports: [
@@ -1324,7 +1385,7 @@ Live long and prosper 🖖🏻
...
]
```
After:
```ts
imports: [
@@ -1336,7 +1397,7 @@ Live long and prosper 🖖🏻
- `ApplicationConfig` has moved, please import `ApplicationConfig` from `@angular/core` instead.
### platform-server
- `PlatformConfig.baseUrl` and `PlatformConfig.useAbsoluteUrl` platform-server config options are deprecated as these were not used.
###
###
| Commit | Type | Description |
| -- | -- | -- |
| [48aa96ea13](https://github.com/angular/angular/commit/48aa96ea13ebfadf2f6b13516c7702dae740a7be) | refactor | remove Angular Compatibility Compiler (ngcc) ([#49101](https://github.com/angular/angular/pull/49101)) |
@@ -1645,11 +1706,11 @@ Alan Agius, Andrew Kushnir, Aristeidis Bampakos, Craig Spence, Doug Parker, Ivá
<a name="15.2.0"></a>
# 15.2.0 (2023-02-22)
## Deprecations
###
###
- Class and `InjectionToken` guards and resolvers are
deprecated. Instead, write guards as plain JavaScript functions and
inject dependencies with `inject` from `@angular/core`.
###
###
| Commit | Type | Description |
| -- | -- | -- |
| [926c35f4ac](https://github.com/angular/angular/commit/926c35f4ac70f5e4d142e545d6d056dd67aac97b) | docs | Deprecate class and InjectionToken and resolvers ([#47924](https://github.com/angular/angular/pull/47924)) |
@@ -1814,21 +1875,21 @@ Alan Agius, Alex Rickabaugh, Andrew Scott, Aristeidis Bampakos, Bob Watson, Jens
- CanLoad guards in the Router are deprecated. Use CanMatch
instead.
- router writable properties
The following strategies are meant to be configured by registering the
application strategy in DI via the `providers` in the root `NgModule` or
`bootstrapApplication`:
* `routeReuseStrategy`
* `titleStrategy`
* `urlHandlingStrategy`
The following options are meant to be configured using the options
available in `RouterModule.forRoot` or `provideRouter`.
* `onSameUrlNavigation`
* `paramsInheritanceStrategy`
* `urlUpdateStrategy`
* `canceledNavigationResolution`
The following options are available in `RouterModule.forRoot` but not
available in `provideRouter`:
* `malformedUriErrorHandler` - This was found to not be used anywhere
@@ -2023,23 +2084,23 @@ Andrew Kushnir
- Keyframes names are now prefixed with the component's "scope name".
For example, the following keyframes rule in a component definition,
whose "scope name" is host-my-cmp:
@keyframes foo { ... }
will become:
@keyframes host-my-cmp_foo { ... }
Any TypeScript/JavaScript code which relied on the names of keyframes rules
will no longer match.
The recommended solutions in this case are to either:
- change the component's view encapsulation to the `None` or `ShadowDom`
- define keyframes rules in global stylesheets (e.g styles.css)
- define keyframes rules programmatically in code.
### compiler-cli
- Invalid constructors for DI may now report compilation errors
When a class inherits its constructor from a base class, the compiler may now
report an error when that constructor cannot be used for DI purposes. This may
either be because the base class is missing an Angular decorator such as
@@ -2048,7 +2109,7 @@ Andrew Kushnir
These situations used to behave unexpectedly at runtime, where the class may be
constructed without any of its constructor parameters, so this is now reported
as an error during compilation.
Any new errors that may be reported because of this change can be resolved either
by decorating the base class from which the constructor is inherited, or by adding
an explicit constructor to the class for which the error is reported.
@@ -2082,16 +2143,16 @@ Andrew Kushnir
### core
- - The ability to pass an `NgModule` to the `providedIn` option for
`@Injectable` and `InjectionToken` is now deprecated.
`providedIn: NgModule` was intended to be a tree-shakable alternative to
NgModule providers. It does not have wide usage, and in most cases is used
incorrectly, in circumstances where `providedIn: 'root'` should be
preferred. If providers should truly be scoped to a specific NgModule, use
`NgModule.providers` instead.
- The ability to set `providedIn: 'any'` for an `@Injectable` or
`InjectionToken` is now deprecated.
`providedIn: 'any'` is an option with confusing semantics and is almost
never used apart from a handful of esoteric cases internal to the framework.
- The bit field signature of `Injector.get()` has been deprecated, in favor of the new options object.
@@ -2694,16 +2755,16 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
- Keyframes names are now prefixed with the component's "scope name".
For example, the following keyframes rule in a component definition,
whose "scope name" is host-my-cmp:
@keyframes foo { ... }
will become:
@keyframes host-my-cmp_foo { ... }
Any TypeScript/JavaScript code which relied on the names of keyframes rules
will no longer match.
The recommended solutions in this case are to either:
- change the component's view encapsulation to the `None` or `ShadowDom`
- define keyframes rules in global stylesheets (e.g styles.css)
@@ -2713,23 +2774,23 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
- Support for Node.js v12 has been removed as it will become EOL on 2022-04-30. Please use Node.js v14.15 or later.
- TypeScript versions older than 4.6 are no longer supported.
- Forms [email] input coercion
Forms [email] input value will be considered as true if it is defined with any value rather
than false and 'false'.
- Since Ivy, TestBed doesn't use AOT summaries. The `aotSummaries` fields in TestBed APIs were present, but unused. The fields were deprecated in previous major version and in v14 those fields are removed. The `aotSummaries` fields were completely unused, so you can just drop them from the TestBed APIs usage.
### forms
- Forms classes accept a generic.
Forms model classes now accept a generic type parameter. Untyped versions of these classes are available to opt-out of the new, stricter behavior.
- objects with a length key set to zero will no longer validate as empty.
This is technically a breaking change, since objects with a key `length` and value `0` will no longer validate as empty. This is a very minor change, and any reliance on this behavior is probably a bug anyway.
### http
- Queries including + will now actually query for + instead of space.
Most workarounds involving custom codecs will be unaffected.
Possible server-side workarounds will need to be undone.
- JSONP will throw an error when headers are set on a reques
JSONP does not support headers being set on requests. Before when
a request was sent to a JSONP backend that had headers set the headers
were ignored. The JSONP backend will now throw an error if it
@@ -2740,7 +2801,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
- This change may cause a breaking change in unit tests that are implicitly depending on a specific
number and sequence of change detections in order for their assertions to pass.
- This may break invalid calls to `TransferState` methods.
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 stricter. Places that use
@@ -2762,7 +2823,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
actually gets set to something completely different. It's set to the
current internal `UrlTree` of the Router at the time navigation
occurs.
With this change, there is no exact replacement for the old value of
`initialUrl` because it was never intended to be exposed.
`Router.url` is likely the best replacement for this.
@@ -2786,7 +2847,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
returned by other guards: only the first value is used.
### zone.js
- in TaskTrackingZoneSpec track a periodic task until it is cancelled
The breaking change is scoped only to the plugin
`zone.js/plugins/task-tracking`. If you used `TaskTrackingZoneSpec` and
checked the pending macroTasks e.g. using `(this.ngZone as any)._inner
@@ -2795,7 +2856,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
previously the `setInterval` macrotask was no longer tracked after its
callback was executed for the first time. Now it's tracked until
the task is explicitly cancelled, e.g with `clearInterval(id)`.
## Deprecations
### common
- The `ngModuleFactory` input of the `NgComponentOutlet` directive is deprecated in favor of a newly added `ngModule` input. The `ngModule` input accepts references to the NgModule class directly, without the need to resolve module factory first.
+14 -4
View File
@@ -64,7 +64,17 @@ load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")
nodejs_register_toolchains(
name = "nodejs",
node_version = "18.13.0",
node_repositories = {
"18.18.2-darwin_arm64": ("node-v18.18.2-darwin-arm64.tar.gz", "node-v18.18.2-darwin-arm64", "9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553"),
"18.18.2-darwin_amd64": ("node-v18.18.2-darwin-x64.tar.gz", "node-v18.18.2-darwin-x64", "5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f"),
"18.18.2-linux_arm64": ("node-v18.18.2-linux-arm64.tar.xz", "node-v18.18.2-linux-arm64", "2e630e18548627f61eaf573233da7949dc0a1df5eef3f486fa9820c5f6c121aa"),
"18.18.2-linux_ppc64le": ("node-v18.18.2-linux-ppc64le.tar.xz", "node-v18.18.2-linux-ppc64le", "b0adff5cf5938266b711d6c724fb134d802e0dee40b3a3f73d162de1b3d11880"),
"18.18.2-linux_s390x": ("node-v18.18.2-linux-s390x.tar.xz", "node-v18.18.2-linux-s390x", "c70ec2074b5e2b42c55bb4b8105418b67bf8a61c500d9376a07430dfcc341fdb"),
"18.18.2-linux_amd64": ("node-v18.18.2-linux-x64.tar.xz", "node-v18.18.2-linux-x64", "75aba25ae76999309fc6c598efe56ce53fbfc221381a44a840864276264ab8ac"),
"18.18.2-windows_amd64": ("node-v18.18.2-win-x64.zip", "node-v18.18.2-win-x64", "3bb0e51e579a41a22b3bf6cb2f3e79c03801aa17acbe0ca00fc555d1282e7acd"),
},
# We need at least Node 18.17 due to some transitive dependencies.
node_version = "18.18.2",
)
# Download npm dependencies.
@@ -196,10 +206,10 @@ cldr_xml_data_repository(
# sass rules
http_archive(
name = "io_bazel_rules_sass",
sha256 = "386ef2b97bd4342e45db450ce7eb5acb343d22f7580c0bbae89186dc009fa14e",
strip_prefix = "rules_sass-d970cb53ad159e7fd1d3a99a00a530cc2c83eff9",
sha256 = "974f5e14c54d300ca63a67910ebfcea6d838d8811fefc61184f2872107152705",
strip_prefix = "rules_sass-888dd9c02709e228b60a8b3e51e2b16b2ca5279d",
urls = [
"https://github.com/bazelbuild/rules_sass/archive/d970cb53ad159e7fd1d3a99a00a530cc2c83eff9.zip",
"https://github.com/bazelbuild/rules_sass/archive/888dd9c02709e228b60a8b3e51e2b16b2ca5279d.zip",
],
)
+26 -1
View File
@@ -1,4 +1,5 @@
load("//:packages.bzl", "link_packages")
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
@@ -96,12 +97,36 @@ copy_to_bin(
srcs = APPLICATION_FILES,
)
bool_flag(
name = "fast_build_mode",
build_setting_default = False,
)
config_setting(
name = "fast",
flag_values = {
":fast_build_mode": "true",
},
)
config_setting(
name = "full",
flag_values = {
":fast_build_mode": "false",
},
)
config_based_architect_flags = select({
":fast": ["--no-prerender"],
":full": ["--prerender"],
})
architect(
name = "build",
args = [
"angular-dev:build",
"--output-path=build",
],
] + config_based_architect_flags,
chdir = "$(RULEDIR)",
data = APPLICATION_DEPS + [
":application_files_bin",
-1
View File
@@ -26,7 +26,6 @@
"index": "src/index.html",
"browser": "src/main.ts",
"server": "src/main.server.ts",
"prerender": true,
"polyfills": ["src/polyfills.ts", "zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
@@ -51,7 +51,8 @@
&:has(.docs-reference-api-tab) {
width: 60%;
}
&:not(.docs-reference-api-tab) {
&:not(:has(.docs-reference-api-tab)) {
width: 100%;
max-width: var(--page-width);
}
}
+3
View File
@@ -65,10 +65,13 @@ copy_to_directory(
"//packages/upgrade:upgrade_docs",
"//packages/upgrade/static:upgrade_static_docs",
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
"//tools/manual_api_docs/blocks:blocks_docs",
"//tools/manual_api_docs/elements:elements_docs",
],
replace_prefixes = {
"adev/src/content": "",
"packages/**/": "api/",
"tools/**/": "api/",
},
)
+1 -1
View File
@@ -43,7 +43,7 @@ It also includes troubleshooting information and any recommended manual changes
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
It also describes supported update paths.
## Resource summary
@@ -79,7 +79,7 @@ If you look at the template for the `name` input again, it is fairly similar to
This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.
Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained to for accessibility purposes.
Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained for accessibility purposes.
## Defining custom validators
@@ -151,7 +151,7 @@ Just as a form group contains a group of controls, the *profileForm* `FormGroup`
</docs-step>
<docs-step title="Save form data">
The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.
The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.
<docs-code header="src/app/profile-editor/profile-editor.component.html (submit event)" path="adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html" visibleRegion="ng-submit"/>
@@ -5,7 +5,7 @@ This tutorial shows you how to create a template-driven form. The control elemen
Template-driven forms use [two-way data binding](guide/templates/two-way-binding) to update the data model in the component as changes are made in the template and vice versa.
<docs-callout helpful title="Template vs Reactive forms">
Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template.Reactive forms provide a model-driven approach to building forms.
Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template. Reactive forms provide a model-driven approach to building forms.
Template-driven forms are a great choice for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see [Choosing an approach](guide/forms#choosing-an-approach)
</docs-callout>
@@ -294,7 +294,7 @@ To let form users add a new actor, you will add a **New Actor** button that resp
<docs-code header="src/app/actor-form/actor-form.component.ts (New Actor method)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.ts" visibleRegion="new-actor"/>
1. Bind the button's click event to a actor-creation method, `newActor()`.
1. Bind the button's click event to an actor-creation method, `newActor()`.
<docs-code header="src/app/actor-form/actor-form.component.html (New Actor button)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="new-actor-button-no-reset"/>
+1 -1
View File
@@ -8,7 +8,7 @@ The localization process includes the following actions.
* Format data for a specific locale
A *locale* identifies a region in which people speak a particular language or language variant.
Possible regions includes countries and geographical regions.
Possible regions include countries and geographical regions.
A locale determines the formatting and parsing of the following details.
* Measurement units including date and time, numbers, and currencies
+5 -1
View File
@@ -345,7 +345,7 @@ Note: even though the `width` property may not always be present, a custom loade
### The `loaderParams` Property
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute take an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute takes an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
A common use for `loaderParams` is controlling advanced image CDN features.
@@ -400,3 +400,7 @@ The `ngSrc` attribute was chosen as the trigger for NgOptimizedImage due to tech
The [image loaders](#configuring-an-image-loader-for-ngoptimizedimage) provider pattern is designed to be as simple as possible for the common use case of having only a single image CDN used within a component. However, it's still very possible to manage multiple image CDNs using a single provider.
To do this, we recommend writing a [custom image loader](#custom-loaders) which uses the [`loaderParams` property](#the-loaderparams-property) to pass a flag that specifies which image CDN should be used, and then invokes the appropriate loader based on that flag.
### Can you add a new built-in loader for my preferred CDN?
For maintenance reasons, we don't currently plan to support additional built-in loaders in the Angular repository. Instead, we encourage developers to publish any additional image loaders as third-party packages.
+3 -3
View File
@@ -13,7 +13,7 @@ Angular CLI includes four builders typically used as `build` targets:
| `@angular-devkit/build-angular:application` | Builds an application with a client-side bundle, a Node server, and build-time prerendered routes with [esbuild](https://esbuild.github.io/). |
| `@angular-devkit/build-angular:ng-packagr` | Builds an Angular library adhering to [Angular Package Format](tools/libraries/angular-package-format). |
Applications generated by `ng new` use `@angular-devkit/build-angular:browser` by default.
Applications generated by `ng new` use `@angular-devkit/build-angular:application` by default.
Libraries generated by `ng generate library` use `@angular-devkit/build-angular:ng-packagr` by default.
You can determine which builder is being used for a particular project by looking up the `build` target for that project.
@@ -26,7 +26,7 @@ You can determine which builder is being used for a particular project by lookin
"architect": {
// `ng build` invokes the Architect target named `build`.
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-devkit/build-angular:application",
&hellip;
},
"serve": { &hellip; }
@@ -39,7 +39,7 @@ You can determine which builder is being used for a particular project by lookin
</docs-code>
This page discusses usage and options of `@angular-devkit/build-angular:browser`.
This page discusses usage and options of `@angular-devkit/build-angular:application`.
## Output directory
+1 -1
View File
@@ -110,7 +110,7 @@ This form differs from the template-driven version in that it no longer exports
Instead, it uses the `name` getter defined in the component class.
Notice that the `required` attribute is still present in the template.
Although it's not necessary for validation, it should be retained to for accessibility purposes.
Although it's not necessary for validation, it should be retained for accessibility purposes.
<a id="custom-validators"></a>
+1 -1
View File
@@ -10,7 +10,7 @@ The localization process includes the following actions.
* Format data for a specific locale
A *locale* identifies a region in which people speak a particular language or language variant.
Possible regions includes countries and geographical regions.
Possible regions include countries and geographical regions.
A locale determines the formatting and parsing of the following details.
* Measurement units including date and time, numbers, and currencies
+5 -1
View File
@@ -338,7 +338,7 @@ Note: even though the `width` property may not always be present, a custom loade
### The `loaderParams` Property
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute take an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute takes an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
A common use for `loaderParams` is controlling advanced image CDN features.
@@ -402,6 +402,10 @@ providers: [
If you use a variable to pass the domain string to the loader, or you're not using a loader, the static analysis will not be able to identify the domain, and no preconnect link will be generated. In this case you should manually add a preconnect link to the document head, as [described above.](#resource-hints).
### Can you add a new built-in loader for my preferred CDN?
For maintenance reasons, we don't currently plan to support additional built-in loaders in the Angular repository. Instead, we encourage developers to publish any additional image loaders as third-party packages.
<!-- links -->
<!-- external links -->
+9 -2
View File
@@ -25,6 +25,13 @@ The `<parent-component>` serves as the context for the `<child-component>`.
`@Input()` lets a parent component update data in the child component.
Conversely, `@Output()` lets the child send data to a parent component.
<div class="alert is-helpful">
Signal-based inputs are available in developer preview and may be a better fit for some use cases as an alternative to `@Input`.
Learn more in the [signal inputs guide](/guide/signal-inputs).
</div>
<a id="input"></a>
## Sending data to a child component
@@ -214,11 +221,11 @@ To make `Input` property as required for a child component while passing values
<code-example header="src/app/item-details-metadata.component.ts" path="inputs-outputs/src/app/item-details-metadata.component.ts" region="use-input-metadata-required"></code-example>
Next, in the parent template add the following:
Next, in the parent template add the following:
<code-example header="src/app/app.component.html" path="inputs-outputs/src/app/app.component.html" region="input-parent-metadata"></code-example>
If required inputs in a child component are not specified in the parent component template will result a compile time error:
If required inputs in a child component are not specified in the parent component template will result a compile time error:
<div class="alert is-helpful">
NG8008: Required input item from component ItemDetailMetadataComponent must be specified.
+142
View File
@@ -0,0 +1,142 @@
# Model inputs
**Model inputs** are a special type of input that enable a component to propagate new values
back to another component.
<div class="alert is-helpful">
Model inputs are currently in [developer preview](/guide/releases#developer-preview).
</div>
When creating a component, you can define a model input similarly to how you create a standard
input.
```typescript
import {Component, model, input} from '@angular/core';
@Component({...})
export class CustomCheckbox {
// This is a model input.
checked = model(false);
// This is a standard input.
disabled = input(false);
}
```
Both types of input allow someone to bind a value into the property. However, **model inputs allow
the component author to write values into the property**.
In other respects, you can use model inputs the same way you use standard inputs. You can read the
value by calling the signal function, including in reactive contexts like `computed` and `effect`.
```typescript
import {Component, model, input} from '@angular/core';
@Component({
selector: 'custom-checkbox',
template: '<div (click)="toggle()"> ... </div>',
})
export class CustomCheckbox {
checked = model(false);
disabled = input(false);
toggle() {
// While standard inputs are read-only, you can write directly to model inputs.
this.checked.set(!this.checked());
}
}
```
When a component writes a new value into a model input, Angular can propagate the new value back
to the component that is binding a value into that input. This is called **two-way binding** because
values can flow in both directions.
## Two-way binding with signals
You can bind a writable signal to a model input.
```typescript
@Component({
...,
// `checked` is a model input.
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
template: '<custom-checkbox [(checked)]="isAdmin" />',
})
export class UserProfile {
protected isAdmin = signal(false);
}
```
In the above example, the `CustomCheckbox` can write values into its `checked` model input, which
then propagates those values back to the `isAdmin` signal in `UserProfile`. This binding keeps that
values of `checked` and `isAdmin` in sync. Notice that the binding passes the `isAdmin` signal
itself, not the _value_ of the signal.
## Two-way binding with plain properties
You can bind a plain JavaScript property to a model input.
```typescript
@Component({
...,
// `checked` is a model input.
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
template: '<custom-checkbox [(checked)]="isAdmin" />',
})
export class UserProfile {
protected isAdmin = false;
}
```
In the example above, the `CustomCheckbox` can write values into its `checked` model input, which
then propagates those values back to the `isAdmin` property in `UserProfile`. This binding keeps
that values of `checked` and `isAdmin` in sync.
## Implicit `change` events
When you declare a model input in a component or directive, Angular automatically creates a
corresponding [output](/guide/inputs-outputs) for that model. The output's name is the model
input's name suffixed with "Change".
```typescript
@Directive({...})
export class CustomCheckbox {
// This automatically creates an output named "checkedChange".
// Can be subscribed to using `(checkedChange)="handler()"` in the template.
checked = model(false);
}
```
Angular emits this change event whenever you write a new value into the model input by calling
its `set` or `update` methods.
## Customizing model inputs
You can mark a model input as required or provide an alias in the same way as a
[standard input](/guide/signal-inputs).
Model inputs do not support input transforms.
## Differences between `model()` and `input()`
Both `input()` and `model()` functions are ways to define signal-based inputs in Angular, but they
differ in a few ways:
1. `model()` defines **both** an input and an output. The output's name is always the name of the
input suffixed with `Change` to support two-way bindings. It will be up to the consumer of your
directive to decide if they want to use just the input, just the output, or both.
2. `ModelSignal` is a `WritableSignal` which means that its value can be changed from anywhere
using the `set` and `update` methods. When a new value is assigned, the `ModelSignal` will emit
to its output. This is different from `InputSignal` which is read-only and can only be changed
through the template.
3. Model inputs do not support input transforms while signal inputs do.
## When to use model inputs
Use model inputs in components that exist to modify a value based on user interaction.
Custom form controls, such as a date picker or combobox, should use model inputs for their
primary value.
Avoid using model inputs as a convenience to avoid introducing an additional class property for
containing local state.
+1 -1
View File
@@ -137,7 +137,7 @@ To add a form group to this component, take the following steps.
|:--- |:--- |
| Create a `FormGroup` instance | Create a property in the component class named `profileForm` and set the property to a new form group instance. To initialize the form group, provide the constructor with an object of named keys mapped to their control. <br /> For the profile form, add two form control instances with the names `firstName` and `lastName`. <code-example header="src/app/profile-editor/profile-editor.component.ts (form group)" path="reactive-forms/src/app/profile-editor/profile-editor.component.1.ts" region="formgroup"></code-example> The individual form controls are now collected within a group. A `FormGroup` instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties \(such as `value` and `untouched`\) and methods \(such as `setValue()`\) as a form control instance. |
| Associate the `FormGroup` model and view | A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view. <code-example header="src/app/profile-editor/profile-editor.component.html (template form group)" path="reactive-forms/src/app/profile-editor/profile-editor.component.1.html" region="formgroup"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> Just as a form group contains a group of controls, the *profileForm* `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. </div> The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in `FormGroup`. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value. |
| Save form data | The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method. <code-example header="src/app/profile-editor/profile-editor.component.html (submit event)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="ng-submit"></code-example> The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use `EventEmitter` to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console. <code-example header="src/app/profile-editor/profile-editor.component.ts (submit method)" path="reactive-forms/src/app/profile-editor/profile-editor.component.ts" region="on-submit"></code-example> The `submit` event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with `submit` type. This lets the user press the **Enter** key to submit the completed form. <br /> Use a `button` element to add a button to the bottom of the form to trigger the form submission. <code-example header="src/app/profile-editor/profile-editor.component.html (submit button)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="submit-button"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#basic-form-validation "Basic form validation.") section. </div> |
| Save form data | The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method. <code-example header="src/app/profile-editor/profile-editor.component.html (submit event)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="ng-submit"></code-example> The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use `EventEmitter` to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console. <code-example header="src/app/profile-editor/profile-editor.component.ts (submit method)" path="reactive-forms/src/app/profile-editor/profile-editor.component.ts" region="on-submit"></code-example> The `submit` event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with `submit` type. This lets the user press the **Enter** key to submit the completed form. <br /> Use a `button` element to add a button to the bottom of the form to trigger the form submission. <code-example header="src/app/profile-editor/profile-editor.component.html (submit button)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="submit-button"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#basic-form-validation "Basic form validation.") section. </div> |
| Display the component | To display the `ProfileEditor` component that contains the form, add it to a component template. <code-example header="src/app/app.component.html (profile editor)" path="reactive-forms/src/app/app.component.1.html" region="app-profile-editor"></code-example> `ProfileEditor` lets you manage the form control instances for the `firstName` and `lastName` controls within the form group instance. <div class="lightbox"> <img alt="Profile Editor with labels and inputs for first and last name as well as a submit button" src="generated/images/guide/reactive-forms/profile-editor-1.gif"> </div> |
<a id="nested-groups"></a>
+1 -1
View File
@@ -184,7 +184,7 @@ The Angular router selects this route any time the requested URL doesn't match a
To set up a wildcard route, add the following code to your `routes` definition.
```
{ path: '**', component: &lt;component-name&gt; }
{ path: '**', component: PageNotFoundComponent }
```
The two asterisks, `**`, indicate to Angular that this `routes` definition is a wildcard route.
+157
View File
@@ -0,0 +1,157 @@
# Signal inputs
Signal inputs allow values to be bound from parent components.
Those values are exposed using a `Signal` and can change during the lifecycle of your component.
<div class="alert is-helpful">
Signal inputs are currently in [developer preview](/guide/releases#developer-preview).
</div>
Angular supports two variants of inputs:
**Optional inputs**
Inputs are optional by default, unless you use `input.required`.
You can specify an explicit initial value, or Angular will use `undefined` implicitly.
**Required inputs**
Required inputs always have a value of the given input type.
They are declared using the `input.required` function.
```typescript
import {Component, input} from '@angular/core';
@Component({...})
export class MyComp {
// optional
firstName = input<string>(); // InputSignal<string|undefined>
age = input(0); // InputSignal<number>
// required
lastName = input.required<string>(); // InputSignal<string>
}
```
An input is automatically recognized by Angular whenever you use the `input` or `input.required` functions as initializer of class members.
## Aliasing an input
Angular uses the class member name as the name of the input.
You can alias inputs to change their public name to be different.
```typescript
class StudentDirective {
age = input(0, {alias: 'studentAge'});
}
```
This allows users to bind to your input using `[studentAge]`, while inside your component you can access the input values using `this.age`.
## Using in templates
Signal inputs are read-only signals.
As with signals declared via `signal()`, you access the current value of the input by calling the input signal.
```html
<p>First name: {{firstName()}}</p>
<p>Last name: {{lastName()}}</p>
```
This access to the value is captured in reactive contexts and can notify active consumers, like Angular itself, whenever the input value changes.
An input signal in practice is a trivial extension of signals that you know from [the signals guide](/guide/signals).
```typescript
export class InputSignal<T> extends Signal<T> { ... }`.
```
## Deriving values
As with signals, you can derive values from inputs using `computed`.
```typescript
import {Component, input, computed} from '@angular/core';
@Component({...})
export class MyComp {
age = input(0);
// age multiplied by two.
ageMultiplied = computed(() => this.age() * 2);
}
```
Computed signals memoize values.
See more details in the [dedicated section for computed](/guide/signals#computed-signals).
## Monitoring changes
With signal inputs, users can leverage the `effect` function.
The function will execute whenever the input changes.
Consider the following example.
The new value is printed to the console whenever the `firstName` input changes.
```typescript
import {input, effect} from '@angular/core';
class MyComp {
firstName = input.required<string>();
constructor() {
effect(() => {
console.log(this.firstName());
});
}
}
```
The `console.log` function is invoked every time the `firstName` input changes.
This will happen as soon as `firstName` is available, and for subsequent changes during the lifetime of `MyComp`.
## Value transforms
You may want to coerce or parse input values without changing the meaning of the input.
Transforms convert the raw value from parent templates to the expected input type.
Transforms should be [pure functions](https://en.wikipedia.org/wiki/Pure_function).
```typescript
class MyComp {
disabled = input(false, {
transform: (value: boolean|string) => typeof value === 'string' ? value === '' : value,
});
}
```
In the example above, you are declaring an input named `disabled` that is accepting values of type `boolean` and `string`.
This is captured by the explicit parameter type of `value` in the `transform` option.
These values are then parsed to a `boolean` with the transform, resulting in booleans.
That way, you are only dealing with `boolean` inside your component when calling `this.disabled()`, while users of your component can pass an empty string as a shorthand to mark your component as disabled.
```html
<my-custom-comp disabled>
```
<div class="alert is-important">
Do not use transforms if they change the meaning of the input, or if they are [impure](https://en.wikipedia.org/wiki/Pure_function#Impure_functions).
Instead, use `computed` for transformations with different meaning, or an `effect` for impure code that should run whenever the input changes.
</div>
## Why should we use signal inputs and not `@Input()`?
Signal inputs are a reactive alternative to decorator-based `@Input()`.
In comparison to decorator-based `@Input`, signal inputs provide numerous benefits:
1. Signal inputs are more **type safe**:
<br/>• Required inputs do not require initial values, or tricks to tell TypeScript that an input _always_ has a value.
<br/>• Transforms are automatically checked to match the accepted input values.
2. Signal inputs, when used in templates, will **automatically** mark `OnPush` components as dirty.
3. Values can be easily **derived** whenever an input changes using `computed`.
4. Easier and more local monitoring of inputs using `effect` instead of `ngOnChanges` or setters.
+196
View File
@@ -0,0 +1,196 @@
# Signal queries
A component or directive can define queries that find child elements and read values from their injectors.
Developers most commonly use queries to retrieve references to components, directives, DOM elements, and more.
There are two categories of query: view queries and content queries.
Signal queries supply query results as a reactive signal primitive. You can use query results in `computed` and `effect`, composing these results with other signals.
<div class="alert is-helpful">
Signal queries are in [developer preview](/guide/releases#developer-preview).
APIs may change based on feedback without going through Angular's deprecation cycle.
</div>
If you're already familiar with Angular queries, you can jump straight to [Comparing signal-based queries to decorator-based queries](#comparing-signal-based-queries-to-decorator-based-queries)
## View queries
View queries retrieve results from the elements in the component's own template (view).
### `viewChild`
You can declare a query targeting a single result with the `viewChild` function.
```ts
@Component({
template: `
<div #el></div>
<my-component />
`
})
export class TestComponent {
// query for a single result by a string predicate
divEl = viewChild<ElementRef>('el'); // Signal<ElementRef|undefined>
// query for a single result by a type predicate
cmp = viewChild(MyComponent); // Signal<MyComponent|undefined>
}
```
### `viewChildren`
You can also query for multiple results with the `viewChildren` function.
```ts
@Component({
template: `
<div #el></div>
@if (show) {
<div #el></div>
}
`
})
export class TestComponent {
show = true;
// query for multiple results
divEls = viewChildren<ElementRef>('el'); // Signal<ReadonlyArray<ElementRef>>
}
```
### View query options
The `viewChild` and the `viewChildren` query declaration functions have a similar signature accepting two arguments:
* a **locator** to specify the query target - it can be either a `string` or any injectable token
* a set of **options** to adjust behavior of a given query.
Signal-based view queries accept only one option: `read`. The `read` option indicates the type of result to inject from the matched nodes and return in the final results.
```ts
@Component({
template: `<my-component/>`
})
export class TestComponent {
// query for a single result with options
cmp = viewChild(MyComponent, {read: ElementRef}); // Signal<ElementRef|undefined>
}
```
## Content queries
Content queries retrieve results from the elements in the component's content — the elements nested inside the component tag in the template where it's used.
### `contentChild`
You can query for a single result with the `contentChild` function.
```ts
@Component({...})
export class TestComponent {
// query by a string predicate
headerEl = contentChild<ElementRef>('h'); // Signal<ElementRef|undefined>
// query by a type predicate
header = contentChild(MyHeader); // Signal<MyHeader|undefined>
}
```
### `contentChildren`
You can also query for multiple results with the `contentChildren` function.
```ts
@Component({...})
export class TestComponent {
// query for multiple results
divEls = contentChildren<ElementRef>('h'); // Signal<ReadonlyArray<ElementRef>>
}
```
### Content query options
The `contentChild` and the `contentChildren` query declaration functions have a similar signature accepting two arguments:
* a **locator** to specify the query target - it can be either a `string` or any injectable token
* a set of **options** to adjust behavior of a given query.
Content queries accept the following options:
* `descendants` By default, content queries find only direct children of the component and do not traverse into descendants. If this option is changed to `true`, query results will include all descendants of the element. Even when `true`, however, queries _never_ descend into components.
* `read` indicates the type of result to retrieve from the matched nodes and return in the final results.
### Required child queries
If a child query (`viewChild` or `contentChild`) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like `@if` or `@for`.
Because of this, the child queries return a signal that potentially have the `undefined` value. Most of the time, and especially for the view child queries, developers author their code such that:
* there is at least one matching result;
* results are accessed when the template was processed and query results are available.
For such cases, you can mark child queries as `required` to enforce presence of at least one matching result. This eliminates `undefined` from the result type signature. If a `required` query does not find any results, Angular throws an error.
```ts
@Component({
selector: 'app-root',
standalone: true,
template: `
<div #requiredEl></div>
`,
})
export class App {
existingEl = viewChild.required('requiredEl'); // required and existing result
missingEl = viewChild.required('notInATemplate'); // required but NOT existing result
ngAfterViewInit() {
console.log(this.existingEl()); // OK :-)
console.log(this.missingEl()); // Runtime error: result marked as required but not available!
}
}
```
## Results availability timing
A signal query authoring functions will be executed as part of the directive instance construction. This happens before we could create a query instance and execute the template’s creation mode to collect any matches. As a consequence, there is a period of time where the signal instance was created (and can be read) but no query results could have been collected. By default Angular will return `undefined` (for child queries) or an empty array (for children queries) before results are available. Required queries will throw if accessed at this point.
Angular computes signal-based query results lazily, on demand. This means that query results are not collected unless there is a code path that reads the signal.
Query results can change over time due to the view manipulation - either through the Angular's control flow (`@if`, `@for` etc.) or by the direct calls to the `ViewContainerRef` API. When you read the value from the query result signal, you can receive different values over time.
Note: to avoid returning incomplete query results while a template is rendered, Angular delays query resolution until it finishes rendering a given template.
## Query declarations functions and the associated rules
The `viewChild`, `contentChild`, `viewChildren` and `contentChildren` functions are special function recognized by the Angular compiler. You can use those functions to declare queries by initializing a component or a directive property. You can never call these functions outside of component and directive property initializers.
```ts
@Component({
selector: 'app-root',
standalone: true,
template: `
<div #el></div>
`,
})
export class App {
el = viewChild('el'); // all good!
constructor() {
const myConst = viewChild('el'); // NOT SUPPORTED
}
}
```
## Comparing signal-based queries to decorator-based queries
Signal queries are an alternative approach to the queries declared using the `@ContentChild`, `@ContentChildren`, `@ViewChild` or `@ViewChildren` decorators. The new approach exposes query results as signals which means that query results can be composed with other signals (using `computed` or `effect`) and drive change detection. Additionally, the signal-based query system offers other benefits:
* **More predictable timing.** You can access query results as soon as they're available.
* **Simpler API surface.** All queries return a signal, and queries with more than one result let you work with a standard array.
* **Improved type safety.** Fewer query use cases include `undefined` in the possible results.
* **More accurate type inference.** TypeScript can infer more accurate types when you use a type predicate or when you specify an explicit `read` option.
* **Lazier updates.** - Angular updates signal-based query results lazily; the framework does no work unless your code explicitly reads the query results.
The underlying query mechanism doesn't change much - conceptually Angular still creates singular "child" or plural "children" queries that target elements in a template (view) or content. The difference is in type of results and the exact timing of the results availability. The authoring format for declaring signal-based queries changed as well: the `viewChild`, `viewChildren`, `contentChild` and `contentChildren` functions used as initializer of class members are automatically recognized by Angular.
@@ -104,7 +104,7 @@ Template input variables can be seen in action in the long-form usage of `NgFor`
```html
<ul>
<ng-template ngFor let-hero [ngForOf]="heroes">
<li>{{hero.name}}
<li>{{hero.name}}</li>
</ng-template>
</ul>
```
@@ -118,7 +118,7 @@ When an `<ng-template>` is instantiated, multiple named values can be passed whi
```html
<ul>
<ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
<li>Hero number {{i}}: {{hero.name}}
<li>Hero number {{i}}: {{hero.name}}</li>
</ng-template>
</ul>
```
+15
View File
@@ -79,6 +79,21 @@ For more information on how to use two-way binding in forms, see Angular [NgMode
</div>
## Two-way binding to signals
Angular supports binding both plain values and signals to two-way bindings. If you pass a writable signal to a two-way binding, Angular will automatically keep it in sync with the underlying input and output.
```typescript
@Component({
template: '<input [(ngModel)]="name">',
imports: [FormsModule]
})
export class MyComp {
name = signal('Bob');
}
```
<!-- links -->
<!-- external links -->
@@ -36,7 +36,7 @@ To get the most out of these developer guides, you should review the following t
</a>
<a href="guide/dependency-injection" class="docs-card" title="Dependency injection">
<section>Dependency injection</section>
<p>Learn about dependency injection. Dependency injection refers to services or objects that a class needs to perform a specific function.</p>
<p>Learn about dependency injection. Dependency injection refers to the pattern Angular uses to create and deliver services or objects that a class needs to perform a specific function.</p>
<p class="card-footer">Dependency injection</p>
</a>
<!-- <a href="guide/rendering-overview" class="docs-card" title="Angular service worker developer guide">
+1 -1
View File
@@ -58,7 +58,7 @@ It also includes troubleshooting information and any recommended manual changes
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
It also describes supported update paths.
<a id="resources"></a>
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

+15
View File
@@ -787,6 +787,21 @@
"title": "Signals",
"tooltip": "Angular signals for optimized change detection"
},
{
"url": "guide/signal-inputs",
"title": "Signal inputs",
"tooltip": "Signal inputs to optimize change detection and developer experience."
},
{
"url": "guide/model-inputs",
"title": "Model inputs",
"tooltip": "Model inputs define inputs that emit a changed event when updated."
},
{
"url": "guide/signal-queries",
"title": "Signal queries",
"tooltip": "Signal queries exposing results as a reactive signal primitive."
},
{
"url": "guide/rxjs-interop",
"title": "RxJS Interop for Signals",
+18 -18
View File
@@ -61,33 +61,33 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.2.0-next.0",
"@angular/cdk": "17.2.0-next.0",
"@angular/common": "17.2.0-next.0",
"@angular/compiler": "17.2.0-next.0",
"@angular/core": "17.2.0-next.0",
"@angular/elements": "17.2.0-next.0",
"@angular/forms": "17.2.0-next.0",
"@angular/material": "17.2.0-next.0",
"@angular/platform-browser": "17.2.0-next.0",
"@angular/platform-browser-dynamic": "17.2.0-next.0",
"@angular/router": "17.2.0-next.0",
"@angular/service-worker": "17.2.0-next.0",
"@angular/animations": "17.2.0-rc.1",
"@angular/cdk": "17.2.0-rc.0",
"@angular/common": "17.2.0-rc.1",
"@angular/compiler": "17.2.0-rc.1",
"@angular/core": "17.2.0-rc.1",
"@angular/elements": "17.2.0-rc.1",
"@angular/forms": "17.2.0-rc.1",
"@angular/material": "17.2.0-rc.0",
"@angular/platform-browser": "17.2.0-rc.1",
"@angular/platform-browser-dynamic": "17.2.0-rc.1",
"@angular/router": "17.2.0-rc.1",
"@angular/service-worker": "17.2.0-rc.1",
"rxjs": "~7.8.0",
"safevalues": "^0.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/architect-cli": "0.1702.0-next.0",
"@angular-devkit/build-angular": "17.2.0-next.0",
"@angular-devkit/architect-cli": "0.1702.0-rc.0",
"@angular-devkit/build-angular": "17.2.0-rc.0",
"@angular-eslint/builder": "16.0.3",
"@angular-eslint/eslint-plugin": "16.0.3",
"@angular-eslint/eslint-plugin-template": "16.0.3",
"@angular-eslint/template-parser": "^16.0.3",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#07da06414a4ce04a8f9710f2bc9346c2c753a0d2",
"@angular/cli": "17.2.0-next.0",
"@angular/compiler-cli": "17.2.0-next.0",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7c4cf003cb4ac849986beaa243d7e85a893612f2",
"@angular/cli": "17.2.0-rc.0",
"@angular/compiler-cli": "17.2.0-rc.1",
"@bazel/bazelisk": "^1.7.5",
"@bazel/buildozer": "^6.0.0",
"@bazel/ibazel": "^0.16.2",
@@ -113,7 +113,7 @@
"eslint": "8.56.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jasmine": "4.1.3",
"eslint-plugin-jsdoc": "48.0.5",
"eslint-plugin-jsdoc": "48.0.6",
"eslint-plugin-prefer-arrow": "1.2.3",
"find-free-port": "^2.0.0",
"firebase-tools": "^13.0.0",
@@ -94,6 +94,10 @@ aio-api-list {
h2 {
margin-top: 16px;
margin-bottom: 16px;
& .deprecated-api-item {
text-decoration: line-through;
}
}
/* API CLASS LIST */
@@ -13,14 +13,14 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"angular": "^1.8.3",
"angular-in-memory-web-api": "~0.17.0",
"angular-route": "1.8.3",
@@ -29,9 +29,9 @@
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/angular": "^1.8.4",
"@types/angular-route": "^1.7.2",
"@types/jasmine": "~5.1.0",
@@ -13,23 +13,23 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"angular-in-memory-web-api": "~0.17.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/jasmine": "~5.1.0",
"@types/node": "^16.11.35",
"copyfiles": "^2.4.1",
@@ -13,24 +13,24 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/elements": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/elements": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"angular-in-memory-web-api": "~0.17.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/jasmine": "~5.1.0",
"@types/node": "^16.11.35",
"jasmine-core": "~5.1.0",
@@ -15,24 +15,24 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/localize": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/localize": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"angular-in-memory-web-api": "~0.17.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/jasmine": "~5.1.0",
"@types/node": "^16.11.35",
"jasmine-core": "~5.1.0",
@@ -13,24 +13,24 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/service-worker": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"@angular/service-worker": "17.1.3",
"angular-in-memory-web-api": "~0.17.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/jasmine": "~5.1.0",
"@types/node": "^16.11.35",
"jasmine-core": "~5.1.0",
@@ -25,15 +25,15 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/router": "17.1.1",
"@angular/upgrade": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"@angular/upgrade": "17.1.3",
"core-js": "^3.0.0",
"rxjs": "~7.8.0",
"systemjs": "~0.19.39",
@@ -42,7 +42,7 @@
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular/compiler-cli": "17.1.1",
"@angular/compiler-cli": "17.1.3",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^13.2.0",
"@types/angular": "^1.8.4",
@@ -13,16 +13,16 @@
},
"private": true,
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/platform-server": "17.1.1",
"@angular/router": "17.1.1",
"@angular/ssr": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/platform-server": "17.1.3",
"@angular/router": "17.1.3",
"@angular/ssr": "17.1.3",
"angular-in-memory-web-api": "~0.17.0",
"express": "^4.18.1",
"rxjs": "~7.8.0",
@@ -30,9 +30,9 @@
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@types/express": "^4.17.13",
"@types/jasmine": "~5.1.0",
"@types/node": "^16.11.35",
+17 -17
View File
@@ -19,20 +19,20 @@
"license": "MIT",
"repository": {},
"dependencies": {
"@angular/animations": "17.1.1",
"@angular/common": "17.1.1",
"@angular/compiler": "17.1.1",
"@angular/core": "17.1.1",
"@angular/elements": "17.1.1",
"@angular/forms": "17.1.1",
"@angular/localize": "17.1.1",
"@angular/platform-browser": "17.1.1",
"@angular/platform-browser-dynamic": "17.1.1",
"@angular/platform-server": "17.1.1",
"@angular/router": "17.1.1",
"@angular/service-worker": "17.1.1",
"@angular/ssr": "17.1.1",
"@angular/upgrade": "17.1.1",
"@angular/animations": "17.1.3",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/elements": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/localize": "17.1.3",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/platform-server": "17.1.3",
"@angular/router": "17.1.3",
"@angular/service-worker": "17.1.3",
"@angular/ssr": "17.1.3",
"@angular/upgrade": "17.1.3",
"angular": "^1.8.3",
"angular-in-memory-web-api": "~0.17.0",
"angular-route": "1.8.3",
@@ -45,9 +45,9 @@
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.1.1",
"@angular/cli": "17.1.1",
"@angular/compiler-cli": "17.1.1",
"@angular-devkit/build-angular": "17.1.3",
"@angular/cli": "17.1.3",
"@angular/compiler-cli": "17.1.3",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^13.2.0",
"@types/angular": "^1.8.4",
+105 -105
View File
@@ -10,23 +10,23 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@angular-devkit/architect@0.1701.1":
version "0.1701.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1701.1.tgz#80e2bc94763e21c273ef6d9f905390f1d935df60"
integrity sha512-vT3ZRAIfNyIg0vJWT6umPbCKiKFCukNkxLe9kgOU0tinZKNr/LgWYaBZ92Rxxi6C3NrAnmAYjsih8x4zdyoRXw==
"@angular-devkit/architect@0.1701.3":
version "0.1701.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1701.3.tgz#974cc8680baa5b8440cfc8039bbc4c46008e80bf"
integrity sha512-K5rvhslbXNwx04cCLviEJCA27MwoJRMMzALFXySi9BqjZnZUOtZnOBuuCdrTPaRmFaYqGO4Im5GNzpbb/NB8zg==
dependencies:
"@angular-devkit/core" "17.1.1"
"@angular-devkit/core" "17.1.3"
rxjs "7.8.1"
"@angular-devkit/build-angular@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-17.1.1.tgz#26a65e3daf8d306e39634b3322ee68ff19d0118c"
integrity sha512-GchDM8H+RQNts731c+jnhDgOm0PnCS3YB12uVwRiGsaNsUMrqKnu3P0poh6AImDMPyXKnIvTWLDCMD8TDziR0A==
"@angular-devkit/build-angular@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-17.1.3.tgz#05634e6a81ade1eb4e12844e37cbf636bf949bc4"
integrity sha512-pusFVSWMnrm2GrF3+Fw19OhA2rNw4WkfTMUruhaKAjW5QIvZ3wHYf+pH//1Ud+tuhFBi9BH7UALP2vnJMu1ehw==
dependencies:
"@ampproject/remapping" "2.2.1"
"@angular-devkit/architect" "0.1701.1"
"@angular-devkit/build-webpack" "0.1701.1"
"@angular-devkit/core" "17.1.1"
"@angular-devkit/architect" "0.1701.3"
"@angular-devkit/build-webpack" "0.1701.3"
"@angular-devkit/core" "17.1.3"
"@babel/core" "7.23.7"
"@babel/generator" "7.23.6"
"@babel/helper-annotate-as-pure" "7.22.5"
@@ -37,7 +37,7 @@
"@babel/preset-env" "7.23.7"
"@babel/runtime" "7.23.7"
"@discoveryjs/json-ext" "0.5.7"
"@ngtools/webpack" "17.1.1"
"@ngtools/webpack" "17.1.3"
"@vitejs/plugin-basic-ssl" "1.0.2"
ansi-colors "4.1.3"
autoprefixer "10.4.16"
@@ -90,18 +90,18 @@
optionalDependencies:
esbuild "0.19.11"
"@angular-devkit/build-webpack@0.1701.1":
version "0.1701.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1701.1.tgz#ab510fef446d2a9a2e71d4b1a688000494b28e09"
integrity sha512-YgNl/6xLmI0XdUCu/H4Jyi34BhrANCDP4N2Pz+tGwnz2+Vl8oZGLPGtKVbh/LKSAmEHk/B6GQUekSBpKWrPJoA==
"@angular-devkit/build-webpack@0.1701.3":
version "0.1701.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1701.3.tgz#584275d98be9e76d5db3d870ff37c57175d04a45"
integrity sha512-fpZtJf6yvXM7mX1T83caeYpa0e3zPv7sgKmx0ZIJKGL8+DETgNcCCeCTgui7HMBcHGCD8yj72DZ8xMMBWwVBIA==
dependencies:
"@angular-devkit/architect" "0.1701.1"
"@angular-devkit/architect" "0.1701.3"
rxjs "7.8.1"
"@angular-devkit/core@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.1.1.tgz#c140e6fcd158cea87e15d0d09f3879fc23a52637"
integrity sha512-b1wd1caegc1p18nTrfPhfHQAZW1GnWWKGldq5MZ8C/nkgJbjjN8SKb1Vw7GONkOnH6KxWDAXS4i93/wdQcz4Bg==
"@angular-devkit/core@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.1.3.tgz#d3b2212f9fa753506b1ccab24ee34edcdcd02e63"
integrity sha512-iuVK4hyW3YhusxIi8zGBvvVA9pWtDT3H6LQbWdVk9D3jXCZBIrEMklvAiJErqficKnUurf6gtFOeA8Fop6GotA==
dependencies:
ajv "8.12.0"
ajv-formats "2.1.1"
@@ -110,33 +110,33 @@
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/schematics@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.1.1.tgz#caaaeff64519d691121a92c92dbfd53581ff0bc4"
integrity sha512-3AtEO7k0Znzg11o51ZqebkW8063QkZ7Y7BC96Oye+wSdpT3ow57P0w0UtOpUNesNKzj1iMuPWqqm4i+YqitjCw==
"@angular-devkit/schematics@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.1.3.tgz#9fc4c82f64af9375249910d26093e449a73f0166"
integrity sha512-zKoWG1hDfvi1vR9Hqoca9hWo9vDg8evmQvGcBW5jXR5ndZi5Oit/uDcGdA8WUKvBd1EG7WMqp0FgcDR9EA9WCw==
dependencies:
"@angular-devkit/core" "17.1.1"
"@angular-devkit/core" "17.1.3"
jsonc-parser "3.2.0"
magic-string "0.30.5"
ora "5.4.1"
rxjs "7.8.1"
"@angular/animations@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-17.1.1.tgz#45b311fd9bd1d4aee1a4bedb49a4164cc622cd51"
integrity sha512-85qm8b4fNKa5zx4YlvCvAb3lWGlRsEcClnpCuwSVP8pXG7n/cW8MsJOh8i/wOkQTr9Ac7vvAE+yFWMi2A/qlTg==
"@angular/animations@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-17.1.3.tgz#be67fc479cfc700d7c1270222003c6d2bc622194"
integrity sha512-AS9CHOjjKqkuAzlKEMJfAkZfkIdSoagB3D8HwvH+ZHo6GVJc9KbtLQn/okNijFK+Fg7QK/hYbQ3lJhjgk0GQDA==
dependencies:
tslib "^2.3.0"
"@angular/cli@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-17.1.1.tgz#3aadcee37b8fd5b4ecb6c7ae8057dfb26973cc73"
integrity sha512-JG/Vf+PScR3PC6u7B+jFF4s5eBByzCpOfCfRFw98nlCqDAOxYOig7wi2Sbp5fnvILQH8vbc/NG8MzdgONrG6Hg==
"@angular/cli@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-17.1.3.tgz#45af3e5041f3df14ff10dc48ee64960f575d5aac"
integrity sha512-ysPWDdqo2cwfeskKVAg8p4C8xuezWcIWyW/ACSjWw6yp4OZvyVd6cGZrc0POVZzAPtTOYJSgWOpF/DCHQFluSg==
dependencies:
"@angular-devkit/architect" "0.1701.1"
"@angular-devkit/core" "17.1.1"
"@angular-devkit/schematics" "17.1.1"
"@schematics/angular" "17.1.1"
"@angular-devkit/architect" "0.1701.3"
"@angular-devkit/core" "17.1.3"
"@angular-devkit/schematics" "17.1.3"
"@schematics/angular" "17.1.3"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.3"
ini "4.1.1"
@@ -152,17 +152,17 @@
symbol-observable "4.0.0"
yargs "17.7.2"
"@angular/common@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-17.1.1.tgz#67f6469329130d7c43d36e1cb3e6220157391ea0"
integrity sha512-YMM2vImWJg7H3Yaej7ncGpFKT28V2Y6X9/rLpRdSKAiUbcbj7GeWtX/upfZGR9KmD08baYZw0YTNMR03Ubv/mg==
"@angular/common@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-17.1.3.tgz#d8411db80510a99a23ad01a56264c3594625936a"
integrity sha512-AzLzoNSeRSNGBQk0K+iG0XdYG36SDeJqYqE8rfoiWuv1NDFLL05UJM2/fQfaMNg0oX5bHOlHUqHFj3sFR/NVpw==
dependencies:
tslib "^2.3.0"
"@angular/compiler-cli@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-17.1.1.tgz#554e4cd090f4d64294bedd03868d3853a6d1af45"
integrity sha512-d6Aev1P92q7wd5u3UcJifzNlU9svxaYI2Ts6MKoD4jY4/GPN/gPDqi20weDMujEgirrkcwGbsCXBRoEGkA5c9A==
"@angular/compiler-cli@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-17.1.3.tgz#f1cbb68eb089c7aa161bab315cdc1a0004fac768"
integrity sha512-bNDHXo3Twub0BZK9OmXly+0REs0RuR1SUXlTAeq+0XubCvnBDvpg9peL7UTTGS5YRo9sUTBnR6faSUA1F5objQ==
dependencies:
"@babel/core" "7.23.2"
"@jridgewell/sourcemap-codec" "^1.4.14"
@@ -173,92 +173,92 @@
tslib "^2.3.0"
yargs "^17.2.1"
"@angular/compiler@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-17.1.1.tgz#b3e182390eb4f54690c0b6dd60d02ac389a1c0b4"
integrity sha512-lEQ5YNMJQm2iO2EZbGkwL3SqnxtE2ENfymgbS023F6ACsnP3kKB2DMwOnIbGgQY4+8r4sJFiMAIjEkj5c9kttg==
"@angular/compiler@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-17.1.3.tgz#157e834eb4b0cb4f339bc8a0da8dcb438e92034a"
integrity sha512-k/s21gPPKStxVOLr6l4Y145OIxyBY7BhTPVOl/qEAgE+IcZ9vkiA8dYl8yjL884Kl1ZKPmFA3AofMJjWjZGNag==
dependencies:
tslib "^2.3.0"
"@angular/core@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-17.1.1.tgz#9da19f0c832d69994c79255c56e81f40ae7ae68a"
integrity sha512-JtNYM9eHr8eUSrGPq/kn0+/F+TSZ7EBWxZhM1ZndOlGu1gA4fGhrDid4ZXIHIs07DbM4NZjMn+LhRyx02YDsSA==
"@angular/core@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-17.1.3.tgz#12f4adb8d1037ba1572c7531997d82a82fbe8fc1"
integrity sha512-2lZ4DRHN8KJ/aQads+YXIcx5Ri9yyeFIlw69m5Pn7wAi/+Rakg7IsclgLaWs7aBtWwMHG7LnqFKxAVq7CjXKtA==
dependencies:
tslib "^2.3.0"
"@angular/elements@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-17.1.1.tgz#1fbbe9291a5029949a6bc5862b3ca11b945d94a9"
integrity sha512-GrPUCSw3RpjX1XmUpXrRdf19YE27hFDqXNpc/t36gWrQ3ndkYfU+I+fhNXigSoBvDW9/Pk+EJXTKX1LJ2CeOdw==
"@angular/elements@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-17.1.3.tgz#944cb425e8c724417e499ab730009ce32d5ed2f7"
integrity sha512-q2JCogiHFB7Ac3IpiQGIUNF7ZDM5t/fF7xlDFvTLto7HHCb0g8y4erzmUmV+QRKX0YKedo5QAkd8w9TFlJ4ecA==
dependencies:
tslib "^2.3.0"
"@angular/forms@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-17.1.1.tgz#1e07572b207f4e78dc8abe9435d00c8fc0f37343"
integrity sha512-rqHVzaJDV8+VbnfC6mDgzX6ooa0X0hmnd+XfuOZaEJ7MtyOmqQ8qas2PAKXU7nMIImYXfYc4O4XWbSc1pRy1Hw==
"@angular/forms@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-17.1.3.tgz#1e87b39e237a1d6f7cc2f78ad8d6b597b5ef08aa"
integrity sha512-aNa0jGLT5d+hnKVrSo8tk3TRo/NLNu1RxLNx8RhIczKAeCK3eD8SvTMy27iJtyXmNG2GWN7QPiDeGepd75nbxQ==
dependencies:
tslib "^2.3.0"
"@angular/localize@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-17.1.1.tgz#f64b1724bdf4f0ab33551ca7bddd5e92d8cba6b8"
integrity sha512-vZc+YhpE7eRWFzdZFOidSjZlTl6+8VH0x2m7WesaSgw2fu1IgOQffZFnaGveAQ8DXbMfgBb4exPNJFPYmNgoSQ==
"@angular/localize@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-17.1.3.tgz#626564dcd89a3f5d26cd31eb2fcdc848e01898ad"
integrity sha512-Jdz5FWcaQLRhEhUHJk0iyMOT1ts4HHcY9B20mBmAd7u3kqEoBc57N8hOX0vHjU4/rk1tGqIvzfhJJf6zqIWcZQ==
dependencies:
"@babel/core" "7.23.2"
"@types/babel__core" "7.20.2"
fast-glob "3.3.1"
yargs "^17.2.1"
"@angular/platform-browser-dynamic@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.1.1.tgz#8d389d9739319a1260df27aceb7f4fffdecd28b7"
integrity sha512-UKI8966nwo+p01EjmQdkepLIeVLpPZTSDZAM4va7CfMO6lbCN5xFecDd/sVbut8J6ySIsbJxyDkP+SHMQjE+xg==
"@angular/platform-browser-dynamic@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.1.3.tgz#7391d547a22b85e95de8ee1f16faf6b876f26ae1"
integrity sha512-0lFhcFJfDzCSSVe8l8OY+UgUiwUwcbxwpvLod3XWBpf1iEUlr5720FIMA3VJYwpW3Oj4Uey3nVm13EMtRqpqdA==
dependencies:
tslib "^2.3.0"
"@angular/platform-browser@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-17.1.1.tgz#758b289f9d4920217c3bf754135e1558cd3d1cc1"
integrity sha512-/80znuEkdDvsF6EX/fe03isQlLCUS9+ldCgB4n0ZL+qAkf2/lJlU3n97SyEN7rzb189U+K1fDe0fb1nDwbbcWQ==
"@angular/platform-browser@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-17.1.3.tgz#3883cf089f2e5bd24b99df1e7c2cdd3df3d97514"
integrity sha512-onPCvdk9f/6OhOo2zP6nfGKpzLma1QIxpFqD3jymbmIJTcVMOOQDMYW3eLtY+uSX8ribcJ7GQcbDGIM4rliTFg==
dependencies:
tslib "^2.3.0"
"@angular/platform-server@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.1.1.tgz#9e0feec18c10974e3eed80e9a4e8cf28be6490a7"
integrity sha512-zZbJL56m1n/btwVV0TNc/YPqXYZp9rcnDEvl9HHFNfoinkQrcxHy6MHQUjEyQ1IAIv9Y0f73pmSbJ86BcloIAw==
"@angular/platform-server@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.1.3.tgz#b30236072a8a6f97198eaf41a121759e62922477"
integrity sha512-EB+sk4oZuUPEbkGSM0jpHAKiZVH98IBlXaRYDcmRCRw1hjZwsxl0kAQNuInSGfwJlyjVF7WCrZ6eNCuxlxXiAQ==
dependencies:
tslib "^2.3.0"
xhr2 "^0.2.0"
"@angular/router@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.1.1.tgz#752e5856360844512f78267bea5364c7b7dac0d7"
integrity sha512-pPIRX0v8agij2dRSU25iwj9qFy0S25cztsy7bGfZ+M510jwRCqu1JsitqXtQ85XSv/bdFqiNiFU0UbwVFl+QiQ==
"@angular/router@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.1.3.tgz#30d12b43079f44fda2ab968cde41da3548450d9e"
integrity sha512-6HigdtFjm+76UU2hiLGLE2SpOecQhD6TnAVTocDuRitpN5m0dyiffBrqxarfNwoZuMdIiXyqClJR4JRo1rJjoQ==
dependencies:
tslib "^2.3.0"
"@angular/service-worker@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-17.1.1.tgz#1cc2823d9a5fce14197f008b3b226dcab4d24a58"
integrity sha512-/ggG10tY5LWsGvJYLGhE0gdL8r9fEmRrZ6i+UCdwG/+Ydyb5GBxLS7OGH6yy0R8WP19FDRmVoDVMFJMtc4JMAw==
"@angular/service-worker@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-17.1.3.tgz#1222de7a4752b6f8dc7bbb5dcc6015db13346b45"
integrity sha512-DSndSwNvNnETiqtFAt/jPUMBUUfAxot7IhEUp19KFbS4HhasSZ+QxkNECN9bT6SUWjeaN7wvPcvR4gdSkQs3Pg==
dependencies:
tslib "^2.3.0"
"@angular/ssr@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/ssr/-/ssr-17.1.1.tgz#8199fc8ce5d321c45592f471b523813ea8d39885"
integrity sha512-HHWk827BBLkVNpwsp31GYO484vXdFGMUoegoHCVd3qZL7gXcaAdN+zreE9E1OnaIImZWpj99le3MAsH6ee69BQ==
"@angular/ssr@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/ssr/-/ssr-17.1.3.tgz#4bab9172d31043baeef4fdafb8a7789567f1704e"
integrity sha512-zgghp3gdh+pv//lYWWynfZOE9jkndSmflojgRWBR4FdAtapdyrm1MaE6TIwC7qT+r/sW0bKypK2fKxwN8sXpHw==
dependencies:
critters "0.0.20"
tslib "^2.3.0"
"@angular/upgrade@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-17.1.1.tgz#2c6b8b4f690782423a499b0ca1c4f9cbd319bf17"
integrity sha512-DfIzp/t3cZZGcCyNghpyzBQFDMKo8qmRADtmR3ToBujERX4cwDcEySa/XBnFDjedcDusQB8pABnLOiatNk3HzA==
"@angular/upgrade@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-17.1.3.tgz#4cf0a05866dced04114cb40de9f9e75d6897c0e4"
integrity sha512-Iw6J9jOg4P+CohWCsbchPnmOiLzuHhs5y0RqdA4+JL0dtTK8FkGkgaI39za74MYAbqNm6Z+q2yzlwyoe3gNEuw==
dependencies:
tslib "^2.3.0"
@@ -1737,10 +1737,10 @@
dependencies:
call-bind "^1.0.2"
"@ngtools/webpack@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-17.1.1.tgz#271177de0a382fccb5ba887a110ada9da79dc653"
integrity sha512-uPWEpRuAUmMBZhYMpkEHNbeI8QAgeVM5lnWM+02lK75u1+sgYy32ed+RcRvEI+8hRQcsCQ8HtR4QubgJb4TzCQ==
"@ngtools/webpack@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-17.1.3.tgz#510c5f6f6bf9ae470b0e43d4560cfc4ffc3a5ed5"
integrity sha512-mszRSb7aMNKHnkh3Jrfo83KVOguX/cUamJJcGIYe9o7tnLGRIoMp4vP0fx6Og4J0/CGDRhSDG4IiJ29aOU7K8A==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@@ -1937,13 +1937,13 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602"
integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==
"@schematics/angular@17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.1.1.tgz#5ac8f09a66e7dff86cbf664b9388a0dde4a97de3"
integrity sha512-1Wqefy1W9Y63g48Fp7BscL95V4U1seDGgZawH6DcJnytJVW89hazao7YREzLYfdoediuw7lU+OHJksWYe4VQww==
"@schematics/angular@17.1.3":
version "17.1.3"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.1.3.tgz#3c3e0f67985ed500d9d3b66fdb63387cc775e9a9"
integrity sha512-hmeasOvzmniy6urtzUKhEqGO67iPuLX/dVtkF4nWp2NTtcEKlvcJobNDMc+CTlX4+ZMPVOvmhDMQqrlfekZ+NQ==
dependencies:
"@angular-devkit/core" "17.1.1"
"@angular-devkit/schematics" "17.1.1"
"@angular-devkit/core" "17.1.3"
"@angular-devkit/schematics" "17.1.3"
jsonc-parser "3.2.0"
"@sigstore/bundle@^2.1.0":
+789 -118
View File
File diff suppressed because it is too large Load Diff
+92 -16
View File
@@ -8,29 +8,105 @@ To setup your local development environment for Angular DevTools, see [DEVELOPIN
## Publish Angular DevTools
To publish Angular DevTools to Firefox or Chrome stores:
Publishing Angular DevTools is a five step process:
1. Sync and update workspace.
1. Update extension version numbers.
1. Publish to Chrome.
1. Publish to Firefox.
1. Commit and merge the updated version numbers.
1. Update the version of the corresponding manifest file. Currently, Angular DevTools uses different versioning compared to the rest of the packages within the monorepo.
* `manifest.chrome.json` for Chrome
* `manifest.firefox.json` for Firefox
1. Build the extension:
* `yarn devtools:build:chrome` for Chrome
* `yarn devtools:build:firefox` for Firefox
1. Find the extension production assets in `dist/bin/devtools/projects/shell-browser/src/prodapp` relative to the root of the Angular monorepo
1. Zip the files in the directory
1. Upload them to the corresponding store
### 1. Sync workspace
### Firefox
Before starting anything, make sure your workspace is up to date with latest changes and dependencies.
1. Go to the Firefox Addons [page](https://addons.mozilla.org/developers/addons)
1. For email enter `devrel@angular.io`
1. Find the password on Valentine
1. Setup Google Authenticator with the 2FA QR code. You can find the QR code on Valentine
```shell
git checkout main
git pull upstream main
nvm install
yarn --frozen-lockfile
```
### Chrome Web Store
### 2. Update extension version numbers
Bump the version numbers listed in
[`manifest.chrome.json`](/devtools/projects/shell-browser/src/manifest/manifest.chrome.json)
and [`manifest.firefox.json`](/devtools/projects/shell-browser/src/manifest/manifest.firefox.json).
### 3. Publish to Chrome Chrome
To publish Angular DevTools to the Chrome Web Store, first build and package the extension.
```shell
# Build the Chrome version.
yarn devtools:build:chrome
# Package the extension.
(cd dist/bin/devtools/projects/shell-browser/src/prodapp && zip -r ~/devtools-chrome.zip *)
```
Then upload it to the Chrome Web Store.
1. Go to the extension [page](https://chrome.google.com/webstore/category/extensions)
1. Make sure your email is part of the Google Group we use for publishing the extension
1. Navigate to "Developer Dashboard"
1. Enter your account credentials
1. You should be able to change the publisher to "Angular"
You can choose to either publish immediately or only get approval but hold to publish at a later time.
Note that even publishing immediately still requires approval from Chrome Web Store before it is
available to users. Historically this has been pretty quick (< 30 minutes), but there is no hard upper
limit on how long a review might take: https://developer.chrome.com/docs/webstore/review-process#review-time.
### 4. Firefox
To publish Angular DevTools as a Firefox Add-on, first build and package the extension.
```shell
# Build the Firefox version.
yarn devtools:build:firefox
# Package the extension.
(cd dist/bin/devtools/projects/shell-browser/src/prodapp ~/devtools-firefox && zip -r ~/devtools-firefox.zip *)
```
Then upload it:
1. Go to the Firefox Addons [page](https://addons.mozilla.org/developers/addons)
1. For email enter `devrel@angular.io`
1. Find the password [on Valentine](http://valentine/#/show/1651707871496288)
1. Setup Google Authenticator with the 2FA QR code. You can find the QR code on Valentine
The Firefox publishing process is slightly more involved than Chrome. In particular, they
require extension source code with instructions to build and run it. Since DevTools exists in
a monorepo with critical build tooling existing outside the `devtools/` directory, we need to
upload the entire monorepo. Package it without dependencies and generated files with the
following command and upload it.
```shell
zip -r ~/angular-source.zip * -x ".git/*" -x "node_modules/*" -x "**/node_modules/*" -x "dist/"
```
Suggested note to reviewer:
> This is a monorepo and includes much more code than just the DevTools extension. The relevant
> code is under `devtools/...` and `devtools/README.md` contains instructions for building and
> testing locally.
>
> The uploaded source is equivalent to
> https://github.com/angular/angular/tree/${permalink to current main}/ with the single change
> of a bumped version number in the `manifest.json` file.
### 5. Commit and merge
Commit the version bump:
```shell
git checkout -b devtools-release
git add . && git commit -m "release: bump Angular DevTools version to 1.0.10"
git push -u origin devtools-release
```
Then create and merge a PR targeting `patch` with this change.
Once the PR merges and both Chrome and Firefox are showing the new version to end users, then
the release is complete!
@@ -32,6 +32,7 @@ import {isChildOf, parentCollapsed} from './directive-forest-utils';
import {IndexedNode} from './index-forest';
import {MatIcon} from '@angular/material/icon';
import {FilterComponent} from './filter/filter.component';
import {MatTooltip} from '@angular/material/tooltip';
@Component({
selector: 'ng-directive-forest',
@@ -45,6 +46,7 @@ import {FilterComponent} from './filter/filter.component';
CdkFixedSizeVirtualScroll,
CdkVirtualForOf,
MatIcon,
MatTooltip,
],
})
export class DirectiveForestComponent {
@@ -3,8 +3,8 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Chrome DevTools adding Angular specific debugging and profiling capabilities.",
"version": "1.0.9",
"version_name": "1.0.9",
"version": "1.0.11",
"version_name": "1.0.11",
"minimum_chrome_version": "102",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
@@ -3,7 +3,7 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Firefox DevTools adding Angular specific debugging and profiling capabilities.",
"version": "1.0.9",
"version": "1.0.11",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"icons": {
"16": "assets/icon16.png",
+3 -1
View File
@@ -1098,7 +1098,9 @@ export interface ModelSignal<T> extends WritableSignal<T> {
// (undocumented)
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
// (undocumented)
[SIGNAL]: ModelSignalNode<T>;
[SIGNAL]: InputSignalNode<T, T>;
// @deprecated (undocumented)
subscribe(callback: (value: T) => void): () => void;
}
// @public @deprecated
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "17.2.0-next.1",
"version": "17.2.3",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@@ -43,18 +43,18 @@
"devtools:build:chrome": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- devtools/projects/shell-browser/src:prodapp",
"devtools:build:firefox": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=firefox -- devtools/projects/shell-browser/src:prodapp",
"devtools:test": "bazelisk test --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- //devtools/...",
"docs": "yarn bazel run --config=aio_local_deps //adev:serve",
"docs:build": "yarn bazel build --config=aio_local_deps //adev:build",
"docs": "[[ -n $CI ]] && echo 'Cannot run this yarn script on CI' && exit 1 || yarn bazel run --config=aio_local_deps //adev:serve --fast_adev",
"docs:build": "[[ -n $CI ]] && echo 'Cannot run this yarn script on CI' && exit 1 || yarn bazel build --config=aio_local_deps //adev:build --fast_adev",
"benchmarks": "ts-node --esm scripts/benchmarks/index.mts"
},
"// 1": "dependencies are used locally and by bazel",
"dependencies": {
"@angular-devkit/build-angular": "17.2.0-next.0",
"@angular-devkit/core": "17.2.0-next.0",
"@angular-devkit/schematics": "17.2.0-next.0",
"@angular/cdk": "17.2.0-next.0",
"@angular/cli": "17.2.0-next.0",
"@angular/material": "17.2.0-next.0",
"@angular-devkit/build-angular": "17.2.0-rc.0",
"@angular-devkit/core": "17.2.0-rc.0",
"@angular-devkit/schematics": "17.2.0-rc.0",
"@angular/cdk": "17.2.0-rc.0",
"@angular/cli": "17.2.0-rc.0",
"@angular/material": "17.2.0-rc.0",
"@babel/cli": "7.23.9",
"@babel/core": "7.23.9",
"@babel/generator": "7.23.6",
@@ -71,7 +71,7 @@
"@rollup/plugin-babel": "^6.0.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@schematics/angular": "17.2.0-next.0",
"@schematics/angular": "17.2.0-rc.0",
"@types/angular": "^1.6.47",
"@types/babel__core": "7.20.5",
"@types/babel__generator": "7.6.8",
@@ -152,9 +152,9 @@
"@actions/core": "^1.10.0",
"@angular-devkit/architect-cli": "^0.1702.0-next",
"@angular/animations": "^17.2.0-next",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#07da06414a4ce04a8f9710f2bc9346c2c753a0d2",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7c4cf003cb4ac849986beaa243d7e85a893612f2",
"@angular/docs": "https://github.com/angular/dev-infra-private-docs-builds.git#87eb92c0cc022678fa3fedbd5ac25b84f285a7fa",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#eacabd9f66aad05822ccb5b0dc893ebe7eb62321",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#c21f93acb618bcaeda52a8065e7b6c9242def182",
"@babel/helper-remap-async-to-generator": "^7.18.9",
"@babel/plugin-proposal-async-generator-functions": "^7.20.7",
"@bazel/bazelisk": "^1.7.5",
@@ -222,7 +222,7 @@
"// 3": "Ensure that transitive dependencies on `https-proxy-agent` are at minimum v5 as older versions patch NodeJS directly, breaking tools like webdriver which is used by the karma-sauce-launcher as an example.",
"// 4": "Ensure that a single instance of the `saucelabs` package is used. Protractor and the Karma sauce launcher pull this package as dependency. A single instance allows for e.g. easier patching in the Karma config.",
"resolutions": {
"**/https-proxy-agent": "7.0.2",
"**/https-proxy-agent": "7.0.3",
"**/saucelabs": "7.5.0"
}
}
+2
View File
@@ -98,5 +98,7 @@ generate_api_manifest(
"//packages/upgrade:upgrade_docs_extraction",
"//packages/upgrade/static:upgrade_static_docs_extraction",
"//packages/upgrade/static/testing:upgrade_static_testing_docs_extraction",
"//tools/manual_api_docs/blocks",
"//tools/manual_api_docs/elements",
],
)
+3 -2
View File
@@ -197,13 +197,14 @@ function getFilteredHeaders(
function makeCacheKey(request: HttpRequest<any>): StateKey<TransferHttpResponse> {
// make the params encoded same as a url so it's easy to identify
const {params, method, responseType, url} = request;
const {params, method, responseType, url, body} = request;
const encodedParams = params
.keys()
.sort()
.map((k) => `${k}=${params.getAll(k)}`)
.join('&');
const key = method + '.' + responseType + '.' + url + '?' + encodedParams;
const strBody = typeof body === 'string' ? body : '';
const key = [method, responseType, url, strBody, encodedParams].join('|');
const hash = generateHash(key);
@@ -98,8 +98,8 @@ describe('TransferCache', () => {
it('should store HTTP calls in cache when application is not stable', () => {
makeRequestAndExpectOne('/test', 'foo');
const key = makeStateKey('432906284');
const transferState = TestBed.inject(TransferState);
const key = makeStateKey(Object.keys((transferState as any).store)[0]);
expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({[BODY]: 'foo'}));
});
@@ -115,7 +115,7 @@ describe('TransferCache', () => {
const transferState = TestBed.inject(TransferState);
expect(JSON.parse(transferState.toJson()) as Record<string, unknown>).toEqual({
'3706062792': {
'2400571479': {
[BODY]: 'foo',
[HEADERS]: {},
[STATUS]: 200,
@@ -123,7 +123,7 @@ describe('TransferCache', () => {
[URL]: '/test-1',
[RESPONSE_TYPE]: 'json',
},
'3706062823': {
'2400572440': {
[BODY]: 'buzz',
[HEADERS]: {},
[STATUS]: 200,
@@ -29,6 +29,7 @@ import {
ɵRuntimeError as RuntimeError,
ɵSafeValue as SafeValue,
ɵunwrapSafeValue as unwrapSafeValue,
ChangeDetectorRef,
} from '@angular/core';
import {RuntimeErrorCode} from '../../errors';
@@ -433,7 +434,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
}
}
if (this.placeholder) {
this.removePlaceholderOnLoad(this, this.imgElement, this.renderer);
this.removePlaceholderOnLoad(this.imgElement);
}
this.setHostAttributes();
}
@@ -648,22 +649,17 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
return Boolean(placeholderConfig.blur);
}
private removePlaceholderOnLoad(
dir: NgOptimizedImage,
img: HTMLImageElement,
renderer: Renderer2,
): void {
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
private removePlaceholderOnLoad(img: HTMLImageElement): void {
const callback = () => {
const changeDetectorRef = this.injector.get(ChangeDetectorRef);
removeLoadListenerFn();
removeErrorListenerFn();
dir.placeholder = false;
});
this.placeholder = false;
changeDetectorRef.markForCheck();
};
const removeErrorListenerFn = renderer.listen(img, 'error', () => {
removeLoadListenerFn();
removeErrorListenerFn();
dir.placeholder = false;
});
const removeLoadListenerFn = this.renderer.listen(img, 'load', callback);
const removeErrorListenerFn = this.renderer.listen(img, 'error', callback);
}
/** @nodoc */
+13 -4
View File
@@ -102,6 +102,7 @@ const _subscribableStrategy = new SubscribableStrategy();
export class AsyncPipe implements OnDestroy, PipeTransform {
private _ref: ChangeDetectorRef | null;
private _latestValue: any = null;
private markForCheckOnValueUpdate = true;
private _subscription: Unsubscribable | Promise<any> | null = null;
private _obj: Subscribable<any> | Promise<any> | EventEmitter<any> | null = null;
@@ -134,7 +135,15 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
transform<T>(obj: Observable<T> | Subscribable<T> | Promise<T> | null | undefined): T | null {
if (!this._obj) {
if (obj) {
this._subscribe(obj);
try {
// Only call `markForCheck` if the value is updated asynchronously.
// Synchronous updates _during_ subscription should not wastefully mark for check -
// this value is already going to be returned from the transform function.
this.markForCheckOnValueUpdate = false;
this._subscribe(obj);
} finally {
this.markForCheckOnValueUpdate = true;
}
}
return this._latestValue;
}
@@ -181,9 +190,9 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
// Note: `this._ref` is only cleared in `ngOnDestroy` so is known to be available when a
// value is being updated.
this._ref!.markForCheck();
if (this.markForCheckOnValueUpdate) {
this._ref?.markForCheck();
}
}
}
}
@@ -9,7 +9,7 @@
import {CommonModule, DOCUMENT, IMAGE_CONFIG, ImageConfig} from '@angular/common';
import {RuntimeErrorCode} from '@angular/common/src/errors';
import {PLATFORM_SERVER_ID} from '@angular/common/src/platform_id';
import {Component, PLATFORM_ID, Provider, Type} from '@angular/core';
import {ChangeDetectionStrategy, Component, PLATFORM_ID, Provider, Type} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -1078,7 +1078,6 @@ describe('Image directive', () => {
fixture.detectChanges();
const nativeElement = fixture.nativeElement as HTMLElement;
const img = nativeElement.querySelector('img')!;
const styles = parseInlineStyles(img);
// Double quotes removed to account for different browser behavior.
expect(img.getAttribute('style')?.replace(/"/g, '').replace(/\s/g, '')).toBe(
`background-size:cover;background-position:50%50%;background-repeat:no-repeat;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEU);filter:blur(${PLACEHOLDER_BLUR_AMOUNT}px);`,
@@ -1100,6 +1099,19 @@ describe('Image directive', () => {
);
});
it('should replace the placeholder with the actual image on load', () => {
setupTestingModule();
const template = '<img ngSrc="path/img.png" width="400" height="300" placeholder="true" />';
const fixture = createTestComponent(template, ChangeDetectionStrategy.OnPush);
fixture.detectChanges();
const nativeElement = fixture.nativeElement as HTMLElement;
const img = nativeElement.querySelector('img')!;
expect(parseInlineStyles(img).has('background-image')).toBe(true);
img.dispatchEvent(new Event('load'));
fixture.detectChanges();
expect(parseInlineStyles(img).has('background-image')).toBe(false);
});
it('should use the placeholderResolution set in imageConfig', () => {
const imageConfig = {
placeholderResolution: 30,
@@ -2177,10 +2189,13 @@ function setUpModuleNoLoader() {
});
}
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}}).createComponent(
TestComponent,
);
function createTestComponent(
template: string,
changeDetection = ChangeDetectionStrategy.OnPush,
): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {
set: {template, changeDetection},
}).createComponent(TestComponent);
}
function parseInlineStyles(img: HTMLImageElement): Map<string, string> {
@@ -12,7 +12,7 @@ import ts from 'typescript';
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
import {absoluteFrom, relative} from '../../../file_system';
import {assertSuccessfulReferenceEmit, DeferredSymbolTracker, ImportedFile, LocalCompilationExtraImportsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {assertSuccessfulReferenceEmit, DeferredSymbolTracker, ImportedFile, ImportedSymbolsTracker, LocalCompilationExtraImportsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {DependencyTracker} from '../../../incremental/api';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {IndexingContext} from '../../../indexer';
@@ -83,7 +83,8 @@ export class ComponentDecoratorHandler implements
private injectableRegistry: InjectableClassRegistry,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean, private perf: PerfRecorder,
private hostDirectivesResolver: HostDirectivesResolver, private includeClassMetadata: boolean,
private hostDirectivesResolver: HostDirectivesResolver,
private importTracker: ImportedSymbolsTracker, private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode,
private readonly deferredSymbolTracker: DeferredSymbolTracker,
private readonly forbidOrphanRendering: boolean, private readonly enableBlockSyntax: boolean,
@@ -225,8 +226,8 @@ export class ComponentDecoratorHandler implements
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
// on it.
const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler, this.compilationMode,
node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter,
this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode,
this.elementSchemaRegistry.getDefaultComponentElementName(), this.useTemplatePipeline);
if (directiveResult === undefined) {
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
@@ -1275,9 +1276,10 @@ export class ComponentDecoratorHandler implements
if (importDecl !== null && this.deferredSymbolTracker.canDefer(importDecl)) {
deferBlockDep.isDeferrable = true;
deferBlockDep.importPath = (importDecl.moduleSpecifier as ts.StringLiteral).text;
deferBlockDep.isDefaultImport = isDefaultImport(importDecl);
deferrableTypes.set(deferBlockDep.symbolName, {
importPath: deferBlockDep.importPath,
isDefaultImport: isDefaultImport(importDecl),
isDefaultImport: deferBlockDep.isDefaultImport,
});
}
}
@@ -13,7 +13,7 @@ import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../cycles
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../../diagnostics';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {DeferredSymbolTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {DeferredSymbolTracker, ImportedSymbolsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, HostDirectivesResolver, LocalMetadataRegistry, ResourceRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
@@ -68,6 +68,7 @@ function setup(
const typeCheckScopeRegistry =
new TypeCheckScopeRegistry(scopeRegistry, metaReader, hostDirectivesResolver);
const resourceLoader = new StubResourceLoader();
const importTracker = new ImportedSymbolsTracker();
const handler = new ComponentDecoratorHandler(
reflectionHost,
@@ -99,6 +100,7 @@ function setup(
/* annotateForClosureCompiler */ false,
NOOP_PERF_RECORDER,
hostDirectivesResolver,
importTracker,
true,
compilationMode,
new DeferredSymbolTracker(checker, /* onlyExplicitDeferDependencyImports */ false),
@@ -9,7 +9,7 @@
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, FactoryTarget, makeBindingParser, R3ClassMetadata, R3DirectiveMetadata, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';
import {Reference, ReferenceEmitter} from '../../../imports';
import {ImportedSymbolsTracker, Reference, ReferenceEmitter} from '../../../imports';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {ClassPropertyMapping, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, HostDirectiveMeta, InputMapping, MatchSource, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
@@ -62,6 +62,7 @@ export class DirectiveDecoratorHandler implements
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean,
private perf: PerfRecorder,
private importTracker: ImportedSymbolsTracker,
private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode,
private readonly useTemplatePipeline: boolean,
@@ -104,8 +105,8 @@ export class DirectiveDecoratorHandler implements
this.perf.eventCount(PerfEvent.AnalyzeDirective);
const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler, this.compilationMode,
node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter,
this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode,
/* defaultSelector */ null, this.useTemplatePipeline);
if (directiveResult === undefined) {
return {};
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, ReflectionHost} from '../../../reflection';
import {CORE_MODULE} from '../../common';
@@ -39,6 +40,18 @@ interface InitializerFunctionMetadata {
isRequired: boolean;
}
/**
* Metadata that can be inferred from an initializer
* statically without going through the type checker.
*/
interface StaticInitializerData {
/** Identifier in the initializer that refers to the Angular API. */
node: ts.Identifier;
/** Whether the call is required. */
isRequired: boolean;
}
/**
* Attempts to identify an Angular class member that is declared via
* its initializer referring to a given initializer API function.
@@ -48,97 +61,110 @@ interface InitializerFunctionMetadata {
*/
export function tryParseInitializerApiMember<FnNames extends InitializerApiFunction[]>(
fnNames: FnNames, member: Pick<ClassMember, 'value'>, reflector: ReflectionHost,
isCore: boolean): InitializerFunctionMetadata&{apiName: FnNames[number]}|null {
importTracker: ImportedSymbolsTracker): InitializerFunctionMetadata|null {
if (member.value === null || !ts.isCallExpression(member.value)) {
return null;
}
const call = member.value;
const staticResult = parseTopLevelCall(call, fnNames, importTracker) ||
parseTopLevelRequiredCall(call, fnNames, importTracker) ||
parseTopLevelCallFromNamespace(call, fnNames, importTracker);
// Extract target. Either:
// - `[input]`
// - `core.[input]`
// - `input.[required]`
// - `core.input.[required]`.
let target = extractPropertyTarget(call.expression);
if (target === null) {
if (staticResult === null) {
return null;
}
// Find if the `target` matches one of the expected APIs we are looking for.
// e.g. `input`, or `viewChild`.
let apiName = fnNames.find(n => n === target!.text);
// Case 1: API is directly called. e.g. `input`
// If no API name was matched, continue looking for `input.required`.
if (apiName !== undefined) {
if (!isReferenceToInitializerApiFunction(apiName, target, isCore, reflector)) {
return null;
}
return {apiName, call, isRequired: false};
}
// Case 2: API is the `.required`
// Ensure there is a property access to `[input].required` or `[core.input].required`.
if (target.text !== 'required' || !ts.isPropertyAccessExpression(call.expression)) {
return null;
}
// e.g. `[input.required]` (the full property access is this)
const apiPropertyAccess = call.expression;
// e.g. `[input].required` (we now extract the left side of the access).
target = extractPropertyTarget(apiPropertyAccess.expression);
if (target === null) {
return null;
}
// Find if the `target` matches one of the expected APIs are are looking for.
apiName = fnNames.find(n => n === target!.text);
// Ensure the call refers to the real API function from Angular core.
if (apiName === undefined ||
!isReferenceToInitializerApiFunction(apiName, target, isCore, reflector)) {
// Once we've statically determined that the initializer is one of the APIs we're looking for, we
// need to verify it using the type checker which accounts for things like shadowed variables.
// This should be done as the absolute last step since using the type check can be expensive.
const resolvedImport = reflector.getImportOfIdentifier(staticResult.node);
if (resolvedImport === null || !(fnNames as string[]).includes(resolvedImport.name)) {
return null;
}
return {
apiName,
call,
isRequired: true,
isRequired: staticResult.isRequired,
apiName: resolvedImport.name as InitializerApiFunction,
};
}
/**
* Extracts the identifier property target of a expression, supporting
* one level deep property accesses.
*
* e.g. `input.required` will return `required`.
* e.g. `input` will return `input`.
*
* Attempts to parse a top-level call to an initializer function,
* e.g. `prop = input()`. Returns null if it can't be parsed.
*/
function extractPropertyTarget(node: ts.Expression): ts.Identifier|null {
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {
return node.name;
} else if (ts.isIdentifier(node)) {
return node;
function parseTopLevelCall(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isIdentifier(node)) {
return null;
}
return null;
return fnNames.some(
name => importTracker.isPotentialReferenceToNamedImport(node, name, CORE_MODULE)) ?
{node, isRequired: false} :
null;
}
/**
* Verifies that the given identifier resolves to the given initializer API
* function expression from Angular core.
* Attempts to parse a top-level call to a required initializer,
* e.g. `prop = input.required()`. Returns null if it can't be parsed.
*/
function isReferenceToInitializerApiFunction(
functionName: InitializerApiFunction, target: ts.Identifier, isCore: boolean,
reflector: ReflectionHost): boolean {
let targetImport: {name: string, from: string}|null = reflector.getImportOfIdentifier(target);
if (targetImport === null) {
if (!isCore) {
return false;
}
// We are compiling the core module, where no import can be present.
targetImport = {name: target.text, from: CORE_MODULE};
function parseTopLevelRequiredCall(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isPropertyAccessExpression(node) || !ts.isIdentifier(node.expression) ||
node.name.text !== 'required') {
return null;
}
return targetImport.name === functionName && targetImport.from === CORE_MODULE;
const expression = node.expression;
const matchesCoreApi = fnNames.some(
name => importTracker.isPotentialReferenceToNamedImport(expression, name, CORE_MODULE));
return matchesCoreApi ? {node: expression, isRequired: true} : null;
}
/**
* Attempts to parse a top-level call to a function referenced via a namespace import,
* e.g. `prop = core.input.required()`. Returns null if it can't be parsed.
*/
function parseTopLevelCallFromNamespace(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isPropertyAccessExpression(node)) {
return null;
}
let apiReference: ts.Identifier|null = null;
let isRequired = false;
// `prop = core.input()`
if (ts.isIdentifier(node.expression) && ts.isIdentifier(node.name) &&
importTracker.isPotentialReferenceToNamespaceImport(node.expression, CORE_MODULE)) {
apiReference = node.name;
} else if (
// `prop = core.input.required()`
ts.isPropertyAccessExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) && ts.isIdentifier(node.expression.name) &&
importTracker.isPotentialReferenceToNamespaceImport(
node.expression.expression, CORE_MODULE) &&
node.name.text === 'required') {
apiReference = node.expression.name;
isRequired = true;
}
if (apiReference === null || !(fnNames as string[]).includes(apiReference.text)) {
return null;
}
return {node: apiReference, isRequired};
}
@@ -8,9 +8,9 @@
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {InputMapping} from '../../../metadata';
import {ClassMember, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {ClassMember, ReflectionHost} from '../../../reflection';
import {tryParseInitializerApiMember} from './initializer_functions';
import {parseAndValidateInputAndOutputOptions} from './input_output_parse_options';
@@ -21,8 +21,8 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseSignalInputMapping(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): InputMapping|null {
const signalInput = tryParseInitializerApiMember(['input'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): InputMapping|null {
const signalInput = tryParseInitializerApiMember(['input'], member, reflector, importTracker);
if (signalInput === null) {
return null;
}
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../../../imports';
import {ModelMapping} from '../../../metadata';
import {ClassMember, ReflectionHost} from '../../../reflection';
@@ -19,8 +20,8 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseSignalModelMapping(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): ModelMapping|null {
const model = tryParseInitializerApiMember(['model'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): ModelMapping|null {
const model = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
if (model === null) {
return null;
}
@@ -9,6 +9,7 @@
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {InputOrOutput} from '../../../metadata';
import {ClassMember, ReflectionHost} from '../../../reflection';
@@ -21,8 +22,10 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseInitializerBasedOutput(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): {call: ts.CallExpression, metadata: InputOrOutput}|null {
const output = tryParseInitializerApiMember(['output', 'ɵoutput'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): {call: ts.CallExpression, metadata: InputOrOutput}|
null {
const output =
tryParseInitializerApiMember(['output', 'ɵoutput'], member, reflector, importTracker);
if (output === null) {
return null;
}
@@ -11,6 +11,7 @@ import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpr
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {tryUnwrapForwardRef} from '../../common';
@@ -35,9 +36,10 @@ const defaultDescendantsValue = (type: QueryFunctionName) => type !== 'contentCh
* @returns Resolved query metadata, or null if no query is declared.
*/
export function tryParseSignalQueryFromInitializer(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost, isCore: boolean):
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
importTracker: ImportedSymbolsTracker):
{name: QueryFunctionName, metadata: R3QueryMetadata, call: ts.CallExpression}|null {
const query = tryParseInitializerApiMember(queryFunctionNames, member, reflector, isCore);
const query = tryParseInitializerApiMember(queryFunctionNames, member, reflector, importTracker);
if (query === null) {
return null;
}
@@ -58,10 +60,10 @@ export function tryParseSignalQueryFromInitializer(
const read = options?.has('read') ? parseReadOption(options.get('read')!) : null;
const descendants = options?.has('descendants') ?
parseDescendantsOption(options.get('descendants')!) :
defaultDescendantsValue(query.apiName);
defaultDescendantsValue(query.apiName as QueryFunctionName);
return {
name: query.apiName,
name: query.apiName as QueryFunctionName,
call: query.call,
metadata: {
isSignal: true,
@@ -10,7 +10,7 @@ import {createMayBeForwardRefExpression, emitDistinctChangesOnlyDefaultValue, Ex
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeRelatedInformation} from '../../../diagnostics';
import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitter} from '../../../imports';
import {assertSuccessfulReferenceEmit, ImportedSymbolsTracker, ImportFlags, Reference, ReferenceEmitter} from '../../../imports';
import {ClassPropertyMapping, DecoratorInputTransform, HostDirectiveMeta, InputMapping, InputOrOutput, isHostDirectiveMetaForGlobalMode} from '../../../metadata';
import {DynamicValue, EnumValue, PartialEvaluator, ResolvedValue, traceDynamicValue} from '../../../partial_evaluator';
import {AmbientImport, ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
@@ -38,9 +38,10 @@ const QUERY_TYPES = new Set<string>(queryDecoratorNames);
*/
export function extractDirectiveMetadata(
clazz: ClassDeclaration, decorator: Readonly<Decorator>, reflector: ReflectionHost,
evaluator: PartialEvaluator, refEmitter: ReferenceEmitter,
referencesRegistry: ReferencesRegistry, isCore: boolean, annotateForClosureCompiler: boolean,
compilationMode: CompilationMode, defaultSelector: string|null, useTemplatePipeline: boolean): {
importTracker: ImportedSymbolsTracker, evaluator: PartialEvaluator,
refEmitter: ReferenceEmitter, referencesRegistry: ReferencesRegistry, isCore: boolean,
annotateForClosureCompiler: boolean, compilationMode: CompilationMode,
defaultSelector: string|null, useTemplatePipeline: boolean): {
decorator: Map<string, ts.Expression>,
metadata: R3DirectiveMetadata,
inputs: ClassPropertyMapping<InputMapping>,
@@ -84,19 +85,19 @@ export function extractDirectiveMetadata(
const inputsFromMeta =
parseInputsArray(clazz, directive, evaluator, reflector, refEmitter, compilationMode);
const inputsFromFields = parseInputFields(
clazz, members, evaluator, reflector, refEmitter, isCore, compilationMode, inputsFromMeta,
decorator);
clazz, members, evaluator, reflector, importTracker, refEmitter, isCore, compilationMode,
inputsFromMeta, decorator);
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
// And outputs.
const outputsFromMeta = parseOutputsArray(directive, evaluator);
const outputsFromFields =
parseOutputFields(clazz, decorator, members, isCore, reflector, evaluator, outputsFromMeta);
const outputsFromFields = parseOutputFields(
clazz, decorator, members, isCore, reflector, importTracker, evaluator, outputsFromMeta);
const outputs = ClassPropertyMapping.fromMappedObject({...outputsFromMeta, ...outputsFromFields});
// Parse queries of fields.
const {viewQueries, contentQueries} =
parseQueriesOfClassFields(members, reflector, evaluator, isCore);
parseQueriesOfClassFields(members, reflector, importTracker, evaluator, isCore);
if (directive.has('queries')) {
const signalQueryFields = new Set(
@@ -750,13 +751,13 @@ function tryGetDecoratorOnMember(
function tryParseInputFieldMapping(
clazz: ClassDeclaration, member: ClassMember, evaluator: PartialEvaluator,
reflector: ReflectionHost, isCore: boolean, refEmitter: ReferenceEmitter,
compilationMode: CompilationMode): InputMapping|null {
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, isCore: boolean,
refEmitter: ReferenceEmitter, compilationMode: CompilationMode): InputMapping|null {
const classPropertyName = member.name;
const decorator = tryGetDecoratorOnMember(member, 'Input', isCore);
const signalInputMapping = tryParseSignalInputMapping(member, reflector, isCore);
const modelInputMapping = tryParseSignalModelMapping(member, reflector, isCore);
const signalInputMapping = tryParseSignalInputMapping(member, reflector, importTracker);
const modelInputMapping = tryParseSignalModelMapping(member, reflector, importTracker);
if (decorator !== null && signalInputMapping !== null) {
throw new FatalDiagnosticError(
@@ -838,8 +839,9 @@ function tryParseInputFieldMapping(
/** Parses the class members that declare inputs (via decorator or initializer). */
function parseInputFields(
clazz: ClassDeclaration, members: ClassMember[], evaluator: PartialEvaluator,
reflector: ReflectionHost, refEmitter: ReferenceEmitter, isCore: boolean,
compilationMode: CompilationMode, inputsFromClassDecorator: Record<string, InputMapping>,
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, refEmitter: ReferenceEmitter,
isCore: boolean, compilationMode: CompilationMode,
inputsFromClassDecorator: Record<string, InputMapping>,
classDecorator: Decorator): Record<string, InputMapping> {
const inputs = {} as Record<string, InputMapping>;
@@ -850,6 +852,7 @@ function parseInputFields(
member,
evaluator,
reflector,
importTracker,
isCore,
refEmitter,
compilationMode,
@@ -1028,8 +1031,8 @@ function assertEmittableInputType(
* initializers for signal-based queries.
*/
function parseQueriesOfClassFields(
members: ClassMember[], reflector: ReflectionHost, evaluator: PartialEvaluator,
isCore: boolean): {
members: ClassMember[], reflector: ReflectionHost, importTracker: ImportedSymbolsTracker,
evaluator: PartialEvaluator, isCore: boolean): {
viewQueries: R3QueryMetadata[],
contentQueries: R3QueryMetadata[],
} {
@@ -1046,7 +1049,7 @@ function parseQueriesOfClassFields(
for (const member of members) {
const decoratorQuery = tryGetQueryFromFieldDecorator(member, reflector, evaluator, isCore);
const signalQuery = tryParseSignalQueryFromInitializer(member, reflector, isCore);
const signalQuery = tryParseSignalQueryFromInitializer(member, reflector, importTracker);
if (decoratorQuery !== null && signalQuery !== null) {
throw new FatalDiagnosticError(
@@ -1106,14 +1109,14 @@ function parseOutputsArray(
/** Parses the class members that are outputs. */
function parseOutputFields(
clazz: ClassDeclaration, classDecorator: Decorator, members: ClassMember[], isCore: boolean,
reflector: ReflectionHost, evaluator: PartialEvaluator,
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, evaluator: PartialEvaluator,
outputsFromMeta: Record<string, string>): Record<string, string> {
const outputs = {} as Record<string, string>;
for (const member of members) {
const decoratorOutput = tryParseDecoratorOutput(member, evaluator, isCore);
const initializerOutput = tryParseInitializerBasedOutput(member, reflector, isCore);
const modelMapping = tryParseSignalModelMapping(member, reflector, isCore);
const initializerOutput = tryParseInitializerBasedOutput(member, reflector, importTracker);
const modelMapping = tryParseSignalModelMapping(member, reflector, importTracker);
if (decoratorOutput !== null && initializerOutput !== null) {
throw new FatalDiagnosticError(
@@ -10,7 +10,7 @@ import ts from 'typescript';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ReferenceEmitter} from '../../../imports';
import {ImportedSymbolsTracker, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
@@ -174,13 +174,15 @@ runInEachFileSystem(() => {
metaReader, new CompoundMetadataReader([metaReader, dtsReader]),
new MetadataDtsModuleScopeResolver(dtsReader, null), refEmitter, null);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const importTracker = new ImportedSymbolsTracker();
const handler = new DirectiveDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, scopeRegistry, metaReader, injectableRegistry,
refEmitter, referenceRegistry,
/*isCore*/ false,
/*strictCtorDeps*/ false,
/*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, /*includeClassMetadata*/ true,
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, importTracker,
/*includeClassMetadata*/ true,
/*compilationMode */ CompilationMode.FULL, /* useTemplatePipeline */ true,
/*generateExtraImportsInLocalMode*/ false);
@@ -0,0 +1,294 @@
/*!
* @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 {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, TypeScriptReflectionHost} from '../../../reflection';
import {makeProgram} from '../../../testing';
import {tryParseInitializerApiMember} from '../src/initializer_functions';
runInEachFileSystem(() => {
describe('initializer function detection', () => {
it('should identify a non-required function that is imported directly', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is imported directly', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a non-required function that is aliased', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model as alias} from '@angular/core';
@Directive()
export class Dir {
test = alias(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is aliased', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model as alias} from '@angular/core';
@Directive()
export class Dir {
test = alias.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a non-required function that is imported via namespace import', () => {
const {member, reflector, importTracker} = setup(`
import * as core from '@angular/core';
@core.Directive()
export class Dir {
test = core.model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is imported via namespace import', () => {
const {member, reflector, importTracker} = setup(`
import * as core from '@angular/core';
@core.Directive()
export class Dir {
test = core.model.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should not identify a valid core function that is not being checked for', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, input} from '@angular/core';
@Directive()
export class Dir {
test = input(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify a function coming from a different module', () => {
const {member, reflector, importTracker} = setup(`
import {Directive} from '@angular/core';
import {model} from '@not-angular/core';
@Directive()
export class Dir {
test = model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify an invalid call on a core function', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model.unknown();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify an invalid call on a core function through a namespace import', () => {
const {member, reflector, importTracker} = setup(`
import {Directive} from '@angular/core';
import * as core from '@angular/core';
@Directive()
export class Dir {
test = core.model.unknown();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should identify shadowed declarations', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
function wrapper() {
function model(value: number): any {}
@Directive()
class Dir {
test = model(1);
}
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
});
it('should identify an initializer function in a file containing an import whose name overlaps with an object prototype member',
() => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
import {toString} from '@unknown/utils';
@Directive()
export class Dir {
test = model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
});
function setup(contents: string) {
const fileName = absoluteFrom('/test.ts');
const {program} = makeProgram([
{
name: absoluteFrom('/node_modules/@angular/core/index.d.ts'),
contents: `
export const Directive: any;
export interface InitializerFunction {
(initialValue: any): any;
required(): any;
unknown(): any;
}
export const input: InitializerFunction;
export const model: InitializerFunction;
`,
},
{
name: absoluteFrom('/node_modules/@unknown/utils/index.d.ts'),
contents: `
export declare function toString(value: any): string;
`,
},
{
name: absoluteFrom('/node_modules/@not-angular/core/index.d.ts'),
contents: `
export interface InitializerFunction {
(initialValue: any): any;
required(): any;
}
export const model: InitializerFunction;
`,
},
{name: fileName, contents}
]);
const sourceFile = program.getSourceFile(fileName);
const importTracker = new ImportedSymbolsTracker();
const reflector = new TypeScriptReflectionHost(program.getTypeChecker());
if (sourceFile === undefined) {
throw new Error(`Cannot resolve test file ${fileName}`);
}
let member: Pick<ClassMember, 'value'>|null = null;
(function walk(node: ts.Node) {
if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'test') {
member = {value: node.initializer ?? null};
} else {
ts.forEachChild(node, walk);
}
})(sourceFile);
if (member === null) {
throw new Error(`Could not resolve a class property with a name of "test" in the test file`);
}
return {member, reflector, importTracker};
}
@@ -74,6 +74,14 @@ export interface InternalOptions {
* @internal
*/
_enableBlockSyntax?: boolean;
/**
* Detected version of `@angular/core` in the workspace. Used by the
* compiler to adjust the output depending on the available symbols.
*
* @internal
*/
_angularCoreVersion?: string;
}
/**
@@ -15,7 +15,7 @@ import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, isF
import {DocEntry, DocsExtractor} from '../../docs';
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
import {absoluteFromSourceFile, AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, DeferredSymbolTracker, ImportRewriter, LocalCompilationExtraImportsTracker, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, DeferredSymbolTracker, ImportedSymbolsTracker, ImportRewriter, LocalCompilationExtraImportsTracker, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
import {IncrementalBuildStrategy, IncrementalCompilation, IncrementalState} from '../../incremental';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
@@ -266,6 +266,7 @@ export class NgCompiler {
readonly ignoreForEmit: Set<ts.SourceFile>;
readonly enableTemplateTypeChecker: boolean;
private readonly enableBlockSyntax: boolean;
private readonly angularCoreVersion: Version|null;
/**
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
@@ -330,7 +331,10 @@ export class NgCompiler {
) {
this.enableTemplateTypeChecker =
enableTemplateTypeChecker || (options['_enableTemplateTypeChecker'] ?? false);
// TODO(crisbeto): remove this flag and base `enableBlockSyntx` on the `angularCoreVersion`.
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
this.angularCoreVersion =
options['_angularCoreVersion'] == null ? null : new Version(options['_angularCoreVersion']);
this.constructionDiagnostics.push(
...this.adapter.constructionDiagnostics, ...verifyCompatibleTypeCheckOptions(this.options));
@@ -791,6 +795,14 @@ export class NgCompiler {
const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
// Only Angular versions greater than 17.2 have the necessary symbols to type check signals in
// two-way bindings. We also allow version 0.0.0 in case somebody is using Angular at head.
const allowSignalsInTwoWayBindings = this.angularCoreVersion === null ||
this.angularCoreVersion.major > 17 ||
(this.angularCoreVersion.major === 17 && this.angularCoreVersion.minor >= 2) ||
(this.angularCoreVersion.major === 0 && this.angularCoreVersion.minor === 0 &&
this.angularCoreVersion.patch === 0);
// First select a type-checking configuration, based on whether full template type-checking is
// requested.
let typeCheckingConfig: TypeCheckingConfig;
@@ -829,6 +841,7 @@ export class NgCompiler {
suggestionsForSuboptimalTypeInference: this.enableTemplateTypeChecker && !strictTemplates,
controlFlowPreventingContentProjection:
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
allowSignalsInTwoWayBindings,
};
} else {
typeCheckingConfig = {
@@ -859,6 +872,7 @@ export class NgCompiler {
suggestionsForSuboptimalTypeInference: false,
controlFlowPreventingContentProjection:
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
allowSignalsInTwoWayBindings,
};
}
@@ -1087,6 +1101,7 @@ export class NgCompiler {
const injectableRegistry = new InjectableClassRegistry(reflector, isCore);
const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
const exportedProviderStatusResolver = new ExportedProviderStatusResolver(metaReader);
const importTracker = new ImportedSymbolsTracker();
const typeCheckScopeRegistry =
new TypeCheckScopeRegistry(scopeReader, metaReader, hostDirectivesResolver);
@@ -1160,7 +1175,7 @@ export class NgCompiler {
this.cycleAnalyzer, cycleHandlingStrategy, refEmitter, referencesRegistry,
this.incrementalCompilation.depGraph, injectableRegistry, semanticDepGraphUpdater,
this.closureCompilerEnabled, this.delegatingPerfRecorder, hostDirectivesResolver,
supportTestBed, compilationMode, deferredSymbolsTracker,
importTracker, supportTestBed, compilationMode, deferredSymbolsTracker,
!!this.options.forbidOrphanComponents, this.enableBlockSyntax,
this.options.useTemplatePipeline ?? SHOULD_USE_TEMPLATE_PIPELINE,
localCompilationExtraImportsTracker),
@@ -1173,6 +1188,7 @@ export class NgCompiler {
injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater,
this.closureCompilerEnabled,
this.delegatingPerfRecorder,
importTracker,
supportTestBed, compilationMode,
this.options.useTemplatePipeline ?? SHOULD_USE_TEMPLATE_PIPELINE,
!!this.options.generateExtraImportsInLocalMode,
@@ -1441,3 +1457,17 @@ function versionMapFromProgram(
}
return versions;
}
/** Representation of a parsed persion string. */
class Version {
readonly major: number;
readonly minor: number;
readonly patch: number;
constructor(public full: string) {
[this.major, this.minor, this.patch] = full.split('.').map(part => {
const parsed = parseInt(part);
return isNaN(parsed) ? -1 : parsed;
});
}
}
@@ -8,12 +8,15 @@
import ts from 'typescript';
import {ConstantEntry, EntryType} from './entities';
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc,} from './jsdoc_extractor';
import {ConstantEntry, EntryType, EnumEntry, EnumMemberEntry, MemberType} from './entities';
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from './jsdoc_extractor';
/** Name of the tag indicating that an object literal should be shown as an enum in docs. */
const LITERAL_AS_ENUM_TAG = 'object-literal-as-enum';
/** Extracts documentation entry for a constant. */
export function extractConstant(
declaration: ts.VariableDeclaration, typeChecker: ts.TypeChecker): ConstantEntry {
declaration: ts.VariableDeclaration, typeChecker: ts.TypeChecker): ConstantEntry|EnumEntry {
// For constants specifically, we want to get the base type for any literal types.
// For example, TypeScript by default extracts `const PI = 3.14` as PI having a type of the
// literal `3.14`. We don't want this behavior for constants, since generally one wants the
@@ -26,16 +29,30 @@ export function extractConstant(
// In the TS AST, the leading comment for a variable declaration is actually
// on the ancestor `ts.VariableStatement` (since a single variable statement may
// contain multiple variable declarations).
const variableStatement = declaration.parent.parent;
const rawComment = extractRawJsDoc(declaration.parent.parent);
const jsdocTags = extractJsDocTags(declaration);
const description = extractJsDocDescription(declaration);
const name = declaration.name.getText();
// Some constants have to be treated as enums for documentation purposes.
if (jsdocTags.some(tag => tag.name === LITERAL_AS_ENUM_TAG)) {
return {
name,
entryType: EntryType.Enum,
members: extractLiteralPropertiesAsEnumMembers(declaration),
rawComment,
description,
jsdocTags: jsdocTags.filter(tag => tag.name !== LITERAL_AS_ENUM_TAG),
};
}
return {
name: declaration.name.getText(),
name: name,
type: typeChecker.typeToString(resolvedType),
entryType: EntryType.Constant,
rawComment,
description: extractJsDocDescription(declaration),
jsdocTags: extractJsDocTags(declaration),
description,
jsdocTags,
};
}
@@ -43,3 +60,40 @@ export function extractConstant(
export function isSyntheticAngularConstant(declaration: ts.VariableDeclaration) {
return declaration.name.getText() === 'USED_FOR_NG_TYPE_CHECKING';
}
/**
* Extracts the properties of a variable initialized as an object literal as if they were enum
* members. Will throw for any variables that can't be statically analyzed easily.
*/
function extractLiteralPropertiesAsEnumMembers(declaration: ts.VariableDeclaration):
EnumMemberEntry[] {
const initializer = declaration.initializer;
if (initializer === undefined || !ts.isObjectLiteralExpression(initializer)) {
throw new Error(`Declaration tagged with "${
LITERAL_AS_ENUM_TAG}" must be initialized to an object literal`);
}
return initializer.properties.map(prop => {
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
throw new Error(`Property in declaration tagged with "${
LITERAL_AS_ENUM_TAG}" must be a property assignment with a static name`);
}
if (!ts.isNumericLiteral(prop.initializer) && !ts.isStringLiteralLike(prop.initializer)) {
throw new Error(`Property in declaration tagged with "${
LITERAL_AS_ENUM_TAG}" must be initialized to a number or string literal`);
}
return ({
name: prop.name.text,
type: `${declaration.name.getText()}.${prop.name.text}`,
value: prop.initializer.getText(),
memberType: MemberType.EnumItem,
jsdocTags: extractJsDocTags(prop),
description: extractJsDocDescription(prop),
memberTags: [],
});
});
}
@@ -11,6 +11,7 @@ export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAnd
export {DefaultImportTracker} from './src/default';
export {DeferredSymbolTracker} from './src/deferred_symbol_tracker';
export {AbsoluteModuleStrategy, assertSuccessfulReferenceEmit, EmittedReference, FailedEmitResult, ImportedFile, ImportFlags, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitKind, ReferenceEmitResult, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesStrategy} from './src/emitter';
export {ImportedSymbolsTracker} from './src/imported_symbols_tracker';
export {LocalCompilationExtraImportsTracker} from './src/local_compilation_extra_imports_tracker';
export {isAliasImportDeclaration, loadIsReferencedAliasDeclarationPatch} from './src/patch_alias_reference_resolution';
export {Reexport} from './src/reexport';
@@ -0,0 +1,109 @@
/*!
* @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';
/**
* A map of imported symbols to local names under which the symbols are available within a file.
*/
type LocalNamesMap = Map<string, Set<string>>;
/** Mapping between modules and the named imports consumed by them in a file. */
type NamedImportsMap = Map<string, LocalNamesMap>;
/**
* Tracks which symbols are imported in specific files and under what names. Allows for efficient
* querying for references to those symbols without having to consult the type checker early in the
* process.
*
* Note that the tracker doesn't account for variable shadowing so a final verification with the
* type checker may be necessary, depending on the context. Also does not track dynamic imports.
*/
export class ImportedSymbolsTracker {
private fileToNamedImports = new WeakMap<ts.SourceFile, NamedImportsMap>();
private fileToNamespaceImports = new WeakMap<ts.SourceFile, LocalNamesMap>();
/**
* Checks if an identifier is a potential reference to a specific named import within the same
* file.
* @param node Identifier to be checked.
* @param exportedName Name of the exported symbol that is being searched for.
* @param moduleName Module from which the symbol should be imported.
*/
isPotentialReferenceToNamedImport(node: ts.Identifier, exportedName: string, moduleName: string):
boolean {
const sourceFile = node.getSourceFile();
this.scanImports(sourceFile);
const fileImports = this.fileToNamedImports.get(sourceFile)!;
const moduleImports = fileImports.get(moduleName);
const symbolImports = moduleImports?.get(exportedName);
return symbolImports !== undefined && symbolImports.has(node.text);
}
/**
* Checks if an identifier is a potential reference to a specific namespace import within the same
* file.
* @param node Identifier to be checked.
* @param moduleName Module from which the namespace is imported.
*/
isPotentialReferenceToNamespaceImport(node: ts.Identifier, moduleName: string): boolean {
const sourceFile = node.getSourceFile();
this.scanImports(sourceFile);
const namespaces = this.fileToNamespaceImports.get(sourceFile)!;
return namespaces.get(moduleName)?.has(node.text) ?? false;
}
/** Scans a `SourceFile` for import statements and caches them for later use. */
private scanImports(sourceFile: ts.SourceFile): void {
if (this.fileToNamedImports.has(sourceFile) && this.fileToNamespaceImports.has(sourceFile)) {
return;
}
const namedImports: NamedImportsMap = new Map();
const namespaceImports: LocalNamesMap = new Map();
this.fileToNamedImports.set(sourceFile, namedImports);
this.fileToNamespaceImports.set(sourceFile, namespaceImports);
// Only check top-level imports.
for (const stmt of sourceFile.statements) {
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteralLike(stmt.moduleSpecifier) ||
stmt.importClause?.namedBindings === undefined) {
continue;
}
const moduleName = stmt.moduleSpecifier.text;
if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
// import * as foo from 'module'
if (!namespaceImports.has(moduleName)) {
namespaceImports.set(moduleName, new Set());
}
namespaceImports.get(moduleName)!.add(stmt.importClause.namedBindings.name.text);
} else {
// import {foo, bar as alias} from 'module'
for (const element of stmt.importClause.namedBindings.elements) {
const localName = element.name.text;
const exportedName =
element.propertyName === undefined ? localName : element.propertyName.text;
if (!namedImports.has(moduleName)) {
namedImports.set(moduleName, new Map());
}
const localNames = namedImports.get(moduleName)!;
if (!localNames.has(exportedName)) {
localNames.set(exportedName, new Set());
}
localNames.get(exportedName)?.add(localName);
}
}
}
}
}
@@ -138,8 +138,7 @@ export interface WritableSignal<T> extends Signal<T> {
asReadonly(): Signal<T>;
}
// Note: needs to be kept in sync with the copies in `render3/reactivity/signal.ts` and
// `ngtsc/typecheck/testing/index.ts` to ensure consistent tests.
// Note: needs to be kept in sync with the copies in `render3/reactivity/signal.ts`.
export function ɵunwrapWritableSignal<T>(value: T|{[WRITABLE_SIGNAL]: T}): T {
return null!;
}
@@ -200,7 +199,7 @@ export interface ModelOptions {
export interface ModelSignal<T> extends WritableSignal<T> {
[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T;
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
subscribe(callback: (value: T) => void): {unsubscribe: () => void};
subscribe(callback: (value: T) => void): () => void;
}
export interface ModelFunction {
@@ -217,7 +216,20 @@ export const viewChildren: any = null!;
export const contentChild: any = null!;
export const contentChildren: any = null!;
export interface OutputEmitter<T> {
emit(value: T): void;
subscribe(listener: (v: T) => void): void;
}
/** Initializer-based output() API. */
export function output<T>(_opts?: {alias?: string}): EventEmitter<T> {
export function output<T>(_opts?: {alias?: string}): OutputEmitter<T> {
return null!;
}
export interface CreateComputedOptions<T> {
equal?: (a: T, b: T) => boolean;
}
export function computed<T>(computation: () => T, options?: CreateComputedOptions<T>): Signal<T> {
return null!;
}
@@ -330,6 +330,11 @@ export interface TypeCheckingConfig {
* opportunities to improve their own developer experience.
*/
suggestionsForSuboptimalTypeInference: boolean;
/**
* Whether the type of two-way bindings should be widened to allow `WritableSignal`.
*/
allowSignalsInTwoWayBindings: boolean;
}
@@ -13,6 +13,24 @@ import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics
import {NgTemplateDiagnostic, SymbolKind} from '../../../api';
import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api';
/** Names of known signal functions. */
const SIGNAL_FNS = new Set([
'WritableSignal',
'Signal',
'InputSignal',
'InputSignalWithTransform',
'ModelSignal',
]);
/** Names of known signal instance properties. */
const SIGNAL_INSTANCE_PROPERTIES = new Set(['set', 'update', 'asReadonly']);
/**
* Names of known function instance properties.
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function#instance_properties
*/
const FUNCTION_INSTANCE_PROPERTIES = new Set(['name', 'length', 'prototype']);
/**
* Ensures Signals are invoked when used in template interpolations.
*/
@@ -26,41 +44,66 @@ class InterpolatedSignalCheck extends
node: TmplAstNode|AST): NgTemplateDiagnostic<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>[] {
if (node instanceof Interpolation) {
return node.expressions.filter((item): item is PropertyRead => item instanceof PropertyRead)
.flatMap((item) => {
if (item instanceof PropertyRead) {
return buildDiagnosticForSignal(ctx, item, component);
}
return [];
});
.flatMap(item => buildDiagnosticForSignal(ctx, item, component));
}
return [];
}
}
function isSignal(symbol: ts.Symbol|undefined): boolean {
return (symbol?.escapedName === 'WritableSignal' || symbol?.escapedName === 'Signal' ||
symbol?.escapedName === 'InputSignal' ||
symbol?.escapedName === 'InputSignalWithTransform' ||
symbol?.escapedName === 'ModelSignal') &&
(symbol as any).parent.escapedName.includes('@angular/core');
const declarations = symbol?.getDeclarations();
return declarations !== undefined && declarations.some(decl => {
const fileName = decl.getSourceFile().fileName;
return (ts.isInterfaceDeclaration(decl) || ts.isTypeAliasDeclaration(decl)) &&
SIGNAL_FNS.has(decl.name.text) &&
(fileName.includes('@angular/core') || fileName.includes('angular2/rc/packages/core'));
});
}
function isFunctionInstanceProperty(name: string): boolean {
return FUNCTION_INSTANCE_PROPERTIES.has(name);
}
function isSignalInstanceProperty(name: string): boolean {
return SIGNAL_INSTANCE_PROPERTIES.has(name);
}
function buildDiagnosticForSignal(
ctx: TemplateContext<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>, node: PropertyRead,
component: ts.ClassDeclaration):
Array<NgTemplateDiagnostic<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>> {
// check for `{{ mySignal }}`
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
if (symbol?.kind === SymbolKind.Expression &&
(isSignal(symbol.tsType.symbol) || isSignal(symbol.tsType.aliasSymbol))) {
const templateMapping =
ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(symbol.tcbLocation)!;
const errorString = `${node.name} is a function and should be invoked: ${node.name}()`;
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
return [diagnostic];
}
// check for `{{ mySignal.name }}` or `{{ mySignal.length }}` or `{{ mySignal.prototype }}`
// as these are the names of instance properties of Function, the compiler does _not_ throw an
// error.
// We also check for `{{ mySignal.set }}` or `{{ mySignal.update }}` or
// `{{ mySignal.asReadonly }}` as these are the names of instance properties of Signal
const symbolOfReceiver = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
if ((isFunctionInstanceProperty(node.name) || isSignalInstanceProperty(node.name)) &&
symbolOfReceiver?.kind === SymbolKind.Expression &&
(isSignal(symbolOfReceiver.tsType.symbol) || isSignal(symbolOfReceiver.tsType.aliasSymbol))) {
const templateMapping =
ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(symbolOfReceiver.tcbLocation)!;
const errorString =
`${(node.receiver as PropertyRead).name} is a function and should be invoked: ${
(node.receiver as PropertyRead).name}()`;
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
return [diagnostic];
}
return [];
}
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -17,7 +17,7 @@ import {factory as interpolatedSignalFactory} from '../../../checks/interpolated
import {ExtendedTemplateCheckerImpl} from '../../../src/extended_template_checker';
runInEachFileSystem(() => {
describe('Interpolated Signal ', () => {
describe('Interpolated Signal', () => {
it('binds the error code to its extended template diagnostic name', () => {
expect(interpolatedSignalFactory.code).toBe(ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED);
expect(interpolatedSignalFactory.name)
@@ -51,7 +51,7 @@ runInEachFileSystem(() => {
});
});
it('should produce a warning when a signal isn\'t invoked', () => {
it('should produce a warning when a signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -81,7 +81,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[1])).toBe(`mySignal2`);
});
it('should produce a warning when a readonly signal isn\'t invoked', () => {
it('should produce a warning when a readonly signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -109,7 +109,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe('count');
});
it('should produce a warning when a computed signal isn\'t invoked', () => {
it('should produce a warning when a computed signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -138,7 +138,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`mySignal2`);
});
it('should produce a warning when an input signal isn\'t invoked', () => {
it('should produce a warning when an input signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -166,7 +166,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`myInput`);
});
it('should produce a warning when a required input signal isn\'t invoked', () => {
it('should produce a warning when a required input signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -194,7 +194,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`myRequiredInput`);
});
it('should produce a warning when a model signal isn\'t invoked', () => {
it('should produce a warning when a model signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -213,7 +213,7 @@ runInEachFileSystem(() => {
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(1);
@@ -222,7 +222,7 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`myModel`);
});
it('should produce a warning when a required model signal isn\'t invoked', () => {
it('should produce a warning when a required model signal is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -241,7 +241,7 @@ runInEachFileSystem(() => {
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(1);
@@ -250,6 +250,40 @@ runInEachFileSystem(() => {
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`myRequiredModel`);
});
it('should produce a warning when a signal in a nested property read is not invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
fileName,
templates: {
'TestCmp': `<div>{{ obj.nested.prop.signal }}</div>`,
},
source: `
import {signal, Signal} from '@angular/core';
export class TestCmp {
obj = {
nested: {
prop: {
signal: signal<number>(0)
}
}
}
}`,
},
]);
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(1);
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED));
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`signal`);
});
it('should not produce a warning when model signals are invoked', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
@@ -270,7 +304,7 @@ runInEachFileSystem(() => {
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {} /* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(0);
@@ -328,7 +362,7 @@ runInEachFileSystem(() => {
expect(diags.length).toBe(0);
});
it('should produce a warning when signal isn\'t invoked on interpolated binding', () => {
it('should produce a warning when signal is not invoked on interpolated binding', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -337,7 +371,7 @@ runInEachFileSystem(() => {
'TestCmp': `<div id="{{mySignal}}"></div>`,
},
source: `
import {signal, Signal, computed} from '@angular/core';
import {signal} from '@angular/core';
export class TestCmp {
mySignal = signal<number>(0);
@@ -365,7 +399,7 @@ runInEachFileSystem(() => {
'TestCmp': `<div id="{{mySignal()}}"></div>`,
},
source: `
import {signal, Signal, computed} from '@angular/core';
import {signal} from '@angular/core';
export class TestCmp {
mySignal = signal<number>(0);
@@ -390,7 +424,7 @@ runInEachFileSystem(() => {
'TestCmp': `<div attr.id="my-{{mySignal}}-item"></div>`,
},
source: `
import {signal, Signal, computed} from '@angular/core';
import {signal} from '@angular/core';
export class TestCmp {
mySignal = signal<number>(0);
@@ -419,7 +453,7 @@ runInEachFileSystem(() => {
'TestCmp': `<div attr.id="my-{{mySignal()}}-item"></div>`,
},
source: `
import {signal, Signal, computed} from '@angular/core';
import {signal} from '@angular/core';
export class TestCmp {
mySignal = signal<number>(0);
@@ -436,7 +470,7 @@ runInEachFileSystem(() => {
expect(diags.length).toBe(0);
});
it('should produce a warning when nested signal isn\'t invoked on interpolated binding', () => {
it('should produce a warning when nested signal is not invoked on interpolated binding', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
@@ -445,7 +479,7 @@ runInEachFileSystem(() => {
'TestCmp': `<div id="{{myObject.myObject2.myNestedSignal}}"></div>`,
},
source: `
import {signal, Signal, computed} from '@angular/core';
import {signal} from '@angular/core';
export class TestCmp {
myObject = {myObject2: {myNestedSignal: signal<number>(0)}};
@@ -519,4 +553,96 @@ runInEachFileSystem(() => {
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(0);
});
['name', 'length', 'prototype', 'set', 'update', 'asReadonly'].forEach(
functionInstanceProperty => {
it(`should produce a warning when a property named '${
functionInstanceProperty}' of a not invoked signal is used in interpolation`,
() => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
fileName,
templates: {
'TestCmp': `<div>{{myObject.mySignal.${functionInstanceProperty}}}</div>`,
},
source: `
import {signal} from '@angular/core';
export class TestCmp {
myObject = { mySignal: signal<{ ${functionInstanceProperty}: string }>({ ${
functionInstanceProperty}: 'foo' }) };
}`,
},
]);
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {}
/* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(1);
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED));
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`mySignal`);
});
it(`should not produce a warning when a property named ${
functionInstanceProperty} of an invoked signal is used in interpolation`,
() => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
fileName,
templates: {
'TestCmp': `<div>{{mySignal().${functionInstanceProperty}}}</div>`,
},
source: `
import {signal} from '@angular/core';
export class TestCmp {
mySignal = signal<{ ${functionInstanceProperty}: string }>({ ${
functionInstanceProperty}: 'foo' });
}`,
},
]);
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {}
/* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(0);
});
it(`should not produce a warning when a property named ${
functionInstanceProperty} of an object is used in interpolation`,
() => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
fileName,
templates: {
'TestCmp': `<div>{{myObject.${functionInstanceProperty}}}</div>`,
},
source: `
import {signal} from '@angular/core';
export class TestCmp {
myObject = { ${functionInstanceProperty}: 'foo' };
}`,
},
]);
const sf = getSourceFileOrError(program, fileName);
const component = getClass(sf, 'TestCmp');
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, program.getTypeChecker(), [interpolatedSignalFactory], {}
/* options */
);
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
expect(diags.length).toBe(0);
});
});
});
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],
@@ -21,6 +21,9 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
deps = [
":test_lib",
],

Some files were not shown because too many files have changed in this diff Show More